Search code examples
reactjscomponentsuse-state

First click not re-rendering component in react


I am trying to create a simple app component that will be in used more than once that will be rendered maybe 30 times in some type of grid format. This component is just an icon really with some functionality. I want the ability to change the color of the icon I am clicking. Say I am rendering this component 50 times and I click on icon 34 then I just want icon 34 to change colors.

These components will all be rendered with a default value of 'green' or false...

THE PROBLEM:

The problem seems to be something fundemental that I don't understand about react. Someone with a lot more knowledge on react can perhaps explain what is happening.

Here is a detailed description of the issue.

So I am able to render the component 50 times like I had said earlier. It looks sort of like this

[ * * * * * * * * * * ]
[ * * * * * * * * * * ]
[ * * * * * * * * * * ]
[ * * * * * * * * * * ]
[ * * * * * * * * * * ]
[ * * * * * * * * * * ]

They all start out green as expected, but when I click one the first time nothing happens, but after a second time the icon changes from green to red as expected. The logic as I understand it is sound.

I take in the original state which is 'false' and so each book will have the value of selected to false.

if the value is false then the useState on the color variable will have an initial value of 'green'

so:

selected = false; color = green;

When an icon is clicked it runs the handle function below.

the function will set selected to the opposite of what it currently is so it will go from false to true

so: selected = true; color = green;

next the function will check selected for true or false. if it is true which it is it will change the color to red. But this is not what happens on the first go. The first click does nothing. I am assuming that it is changing it to green without me being able to tell because it is already green unless that is a wrong assumption.

here is the code to better re-make my issue

import react, {useState} from 'react'
import { FaBook } from 'react-icons/fa'





export default function BookIconFunc(props) {


    const [selected, setSelected] = useState(props.checkedOut)
    const [color, setColor] = useState(selected ? 'red' : 'green')

    /* 
    
    if selected = false that means it is available
    when clicked it should turn red
    
    */

    const handleColorChange = () => {
        console.log("HELLO")
        setSelected(!selected)
        
        if(selected){
            setColor('red')
            
        } else {
            setColor('green')
            
        }
    }

    const indBook = {
        margin: "0.5rem",
        color: color
    }





    return <FaBook style={indBook} size={32} onClick={handleColorChange}>

    </FaBook>

in a seperate component I am doing this

array = [1,2,3,4,5,6,7,8]
array.map((arr, index) => { 

    <BookIconFunc checkedOut={false}/>
}

I can tell that there is something I don't fully understand about react. I am just not sure as to what that is. I am very new to the language. thanks for any help.


Solution

  • The issue you are experiencing is a result of setSelected being asynchronous.

    In handleColorChange when checking if(selected) selected would still be the previous value.

    A way around this is to temporarily store your newly calculated value for use in further calculations, for example:

    const handleColorChange = () => {
        const newValue = !selected;
    
        setSelected(!newValue);
        
        if(newValue){
            setColor('red')
            
        } else {
            setColor('green')
            
        }
    }
    

    If I may make a suggestion, if color directly relies on the value of selected then you are better off not storing the value and just calculating it each render. For example:

    const color = selected ? 'red' : 'green';
    

    In the future if you have a more complex calculation that relies only on the value of other variables then you can make use of useMemo. For example:

    const color = useMemo(() => {
        // usually a more complex / costly calculation.
        return selected ? 'red' : 'green';
    }, [selected] /* Only update on change of selected */);