-1

The following code works, i.e. updates the object state variable successfully:

import { useState } from 'react'
import './App.css';

function App() {
    const [config, setConfig] = useState({
        status: "online",
        maxFiles: 99
    });

    const addConfigEntry = () => {
        setConfig(n => {
            const n2 = { ...n };
            n2.errors = 0;
            return n2;
        })
    };
    return (
        <div className="App">
            <button onClick={addConfigEntry}>Add a Entry</button>
            <ul>
                {Object.entries(config).map((entry, index) => {
                    return (
                        <li key={index}>{entry[0]} = {entry[1]}</li>
                    )
                })}
            </ul>
        </div>
    );
}

export default App;

But I'm getting this warning from TypeScript which makes me think that this is not the optimal way to update an object variable:

enter image description here

Is there a more correct, React-compliant way to update an object variable?

Addendum

Note that this does not work (response to a comment):

setConfig({ ...config, errors: 0 }); // will not be added!
setConfig({ ...config, errors2: 0 });
Edward Tanguay
  • 189,012
  • 314
  • 712
  • 1,047
  • `setConfig({ ...config, errors: 0 });` – zerkms Sep 15 '21 at 04:33
  • 1
    But you haven't declared type to your state. I am assuming that's why you are getting the warning. https://stackoverflow.com/questions/53650468/set-types-on-usestate-react-hook-with-typescript You can declare a type of your state, with optional error, then your code should work, or assign the `errors` with null value during state initialization. – Hassan Imam Sep 15 '21 at 04:33
  • @zerkms but that solution works only once, if you call setConfig twice like that, the first change will be overwritten, see addendum above – Edward Tanguay Sep 15 '21 at 04:43
  • @EdwardTanguay I only commented about the code you presented, I cannot predict what else you have in mind :shrug: For one call - it's perfectly valid. – zerkms Sep 15 '21 at 04:48
  • @zerkms I think so because it is so. :-) Try it out. Only the last setConfig will work (in most cases). You need to use an updater function to overcome this, as in my original code. It's an async issue I believe. – Edward Tanguay Sep 15 '21 at 04:49
  • @EdwardTanguay no it's not an async issue. It's the expected behaviour. React batches updates together, applies them all and then renders the final result. If you're changing a value twice in a function and return it, you expect that return value to be what it was last changed to, why should a render function behave differently? – coagmano Sep 15 '21 at 05:09
  • @coagmano That's what I mean in effect: If you send a function to setConfig() instead of the values, these get processed in an asynchronous fashion, and not in a synchronous fashion as you say, i.e. all in one batch. – Edward Tanguay Sep 15 '21 at 05:35
  • Ahh okay you're talking about adding different properties, not the same one – coagmano Sep 15 '21 at 06:23

1 Answers1

2

When you say,

const [config, setConfig] = useState({
        status: "online",
        maxFiles: 99
    });

TypeScript infers the type of config based on the initial state object you have passed. So, it thinks config looks like:

type InferredConfig = {
  status: string;
  maxFiles: number;
}

Notice there is no errors in the interpreted type and that's why you see the error. So, we need to tell the type explicitly.

You need to define appropriate type. This is one way:

import { useState } from 'react'
import './App.css';

// 1. We are defining the type, that *may* have errors prop
type Config = {
  status: string;
  maxFiles: number;
  errors?: number;
}

function App() {
    // 2. tell `useState` that it manages an object of type `Config`
    const [config, setConfig] = useState<Config>({
        status: "online",
        maxFiles: 99
    });

    const addConfigEntry = () => {
        setConfig(n => {
            const n2 = { ...n };
            n2.errors = 0;  // 3. No error
            return n2;
        })
    };
    return (
        <div className="App">
            <button onClick={addConfigEntry}>Add a Entry</button>
            <ul>
                {Object.entries(config).map((entry, index) => {
                    return (
                        <li key={index}>{entry[0]} = {entry[1]}</li>
                    )
                })}
            </ul>
        </div>
    );
}

export default App;
Nishant
  • 54,584
  • 13
  • 112
  • 127