1

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>         
  )
}
rtwhite
  • 399
  • 1
  • 3
  • 8
  • thats because since there is no change in state value the useeffect will not be triggered and nothing will re-render. – Sujit.Warrier Oct 05 '20 at 02:31
  • totally. I'm just unsure why it doesn't update when the asset is re selected. Should you be able to update state with the same data or previous data when the asset is clicked more than once? – rtwhite Oct 05 '20 at 02:34

1 Answers1

1

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.

LesmoCasanova
  • 26
  • 1
  • 4