Felipe Rueda
Felipe Rueda

Reputation: 97

React component rendering twice when using useState hook

I haven't been able to understand why my App react component is rendering twice, as seen in the gif below.

enter image description here

I inserted a console.log just before returning the component to see how many times my component was rendering.

Whenever I remove the useState hook, my app renders just once as I suppose should be. Any guidance on why this is happening is welcome

import React, { useState, useEffect } from 'react';

const ListItem = ({ title, url, author, num_comments, points }) => {
  return (
    <div>
      <span>
        <a href={url} target='_blank' rel='noopener noreferrer'>
          {title}
        </a>{' '}
        by {author}
      </span>
      <br />
      <span>Comments: {num_comments}</span>
      <br />
      <span>Points: {points}</span>
      <hr />
    </div>
  );
};

const List = ({ list }) => {
  return list.map(({ objectID, ...item }) => (
    <ListItem key={objectID} {...item} />
  ));
};

const Search = ({ search, onSearch }) => {
  return (
    <div>
      <label htmlFor='search'>Search: </label>
      <input id='search' type='text' value={search} onChange={onSearch} />
      <p>
        Searching for <strong>{search}</strong>
      </p>
    </div>
  );
};

const App = () => {
  const stories = [
    {
      title: 'React',
      url: 'https://reactjs.org/',
      author: 'Jordan Walke',
      num_comments: 3,
      points: 4,
      objectID: 0,
    },
    {
      title: 'Redux',
      url: 'https://redux.js.org/',
      author: 'Dan Abramov, Andrew Clark',
      num_comments: 2,
      points: 5,
      objectID: 1,
    },
  ];

  const [search, setSearch] = useState(localStorage.getItem('search') || '');

  useEffect(() => {
    localStorage.setItem('search', search);
  }, [search]);

  const handleSearch = (event) => {
    setSearch(event.target.value);
  };

  console.log('rendered');

  return (
    <div className='App'>
      <h1>My Hacker Stories</h1>
      <Search search={search} onSearch={handleSearch} />
      <hr />
      <List
        list={stories.filter((story) =>
          story.title.toLowerCase().includes(search.toLowerCase())
        )}
      />
    </div>
  );
};

export default App;

Upvotes: 2

Views: 3029

Answers (4)

Pulkit Gupta
Pulkit Gupta

Reputation: 1059

Even I was wondering why my controls are rendering twice.

I had no useEffect hook in most of my components and mostly I am just setting the state(immutably) as the user is entering data in input boxes. This is also happening in every component that has two why binding.

DevTools: I tried my app in a browser with no devtools installed and still the same issue.

React.StrictMode I think this is the potential culprit. When I removed this tag from index.js all the components start working correctly. I also read in the official documentation that the strictMode checks are executed only in dev mode and are ignored in production build.

This makes me think that our code is correct and we can ignore the re-rendering issue in dev.

Upvotes: 5

denislexic
denislexic

Reputation: 11342

Your ‘setSearch’ is updating the vue for the input box, and then your ‘useEffect’ updates it again when search changes.

Remove the useEffect

Then

const handleSearch = (event) => {
    setSearch(event.target.value);
    localStorage.setItem('search', event.target.value)
  }

Here is a sandbox link: https://codesandbox.io/s/dawn-night-h2xiz?file=/src/App.js

It indeed doesn't fix it, but will probably avoid you some issues in the future.

The double render should only happen in development mode and not production. See Dan Abramov reponse here: https://github.com/facebook/react/issues/15074

Upvotes: 2

Jagrati
Jagrati

Reputation: 12222

Check this out : https://github.com/facebook/react-devtools/issues/1297

The "unexpected re-render" isn't actually caused by useEffect specifically– but rather, it's the way DevTools "inspects" hooks values by re-rendering the function component in isolation.

While I understand that unexpected renders can indicate problems in some cases, this particular one shouldn't actually be a problem for several reasons:

The renders aren't recursive. (Child components aren't rendered.) The renders only happen for users with DevTools installed, and even then– only impact a single component (the one currently selected in the tree). The renders don't have side effects (e.g. the DOM won't be updated).

Upvotes: 4

Raz Chiriac
Raz Chiriac

Reputation: 416

I agree with @denislexic, here's one way to fix the issue.

Instead of

useEffect(() => {
  localStorage.setItem('search', search);
}, [search]);

const handleSearch = (event) => {
  setSearch(event.target.value);
};

Let's do the following:

const handleSearch = (event) => {
  const search = event.target.value;
  setSearch(search);
  localStorage.setItem('search', search);
};

This accomplishes the same 2 tasks (save to state, and to localStorage) in one routine instead of 2. Hint: useEffect causes a re-render

Hope that helps.

Cheers! 🍻

Upvotes: 0

Related Questions