Reputation: 399
I'm having an issue where useEffect isn't triggering a re-render based on useState changing or useState isn't changing which isn't triggering useEffect. I noticed this issue once I selected an asset that should update useState as the selected component and then I select another its no problem but once I select an asset that has already been selected.. nothing happens? Any suggestions or anything is greatly appreciated!! Thanks!
export default function SingleAsset({svg, name, size = '60', group}) {
const [assetType, setAssetType] = React.useState(null);
const [officeType, setOfficeType] = React.useState(null);
const [industrialType, setIndustrialType] = React.useState(null);
const [financingType, setFinancingType] = React.useState(null);
const [investmentType, setInvestmentType] = React.useState(null)
const acquistionStatus = useSelector(state => state.Acquisition)
const dispatch = useDispatch()
const classes = useStyles()
React.useEffect(() => {
if(financingType === 'Acquisition') {
const data = {financingType}
dispatch(updateFormData(data))
dispatch(toggleAcquisitionOn())
}
if(financingType) {
if(financingType !== 'Acquisition') dispatch(toggleAcquisitionOff())
const data = {financingType}
dispatch(updateFormData(data))
}
if(industrialType) {
const data = {industrialType}
dispatch(updateFormData(data))
}
if(officeType) {
const data = {officeType}
dispatch(updateFormData(data))
}
if(investmentType) {
const data = {investmentType}
dispatch(updateFormData(data))
console.log(data)
}
if(assetType) dispatch(updateAssetData(assetType))
console.log(financingType)
console.log(officeType)
console.log(industrialType)
console.log(investmentType)
},[investmentType,assetType,officeType,industrialType,financingType])
const handleSelect = (group, name) => {
switch(group) {
case 'main':
setAssetType(name)
break
case 'office':
setOfficeType(name)
break
case 'industrial':
setIndustrialType(name)
break
case 'financing':
setFinancingType(name)
break
case 'investment':
setInvestmentType(name)
break
default:
throw new Error('group not found')
}
}
return (
<Grid
className={classes.container}
item
>
<Grid
container
direction="column"
alignItems="center"
>
<IconButton onClick={() => handleSelect(group, name)}>
<img src={svg} color="white" height={size} />
</IconButton>
<Typography
variant="body1"
color="white"
align="center"
>
{name}
</Typography>
</Grid>
</Grid>
)
}
Upvotes: 1
Views: 66
Reputation: 26
That's actually an expected behavior.
React uses "shallow comparison" (check this other great question for more on that), which essentially means it'll compare the previous and new value with ===
. This is the reason one should not mutate state objects. Because of this, when your code tries to update the state to the same value it already has it won't actually do it... it's the same, so no re-render will be triggered.
To solve this, we can force React to update with some clever coding that will make React detect a state change:
// Setup a new state
const [, updateState] = useState();
// Create a function that will update state with a new object
// This works because {} === {} is always false, making React trigger a re-render
const forceUpdate = useCallback(() => updateState({}), []);
Your sample code would be something like this:
export default function SingleAsset({svg, name, size = '60', group}) {
const [, updateState] = useState();
const forceUpdate = useCallback(() => updateState({}), []);
const [assetType, setAssetType] = React.useState(null);
const [officeType, setOfficeType] = React.useState(null);
const [industrialType, setIndustrialType] = React.useState(null);
const [financingType, setFinancingType] = React.useState(null);
const [investmentType, setInvestmentType] = React.useState(null)
const acquistionStatus = useSelector(state => state.Acquisition)
const dispatch = useDispatch()
const classes = useStyles()
React.useEffect(() => {
if(financingType === 'Acquisition') {
const data = {financingType}
dispatch(updateFormData(data))
dispatch(toggleAcquisitionOn())
}
if(financingType) {
if(financingType !== 'Acquisition') dispatch(toggleAcquisitionOff())
const data = {financingType}
dispatch(updateFormData(data))
}
if(industrialType) {
const data = {industrialType}
dispatch(updateFormData(data))
}
if(officeType) {
const data = {officeType}
dispatch(updateFormData(data))
}
if(investmentType) {
const data = {investmentType}
dispatch(updateFormData(data))
console.log(data)
}
if(assetType) dispatch(updateAssetData(assetType))
console.log(financingType)
console.log(officeType)
console.log(industrialType)
console.log(investmentType)
},[investmentType,assetType,officeType,industrialType,financingType])
const handleSelect = (group, name) => {
switch(group) {
case 'main':
setAssetType(name)
break
case 'office':
setOfficeType(name)
break
case 'industrial':
setIndustrialType(name)
break
case 'financing':
setFinancingType(name)
break
case 'investment':
setInvestmentType(name)
break
default:
throw new Error('group not found')
}
}
return (
<Grid
className={classes.container}
item
>
<Grid
container
direction="column"
alignItems="center"
>
<IconButton onClick={() => {
handleSelect(group, name);
forceUpdate(); // <-- this is were the magic happens
}}>
<img src={svg} color="white" height={size} />
</IconButton>
<Typography
variant="body1"
color="white"
align="center"
>
{name}
</Typography>
</Grid>
</Grid>
)
}
Please note forcing a re-render should be a last resort, re-rendering can be a rather expensive operation and should be handled with care.
Upvotes: 1