Wednesday, May 4, 2022

React-window not refreshing FixedSizedList on state change

Admittedly, I'm fairly new to react and react-native is more my playground, but here was the issue.  I wanted to use react-window for a list of checkboxes.  The list was populated from a database of values where some were already selected.  Those values were in the state.  When a user clicks a checkbox, it should update the state and change the "checked" value of the box to the opposite of the current value.

Here's the issue -- react-window only re-renders when you scroll.  That's what it's designed to do to minimize data being loaded.  So, what I would get is everything working, but the checkboxes wouldn't change from checked to unchecked (and vice versa) unless I clicked THEN scrolled.

I tried LOTS of ways to get around this including using the scrollToItem method to try to get the window to scroll down and then back.  That partially worked.  I also tried using getElementById (going back to my old HTML days) but that doesn't work in React, and refs with 400 items seemed out of the question.  There was mention on gitHub of a resetAfterIndex method, but I couldn't get it to work.

Finally, I was able to force a refresh by using the "overscanCount" prop of the FixedSizeList.  The rows function  stayed the same, but I set a state variable called "overScan," set the FixedSizeList prop of overscanCount to that state variable, and then had the dataCheck funcion update the overScan state variable everytime it was called.  This forced the FixedSizeList to refresh anytime a checkbox was checked.

I don't LOVE the solution, but I could find no other way to make this work.  Best of luck!


Original Code


rows = ({ index, style }) => {
    var data = this.state.myData[index]
    return (
      <ListItem style={style} component="div" disablePadding >
        <ListItemButton role={undefined} onClick={()=>this.dataCheck(data.value)} dense >
          <ListItemIcon>
              <Checkbox checked={data.selected} />
          </ListItemIcon>
          <ListItemText primary={`${data.descr} - ${data.label}`} />
        </ListItemButton>
      </ListItem>
    );
  }

  dataCheck = (checkValue) => {
    let data = this.state.myData
    let dataIndex = findIndex(data, {'value': checkValue})
    if(data[dataIndex].selected == false){
      data[dataIndex].selected = true
    } else {
      data[dataIndex].selected = false
    }
    this.setState({myData: data})
    }
  }

  render(){
    return (
      <Box component='main' flexGrow={1} width={'96%'} sx={{ p: '2%' }} display={'flex'} justifyContent={'flex-start'}
        flexDirection={'column'}>
          <FixedSizeList height={300} width={'100%'} itemCount={this.state.myData.length} itemSize={40} >
            {this.rows}
          </FixedSizeList>
</Box>
)
}

New code with overScan as a state variable.

I don't LOVE the solution, but I could find no other way to make this work.  Best of luck!


  dataCheck = (checkValue) => {
    let data = this.state.myData
    let dataIndex = findIndex(data, {'value': checkValue})
    if(data[dataIndex].selected == false){
      data[dataIndex].selected = true
    } else {
      data[dataIndex].selected = false
    }
    if(this.state.overScan == 1){
      this.setState({myData: data, overScan: 2})
    } else {
      this.setState({myData: data, overScan: 1})
    }
    }
  }

  render(){
    return (
      <Box component='main' flexGrow={1} width={'96%'} sx={{ p: '2%' }} display={'flex'} justifyContent={'flex-start'}
        flexDirection={'column'}>
          <FixedSizeList overscanCount={this.state.overScan} height={300} width={'100%'} itemCount={this.state.myData.length} itemSize={40} >
            {this.rows}
          </FixedSizeList>
</Box>
)
}