How to retain State values after setting them in UseEffect

I am currently having trouble with this code

const commerce = new Commerce(process.env.REACT_APP_API_KEY)
  const [shopItems,setShopItems] = useState("")
  useEffect(()=> {
    commerce.products.list().then((product) => {
      setShopItems(product)
      console.log(shopItems.data)
    });
  }, [])

  function categorize(category) {
    let categoryarray = []
    shopItems.data.forEach(el=> {
      if (el.categories.slug == category) categoryarray.push(el);
    })
    console.log(categoryarray)
  }

whenever I call categorize it says that shopItems has not been defined although I clearly defined it when calling useEffect. Can anyone help me with this? Thanks in Advance.

1 Like

What is the exact error message? Is it saying that shopItems is undefined or is it that shopItems.data is undefined.

Also, that forEach method - I think filter would be a better fit there.

it says that shopItems itself reverted back to the original state value, which is just “” (cant read .data of undefined), and yes, I totally forgot about filter. Thanks

reverted back to the original state value, which is just “” (cant read .data of undefined)

I don’t understand that statement. If it reverts back to "", then shopItems would be defined, it’s data that wouldn’t be.

Also, if shopItems is expected to be an object, then I would expect its default to be an empty object or null.

oh sorry, I meant shopItems.data was undefined. weird thing is, when i console log shopItems.data in useEffect it also returns undefined. But, if I console.log(shopItems) before console.log(shopItems.data) it goes back to normal

Well, setting state is not synchronous. It’s probably a race condition - it simply hasn’t been updated by the time you check it.

That being said, that doesn’t explain why it would be undefined in your function.

Can you log out shopItems at the beginning of categorize? Do you call _ setShopItems_ anywhere else?

No, I didnt call setShopItems from anywehre else in my code. When I console.log shopItems in categorize, it returns the original useState value ("").

How is categorize being called? Are you sure that shopItems is getting populated first?

If you are calling it somewhere in your component “automatically” (as opposed to triggered by a user interaction) then it may be called before that variable is set. One solution would be to put protection, like:

shopItems.data && shopItems.data.forEach(el=> {

or

shopItems.data?.forEach(el=> {

if you’re set up for optional chaining.


Do you have a repo for this?

it’s not called automatically, I mapped the categorize function to an onClick event, so I doubt that it’s the case. I have not uploaded this project so no repo yet im afraid

Sorry, but then I’m not sure how to help. There is something wrong with the sequence of events here and I don’t think I can get it without more info. Could you at least post that entire file?

yeah, sure.

import {ThemeProvider} from '@material-ui/core'
import theme from './theme';
import './App.css';
import Navbar from './components/Navbar';
import LandingPage from './components/LandingPage';
import Commerce from '@chec/commerce.js';
import { useEffect, useState } from 'react';


function App() {
  const commerce = new Commerce(process.env.REACT_APP_API_KEY)
  const [shopItems,setShopItems] = useState("s")
  useEffect(()=> {
    commerce.products.list().then((product) => {
      setShopItems(product)
    
    });
  }, [])

  function categorize() {
    // let categoryarray = []
    // shopItems.data.forEach(el=> {
    //   if (el.categories.slug == category) categoryarray.push(el);
    // })
    // console.log(categoryarray)
    alert("hi")
    console.log(shopItems)
  }
  return (
    <ThemeProvider theme = {theme}>
      <div className="App" onClick = {categorize}>
        <Navbar />
        <LandingPage items = {shopItems} ></LandingPage>
      </div>
    </ThemeProvider>
    
  );
}

export default App;

the onClick function on the App div was just to test the categorize function btw. Once i sort it out I would remove the onClick property

If you want to log out shopItems you can create a useEffect just for logging state. Just put the state you want to log in the dependencies so it gets logged any time it changes.

useEffect(() => {
  console.log(shopItems);
}, [shopItems]);

If shopItems.data is undefined inside your onClick function you might not be getting back the data as expected. I would suggest you just log out everything. Log commerce first to check it, then log product inside the .then() and then check shopItems inside the useEffect log.

it’s working now, but then it never stops logging, which I want to avoid, although it is not my top prioriity (although I can just stop console logging, but idk the implications it has on memory usage and such) . Any other suggestions?

EDIT: nevermind, apparently once i erased console.log(shopItems) from my useEffect, the error comes back again.

This type of logging is really only meant for development, not production. So it doesn’t really matter what effect it might have on performance. Although you would have to have an incredible amount of logging to affect performance and memory usage.

I don’t understand what you mean by this, nor do I know which useEffect you are talking about. I don’t see how removing a log from a useEffect can cause an error, even though you haven’t actually told us what error, “the error” is not very helpful information.

We do not have access to your data so we can’t know what is expected. We can only assume that shopItems.data is an array of objects that should be available as state after you set it inside the async code.

You will likely have to provide us with a live working example on something like CodeSandbox.

im not really sure how to do that in codesandbox (with Commerce.js and all that) but I do have a stackoverflow post (that no one has replied to yet unfortunately), which shows the data fetched.

and yes shopItems.data is indeed an array of objects.

I too am confused by this, since it doesnt really make any sense to me. Right after I remove the console.log(shopItems) it says:

TypeError: Cannot read properties of undefined (reading ‘forEach’)

actually this was the case the entire time. I waited for 2 seconds and everything went back to normal. That was really stupid and impatient of me. Thanks!

I’m still not sure I understand. What I infer is that the Promise in useState was taking longer to finish than you were expecting so you were calling categorize before the data was ready?

That should be easy to figure out with some carefully placed log statements. In any case, if that is what is happening, I would disable the button until the data is ready, either through a state variable or by checking if the data is valid (again, it should never be a string if the final data is an object.)