React useEffect only works if I make a change to the code once the page is open

I’m having a problem in React where the code I have for my useEffect in a component works, but only if I manually make a change to the code (not even a substantial one, it can be as small as adding or removing a console.log) after loading the component. Here’s a link to a screen recording of what happens.

I am using the Material-UI Autocomplete component to create a list, but it’s like the useEffect won’t actually kick in when I load the page with the component until I make some sort of change to the code after the component loads (again: even something as small as commenting out or adding a console.log works). Once I make that change, the list will appear. But if I navigate away from the page or reload, I’m back to square 1 again.

Similarly, when I try to select an option in the list, it looks as if it didn’t work until I make a change to the code. Only then will the selection change I made to the list appear.

Here’s the code I have for that component:

import React, { useState, useEffect } from 'react'
import { makeStyles } from '@material-ui/core/styles'
import Checkbox from '@material-ui/core/Checkbox'
import TextField from '@material-ui/core/TextField'
import Autocomplete from '@material-ui/lab/Autocomplete'
import Typography from '@material-ui/core/Typography'

import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank'
import CheckBoxIcon from '@material-ui/icons/CheckBox'

const icon = <CheckBoxOutlineBlankIcon fontSize='small' />
const checkedIcon = <CheckBoxIcon fontSize='small' />

const useStyles = makeStyles((theme) => ({
  root: {
    margin: 'auto'
  },
  paper: {
    width: 250,
    height: 230,
    overflow: 'auto',
    backgroundColor: '#bdbdbd'
  },
  button: {
    margin: theme.spacing(0.5, 0)
  },
  checkbox: {
    color: '#FFF342'
  },
  textfield: {
    backgroundColor: '#bdbdbd'
  }
}))

const WordSelect = ({ newListState, setNewListState }) => {
// const WordSelect = ({ newListState, setNewListState }) => {
  const classes = useStyles()

  const [wordsText, setWordsText] = useState({
    wordOptions: []
  })

  useEffect(() => {
    const wordsText = []

    newListState.words.map(x => {
      wordsText.push(x.text)
    })

    // console.log(wordsText)

    setWordsText({ ...wordsText, wordOptions: wordsText })
  }, []) // eslint-disable-line react-hooks/exhaustive-deps


  return (
    <>
      <Autocomplete
        multiple
        id='checkboxes-tags-demo'
        disableCloseOnSelect
        value={newListState.selected}
        options={wordsText.wordOptions}
        getOptionLabel={(option) => option}
        getOptionSelected={(option, value) => option === value}
        onChange={(event, newValue) => {
          newListState.selected = newValue
        }}
        renderOption={(option, { selected }) => (
          <>
            <Checkbox
              className={classes.checkbox}
              icon={icon}
              checkedIcon={checkedIcon}
              style={{ marginRight: 8 }}
              checked={selected}
            />
            <Typography className={classes.checkbox}>
              {option}
            </Typography>
          </>
        )}
        style={{ width: 500 }}
        renderInput={(params) => (
          <TextField {...params} className={classes.textfield} variant='outlined' placeholder='Select Words' />
        )}
      />
    </>
  )
}

export default WordSelect

I’m really not sure what’s going on. It seems like the code must be right since the types of changes I make to get it to show up should have no effect on what renders on the page, but then why won’t it render from the start? Any tips appreciated.

Answer

You have not added newListState to your useEffect dependency array. Therefore, useEffect runs only on the first render of the component. When you do a change in code, live reload reloads the component and the useEffect hook runs.

Try this:

useEffect(() => { const wordsText = []

newListState.words.map(x => {
  wordsText.push(x.text)
})

// console.log(wordsText)

setWordsText({ ...wordsText, wordOptions: wordsText })

}, [setWordsText, newListState])

You really shouldn’t be using ‘ // eslint-disable-line react-hooks/exhaustive-deps’ as it helps catch problems like these. It also might help you catch some nasty bugs due to stale data or functions.