简体   繁体   中英

There's a bug with Search and Pagination in React

I'm building my site in React and I have created pagination and search. When I search for something on the site, it only works when after that I go to another page. I think this is due to the fact that Softwares and Pagination are in the same component.

Then I tried lifting-state-up, but I got an error: React Minified Error # 31.

Here's Pagination component:

const Paginator = ({
    total, // Total records
    startPage = 1,
    totalPages = null,
    onMovePage = null,
  }) => {

  ...

  return (
    <>
      <section id={styles.paginator}>

        <Header/>

        ...

        {range(1, totalPages+1).map(p => (
          <PagItem key={p} handleClick={ () => {setCurrentPage(p); onMovePage && onMovePage({currentPage: p})} } title={p} name={p} />
        ))}

        ...
      </section>
    </>
  );
};

Here's Softwares component:

const Softwares = ({ search }) => {

  const [softwares, setSoftwares] = useState([]);
  const [total, setTotal] = useState(null);
  const [totalPages, setTotalPages] = useState(null);
  const [valid, setValid] = useState(false);

  const fetchData = async ({ currentPage }) => {

    const SEARCH = search ? `?search=${search}` : '';
    const CURRENT_PAGE = currentPage && SEARCH === '' ? `?page=${currentPage}` : '';

    const response = await fetch(`http://127.0.0.1:8000/api/software/${CURRENT_PAGE}${SEARCH}`);

    const data = await response.json();

    setSoftwares(data.results);
    setTotal(data.count);
    setTotalPages(data.total_pages);
    setValid(true);

  }

  useEffect(() => {
    fetchData({ currentPage: 1 });
  }, []);

  return (
    <>
    {
      valid &&
      <section className={styles.softwares}>
        <Header header={"new softwares"} />
        {softwares.map(s => (
          <Article key={s.id} pathname={s.id} title={s.title} image={s.image} pubdate={s.pub_date} icon={s.category.parent.img} categoryID={s.category.id} categoryName={s.category.name} dCount={s.counter} content={s.content} />
        ))}
        <Paginator totalPages={totalPages} total={total} onMovePage={fetchData} />
      </section>
    }
    </>
  );
};

SearchForm in Header component:

const Header = ({ handleChange, handleClick }) => {

  return (
    ...

        <SearchForm handleChange={handleChange} handleClick={handleClick} />

    ...
  );
};

const SearchForm = ({ style, handleChange, handleClick }) => {

  return (
    <div style={style}>
      <form>
        <input
          type="text"
          onChange={handleChange}
        />
        <SearchButton onClick={handleClick} />
        <small>ENTER</small>
      </form>
    </div>
  );
};

const SearchButton = ({onClick }) => {
  return (
    <button type="button" onClick={onClick}>
      <FontAwesomeIcon icon={faSearch} />
    </button>
  );
};

And part of Search in App component:

const App = () => {

    ...
    
    // Search
  const [search, setSearch] = useState('');
  const [shouldFetch, setShouldFetch] = useState(false);

  const handleChange = (e) => {
    setSearch(e.target.value);
  }

  useEffect(() => {

    if (shouldFetch) {

      (async () => {

        const response = await fetch(`http://127.0.0.1:8000/api/software/?search=${search}`);

        const data = await response.json();

        setShouldFetch(false);

      })()

    }

  }, [shouldFetch]);

  const handleClick = () => setShouldFetch(true);

    return (
        <div className="App">

      <Header handleChange={handleChange} handleClick={handleClick} />

      ...

      <Switch>
        <Route path="/" exact render={props => <Softwares {...props} search={search} />} />
      </Switch>

            {/* Actually I'd like to use Paginator here, but it 
                    throws the error: React Minified Error # 31 */}

            ...

    </div>
    );

}

So, how can this be done?

The problem is your useEffect dependencies (or lack thereof).

Here's the relevant section of the code:

const Softwares = ({ search }) => {

  const [softwares, setSoftwares] = useState([]);
  const [total, setTotal] = useState(null);
  const [totalPages, setTotalPages] = useState(null);
  const [valid, setValid] = useState(false);

  const fetchData = async ({ currentPage }) => {

    const SEARCH = search ? `?search=${search}` : '';
    const CURRENT_PAGE = currentPage && SEARCH === '' ? `?page=${currentPage}` : '';

    const response = await fetch(`http://127.0.0.1:8000/api/software/${CURRENT_PAGE}${SEARCH}`);

    const data = await response.json();

    setSoftwares(data.results);
    setTotal(data.count);
    setTotalPages(data.total_pages);
    setValid(true);

  }

  useEffect(() => {
    fetchData({ currentPage: 1 });
  }, []);

The empty dependency array means that you are running the effect that calls fetchData one time when the component mounts. Clicks in the Pagination component will call the fetchData function directly. Changes to search do not cause fetchData to re-run . The data depends on the search so search should be a dependency.

The fetchData function is fine in this component. The state that I would recommend lifting up is to lift the currentPage up from Pagination into Softwares . The onMovePage callback can just update the currentPage state. That way you can call fetchData only through your effect and run the effect whenever either search or currentPage changes.

const Softwares = ({ search }) => {

  const [softwares, setSoftwares] = useState([]);
  const [total, setTotal] = useState(null);
  const [totalPages, setTotalPages] = useState(null);
  const [valid, setValid] = useState(false);
  const [currentPage, setCurrentPage] = useState(1);

  useEffect(() => {
    // defining the function inside of the useEffect
    // lets eslint exhaustive dependency checks work their magic
    const fetchData = async () => {

      const SEARCH = search ? `?search=${search}` : '';
      const CURRENT_PAGE = currentPage && SEARCH === '' ? `?page=${currentPage}` : '';
  
      const response = await fetch(`http://127.0.0.1:8000/api/software/${CURRENT_PAGE}${SEARCH}`);
  
      const data = await response.json();
  
      setSoftwares(data.results);
      setTotal(data.count);
      setTotalPages(data.total_pages);
      setValid(true);
    }
  
    // need to define and call in separate steps when using async functions
    fetchData();

  }, [currentPage, search]);

  return (
    ...
        <Paginator page={currentPage} totalPages={totalPages} total={total} onMovePage={setCurrentPage} />
    ...
  );
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM