1

I have code where if a function is invoked it will call toggleCheckedUser and pass along information about which object property to toggle. Then saves the modified object back to state (selectedSendTo).

However, when I run this, the toggle it works, but when I try to edit a second property, before changing it I try console.log(selectedSendTo) I always get the initial value whether it be an empty object {} or false instead of the previously updated object.

When I use useEffect to spy on selectedSendTo I can see that the setSelectedSendTo() function correctly updated the object. So for some reason when I revisit the object it's empty.

const [selectedSendTo, setSelectedSendTo] = useState(false);
const toggleCheckedUser = (companyID, contactID) => {
    console.log(companyID, contactID); 
    console.log(selectedSendTo); // THIS VALUE IS always the same as INITIAL value
    console.log(selectedSendTo[companyID]);
    if(selectedSendTo[companyID] && 
        selectedSendTo[companyID][contactID] === true){
            //remove it
            delete(selectedSendTo[companyID][contactID]);
    }else{
        setSelectedSendTo({
            ...selectedSendTo,
            [companyID]:{
                ...selectedSendTo[companyID],
                [contactID]: true,
            }
        })
    }
}

Here is the DOM:

<CustomCheckbox 
    className="person__checkbox" name={`checkbox-${contactID}`}
    alreadySelected={
       selectedSendTo[company.companyID] &&
       selectedSendTo[company.companyID][contactID]
      }
    onChange={() => toggleCheckedUser(company.companyID, contactID)} 
/>

UPDATE, A POSSIBLE SOLUTION

I found that the following works:

To be able to access the current value from useState I used useRef

const selectedSendToRef = useRef();
useEffect( () => {
        selectedSendToRef.current = selectedSendTo;
    }, [selectedSendTo])

Then inside of my function, I can use selectedSendToRef.current to access the most recent value of `selectedSendTo.

When updating state, I can access the most recent version from state using

setSelectedSendTo( prevValue => ....)

const toggleCheckedUser = (companyID, contactID) => {
    console.log(companyID, contactID, selectedSendToRef.current);
    console.log('selectedSendTo[companyID]: ', selectedSendTo[companyID]);

    let newValue; 
    if(selectedSendToRef.current[companyID] && 
        selectedSendToRef.current[companyID][contactID] === true){
        newValue = false;
    }else{
        newValue = true;
    }
    setSelectedSendTo(prevValue => (
        {
            ...prevValue,
            [companyID]:{
                ...prevValue[companyID],
                [contactID]: newValue,
            }
        }
    ));
}

UPDATE 2: The Real Solution

Okay so it seems like the problem was that even after a render, the child component was not receiving the updated state because of how I had used nested functions to create the elements.

Here is how I had things

<Main Component>
    <div>
      {Object_1}
    <div>
</Main Componenent

and object_1 was defined something like this:

const Object_1 = 
   <React.Fragment>
        <h1>Random Header</h1>
        {StateObject_Containg_Elements}
   </React.Fragment>

Now to create the state object that conatined the elements I wanted to display I was using a funciton called by a useEffect hook. Basically when the server sent back data that I needed, I would tell the useEffect hook to run a function called createElements

const createElements = (data) => {
    const elements = Object.keys(data).map( item => return(
       <ul>
           {subFunction1(item)}
       </ul>

     subFunction1(item){
        item.contacts.map( name => {
            reutrn <CustomCheckbox name={name} checked={selectedSendTo[name]}
        })
     }
    saveElementsToState(elements);
}

As you can see we basically have a function that runs 1 time (on server response) that triggers a function that creates the array of elements that we want to display which has its own nested subfunction that includes the child component that we are asking to watch a different state object to know whether it should be checked or not.

So What I did was simplify things, I turned {Object_1} into it's own functional component, lets call it <Object1 />. Inside the component instead of calling a function I just put the function code in there to loop through and return the elements (no longer saving elements to state) and lastly I no longer needed the useEffect since just updating the state object with the data once it gets it from the server would cause my subcomponent to re-render and create the elements. Inside the sub-component I simply return null if the data in state is null.

That fixed all my problems.

so now it looks something like this:

const Object1 = () => {
   if(!data)return null;

   return(
     Object.keys(data).map( item => return(
           <ul>
               {subFunction1(item)}
           </ul>

         subFunction1(item){
            item.contacts.map( name => {
                reutrn <CustomCheckbox name={name} checked={selectedSendTo[name]}
            })
         }
   )
}


return(
   <div>
     <Object1 /> //This is what contains/creates the elements now
   </div>
)
Badrush
  • 1,247
  • 1
  • 17
  • 35
  • 1
    Does this answer your question? [What is the intention of using React's useCallback hook in place of useEffect?](https://stackoverflow.com/questions/54371244/what-is-the-intention-of-using-reacts-usecallback-hook-in-place-of-useeffect) – Emile Bergeron Dec 06 '19 at 20:37
  • Also, [don't mutate the state](https://stackoverflow.com/q/37755997/1218980) directly (e.g. with `delete`). – Emile Bergeron Dec 06 '19 at 20:40
  • Regarding the delete, I should use the spread operator then delete (then update state), correct? – Badrush Dec 06 '19 at 20:42
  • 1
    There's really no need to remove the key from the object, just setting it to `false` or `null` should be enough. – Emile Bergeron Dec 06 '19 at 20:46
  • Okay. As well, I updated my question with the DOM to give you a better idea. The toggle funciton is triggered by the `onChange` event. – Badrush Dec 06 '19 at 20:51
  • wrapping `toggleCheckedUser` in `useCallback` didn't help. I figured out half the issue. I need to use `setSelectedSendTo(prevValue => ( . . . .` to access the old state value when updating. But inside of the parent function, I have no way of doing that. – Badrush Dec 06 '19 at 20:55
  • You're calling the function instead of passing a new callback. Instead: `onChange={() => toggleCheckedUser(company.companyID, contactID)}` – Emile Bergeron Dec 06 '19 at 20:56
  • Okay. You're right, that was just a typo from when I tried useCallback. But the issue still exists. – Badrush Dec 06 '19 at 20:58
  • I'm not able to reproduce the problem with the code you shared, please provide a [mcve] that demonstrates the problem. – Emile Bergeron Dec 06 '19 at 21:05
  • You should also [use the _updater_ function to receive the current state](https://stackoverflow.com/q/55510565/1218980) and use it to build the new state. – Emile Bergeron Dec 06 '19 at 21:08
  • 1
    Thanks for your help. I figured out a solution that uses useRef() and I'll post it as an update. – Badrush Dec 06 '19 at 21:32
  • 2
    Using ref here is code smell. It's a patch to a problem you haven't found yet. Unfortunately, without a [mcve], we can't really help. Your code should work as-is (except the `delete` which is probably one of the reasons why it's causing you issues). – Emile Bergeron Dec 06 '19 at 21:41
  • 2
    Agreed, `useRef` is an escape hatch for when you want to store a changing value but not re-render when that value changes. It's rare that you'll need to use it in a component directly; it's more commonly used in custom hooks. – backtick Dec 08 '19 at 20:46
  • Appreciate the feedback. I found this guide as the inspiration to use `useRef` (but I didn't use the useCallback) https://reactjs.org/docs/hooks-faq.html#how-to-read-an-often-changing-value-from-usecallback – Badrush Dec 09 '19 at 15:23
  • @EmileBergeron also, I removed the `delete` command (you can see it in the updated code) and it doesn't change the fact that inside of that `toggleCheckedUser` function, the value of the state object: `selectedSendTo` is still the initial value. I believe it's because the parent container is not re-rendering when I select the checkbox but the child component (``) is. If that's even possible. – Badrush Dec 09 '19 at 15:31
  • 1
    Here's [how to create an interactive stack snippet that uses JSX and React](https://meta.stackoverflow.com/a/338538/1218980). If you're able to demonstrate the problem in your question, we'll be able to help more. You're focusing too much on the callback when the problem is probably elsewhere. – Emile Bergeron Dec 09 '19 at 15:36
  • Thanks. I tried adding a snippet but it won't run (you can take a look though at my code. – Badrush Dec 09 '19 at 15:57

0 Answers0