2

The below block of code works. Component rerenders when state changes from parent (MainBox)

export function MainBox(){ const [valuestate, setvalueState] = useState() return( <div> <Component value={valuestate}/> </div> ) } 

However I initially want to have a skeleton animation div, and make an api call to fetch some data. Once the data arrives I then render the Component. However when the state changes the Component does not rerender. On top of that, the value prop is always undefined

export function MainBox(){ const [valuestate, setvalueState] = useState() const [divstate, setdivState] = useState(<div className='skeleton'/>) useEffect(() =>{ GetUserInfo() },[]) async function GetUserInfo(){ let response = await fetch('some url', { method: 'GET', }).then(res => {return res.json() }).then(data => data) setvalueState(response) setdivState(<Component value={valuestate}/>) } return( {divstate} ) } 

The problem is that: Although Component does render the value withing in always undefined (regardless of how many times i change the valuestate from parent (MainBox)). Am I doing something wrong? Is there a different way to go about this?

1
  • .then(data => data) 🤨 Commented Oct 22 at 0:07

1 Answer 1

3

Don't store React components/JSX markup in React state, you are just asking for rendering issues due to Javascript Closures, specifically stale closures over old state.

The main issue here is that you have stored a specific instance of Component with a specific valuestate prop value from the specific render cycle the state update was enqueued and it will never see a different value unless the divstate state is updated with a newer value, i.e. the useEffect hook runs again or setdivState is called with a new value.

A secondary issue is that React does not immediately update state; there will be at least one render cycle between setvalueState(response) enqueueing a state update and setdivState(<Component value={valuestate} />) being able to reference any updated valuestate state value. In other words, you have used the yet-to-be-updated undefined valuestate value from the current render cycle the effect is running from.

In React, rendering UI is a function of state and props, you should store only data in state, and map the state to the expected rendered result, i.e. the JSX markup you wish to display.

Solution suggestion here is to eliminate the divstate state and just conditionally render either the loading skeleton or the Component with the current valueState value.

Example Implementation: (note that I've renamed valuestate to something a bit more informative and readable)

export function MainBox() { const [userData, setUserData] = useState(); useEffect(() => { async function getUserData() { try { const response = await fetch("some url", { method: "GET", }); const userData = await res.json(); // Got user data, update state to data setUserData(userData); } catch { // Error loading user data, set null setUserData(null); } } getUserData(); }, []) // While user data is loading if (userData === undefined) { return <div className="skeleton" />; } // User data is null, no data to display if (userData === null) { return <div>No data</div>; } // Have data, render component and pass as props return <Component value={userData} />; } 
Sign up to request clarification or add additional context in comments.

2 Comments

I'm a fan of explicit loading state instead of differentiating undefined from null. It separates the state of your app from the state of your data
Either works. I think it might typically boil down to how you want to treat null versus undefined. In simple cases like this answer example a 3-ary state is sufficient for demonstration, but in more complex scenarios a dedicated loading state is certainly beneficial.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.