89

Is it possible to store an object in the state of a React component? If yes, then how can we change the value of a key in that object using setState? I think it's not syntactically allowed to write something like:

this.setState({ abc.xyz: 'new value' });

On similar lines, I've another question: Is it okay to have a set of variables in a React component such that they can be used in any method of the component, instead of storing them in a state?

You may create a simple object that holds all these variables and place it at the component level, just like how you would declare any methods on the component.

Its very likely to come across situations where you include a lot of business logic into your code and that requires using many variables whose values are changed by several methods, and you then change the state of the component based on these values.

So, instead of keeping all those variables in the state, you only keep those variables whose values should be directly reflected in the UI.

If this approach is better than the first question I wrote here, then I don't need to store an object in the state.

Rick
  • 4,030
  • 9
  • 24
  • 35
Rahul Dole
  • 2,723
  • 2
  • 24
  • 30

7 Answers7

93
  1. this.setState({ abc.xyz: 'new value' }); syntax is not allowed. You have to pass the whole object.

    this.setState({abc: {xyz: 'new value'}});
    

    If you have other variables in abc

    var abc = this.state.abc;
    abc.xyz = 'new value';
    this.setState({abc: abc});
    
  2. You can have ordinary variables, if they don't rely on this.props and this.state.

H. Pauwelyn
  • 13,575
  • 26
  • 81
  • 144
kiran
  • 1,246
  • 1
  • 10
  • 16
  • 13
    If you want to keep other properties in the object, a method like underscore's [extend](http://underscorejs.org/#extend) is useful: `this.setState({abc: _.extend(this.state.abc, {xyz: 'new value'})});` – smhg Nov 24 '14 at 14:31
  • @smhg you should post that as an answer. I think it's the cleanest solution. Let me know when you do so I can upvote it :) – Zoltán Jun 24 '15 at 19:01
  • @Zoltán it is actually the same as the update addon answer (currently above). React's `React.addons.update` helper is a replacement for underscore's `extend`. – smhg Jun 25 '15 at 06:31
  • 1
    @smhg not quite, `update` is more powerful in that it supports _commands_ for modifying complex objects inside state. When you don't need these special features, I think `_.extend` provides a much cleaner syntax, especially for someone who reads the code later and is not familiar with the purpose of `update`. – Zoltán Jun 25 '15 at 10:17
  • @Zoltán, `update` has indeed other use-cases too. So they aren't the same and there indeed might be a performance penalty to `update`. However, I don't think this is useful enough to be a separate answer. It depends too much on your project's structure. For some including another library might be too much (even `lodash.assign`). If the comment is not visible enough, maybe it is better to add it to the original answer? – smhg Jun 29 '15 at 14:01
  • 2
    @smhg, to add on to yours, you can use `Object.assign` if your browser supports ES6 (or just use a polyfill) – Jay Aug 24 '15 at 21:09
  • How do you then retreive the state on a form element - would it simply be value={this.state.abc.xyz} ? – JoeTidee Apr 30 '16 at 19:50
  • 1
    @kiran setting a complex object to a variable does NOT copy it, it simply makes another reference to the same object. So, `var abc = this.state.abc; abc.xyz = 'new value';` is the same as `this.state.abc.xyz='new value'` – jasonseminara Aug 09 '16 at 19:17
  • Minor ES2015 improvement to that is shorthand object property: `this.setState({ abc })` – Sam A. Horvath-Hunt Jan 24 '17 at 16:00
  • What's difference in using this: `this.state.abc.property = 'new property'`? – TomSawyer Jul 13 '17 at 08:05
  • 2
    This answer looks suspiciously like OP is modifying state directly, which is a big no-no. Assuming that `abc` has only primitive fields, the proper first line should be `var abc = { ...this.state.abc };` –  Mar 21 '20 at 10:20
60

You can use ES6 spread on previous values in the object to avoid overwrite

this.setState({
     abc: {
            ...this.state.abc,
            xyz: 'new value'
           }
});
Jaich
  • 134
  • 1
  • 11
PranavPinarayi
  • 3,037
  • 5
  • 21
  • 32
40

In addition to kiran's post, there's the update helper (formerly a react addon). This can be installed with npm using npm install immutability-helper

import update from 'immutability-helper';

var abc = update(this.state.abc, {
   xyz: {$set: 'foo'}
});

this.setState({abc: abc});

This creates a new object with the updated value, and other properties stay the same. This is more useful when you need to do things like push onto an array, and set some other value at the same time. Some people use it everywhere because it provides immutability.

If you do this, you can have the following to make up for the performance of

shouldComponentUpdate: function(nextProps, nextState){
   return this.state.abc !== nextState.abc; 
   // and compare any props that might cause an update
}
Brigand
  • 84,529
  • 20
  • 165
  • 173
  • So I'm imagining it would be much easier to just store the state in something besides `this.state`, and call `render()` anytime you change something inside of it; is that considered a complete no-no among experienced React developers? – Andy Mar 26 '15 at 01:36
  • render is a pure function, it doesn't actually *do* anything. It's called by react when there's an update, and updates are caused by setState calls. Best practices say your render should produce the same output for the same props and state. This also allows optimizations like the shouldComponentUpdate to be simple. – Brigand Mar 26 '15 at 02:17
  • Wow, have I been away from SO for this long? I actually ended up using some basic reactive tools to call `forceUpdate()` whenever given fields of a Backbone model changed. Because of that I can simply override `shouldComponentUpdate` to return false. `render` does still produce the same output for the same `props` and "state" (i.e. the Backbone models, I'm not storing anything in `this.state`). – Andy Apr 04 '15 at 20:13
  • Is this still avaliable? Do I need to import anything else? Using React and there is nothing named addons in the React element... – Sebastialonso Feb 25 '16 at 18:12
  • 1
    @Sebastialonso `require('react-addons-update')` and install it of course. – Brigand Feb 25 '16 at 21:31
  • 2
    Seems like this helper is now legacy, and this library is recommended instead: https://github.com/kolodny/immutability-helper – asiop Apr 26 '17 at 09:36
17

UPDATE This answer is many years old and is now obsolete.

  1. You should probably use hooks and/or refactor your code if you're trying to do a deep update.
  2. You should probably be using Functional Components with useState hooks or a reducer.
  3. I'm keeping this answer alive for historical context.

Beyond here be DRAGONS!


this.setState({abc: {xyz: 'new value'}}); will NOT work, as state.abc will be entirely overwritten, not merged.-

This works for me:

this.setState((previousState) => {
  previousState.abc.xyz = 'blurg';
  return previousState;
});

Unless I'm reading the docs wrong, Facebook recommends the above format. https://facebook.github.io/react/docs/component-api.html

Additionally, I guess the most direct way without mutating state is to directly copy by using the ES6 spread/rest operator:

const newState = { ...this.state.abc }; // deconstruct state.abc into a new object-- effectively making a copy
newState.xyz = 'blurg';
this.setState(newState);
jasonseminara
  • 436
  • 4
  • 14
  • I tested your first way using `componentDidUpdate()` and it correctly held the previous state while doing `this.setState({[abc.xyz]: 'blurg'})` gave incorrect previous state so I'm presuming it's good. – Martin Dawson Aug 29 '16 at 17:10
  • The previousState wasn't actually the previousState. It was the currentState, which is incorrect. – Martin Dawson Sep 03 '16 at 21:16
  • 1
    `...this.state.abc` [will only copy over "methods" defined via object literals (since these are enumerable) but not methods defied via the "class" mechanism (since these aren't enumerable).](https://stackoverflow.com/questions/728360/how-do-i-correctly-clone-a-javascript-object#comment60201963_30042948) – bmaupin Jul 28 '17 at 15:02
  • 2
    `previousState.abc.xyz = 'blurg';` mutates the current state which is an anti-pattern in React. – Emile Bergeron Apr 28 '20 at 15:36
  • "Unless I'm reading the docs wrong, Facebook recommends the above format". Nowhere in that documentation does it recommend that format. – John Harding May 26 '22 at 20:40
  • Note the date on the solution (2016). This is now obsolete. – jasonseminara Jul 08 '22 at 19:14
9

Easier way to do it in one line of code

this.setState({ object: { ...this.state.object, objectVarToChange: newData } })
Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
Kevin Danikowski
  • 4,620
  • 6
  • 41
  • 75
8

Even though it can be done via immutability-helper or similar I do not wan't to add external dependencies to my code unless I really have to. When I need to do it I use Object.assign. Code:

this.setState({ abc : Object.assign({}, this.state.abc , {xyz: 'new value'})})

Can be used on HTML Event Attributes as well, example:

onChange={e => this.setState({ abc : Object.assign({}, this.state.abc, {xyz : 'new value'})})}
Ogglas
  • 62,132
  • 37
  • 328
  • 418
2

If you want to store an object in the state using functional components you can try the following.

import React from 'react';
import {useState, useEffect} from 'react';

const ObjectState= () => {
    const [username, setUsername] =  useState({});

    const usernameSet = () => {
        const name = {
            firstname: 'Naruto',
            familyname: 'Uzmaki' 
        }
        setUsername(prevState => name);
    }

    return(
        <React.Fragment>
            <button onClick= {usernameSet}>
                Store Object
            </button>
           {username.firstname} {username.familyname} 
        </React.Fragment>
    )
}

export default ObjectState;

If you want to add an object to a pre-existing object.

import React from 'react';
import {useState, useEffect} from 'react';

const ObjectState= () => {
    const [username, setUsername] =  useState({village: 'Konoha'});

    const usernameSet = () => {
        setUsername((prevState) => {
            const data = {
                ...prevState, 
                firstname: 'Naruto',
                familyname: 'Uzmaki' 
            }
            return data
        });
    }

    return(
        <React.Fragment>
            <button onClick= {usernameSet}>
                Store Object
            </button>
            {username.village} {username.firstname} {username.familyname} 
        </React.Fragment>
    )
}

export default ObjectState;

P.S. : Naming the component 'Object' leads to an 'Maximum call stack size exceeded error'. Other names are fine but for some reason 'Object' is not. Like the following is not ok.

    const Object = () => {
        // The above code
    };

    export default Object;

If anyone knows why or how to prevent it please add it to the comments.

Manil Malla
  • 293
  • 2
  • 6
  • 1
    The original question is well over 6 years old and only refers to class-based component state (which was the only state mechanism). Class-based state and hook-based state behave completely different. But not a single word of that in your answer. And using a useEffect to initialize the state is just bizarrely cumbersome. – Martin Jun 01 '21 at 08:24
  • @Martin Thanks for the comment. I was working on something where the state had to be initialized by using 'useEffect' so it did not occur to me that it might be so cumbersome so thanks for pointing that out. I have changed the answer to something that may be less cumbersome. – Manil Malla Jun 01 '21 at 08:45
  • Agreed. This answer is also an anti-pattern, as you probably shouldn't be storing an object in the state but rather breaking it up into its simple key/value pairs. Updating is a lot easier, and you don't run the risk of memory leaks or shallow copies of your object. – jasonseminara Jan 18 '23 at 17:48