1

我是 React 的新手,对 Next.js 更是陌生

我有一个输入,用户在所有可用城市的列表中按名称搜索城市。

我读过 useSWR 可能会很有趣(在此之前,我在 useEffect 中使用 axios 发出请求)。

一旦我得到了城市数组,我会做一张地图来返回所有匹配的请求(然后我用它来做一个自动完成)。

但是我收到这样的错误:
“错误:重新渲染太多。React 限制了渲染的数量以防止无限循环。”
如果我只是获取数据,它可以工作,但如果我在数组上做地图,我会得到一个无限循环,我不知道为什么。

我的代码:

import React, { useState, useEffect } from "react";
import styles from "./searchBar.module.css";
import { useRouter } from "next/router";
import axios from "axios";
import useSWR from "swr";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faSearch, faAngleDown } from "@fortawesome/free-solid-svg-icons";
import installationType from "../public/installation_type_ids.json";

const SearchBar = ({
  setSearchSport,
  setSearchCity,
  searchCity,
  searchSport,
  setSearchType,
  searchType,
  setPage,
}) => {
  const router = useRouter();

  // States for search bar request
  const [city, setCity] = useState(searchCity);
  const [cityData, setCityData] = useState([]);
  const [sport, setSport] = useState(searchSport);
  const [title, setTitle] = useState("");
  const [type, setType] = useState(searchType);
  const [autoComplete, setAutoComplete] = useState([]);
  const [displayAutoComplete, setDisplayAutoComplete] = useState(false);

  // handle submit button
  const handleSubmit = (e) => {
    e.preventDefault();
    setSearchCity(city);
    setSearchSport(sport);
    type ? setSearchType(type) : setSearchType("ALL");
    setPage(0);
    if (router.pathname !== "/points-de-rencontre-sportive")
      router.push("/points-de-rencontre-sportive");
  };

  const url = "https://bouge-api.herokuapp.com/v1.0/city/ids";

  const fetcher = (...args) => fetch(...args).then((res) => res.json());

  const { data: result, error } = useSWR(url, fetcher);

  if (error) return <h1>Oups ! Une erreur est survenue...</h1>;
  if (!result) return <h1>Chargement en cours...</h1>;

  const handleTest = (e) => {
    setCity(e.target.value);
    e.target.value === 0
      ? setDisplayAutoComplete(false)
      : setDisplayAutoComplete(true);
    if (result && result.data) {
      const dataMapped = result.data.map((city) => {
        return { city: city.name, type: "city" };
      });
      let tab = [];
      dataMapped.map((item, i) => {
        item.name
        if (item.name.toLowerCase().includes(city)) {
          tab.push(item);
        }
        return setAutoComplete(tab);
      });
    }
  };

  // autocomplete rendering
  const renderAutoComplete = autoComplete.map((elem, index) => {
    console.log(elem);
    if (index <= 9) {
      return (
        <div
          className={styles.autocompleteDiv}
          key={index}
          onClick={() => {
            if (elem.type === "city") {
              setCity(elem.city);
            }
            if (elem.type === "sport") {
              setSport(elem.sport);
            }
            setDisplayAutoComplete(false);
          }}
        >
          <p>{elem.type === "city" ? elem.city : elem.sport}</p>
        </div>
      );
    } else {
      return null;
    }
  });

  return (
    <div className={styles.searchBar}>
      <form className={styles.form} onSubmit={handleSubmit}>
        <div>
          <label htmlFor="city">Ville</label>
          <input
            type="text"
            id="city"
            placeholder="Où veux-tu jouer?"
            value={city}
            onChange={handleTest}
            autoComplete="off" // disable chrome auto complete
          />
        </div>

        <div>
          <label htmlFor="sport">Sport</label>
          <input
            type="text"
            id="sport"
            placeholder="Spécifie le sport"
            value={sport}
            onChange={(e) => {
              setSport(e.target.value);
            }}
            autoComplete="off" // disable chrome auto complete
          />
        </div>
        <div>
          <label htmlFor="title">Nom</label>
          <input
            type="text"
            id="title"
            placeholder="Recherche par nom"
            value={title}
            onChange={(e) => {
              setTitle(e.target.value);
              let tab = [];
              installationType.map((item, i) => {
                if (item.installation_type.includes(title)) {
                  tab.push(item);
                }
                return setAutoComplete(tab);
              });
              console.log(tab);
            }}
            autoComplete="off" // disable chrome auto complete
          />
        </div>
        <div>
          <label htmlFor="type">Type</label>
          <select
            type="text"
            id="type"
            placeholder="Type de structure"
            value={type}
          >
            <option value="ALL" defaultValue>
              Type de structure
            </option>
            <option value="AS">Association</option>
            <option value="PRIV">Privé</option>
            <option value="PUB">Public</option>
            <option value="EVENT">Activité</option>
          </select>
          <i>
            <FontAwesomeIcon
              icon={faAngleDown}
              className={styles.selectIcon}
            ></FontAwesomeIcon>
          </i>
        </div>
        <button>
          <i>
            <FontAwesomeIcon
              icon={faSearch}
              className="fa-lg"
            ></FontAwesomeIcon>
          </i>
          Rechercher
        </button>
      </form>
      {displayAutoComplete ? (
        <div className={styles.searchResults}>{renderAutoComplete}</div>
      ) : null}
    </div>
  );
};

export default SearchBar;

4

1 回答 1

0

获取数据后,调用setCityData方法更新城市数据,这会导致组件重新渲染并再次运行 SearchBar 组件中的代码,因此它setCityData再次调用然后继续重新渲染 => 无限重新渲染。

我认为您应该将其放入 useEffect 中:

useEffect(() => {
  if (result && result.data) {
    const dataMapped = result.data.map((city) => {
      return { city: city.name, type: "city" };
    });
    setCityData(dataMapped)
  }
}, [result])

所以只有当结果有数据时才会更新城市数据

于 2021-01-19T08:49:18.983 回答