1
votes

I've been trying to add optional JSX based on the circumstance, so I have initialized them to null and updated them once the time is right so they contain JSX.

//initialize potentially unused parts
let monitor = null;
let keyboard = null;

...

Later in my JSX I use these to only display information if it exists.

{monitor}
{keyboard}

I did some research and found that my states were not updating because useState is asynchronous, so I set the useEffect hook in attempt to update these variables when state is finally updated.

const [monitorState, setMonitorState] = useState(0);
const [keyboardState, setKeyboardState] = useState(0);

useEffect(() => {
    monitor = (
        <li>
            <span className="bold">Monitor: </span>
            {monitorState.title}}
            <span className="right">${monitorState.price}</span>
        </li>
    );
}, [monitorState]);

const handler = (item) => {
    if (item.type === "monitor") {
        setMonitorState(item);
    }
    ...
}

This handler is called when an add button is clicked in a modal component:

<a
   onClick={() => props.handler(item)}
   href="#!"
   className="modal-close btn waves-light waves-effect grey darken-3 
   white-text modal-item-add">
   Add
</a>

However, I get an error:

Assignments to the 'monitor' variable from inside React Hook useEffect will be lost after each render. To preserve the value over time, store it in a useRef Hook and keep the mutable value in the '.current' property. Otherwise, you can move this variable directly inside useEffect.

I'm confused how exactly useRef will solve this problem and how to even implement that given this issue. I've looked at other threads but none of them have been done updating a JSX variable.

2
Could you post more of your code?Adrian Lynch

2 Answers

4
votes

Every time a functional component rerenders, the function is being called again by underlying React code. What would happen in this rerender is that all the variables inside the function would get reinitialised except the state and refs as these are stored seperately by React.

As the recommendation stands, let's look at how to correct this using refs:

const monitorRef = useRef(null);

useEffect(() => {
    monitorRef.current = (
        <li>
            <span className="bold">Monitor: </span>
            {monitorState.title}}
            <span className="right">${monitorState.price}</span>
        </li>
    );
}, [monitorState]);

return (
  <div>{monitorRef.current}</div>
)

Here the ref value is stored with React and hence is not destroyed on rerenders.

However, from the bits of code that you have shared, it seems like you could have just added the monitor code to render:

const [monitorState, setMonitorState] = useState(null);

const handler = (item) => {
    if (item.type === "monitor") {
        setMonitorState(item);
    }
    ...
}

return monitorState ? (
  <li>
     <span className="bold">Monitor: </span>
     {monitorState.title}}
     <span className="right">${monitorState.price}</span>
  </li>
) : null;
1
votes

You can think of useRef as a mutable object that remembers its value each render. Its value is always stored inside the current property. Thus, you could solve your problem this way:

const monitor = useRef(null)
and inside your useEffect:
monitor.current = ( <li>...</li> )

The above is one way to solve the problem using useRef, but I find it a little confusing. I think this problem is easily solved using javascript's && operator.

In your JSX, simply put:

{monitorState?.title && monitorState?.price &&
  <li>...</li>
)}

Of course if you wanted to be really accurate, you could check if title and price are undefined as well, since they could be empty string and 0, respectively.