Home > database >  How can I unrender a component conditionally?
How can I unrender a component conditionally?

Time:07-21

In my application I want to only display a component if two conditions are true. From the parent, this is what it looks like:

 {!online ? (
       <TouchableOpacity
           onPress={changeOnlineStatus}
       >
       <Text>
           Go online
       </Text>
       </TouchableOpacity>
  ) : online && queue === 0 ? (
       <TouchableOpacity
           onPress={changeOnlineStatus}
       >
            <Text>
                  Go offline
            </Text>
       </TouchableOpacity>
  ) : (
       <Service
           details={userDetails.queueItems[0].details}
       />
)}

Here, changeOnlineStatus status simply changes the online variable so that the two TouchableOpacities switch. The problem is the Service component. It should only be rendered if the queue is populated, but the queue exists as an array in a constants file. In the Service component's file, the queue is changed by a timer with the following states/hooks:

// timer value of 5
const [timeRemaining, setTimeRemaining] = useState(5);

// timer counts down to 0 and then removes the first element of the array
    useEffect(() => {
        setTimeout(() => {
            if (timeRemaining > 0) {
                setTimeRemaining(timeRemaining - 1);
            } else {
                userDetails.queueItems.shift();
                return;
            }
        }, 1000);
    }, [timeRemaining]);

So when the time runs out, the array has no elements, but the Service component stays rendered. Why isn't it removed since the condition (a populated array) is no longer true?

If it's important, the queue exists in a constants file and looks like this:

queueItems: [
    {
        title: "Test",

    },
]

CodePudding user response:

Components are rendered only when state or props are changing, you need to define the queue as a state in the parent component and inject the queue and the setQueue to the Service Component.

CodePudding user response:

I found a solution for this. As another user said, it has to do with mutating the state of the array (or an object) without changing its reference. A more detailed explanation can be found here.

The fix is super easy and involves making a copy of the array with the spread operator like so:

const [queue, setQueue] = useState(userDetails.queueItems);
const copiedQueue = [...queue];

Since React will only rerender when the reference has changed, simply editing the array/object won't trigger a rerender since the reference for JavaScript arrays and objects is not changed when the array or object is changed.

This can only be achieved by creating a copy of the array or object, and that copy will have a different reference. So, when I need the state to change for this array, instead of changing it directly with setQueue I should have been updating copiedQueue and setting the state of queue with copiedQueue like so:

copiedQueue.shift();
setQueue(copiedQueue);

And since I was making this change in a separate component, I simply needed to pass a prop to the component so I could update the state from there. I changed:

const [timeRemaining, setTimeRemaining] = useState(5);

    useEffect(() => {
        setTimeout(() => {
            if (timeRemaining > 0) {
                setTimeRemaining(timeRemaining - 1);
            } else {
                userDetails.queueItems.shift();
                return;
            }
        }, 1000);
    }, [timeRemaining]);

To:

useEffect(() => {
    setTimeout(() => {
        if (timeRemaining > 0) {
            setTimeRemaining(timeRemaining - 1);
        } else {
            // this is the prop passed in the parent
            // in the parent it's a function that updates the state
            onChange(true);
            return;
        }
    }, 1000);
}, [timeRemaining]);

I hope this helps someone in the future!

  • Related