Search code examples
javascriptreactjsoptimizationnext.jsreact-props

Search bar stopping props from changing


On my site, the <ArticleList> is supposed to update when one navigates between columns. This works when you go from the home page to a column, or from an article to a column. But if you go from column to column, it doesn't work. The page doesn't update at all, but the url changes. The links to each column stay the same, as they are part of the <Layout> component, which every page has.

Edit

I figured out now that I can just use <a> and omit <Link> entirely, but this would slow down the page navigation.

Edit 2

This is part of my <Layout> component where I render the links to the columns:

<nav className={layout.columnContainer}>
  {columns.map(({ id, name }) =>
    this.props.currentColumn ? (
      <a key={id} href={`/columns/${name}`}>
        {name}
      </a>
    ) : (
      <Link key={id} href="/columns/[name]" as={`/columns/${name}`}>
        <a>{name}</a>
      </Link>
    ),
  )}
</nav>

Edit 3

My minimal reproducible example is on GitHub, and I get the same unexpected results.

Edit 4

I found that the reason it wasn't working was I implemented a search bar that put the children prop in a state and modified this.

Constructor:

  constructor(props) {
    super(props);
    this.searchArticlesKeyType = this.searchArticlesKeyType.bind(this);
    this.state = {displayedMain: props.children};
  }

Inside the render method are the column links (nav) and the problematic search input element.

<nav className={layout.columnContainer}>
{
      columns.map(({id, name}) => (
       <Link key={id} href="/columns/[name]" as={`/columns/${name}`}><a>{name}</a></Link>
      ))
}
</nav>
<div className={layout.search}>
   <input type="search" name="q" onKeyUp={this.searchArticlesKeyType} />
</div>
async searchArticlesKeyType(e) {
   // Some code
      
   this.setState({
      displayedMain: <ArticleList articles={JSON.stringify(filteredArticles)}/>
     
    // More code
  });
}

Solution

  • I think your main issue here is the way you're implementing the search feature, you don't want to store components in state instead you need to pass the search text to the articlelist component and do the filtering there.

    There are several ways to implement communication between 2 unrelated components, it could be via context, redux, or even make a portal in the layout to render the seach input from the column component, but in this case I think the best option is to store the search text in the url:

    First make the input event update the url using next/router, your layout will look like this:

    import { useRouter } from 'next/router'
    ...
    function Layout(props) {
      const {columns} = props;
      const { push, asPath, query } = useRouter()
    
      const searchArticlesKeyType = (e) => {
        const q = e.target.value;
        const [url] = asPath.split('?');
        push(`${url}?q=${q}`, undefined, { shallow: true });
      } 
    
      return (
        <div>
          ...
            <div>
              <input type="search" name="q" defaultValue={query.q} onKeyUp={searchArticlesKeyType} />
            </div>
          ...
        </div>
      )
    }
    

    And then you do the filtering in articlelist component

    import Link from "next/link";
    import { useRouter } from 'next/router'
    
    export default function ArticleList(props) {
      const { query } = useRouter();
      const q = query.q || "";
      const filteredArticles = props.articles.filter(
        (item) => item.title.includes(q) || item.body.includes(q)
      );
      return (
        <ul className="grid">
          {filteredArticles.map((item) => (
            <div key={item.id}>
              <Link
                key={item.id}
                href="/articles/[title]"
                as={`/articles/${item.title}`}
              >
                <a>
                  <p>
                    <strong>{item.title}</strong>
                  </p>
                  <p>{item.body.substring(0, 100)}</p>
                </a>
              </Link>
            </div>
          ))}
        </ul>
      );
    }