最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - More than needed React components re-rendering when typing in input - Stack Overflow

programmeradmin0浏览0评论

I am taking input from a search input field using searchInput and setSearchInput useState hook and after I press submit button, I call fetchSearchData function providing it the input text and setCompanies hook, where panies are updated with the fetched list of panies from the API.

Then panies are passed to another ponent CompanyList where a map function is called there.

The problem is whenever I type in the search field, the CompanyList ponent is re-rendered although I did not press submit. I understand that setSearchInput will re-render SearchBar ponent whenever I type in it, but I don't get why CompanyList re-renders.

Search page source code:


const Search = () => {
    const [panies, setCompanies]=useState([]); //List of panies returned from searching
    const [searchInput, setSearchInput] = useState(""); //Search field input

    //Update search text whenever the user types in
    const onSearchChange = (e) => {
        setSearchInput(e.target.value)
    }

    //use the API providing it the search input, and 
    //setCompanies hook to update list of panies
    const onSearchSubmit = (e) => {
        e.preventDefault()
        fetchSearchData(searchInput, setCompanies)
    }


    return (
        <div>
            <Container>
                <Row className={"searchFilterBar"}>
                    <Col sm={6} md={8} className={"searchBar"}>
                        <SearchBar onSubmit={onSearchSubmit} onChange={onSearchChange} value={searchInput} />
                    </Col>
                    <Col sm={6} md={4} className={"filterBar"}>
                    </Col>
                </Row>
                <CompanyList panies={panies} ></CompanyList>
                <Row>
                </Row>
            </Container>
        </div>
    )
}
export default Search;

SearchBar ponent source code:

const SearchBar = ({value,onSubmit, onChange}) => {
    return (
        <Form
            className="search-form"
            onSubmit={onSubmit}
            >
            <div className="input-group">
                <span className="input-group-text rubik-font">
                    <i className="icon ion-search"></i>
                </span>
                <input
                    className="form-control rubik-font"
                    type="text"
                    placeholder="Search for panies that start with..."
                    onChange={onChange}
                    value={value}
                />
                <Button className="btn btn-light rubik-font" type="submit">Search </Button>
            </div>
        </Form>
    )

}

CompanyList ponent source code:

function MapDataToCompanyList(response) {
    console.log(response); //Logging occurs here
    if(!response || response===undefined || response.length===0)
    {
        return (<ErrorBoundary message={noCompaniesError.message}></ErrorBoundary>)
    }
    return response.map((pany) => {
        return (
            <Col key={pany._id} xs={12} md={6} lg={4} className="mt-2">
                <CompanyCard
                    id={pany._id}
                    logo={pany.logo}
                    title={pany.name}
                    logoBackground={pany.logoBackground}
                    progLangs={pany.progLangs}
                    backend={pany.backend}
                    frontend={pany.frontend}
                    url={pany.url}
                >
                </CompanyCard>
            </Col>
        )
    })
}
const CompanyList = (props) => {
    const {panies} = props
    return (
        <div>
            <Container className="mt-3">
                <Row>
                    {
                    MapDataToCompanyList(panies)
                    }
                </Row>
            </Container>
        </div>
    )
}

export default CompanyList;

FetchSearchData function source code:

export const fetchSearchData = (query, cb)=>{
    const uri = process.env.NODE_ENV === 'development' ?
    `http://localhost:3000/api/panies/name/${query}` :
    ``;
    axios.get(uri, {
        timeout: MAX_TIMEOUT
    })
    .then((response)=>{
        cb(response.data.data)
    })
    .catch((error)=>{
        console.log(error)
    })
}

As seen above, empty list of panies is logged when the page first loads, then I typed three characters and the it logged three time which means the map function called three times.

Even then if I pressed submit and retrieved list of panies normally, whenever I type it will keep printing the array of panies that was fetched.

Sorry if I missed something, I am still new to React.

I am taking input from a search input field using searchInput and setSearchInput useState hook and after I press submit button, I call fetchSearchData function providing it the input text and setCompanies hook, where panies are updated with the fetched list of panies from the API.

Then panies are passed to another ponent CompanyList where a map function is called there.

The problem is whenever I type in the search field, the CompanyList ponent is re-rendered although I did not press submit. I understand that setSearchInput will re-render SearchBar ponent whenever I type in it, but I don't get why CompanyList re-renders.

Search page source code:


const Search = () => {
    const [panies, setCompanies]=useState([]); //List of panies returned from searching
    const [searchInput, setSearchInput] = useState(""); //Search field input

    //Update search text whenever the user types in
    const onSearchChange = (e) => {
        setSearchInput(e.target.value)
    }

    //use the API providing it the search input, and 
    //setCompanies hook to update list of panies
    const onSearchSubmit = (e) => {
        e.preventDefault()
        fetchSearchData(searchInput, setCompanies)
    }


    return (
        <div>
            <Container>
                <Row className={"searchFilterBar"}>
                    <Col sm={6} md={8} className={"searchBar"}>
                        <SearchBar onSubmit={onSearchSubmit} onChange={onSearchChange} value={searchInput} />
                    </Col>
                    <Col sm={6} md={4} className={"filterBar"}>
                    </Col>
                </Row>
                <CompanyList panies={panies} ></CompanyList>
                <Row>
                </Row>
            </Container>
        </div>
    )
}
export default Search;

SearchBar ponent source code:

const SearchBar = ({value,onSubmit, onChange}) => {
    return (
        <Form
            className="search-form"
            onSubmit={onSubmit}
            >
            <div className="input-group">
                <span className="input-group-text rubik-font">
                    <i className="icon ion-search"></i>
                </span>
                <input
                    className="form-control rubik-font"
                    type="text"
                    placeholder="Search for panies that start with..."
                    onChange={onChange}
                    value={value}
                />
                <Button className="btn btn-light rubik-font" type="submit">Search </Button>
            </div>
        </Form>
    )

}

CompanyList ponent source code:

function MapDataToCompanyList(response) {
    console.log(response); //Logging occurs here
    if(!response || response===undefined || response.length===0)
    {
        return (<ErrorBoundary message={noCompaniesError.message}></ErrorBoundary>)
    }
    return response.map((pany) => {
        return (
            <Col key={pany._id} xs={12} md={6} lg={4} className="mt-2">
                <CompanyCard
                    id={pany._id}
                    logo={pany.logo}
                    title={pany.name}
                    logoBackground={pany.logoBackground}
                    progLangs={pany.progLangs}
                    backend={pany.backend}
                    frontend={pany.frontend}
                    url={pany.url}
                >
                </CompanyCard>
            </Col>
        )
    })
}
const CompanyList = (props) => {
    const {panies} = props
    return (
        <div>
            <Container className="mt-3">
                <Row>
                    {
                    MapDataToCompanyList(panies)
                    }
                </Row>
            </Container>
        </div>
    )
}

export default CompanyList;

FetchSearchData function source code:

export const fetchSearchData = (query, cb)=>{
    const uri = process.env.NODE_ENV === 'development' ?
    `http://localhost:3000/api/panies/name/${query}` :
    ``;
    axios.get(uri, {
        timeout: MAX_TIMEOUT
    })
    .then((response)=>{
        cb(response.data.data)
    })
    .catch((error)=>{
        console.log(error)
    })
}

As seen above, empty list of panies is logged when the page first loads, then I typed three characters and the it logged three time which means the map function called three times.

Even then if I pressed submit and retrieved list of panies normally, whenever I type it will keep printing the array of panies that was fetched.

Sorry if I missed something, I am still new to React.

Share Improve this question edited Feb 21, 2022 at 14:16 Youssouf Oumar 46.3k16 gold badges101 silver badges104 bronze badges asked Feb 21, 2022 at 11:50 miserylordmiserylord 3656 silver badges15 bronze badges 3
  • Try to add 'e.preventDefault()' in the onChange function – Salah ED Commented Feb 21, 2022 at 12:06
  • You understood why this happens by reading the below answers. Now, if you really want to see how many times CompanyList will be infact re-rendereder, you could use a useEffect with empty deps list like: useEffect(() => { console.log("CompanyList rendering"); }); – Giovanni Esposito Commented Feb 21, 2022 at 12:09
  • I'm sure you made a typo, but just pointing out that this is not "empty deps" (onMount) that is "undefined deps" (everyRender) – Phil Commented Sep 4, 2023 at 14:49
Add a ment  | 

3 Answers 3

Reset to default 4

You do not need to maintain a state for input field. You can use useRef and pass it to input like below.

<input
  ref={inputRef}
  className="form-control rubik-font"
  type="text"
  placeholder="Search for panies that start with..."
/>

And you can get get value inside onSearchSubmit using inputRef.current.value

This will not re-render you ponent on input change.

When you call setSearchInput(e.target.value), Search ponent will re-render cause its state has changed. Search ponent re-renders means every tag nested in it will re-render (except the ones passed via children). That is the normal behaviour of React. If you want to avoid that, you would wanna use React.memo for CompanyList. Or you could use useRef to bind the input like so:

const Search = () => {
  const [panies, setCompanies] = useState([]); //List of panies returned from searching
  const inputRef = React.useRef(null);
  //use the API providing it the search input, and
  //setCompanies hook to update list of panies
  const onSearchSubmit = (e) => {
    e.preventDefault();
    fetchSearchData(inputRef.current.value, setCompanies);
    inputRef.current.value = "";
  };

  return (
    <div>
      <Container>
        <Row className={"searchFilterBar"}>
          <Col sm={6} md={8} className={"searchBar"}>
            <SearchBar inputRef={inputRef} onSubmit={onSearchSubmit} />
          </Col>
          <Col sm={6} md={4} className={"filterBar"}></Col>
        </Row>
        <CompanyList panies={panies}></CompanyList>
        <Row></Row>
      </Container>
    </div>
  );
};
export default Search;
const SearchBar = ({ onSubmit, inputRef }) => {
  return (
    <Form className="search-form" onSubmit={onSubmit}>
      <div className="input-group">
        <span className="input-group-text rubik-font">
          <i className="icon ion-search"></i>
        </span>
        <input
          ref={inputRef}
          className="form-control rubik-font"
          type="text"
          placeholder="Search for panies that start with..."
        />
        <Button className="btn btn-light rubik-font" type="submit">
          Search
        </Button>
      </div>
    </Form>
  );
};

I don't get why CompanyList re-renders.

Because it's nested in your Search ponent, and it's not React.memo'd (or a PureComponent).

Yes, the ponent is updated, but that doesn't mean it necessarily causes a DOM reconciliation.

In any case, React is pletely at liberty of calling your ponent function as many times as it likes (and indeed, in Strict Mode it tends to call them twice per update to make sure you're not doing silly things), so you should look at side effects (such as console logging) in your ponent function (which you shouldn't have in the first place) as performance guidelines.

发布评论

评论列表(0)

  1. 暂无评论