Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React not re-rendering after array state update

I have a checkbox list UI that is rendered from an array. After I update the array the checkbox list state does not update.

I moved the code where the list is mapped but it does not change the results. The DOM re-render does not happen, see gif below.

I have been looking arround and I see that this issue is already reported however the solution about moving the list.map code out of the function it did not work for me.

Could you suggest me a solution? What is the source of this problem?

This is the current state

import React,{ useState } from "react"

import
{
    Box,
    DropButton,
    Grid,
    Text,
    Calendar,
    RangeInput,
    CheckBox
} from "grommet"

const CalButton = ( { label,onSelect } ) =>
{
    return (
        <DropButton
            label={ label }
            dropAlign={ { top: 'bottom',left: 'left' } }
            margin="small"
            dropContent={
                <Box pad="medium" background="ligth-3" elevation="small">
                    <Calendar range size="medium" onSelect={ onSelect } />
                </Box>
            } />
    )
}

const RangeButton = ( { label,value,onChange,min,max,step,unit,header } ) =>
{

    return (
        <DropButton
            label={ value === null ? label : value + ' ' + unit }
            dropAlign={ { top: 'bottom',left: 'left' } }
            margin="small"
            dropContent={
                <Box pad="medium"
                    background="ligth-3"
                    elevation="small"
                    align="center"
                >
                    <Text size="small">{ header }</Text>
                    <RangeInput
                        value={ value }
                        min={ min } max={ max }
                        onChange={ onChange }
                        step={ step }
                    />
                    <Text weight="bold">{ value }</Text>
                    <Text weight="normal">{ unit }</Text>

                </Box>
            } />
    )
}

const FeaturesButton = ( { label,features,onChange } ) =>
{
    const FeaturesList = ( { features,onChange } ) => (
        <>
            { features.map( ( item,idx ) => (
            <CheckBox
                key={ item.name }
                label={ item.name }
                checked={ item.checked }
                onChange={ e => onChange( e,idx ) } />) 
                )
            }
        </>
    )



    return (
        <DropButton
            label={ label }
            dropAlign={ { top: 'bottom',left: 'left' } }
            margin="small"
            dropContent={
                <Box pad="medium"
                    background="ligth-3"
                    elevation="small"
                    align="start"
                    direction="column"
                    gap="small"
                >
                    <FeaturesList 
                        features={features} 
                        onChange={onChange} />

                </Box>
            } />
    )
}


const destApp = () =>
{

    const [ windStrength,setWindStrength ] = useState( null )
    const [ windFrequency,setWindFrequency ] = useState( null )
    const [ cost,setCost ] = useState( null )

    const date = new Date()
    const [ month,setMonth ] = useState( date.getMonth() )

    const [ listFeatures,setListFeatures ] = useState( [
        {
            name: 'Butter flat water',
            checked: false,
        },
        {
            name: 'Moderately flat water',
            checked: false,
        },
        {
            name: '1-2m Waves',
            checked: false,
        },
        {
            name: '2m+ Waves',
            checked: false,
        },
        {
            name: 'Beginer Friendly',
            checked: false,
        },
        {
            name: 'Kite-in-kite-out',
            checked: false,
        },
        {
            name: 'Nightlife',
            checked: false,
        }

    ] )
    const months = [ 'January','February','March','April','May','June','July','August','September','October','November','December' ];

    const updateFeaturesList = ( e,idx ) =>
    {

        listFeatures[ idx ].checked = e.target.checked
        const newFeatures = listFeatures
        setListFeatures( newFeatures )
        console.log( "Updated features list",newFeatures,e.target.checked )
    }

    return (
        <Grid rows={ [ "xsmall","fill" ] }
            areas={ [ [ "filterBar" ],[ "results" ] ] }
            gap="xxsmall">
            <Box gridArea="filterBar"
                direction="row-responsive"
                gap="xsmall"
                pad="xsmall"
                justify="center" >
                <CalButton label={ months[ month ].toLowerCase() } onSelect={ ( data ) => console.log( data ) } />
                <RangeButton
                    label="wind strength"
                    header="What's your wind preference?"
                    min="15"
                    max="45"
                    unit="kts"
                    value={ windStrength }
                    step={ 1 }
                    onChange={ ( e ) =>
                    {
                        setWindStrength( e.target.value )
                        console.log( windStrength )
                    } } />
                <RangeButton
                    label="wind frequency"
                    header="How often does your destination need to be windy?"
                    min="1"
                    max="7"
                    unit="days/week"
                    value={ windFrequency }
                    step={ 1 }
                    onChange={ ( e ) =>
                    {
                        setWindFrequency( e.target.value )
                        console.log( windFrequency )
                    } } />
                <RangeButton
                    label="cost"
                    header="Average daily cost: 1 lunch, diner and doubble room at a midrange hotel?"
                    min="10"
                    max="400"
                    unit="€"
                    value={ cost }
                    step={ 1 }
                    onChange={ ( e ) =>
                    {
                        setCost( e.target.value )
                        console.log( cost )
                    } } />

                <FeaturesButton
                    label="important features "
                    features={ listFeatures }
                    onChange={ updateFeaturesList }
                />
            </Box>
            <Box gridArea="results"
                margin="">
                Results go in here!

            </Box>


        </Grid>

    )
}

export default destApp 
like image 763
Muntram van Chen Avatar asked Sep 11 '19 10:09

Muntram van Chen


2 Answers

The problem is in updateFeaturesList, you are mutating the state directly in this line listFeatures[ idx ].checked = e.target.checked, the state reference stay the same and so react does not know if it should re-render.

What you can do is copy the state, before changing it :

const updateFeaturesList = ( e,idx ) =>
{

    const newFeatures = [...listFeatures];
    newFeatures[ idx ].checked = e.target.checked
    setListFeatures( newFeatures )
    console.log( "Updated features list",newFeatures,e.target.checked )
}
like image 189
Mohamed Ramrami Avatar answered Oct 13 '22 21:10

Mohamed Ramrami


You're mutating the original state in your updateFeaturesList function. Use the functional form of setState to update your current feature list:

const updateFeaturesList = (e, idx) => {
  const { checked } = e.target;
  setListFeatures(features => {
    return features.map((feature, index) => {
      if (id === index) {
        feature = { ...feature, checked };
      }

      return feature;
    });
  });
};

Also note that calling console.log("Updated features list", newFeatures,e.target.checked) immediately after setting the state won't show the updated state, since setting state is async.

like image 30
Clarity Avatar answered Oct 13 '22 20:10

Clarity