brenobarreto
brenobarreto

Reputation: 325

React children components not re-rendering when parent's state changes

I have a component Form which returns a component Menu that is conditionally rendered depending on a switch.

This component Menu has a bunch of children components, including an Accordion (whose items render a card each) and a SpeedDial (I'm using Material UI).

When the user clicks a specific SpeedDial action button, the accordion should present an additional item.

This is a summarized hierarchy:

Form <- Menu <- MenuTabs <- TabPanel <- Accordion <- SpeedDial

Currently the state object is being changed as I expected but the Accordion isn't automatically re-rendered. The user needs to refresh the screen to see the new item.

I don't understand why, since I believed that when the state of a parent component changed (the Form in my case) the children of this component would be re-rendered (the Accordion in my case).

The parent component (Form) has the following state:

    const [dishCards, setDishCards] = React.useState(
        [
            {
                tabLabel: 'General',
                tabDishes: [
                    {
                        dishName: 'Dish 1',
                        dishPrice: '',
                        vegan: false,
                        vegetarian: false,
                        spicy: 0
                    }
                ]
            }
        ]
    );

The Accordion component renders the cards depending on the state:

    return (
        <div>
            {keys.map(cardNum => {
                const dishName = dishCards[currentTab].tabDishes[cardNum].dishName;
                return (
                    <Accordion key={cardNum}>
                        <AccordionSummary
                            expandIcon={<ExpandMoreIcon />}
                            aria-controls="panel1a-content"
                        >
                            <Typography>{dishName}</Typography>
                        </AccordionSummary>
                        <AccordionDetails>
                            <DishCard formik={formik} currentTab={currentTab} num={cardNum} dishCards={dishCards} />
                        </AccordionDetails>
                    </Accordion>
                )
            })}
            <Stack direction="row">
                <EditSpeedDial formik={formik} currentTab={currentTab} open={editDialogOpen} setOpen={setEditDialogOpen} handleDialogClose={handleEditDialogClose} />
                <AddSpeedDial formik={formik} currentTab={currentTab} open={createDialogOpen} setOpen={setCreateDialogOpen} handleDialogClose={handleCreateDialogClose} dishCards={dishCards} setDishCards={setDishCards}/>
            </Stack>
        </div>
    );

While the SpeedDial component sets the state as such:

    function handleNewDish() {
        const dishesCount = dishCards[currentTab].tabDishes.length;

        const newDish = {
            dishName: `Dish ${dishesCount+1}`,
            dishPrice: '',
            vegan: false,
            vegetarian: false,
            spicy: 0
        }

        const newDishCards = dishCards;
        newDishCards[currentTab].tabDishes.push(newDish);

        setDishCards(
            newDishCards
        );
    }

I saw a similar question and the answer had to do with not directly mutating the state but I believe I'm not doing that, since I'm assigning the previous state (dishCards) to a new variable before mutating it.

(By the way, I know I could use Redux to better manage state but nevertheless I'd like to understand why the behavior is not as I expected.)

Upvotes: 0

Views: 57

Answers (1)

rantao
rantao

Reputation: 1832

Inside handleNewDish, try changing:

const newDishCards = dishCards;

to:

const newDishCards = [...dishCards];

By spreading the old array into a new one, you change the reference of newDishCards and dishCards, telling React to re-render when you call setDishCards

Upvotes: 1

Related Questions