50

If I have a React component that had a property set on its state:

onClick() {
    this.setState({ foo: 'bar' });
}

Is it possible to remove "foo" here from Object.keys(this.state)?

The replaceState method looks like the obvious method to try but it's since been deprecated.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
Joshua
  • 6,320
  • 6
  • 45
  • 62

11 Answers11

40

You can set foo to undefined, like so

var Hello = React.createClass({
    getInitialState: function () {
        return {
            foo: 10,
            bar: 10
        }
    },

    handleClick: function () {
        this.setState({ foo: undefined });
    },

    render: function() {
        return (
            <div>
                <div onClick={ this.handleClick.bind(this) }>Remove foo</div>
                <div>Foo { this.state.foo }</div>
                <div>Bar { this.state.bar }</div>
            </div>
        );
    }
});

Example

Update

The previous solution just remove value from foo and key skill exists in state, if you need completely remove key from state, one of possible solution can be setState with one parent key, like so

var Hello = React.createClass({
  getInitialState: function () {
    return {
      data: {
        foo: 10,
        bar: 10
      }
    }
  },
     
  handleClick: function () {
    const state = {
      data: _.omit(this.state.data, 'foo')
    };
    
    this.setState(state, () => {
      console.log(this.state);
    });
  },
        
  render: function() {
    return (
      <div>
        <div onClick={ this.handleClick }>Remove foo</div>
        <div>Foo { this.state.data.foo }</div>
        <div>Bar { this.state.data.bar }</div>
      </div>
    );
  }
});

ReactDOM.render(<Hello />, document.getElementById('container'))
<script src="https://cdn.jsdelivr.net/lodash/4.17.4/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>
Oleksandr T.
  • 76,493
  • 17
  • 173
  • 144
36
var Hello = React.createClass({
getInitialState: function () {
    return {
        foo: 10,
        bar: 10
    }
},

handleClick: function () {
    let state = {...this.state};
    delete state.foo;
    this.setState(state);
},

render: function() {
    return (
        <div>
            <div onClick={ this.handleClick.bind(this) }>Remove foo</div>
            <div>Foo { this.state.foo }</div>
            <div>Bar { this.state.bar }</div>
        </div>
    );
}

});

GAURAV
  • 647
  • 6
  • 18
  • 2
    not sure why this is getting downvoted this is 100% the appropriate answer – Paidenwaffle Feb 20 '20 at 18:54
  • 1
    Yeah the key is to delete from the copy not the original, and to know where to use the pattern at all. Don't remove keys from complex state objects. If you use state objects as simple maps such as registering multiple programmatically managed instances of a child component within a parent (toasts in a toaster by timestamp for me) then do this. – Kyle Zimmer Feb 17 '21 at 23:09
  • This seems to be simple and legit – Aashiq May 19 '21 at 04:56
  • Don't know why the most appropriate is at the bottom. Thanks man. – Daman Arora Jul 26 '21 at 07:20
  • 1
    If the key and value are set, this operation will not delete the key – Zijian Jan 19 '22 at 21:21
6

In ReactCompositeComponent.js in the React source on GitHub is a method called _processPendingState, which is the ultimate method which implements merging state from calls to component.setState;

``` _processPendingState: function(props, context) { var inst = this._instance; var queue = this._pendingStateQueue; var replace = this._pendingReplaceState; this._pendingReplaceState = false; this._pendingStateQueue = null;

if (!queue) {
  return inst.state;
}

if (replace && queue.length === 1) {
  return queue[0];
}

var nextState = replace ? queue[0] : inst.state;
var dontMutate = true;
for (var i = replace ? 1 : 0; i < queue.length; i++) {
  var partial = queue[i];
  let partialState = typeof partial === 'function'
    ? partial.call(inst, nextState, props, context)
    : partial;
  if (partialState) {
    if (dontMutate) {
      dontMutate = false;
      nextState = Object.assign({}, nextState, partialState);
    } else {
      Object.assign(nextState, partialState);
    }
  }
}

```

In that code you can see the actual line that implements the merge;

nextState = Object.assign({}, nextState, partialState);

Nowhere in this function is there a call to delete or similar, which means it's not really intended behaviour. Also, completely copying the stat, deleting the property, and calling setState won't work because setState is always a merge, so the deleted property will just be ignored.

Note also that setState does not work immediately, but batches changes, so if you try to clone the entire state object and only make a one-property change, you may wipe over previous calls to setState. As the React document says;

React may batch multiple setState() calls into a single update for performance.

Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.

What you could look to do is actually add more info;

this.setState({ xSet: true, x: 'foo' });
this.setState({ xSet: false, x: undefined });

This is ugly, granted, but it gives you the extra piece of info you need to differentiate between a value set to undefined, and a value not set at all. Plus it plays nice with React's internals, transactions, state change batching, and any other horror. Better to take a bit of extra complexity here than try to second-guess Reacts internals, which are full of horrors like transaction reconciliation, managing deprecated features like replaceState, etc

Steve Cooper
  • 20,542
  • 15
  • 71
  • 88
  • "setState is always a merge, so the deleted property will just be ignored" This is correct. After trying just about everything and debugging into setState for a few hours; there is no way (that I can find) to get an existing key in the state object to go to undefined / deleted. Once it's in there, it will remain in there forever. The best I can come up with is to set it to null ... which I am not too happy about. – John Newman Sep 12 '18 at 14:39
  • @JohnNewman well I bumped into similar issue but I had an issue in my logger for setState. As soon as I moved logging into callback function in setState( object, callback) I saw correct results. – Roman Podlinov Apr 16 '20 at 13:41
4

When we use undefined or null to remove a property, we actually do not remove it. Thus, for a Javascript object we should use the delete keyword before the property:

//The original object:
const query = { firstName:"Sarah", gender: "female" };

//Print the object:
console.log(query);

//remove the property from the object:
delete query.gender;

//Check to see the property is deleted from the object:
console.log(query);

However, in React Hooks we use hooks and the above method might cause some bugs especially when we use effects to check something when the state changes. For this, we need to set the state after removing a property:

import { useState, useEffect } from "react";

const [query, setQuery] = useState({firstName:"Sarah", gender:"female"});
 
//In case we do something based on the changes
useEffect(() => {
    console.log(query);
  }, [query]);

//Delete the property:
delete query.gender;

//After deleting the property we need to set is to the state:
setQuery({ ...query });
Amiri
  • 2,417
  • 1
  • 15
  • 42
3

Previous solution - is antipattern, because it change this.state. It is wrong!

Use this (old way):

let newState = Object.assign({}, this.state) // Copy state
newState.foo = null // modyfy copyed object, not original state
// newState.foo = undefined // works too
// delete newState.foo // Wrong, do not do this
this.setState(newState) // set new state

Or use ES6 sugar:

this.setState({...o, a:undefined})

Pretty sweet, don't you? ))

In old React syntax (original, not ES6), this has this.replaceState, that remove unnecessary keys in store, but now it is deprecated

MiF
  • 638
  • 7
  • 13
  • 4
    Both of these solutions are wrong, too. The first reason is that, `Object.assign()` [won't do a deep clone](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Deep_Clone), so you might end up directly modifying state without intending to in more complex apps. The second problem occurs in both of your solutions and it is the same as the answer by @alexander-t. You're not removing the key from `Object.keys()` as requested. You're just changing its value. – Vince Mar 10 '17 at 05:33
  • 4
    This topic has no emotional significance for me and nobody can vote twice. I voted your answer down once because it doesn't solve the problem posed by the question. I'm not sure how this solves any problem, though. If you iterate over the properties of the state object to create a list or a table, setting the value of one of them to `null` / `undefined` will at least generate an empty list item or table row. If the code is written to use properties of child objects, setting the value to `null` / `undefined` will lead to errors that are difficult to troubleshoot. – Vince Mar 12 '17 at 14:32
2

You can use Object.assign to make a shallow copy of your application's state at the correct depth and delete the element from your copy. Then use setState to merge your modified copy back into the application's state.

This isn't a perfect solution. Copying an entire object like this could lead to performance / memory problems. Object.assign's shallow copy helps to alleviate the memory / performance concerns, but you also need to be aware of which parts of your new object are copies and which parts are references to data in the application state.

In the example below, modifying the ingredients array would actually modify the application state directly.

Setting the value of the undesired element to null or undefined doesn't remove it.

const Component = React.Component;

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      "recipes": {
        "1": {
          "id": 1,
          "name": "Pumpkin Pie",
          "ingredients": [
            "Pumpkin Puree",
            "Sweetened Condensed Milk",
            "Eggs",
            "Pumpkin Pie Spice",
            "Pie Crust"
          ]
        },
        "2": {
          "id": 2,
          "name": "Spaghetti",
          "ingredients": [
            "Noodles",
            "Tomato Sauce",
            "(Optional) Meatballs"
          ]
        },
        "3": {
          "id": 3,
          "name": "Onion Pie",
          "ingredients": [
            "Onion",
            "Pie Crust",
            "Chicken Soup Stock"
          ]
        },
        "4": {
          "id": 4,
          "name": "Chicken Noodle Soup",
          "ingredients": [
            "Chicken",
            "Noodles",
            "Chicken Stock"
          ]
        }
      },
      "activeRecipe": "4",
      "warningAction": {
        "name": "Delete Chicken Noodle Soup",
        "description": "delete the recipe for Chicken Noodle Soup"
      }
    };
    
    this.renderRecipes = this.renderRecipes.bind(this);
    this.deleteRecipe = this.deleteRecipe.bind(this);
  }
  
  deleteRecipe(e) {
    const recipes = Object.assign({}, this.state.recipes);
    const id = e.currentTarget.dataset.id;
    delete recipes[id];
    this.setState({ recipes });
  }
  
  renderRecipes() {
    const recipes = [];
    for (const id in this.state.recipes) {
      recipes.push((
        <tr>
          <td>
            <button type="button" data-id={id} onClick={this.deleteRecipe}
            >&times;</button>
          </td>
          <td>{this.state.recipes[id].name}</td>
        </tr>
      ));
    }
    return recipes;
  }
                
  render() {
    return (
      <table>
        {this.renderRecipes()}
      </table>
    );
  }
}

ReactDOM.render(<App />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<main id="app"></main>
Vince
  • 3,962
  • 3
  • 33
  • 58
0

Only way is to create a deep copy and then delete that property from the deep clone and return the deep clone from the setState updater function

this.setState(prevState => {
            const newState = {
              formData: {
                ...prevState.formData,
                document_details: {
                  ...prevState.formData.document_details,
                  applicant: {
                    ...prevState.formData?.document_details?.applicant
                    keyToBeDeleted: dummVAlue //this is redundant
                  }
                }
              }
            };
            delete newState.formData.document_details.applicant.keyToBeDeleted;
            return newState;
          });
Akshay Vijay Jain
  • 13,461
  • 8
  • 60
  • 73
0

Use dot-prop-immutable

import dotPropImmutable from "dot-prop-immutable";

onClick() {
    this.setState(
       dotPropImmutable.delete(this.state, 'foo')
    );
}
Andrew Zhilin
  • 1,654
  • 16
  • 11
-1

If the removal is in a function and the key needs to be a variable, try this :

removekey = (keyname) => {
    let newState = this.state;
    delete newState[keyname];
    this.setState(newState)
    // do not wrap the newState in additional curly braces  
}

this.removekey('thekey');

Almost the same as steve's answer, but in a function.

dwilbank
  • 2,470
  • 2
  • 26
  • 37
Taj Virani
  • 23
  • 1
  • 1
    direct state mutation which is not good. In this situation newState is just pointing at this.state its not a copy of it. 95% of the time you won't see a bug with this but it definitely will cause hard to debug issues down the line. Use object.assign to make a copy – Paidenwaffle Feb 20 '20 at 18:52
  • 1
    let newState = this.state // isn't newState points to same reference as this.state, deleting from newState, will delete from this.state, thus mutating// which is not to be done – Akshay Vijay Jain Mar 20 '20 at 12:03
-1

If you want to completely reset the state (removing a large number of items), something like this works:

this.setState(prevState => {
    let newState = {};
    Object.keys(prevState).forEach(k => {
        newState[k] = undefined;
    });
    return newState;
});

Using this variant of setState allows you to access the whole state during the call, whereas this.state could be a little out of date (due to prior setState calls not yet having been fully processed).

Malvineous
  • 25,144
  • 16
  • 116
  • 151
-1

I think this is a nice way to go about it =>

//in constructor
let state = { 
   sampleObject: {0: a, 1: b, 2: c }
}

//method
removeObjectFromState = (objectKey) => {
   let reducedObject = {}
   Object.keys(this.state.sampleObject).map((key) => {
      if(key !== objectKey) reducedObject[key] = this.state.sampleObject[key];
   })
   this.setState({ sampleObject: reducedObject });
}