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?
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
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 )
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With