0

When setting useState const [searchString, setString] = useState([]) with a string setString("some string") it will be sent it immediately and shown at the other component <Home/>,
BUT when I send an array it will be stated settingResults(resultArray) only after the second onClick here.
I tried settingResults([...resultArray])- its the same.

All functions checked with console.log().

Header.jsx

function Header({ settingResults }) {
  const [productsObj, setObjs] = useState([]);
  const [searchString, setString] = useState('');
  let resultArray = [];


  const onChangHandler = (e) => {
    setString(e.target.value);
  };

  const activeSearch = () => {
    if (searchString.length > 0) {
      resultArray = productsObj.filter((obj) =>
        obj.productName.toLowerCase().includes(searchString.toLowerCase())
      );
      if (resultArray.length !== 0) {

        settingResults(resultArray);
      }
    }
    resultArray = [];
  };

  return (
    <div>
      <header className='header-shop'>
        Welcome to Vitamins Store
        <br />
        <input
          placeholder='Search here'
          value={searchString}
          onChange={(e) => onChangHandler(e)}
        />
        <button onClick={activeSearch}>Search</button>
      </header>
    </div>
  );
}

App.js

function App() {
  const [searchString, setString] = useState([]);

  return (
    <div className='App'>
      <Header settingResults={setString} />
      <Home searchResults={searchString} />
      <Footer />
    </div>
  );
}

Home.jsx

function Home({ searchResults }) {
  const [itemSearchResults, setResults] = useState([]);
  const [viewResults, setViewResult] = useState(null);
  let itemsFound = [];

  useEffect(() => {
    setResults(searchResults);
    itemsFound = itemSearchResults.map((productObj) => {
      return (
        <div key={productObj.id}>
          {productObj.productName} <br />
          {productObj.price}
          <br />
          <img src={productObj.image} />
        </div>
      );
    });
    setViewResult(itemsFound);
  }, [searchResults]);

  return (
    <div>
      <h3>Home</h3>
      <h1>{viewResults}</h1>
    </div>
  );
}

Anyone know why useState won't work first time with an object-array ?

ExtraSun
  • 528
  • 2
  • 11
  • 31
  • I believe your ```resultArray``` be re-rendered every time you set a value with ```setString```. Maybe you can use ```useRef``` instead to prevent this to happening. – Raden Kriting Jul 05 '21 at 15:54
  • using `let resultArray` means the reference is always the same... you should probably store that in a state. – Noriller Jul 05 '21 at 16:09
  • Can you post the `Home` code where the results get used? You should consider calling `setttingResults` even when the results or search are empty in order to clear past searches. – edemaine Jul 05 '21 at 16:17
  • @edemaine As you asked `Home` is on ! – ExtraSun Jul 05 '21 at 16:30
  • @Noriller When doing that it will take 3 times(!!!) to onClick to send the array props. – ExtraSun Jul 05 '21 at 16:38
  • There's also a couple working example of your code in [my answer](https://stackoverflow.com/a/68248921/1218980) on your previous question, which shows exactly what to do to list the results. (see the interactive "Show code snippet" sections) – Emile Bergeron Jul 05 '21 at 17:05

1 Answers1

0

The issue with your code are these two lines:

    setResults(searchResults);
    itemsFound = itemSearchResults...

Calling setResults does not immediately change itemSearchResults. Instead, it queues a change to happen after the current render. But this code won't get called again until searchResults changes again.

Why are you using itemSearchResults at all? It seems like you could just use searchResults, like the following minimal change: (but see below for better answers)

function Home({ searchResults }) {
  const [viewResults, setViewResult] = useState(null);

  useEffect(() => {
    const itemsFound = searchResults.map((productObj) => {
      return (
        <div key={productObj.id}>
          {productObj.productName} <br />
          {productObj.price}
          <br />
          <img src={productObj.image} />
        </div>
      );
    });
    setViewResult(itemsFound);
  }, [searchResults]);

  return (
    <div>
      <h3>Home</h3>
      <h1>{viewResults}</h1>
    </div>
  );
}

This code would also be better written with useMemo:

function Home({ searchResults }) {
  const viewResults = useMemo(() => {
    return searchResults.map((productObj) => {
      return (
        <div key={productObj.id}>
          {productObj.productName} <br />
          {productObj.price}
          <br />
          <img src={productObj.image} />
        </div>
      );
    });
  }, [searchResults]);

  return (
    <div>
      <h3>Home</h3>
      <h1>{viewResults}</h1>
    </div>
  );
}

As pointed out in the comments, it's dangerous to memoize in this way, and you'd be better off directly rendering like so:

function Home({ searchResults }) {
  return (
    <div>
      <h3>Home</h3>
      <h1>
        {searchResults.map((productObj) =>
          <div key={productObj.id}>
            {productObj.productName} <br />
            {productObj.price}
            <br />
            <img src={productObj.image} />
          </div>
        )}
      </h1>
    </div>
  );
}

// This will guarantee re-rendering only when the props change.
Memo = React.memo(Memo);
edemaine
  • 2,699
  • 11
  • 20
  • Thank you very much! it's now working, I am confused with the `useState` , It happened in the `Header` component but wasn't read at the `Home` component. Now you explained me why. I think with setState() in class based component its working immediately (idk really) becasue I use Hooks, think they are much more better. Now going to read about `useMemo`. – ExtraSun Jul 05 '21 at 16:57
  • 1
    Do not memoize JSX, just render it. – Emile Bergeron Jul 05 '21 at 17:00
  • I'm trying to understand `useMemo`, if same search result are being send in props to `Home` so `useMemo` will prevent rendering the DOM ? *How can I check it* ? In ***reactjs.org*** - "useMemo will only recompute the memoized value when one of the dependencies has changed" - What does this mean ? – ExtraSun Jul 05 '21 at 17:18
  • @EmileBergeron Thanks for showing here, Can you explain better "Do not memoize JSX" ? I'm reading a-lot but didn't figure it out. – ExtraSun Jul 05 '21 at 17:30
  • 1
    @Sunny rendered elements (JSX) should only be placed inside the render phase. In the case of memoized elements, older elements that were rendered in a previous phase will be used for the current render phase, which is similar to rendering outside the render phase. The lifecycle of these elements could be messed up, there's a lot of potential problems with this approach, which would be an anti-pattern in React. – Emile Bergeron Jul 05 '21 at 17:47
  • Also, there's [`React.memo`](https://reactjs.org/docs/react-api.html#reactmemo) which is different than `useMemo`, and would be fine to use on this component, though probably unnecessary. – Emile Bergeron Jul 05 '21 at 17:49
  • This answer is also suggesting to [put elements inside the state, which is another anti-pattern in React](https://stackoverflow.com/a/47875226/1218980). Here's [how to render a list of objects](https://stackoverflow.com/q/41374572/1218980). – Emile Bergeron Jul 05 '21 at 18:00