Home > Net >  More than needed React components re-rendering when typing in search input
More than needed React components re-rendering when typing in search input

Time:02-21

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 companies are updated with the fetched list of companies from the API.

Then companies are passed to another component CompanyList where a map function is called there.

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

Search page source code:


const Search = () => {
    const [companies, setCompanies]=useState([]); //List of companies 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 companies
    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 companies={companies} ></CompanyList>
                <Row>
                </Row>
            </Container>
        </div>
    )
}
export default Search;

SearchBar component 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 companies that start with..."
                    onChange={onChange}
                    value={value}
                />
                <Button className="btn btn-light rubik-font" type="submit">Search </Button>
            </div>
        </Form>
    )

}

CompanyList component 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((company) => {
        return (
            <Col key={company._id} xs={12} md={6} lg={4} className="mt-2">
                <CompanyCard
                    id={company._id}
                    logo={company.logo}
                    title={company.name}
                    logoBackground={company.logoBackground}
                    progLangs={company.progLangs}
                    backend={company.backend}
                    frontend={company.frontend}
                    url={company.url}
                >
                </CompanyCard>
            </Col>
        )
    })
}
const CompanyList = (props) => {
    const {companies} = props
    return (
        <div>
            <Container className="mt-3">
                <Row>
                    {
                    MapDataToCompanyList(companies)
                    }
                </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/companies/name/${query}` :
    ``;
    axios.get(uri, {
        timeout: MAX_TIMEOUT
    })
    .then((response)=>{
        cb(response.data.data)
    })
    .catch((error)=>{
        console.log(error)
    })
}

Screenshot for the re-rendering problem

As seen above, empty list of companies 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.

enter image description here

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

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

CodePudding user response:

When you call setSearchInput, Search component will get re-rendered, because its state has changed. Search component re-renders means every tag nested in its return will get r-rendered (except the ones passed via children). That is the normal behavior of React. Now ff you wanna avoid this this, you would wanna use React.memo for CompanyList. Or you could use useRef to bind the input like so:

const Search = () => {
    const [companies, setCompanies]=useState([]); //List of companies returned from searching
    const inputRef= React.useRef(null)
    //use the API providing it the search input, and 
    //setCompanies hook to update list of companies
    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 companies={companies} ></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 companies that start with..."
               
                />
                <Button className="btn btn-light rubik-font" type="submit">Search </Button>
            </div>
        </Form>
    )

}

CodePudding user response:

I don't get why CompanyList re-renders.

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

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

In any case, React is completely at liberty of calling your component 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 component function (which you shouldn't have in the first place) as performance guidelines.

CodePudding user response:

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 companies that start with..."
/>

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

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

  • Related