React Hook doesn't update

Viewed 415

React and JS in general are new to me, so I'm still kinda bad at this. What I understand the least in this whole situation are React Hooks and the problem with them not updating sometimes. I know things like why and how you have to avoid multiple calls of a setter and stuff like that, but this particular issue is hard for me to understand or even google correctly. Either I find class-based this.state stuff or unanswered questions here.

How to make the hook update, when I need it. For example, this is Autocomplete component from Material-UI. when I select an option in it I want to put its value into newFKSchema const in getTables to work with it. Only first three lines in this method are important for this question. But console.log shows that newFKSchema is empty. And if I choose another option, newFKSchema will have the previous option in it. So there is like a one-turn delay. I don't understand why exactly is that happening and how to make it work like I want. Sorry for messy code, I've already made some attempts to make it work.

If it is not hard for you, after providing a solution please explain it a little.

  const allSchemas = [
    '',
    'ADDITIONAL',
    'BOOKKEEPING',
    'CB',
    'DEPOSIT',
    'DOGORG',
    'GENERALUSE',
    'NSI',
    'PAYMENTS',
    'PLASTIC',
    'SAFECELL',
    'SWIFT',
    'TARIFF',
  ];
  const [newFKSchema, setNewFKSchema] = React.useState(allSchemas[0]);

  const getTables = (schema) => {
    var newSchema = schema;
    setNewFKSchema(newSchema);
    console.log(newFKSchema);

    if (newFKSchema !== '') {
      superagent
      .get('/api/tech')
      .query({schema: schema})
      .then((response) =>{
        if (response.status === 200) {
          const newTables = [];
          response.body.forEach(e => {newTables.push(e)});
          setAllTables(newTables);
        }
      })
      .catch(error => {
        if (error.response.statusCode === 400) {
            alert("No tables were found!");
        }
      }); 
    }
  }

  <FormControl fullWidth>
    <Autocomplete
      id="autocomplete-schema"
      options={allSchemas.slice(1)}
      value={newFKSchema}
      onChange={(event, value) => {setNewFKSchemaError(false); getTables(value)}} //setNewFKSchema(value); 
      renderInput={(params) => <TextField {...params} label="Schema name" variant="outlined" fullWidth />} />
    <FormHelperText className={classes.formHelper}>
      {newFKSchemaError ? "Needs input!" : ''} 
    </FormHelperText>
  </FormControl>  
3 Answers

Ok so let's walk through what happens here:

you call getTables(value) in the onChange trigger, with the parameters event and value.

value is set to newFKSchema, which you defined as allSchemas[0], which is ''

then you do this:

const getTables = (schema) => {
    var newSchema = schema;
    setNewFKSchema(newSchema);
    console.log(newFKSchema);

We know that schema is ''. so newSchema will also be '' and setNewFKSchema will set the schema to '' as well and obviously console log will also log ''

So, what did you expect to happen exactly?

React state updates are asynchronous and you are doing a console.log soon after setNewFKSchema, hence you see the previous value.

Fix

const getTables = (schema) => {
    var newSchema = schema;
    setNewFKSchema(newSchema);
    console.log(newFKSchema); //<------ don't do this
    console.log(newSchema);//<------ do this

    if (newFKSchema !== '') {
  ...

One improvement you can make to your code is to simply do setnewFKSSchema upon onChange and use a useEffect with newFKSSchema as dependency. This will basically call your api and do things when you do a select.

Like this

...
useEffect(() => {
    // var newSchema = schema;
    //setNewFKSchema(newSchema);
    console.log(newFKSchema); //<---- this will always print latest value

    if (newFKSchema !== '') {
      superagent
      .get('/api/tech')
      .query({schema: schema})
      .then((response) =>{
        if (response.status === 200) {
          const newTables = [];
          response.body.forEach(e => {newTables.push(e)});
          setAllTables(newTables);
        }
      })
      .catch(error => {
        if (error.response.statusCode === 400) {
            alert("No tables were found!");
        }
      }); 
    }
}, [newFKSSchema])

Autocomplete

<Autocomplete
      id="autocomplete-schema"
      options={allSchemas.slice(1)}
      value={newFKSchema}
      onChange={(event, value) => {setNewFKSchemaError(false); setNewFKSchema(value)}} 
      renderInput={(params) => <TextField {...params} label="Schema name" variant="outlined" fullWidth />} />

I would use place getTables in useEffect with newFKSchema as a dependency

Example

const allSchemas = [
    '',
    'ADDITIONAL',
    'BOOKKEEPING',
    'CB',
    'DEPOSIT',
    'DOGORG',
    'GENERALUSE',
    'NSI',
    'PAYMENTS',
    'PLASTIC',
    'SAFECELL',
    'SWIFT',
    'TARIFF',
  ];

const { FormControl, FormHelperText, TextField } = window.MaterialUI;

const { Autocomplete } = window.MaterialUILab;

const { useState, useEffect } = React;

const renderInput = (params) => <TextField {...params} label="Schema name" variant="outlined" fullWidth />

const getTables = (schema) => Promise.resolve([1,2,3])

const App = () => {
  const [{error, schemas, schema}, setData] = useState({
  error: false,
  schemas: [],
  schema: null,
  });
  const [tables, setTables] = useState([]);
  
  useEffect(() => {
    setData(data => ({
      ...data,
      schemas: allSchemas
    }))
  
  }, []);
  
   useEffect(() => {
   console.log(schema);
    if(!schema) {
      return () => {};
    }
    
    getTables(schema).then(result => {
    
      setTables(schema);
    
    })
  
  }, [schema]);
  
  const onChange = (event, schema) => {

    setData(data => ({
      ...data,
      schema,
      error: schema ? false : true
    }))
  }

return <FormControl fullWidth>
    <Autocomplete
      id="autocomplete-schema"
      options={schemas}
      value={schema}
      onChange={onChange}
      renderInput={renderInput} />
    <FormHelperText>
      {error ? "Needs input!" : ''} 
    </FormHelperText>
  </FormControl>  
}

ReactDOM.render(
    <App />,
    document.getElementById('root')
  );
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script src="https://unpkg.com/@material-ui/core@latest/umd/material-ui.development.js"></script>
<div id="root"></div>
<script src="https://unpkg.com/material-ui-lab-umd@4.0.0-alpha.32/material-ui-lab.development.js"></script>
<div id="root"></div>

Related