How to reset state with componentDidMount? - reactjs

I'm a newbie to React and I am working on a quiz. What I would like to do now is reset the classnames to it's initial state when you get a new question. I think I want to use componentDidUpdate but not really sure how it works.
componentDidUpdate() {
this.setState({
classNames: ["", "", "", ""]
});
}
Here is the full component code:
class Answers extends React.Component {
constructor(props) {
super(props);
this.state = {
isAnswered: false,
classNames: ["", "", "", ""]
};
this.checkAnswer = this.checkAnswer.bind(this);
}
checkAnswer(e) {
let { isAnswered } = this.props;
if (!isAnswered) {
let elem = e.currentTarget;
let { correct, increaseScore } = this.props;
let answer = Number(elem.dataset.id);
let updatedClassNames = this.state.classNames;
if (answer === correct) {
updatedClassNames[answer - 1] = "right";
increaseScore();
} else {
updatedClassNames[answer - 1] = "wrong";
}
this.setState({
classNames: updatedClassNames
});
this.props.showButton();
}
}
componentDidUpdate() {
this.setState({
classNames: ["", "", "", ""]
});
}
render() {
let { answers } = this.props;
let { classNames } = this.state;
return (
<div id="answers">
<ul>
<li onClick={this.checkAnswer} className={classNames[0]} data-id="1">
<p>{answers[0]}</p>
</li>
<li onClick={this.checkAnswer} className={classNames[1]} data-id="2">
<p>{answers[1]}</p>
</li>
<li onClick={this.checkAnswer} className={classNames[2]} data-id="3">
<p>{answers[2]}</p>
</li>
<li onClick={this.checkAnswer} className={classNames[3]} data-id="4">
<p>{answers[3]}</p>
</li>
</ul>
</div>
);
}
}
export default Answers;
Any help is appreciated! And feedback on the whole code project is also much appreciated since I am learning.
Below is a link the complete project:
https://codesandbox.io/s/another-quiz-mfmop

There is an easy fix for this (and recommended as a React best practice), if you change the key for the answers, working demo: https://codesandbox.io/s/another-quiz-wgycs
<Answers
key={question} // <-- oh hi
answers={answers}
correct={correct}
...
Ideally you would use an id, and since most modern data structures have an id, this would make it ideal to use key={question_id} as the key has to be unique:
{
id: 1
question: 'What does CSS stand for?',
answers: [...],
correct: 3
},
{
id: 2,
....
}
If not, you would have to use prevProps:
componentDidUpdate(prevProps) {
if (this.props.question !== prevProps.question) {
this.setState(....)
}
}
I really recommend the key way, as this will force the creation of a new component, in practice if you need to keep checking for changing props, it can become a bit hard to keep track.
Remember, ideally there should be an id, because if the question text is the same, it would lead to a nasty hard-to-find bug.
Also, instead of saving the classnames, it's better to just save selected as an index and choose the right classname on the render method.

componentDidUpdate() is invoked immediately after updating occurs. This method is not called for the initial render.
I have face this kind of scenario recently. you have to the same code in componentDidUpdate() {}. Here is what I did.
componentDidUpdate(prevProps) {
if (this.props.questions !== prevProps.questions) {
const shuffledAnswerOptions = this.props.questions.map(question =>
question.answer_options &&
this.shuffleArray(question.answer_options)
);
this.setState({
current_question:this.props.questions &&
this.props.questions[0],
question_image_url: this.props.questions &&
this.props.questions[0] &&
this.props.questions[0].question_image_url,
answerOptions: shuffledAnswerOptions[0],
numberOfQuestions: this.props.questions &&
this.props.questions.length
});
}
}
In your case you prevSate parameter as well.
here is a sample implementation:
componentDidUpdate(prevProps, prevState) {
if(this.state.assignment !== prevState.assignment){
document.getElementById(prevState.assignment.id) && document.getElementById(prevState.assignment.id).classList.remove("headactiveperf");
}
if(this.state.assessment !== prevState.assessment){
document.getElementById(prevState.assessment.id) && document.getElementById(prevState.assessment.id).classList.remove("headactiveperf");
}
if(this.state.study_group_id !== prevState.study_group_id){
document.getElementById(prevState.study_group_id) && document.getElementById(prevState.study_group_id).classList.remove("klassactiveperf");
}
}

First of all, You have to add a button to reset the classes names and this button will call a function for resetting them like:
import React from "react";
class Answers extends React.Component {
constructor(props) {
super(props);
this.state = {
isAnswered: false,
classNames: ["", "", "", ""]
};
}
checkAnswer = e => {
let { isAnswered } = this.props;
if (!isAnswered) {
let elem = e.currentTarget;
let { correct, increaseScore } = this.props;
let answer = Number(elem.dataset.id);
let updatedClassNames = this.state.classNames;
if (answer === correct) {
updatedClassNames[answer - 1] = "right";
increaseScore();
} else {
updatedClassNames[answer - 1] = "wrong";
}
this.setState({
classNames: updatedClassNames
});
this.props.showButton();
}
};
reset = () => {
this.setState({
isAnswered: false,
classNames: ["", "", "", ""]
});
};
render() {
let { answers } = this.props;
let { classNames } = this.state;
return (
<div id="answers">
<button onClick={this.reset}>RESET</button>
<ul>
<li onClick={this.checkAnswer} className={classNames[0]} data-id="1">
<p>{answers[0]}</p>
</li>
<li onClick={this.checkAnswer} className={classNames[1]} data-id="2">
<p>{answers[1]}</p>
</li>
<li onClick={this.checkAnswer} className={classNames[2]} data-id="3">
<p>{answers[2]}</p>
</li>
<li onClick={this.checkAnswer} className={classNames[3]} data-id="4">
<p>{answers[3]}</p>
</li>
</ul>
</div>
);
}
}
export default Answers;

The problem is that if you change state in componentDidUpdate it will trigger another update right away and therefore run componentDidUpdate again and result in an infinite loop. So you should either move the setState somewhere else, or put it behind a condition. e.g.:
componentDidUpdate() {
if (!this.state.classNames.every(className => className === "") { // Check if there is an item in the array which doesn't match an empty string ("")
this.setState({ // Only update state if it's necessary
classNames: ["", "", "", ""]
});
}
}
You can find an updated CodeSandbox here

Related

How to Add filter into a todolist application in Reactjs with using .filter

im new to react, trying to make an todolist website, i have the add and delete and displaying functionality done, just trying to add an search function, but i cant seem to get it working, where as it doesn't filter properly.
i basically want to be able to filter the values on the todos.title with the search value. such as if i enter an value of "ta" it should show the todo item of "take out the trash" or any item that matches with that string.
when i try to search, it gives random outputs of items from the filtered, i am wondering if my filtering is wrong or if i am not like displaying it correctly.
ive tried to pass the value into todo.js and display it there but didn't seem that was a viable way as it it should stay within App.js.
class App extends Component {
state = {
todos: [
{
id: uuid.v4(),
title: "take out the trash",
completed: false
},
{
id: uuid.v4(),
title: "Dinner with wife",
completed: true
},
{
id: uuid.v4(),
title: "Meeting with Boss",
completed: false
}
],
filtered: []
};
// checking complete on the state
markComplete = id => {
this.setState({
todos: this.state.filtered.map(todo => {
if (todo.id === id) {
todo.completed = !todo.completed;
}
return todo;
})
});
};
//delete the item
delTodo = id => {
this.setState({
filtered: [...this.state.filtered.filter(filtered => filtered.id !== id)]
});
};
//Add item to the list
addTodo = title => {
const newTodo = {
id: uuid.v4(),
title,
comepleted: false
};
this.setState({ filtered: [...this.state.filtered, newTodo] });
};
// my attempt to do search filter on the value recieved from the search field (search):
search = (search) => {
let currentTodos = [];
let newList = [];
if (search !== "") {
currentTodos = this.state.todos;
newList = currentTodos.filter( todo => {
const lc = todo.title.toLowerCase();
const filter = search.toLowerCase();
return lc.includes(filter);
});
} else {
newList = this.state.todos;
}
this.setState({
filtered: newList
});
console.log(search);
};
componentDidMount() {
this.setState({
filtered: this.state.todos
});
}
componentWillReceiveProps(nextProps) {
this.setState({
filtered: nextProps.todos
});
}
render() {
return (
<div className="App">
<div className="container">
<Header search={this.search} />
<AddTodo addTodo={this.addTodo} />
<Todos
todos={this.state.filtered}
markComplete={this.markComplete}
delTodo={this.delTodo}
/>
</div>
</div>
);
}
}
export default App;
search value comes from the header where the value is passed through as a props. i've checked that and it works fine.
Todos.js
class Todos extends Component {
state = {
searchResults: null
}
render() {
return (
this.props.todos.map((todo) => {
return <TodoItem key={todo.id} todo = {todo}
markComplete={this.props.markComplete}
delTodo={this.props.delTodo}
/>
})
);
}
}
TodoItem.js is just the component that displays the item.
I not sure if this is enough to understand the issue 100%, i can add more if needed.
Thank you
Not sure what is wrong with your script. Looks to me it works fine when I am trying to reconstruct by using most of your logic. Please check working demo here: https://codesandbox.io/s/q9jy17p47j
Just my guess, it could be there is something wrong with your <TodoItem/> component which makes it not rendered correctly. Maybe you could try to use a primitive element such as <li> instead custom element like <TodoItem/>. The problem could be your logic of markComplete() things ( if it is doing hiding element works ).
Please let me know if I am missing something. Thanks.

Lifecycle hooks - Where to set state?

I am trying to add sorting to my movie app, I had a code that was working fine but there was too much code repetition, I would like to take a different approach and keep my code DRY. Anyways, I am confused as on which method should I set the state when I make my AJAX call and update it with a click event.
This is a module to get the data that I need for my app.
export const moviesData = {
popular_movies: [],
top_movies: [],
theaters_movies: []
};
export const queries = {
popular:
"https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=###&page=",
top_rated:
"https://api.themoviedb.org/3/movie/top_rated?api_key=###&page=",
theaters:
"https://api.themoviedb.org/3/movie/now_playing?api_key=###&page="
};
export const key = "68f7e49d39fd0c0a1dd9bd094d9a8c75";
export function getData(arr, str) {
for (let i = 1; i < 11; i++) {
moviesData[arr].push(str + i);
}
}
The stateful component:
class App extends Component {
state = {
movies = [],
sortMovies: "popular_movies",
query: queries.popular,
sortValue: "Popularity"
}
}
// Here I am making the http request, documentation says
// this is a good place to load data from an end point
async componentDidMount() {
const { sortMovies, query } = this.state;
getData(sortMovies, query);
const data = await Promise.all(
moviesData[sortMovies].map(async movie => await axios.get(movie))
);
const movies = [].concat.apply([], data.map(movie => movie.data.results));
this.setState({ movies });
}
In my app I have a dropdown menu where you can sort movies by popularity, rating, etc. I have a method that when I select one of the options from the dropwdown, I update some of the states properties:
handleSortValue = value => {
let { sortMovies, query } = this.state;
if (value === "Top Rated") {
sortMovies = "top_movies";
query = queries.top_rated;
} else if (value === "Now Playing") {
sortMovies = "theaters_movies";
query = queries.theaters;
} else {
sortMovies = "popular_movies";
query = queries.popular;
}
this.setState({ sortMovies, query, sortValue: value });
};
Now, this method works and it is changing the properties in the state, but my components are not re-rendering. I still see the movies sorted by popularity since that is the original setup in the state (sortMovies), nothing is updating.
I know this is happening because I set the state of movies in the componentDidMount method, but I need data to be Initialized by default, so I don't know where else I should do this if not in this method.
I hope that I made myself clear of what I am trying to do here, if not please ask, I'm stuck here and any help is greatly appreciated. Thanks in advance.
The best lifecycle method for fetching data is componentDidMount(). According to React docs:
Where in the component lifecycle should I make an AJAX call?
You should populate data with AJAX calls in the componentDidMount() lifecycle method. This is so you can use setState() to update your component when the data is retrieved.
Example code from the docs:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: []
};
}
componentDidMount() {
fetch("https://api.example.com/items")
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result.items
});
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<ul>
{items.map(item => (
<li key={item.name}>
{item.name} {item.price}
</li>
))}
</ul>
);
}
}
}
Bonus: setState() inside componentDidMount() is considered an anti-pattern. Only use this pattern when fetching data/measuring DOM nodes.
Further reading:
HashNode discussion
StackOverflow question

Multiple dropdowns without repeating code in ReactJS

I have created dropdown onMouseOver with help of state. So far its working good enough. Because i don't have much knowledge about ReactJS i'm not sure is it possible to make multiple dropdowns with this or different method without writing all code over and over again.
Here is my code:
..........
constructor(props) {
super(props);
this.handleMouseOver = this.handleMouseOver.bind(this);
this.handleMouseLeave = this.handleMouseLeave.bind(this);
this.state = {
isHovering: false
}
}
handleMouseOver = e => {
e.preventDefault();
this.setState({ isHovering: true });
};
handleMouseLeave = e => {
e.preventDefault();
this.setState({ isHovering: false })
};
............
<ul className="menu">
<li onMouseOver={this.handleMouseOver} onMouseLeave={this.handleMouseLeave}>Categories
{this.state.isHovering?(
<ul className="dropdown">
<li>Computerss & Office</li>
<li>Electronics</li>
</ul>
):null}
</li>
</ul>
............
So if I want to add one more dropdown I need to make new state and 2 more lines in constructor() and 2 functions to handle MouseOver/Leave.So repeating amount would be about this:
constructor(props) {
super(props);
this.handleMouseOver = this.handleMouseOver.bind(this);
this.handleMouseLeave = this.handleMouseLeave.bind(this);
this.state = {
isHovering: false
}
}
handleMouseOver = e => {
e.preventDefault();
this.setState({ isHovering: true });
};
handleMouseLeave = e => {
e.preventDefault();
this.setState({ isHovering: false })
};
I will have maybe 10+ dropdowns and at the end will be load of codes. So is there any possibility to not repeat code ? Thank You!
You should use your event.target to achieve what you want. With this, you'll know which dropdown you're hovering and apply any logic you need. You can check for example if the dropdown you're hovering is the category dropdown like this:
if(e.target.className === "class name of your element")
this.setState({hoveredEl: e.target.className})
then you use it this state in your code to show/hide the element you want.
you can check an example on this fiddle I've created: https://jsfiddle.net/n5u2wwjg/153708/
I don't think you're going to need the onMouseLeave event, but if you need you can follow the logic I've applied to onMouseOver
Hope it helps.
1. You need to save the state of each <li> item in an array/object to keep a track of hover states.
constructor(props) {
super(props);
...
this.state = {
hoverStates: {} // or an array
};
}
2. And set the state of each item in the event handlers.
handleMouseOver = e => {
this.setState({
hoverStates: {
[e.target.id]: true
}
});
};
handleMouseLeave = e => {
this.setState({
hoverStates: {
[e.target.id]: false
}
});
};
3. You need to set the id (name doesn't work for <li>) in a list of menu items.
Also make sure to add key so that React doesn't give you a warning.
render() {
const { hoverStates } = this.state;
const menuItems = [0, 1, 2, 3].map(id => (
<li
key={id}
id={id}
onMouseOver={this.handleMouseOver}
onMouseLeave={this.handleMouseLeave}
className={hoverStates[id] ? "hovering" : ""}
>
Categories
{hoverStates[id] ? (
<ul className="dropdown menu">
<li>#{id} Computerss & Office</li>
<li>#{id} Electronics</li>
</ul>
) : null}
</li>
));
return <ul className="menu">{menuItems}</ul>;
}
4. The result would look like this.
You can see the working demo here.
Shameless Plug
I've written about how to keep a track of each item in my blog, Keeping track of on/off states of React components, which explains more in detail.

React: Google Places API/ Places Details

I have the following code which retrieves Google Places Reviews based on Google Places API. I have incorporated the logic to work as a React life cycle component. Currently, I am unable to setState and correctly bind the object. I could use some help understanding where my logic is failing.
export default class Reviews extends React.Component{
constructor(props){
super(props);
this.state = {
places: []
}
}
componentDidMount(){
let map = new google.maps.Map(document.getElementById("map"), {
center: {lat:40.7575285, lng: -73.9884469}
});
let service = new google.maps.places.PlacesService(map);
service.getDetails({
placeId: 'ChIJAUKRDWz2wokRxngAavG2TD8'
}, function(place, status) {
if (status === google.maps.places.PlacesServiceStatus.OK) {
console.log(place.reviews);
// Intended behavior is to set this.setState({places.place.reviews})
}
})
}
render(){
const { places } = this.state;
return(
<div>
<p>
{
places.map((place) => {
return <p>{place.author_name}{place.rating}{place.text}</p>
})
}
</p>
</div>
)
}
}
You can't use this that way in a callback. When the function is called the this in, this.setState({places.place.reviews}) doesn't point to your object. One solution is to use => function notation which will bind this lexically.
service.getDetails({
placeId: 'ChIJAUKRDWz2wokRxngAavG2TD8'
}, (place, status) => {
if (status === google.maps.places.PlacesServiceStatus.OK) {
console.log(place.reviews);
this.setState({places: place.reviews})
}
})
}
Alternatively you can make a new reference to this and us it in the function. Something like
var that = this
...
that({places.place.reviews})
The first option is nicer, but requires an environment where you can use ES6. Since your using let you probably are okay.
With some tweaking -- I got the code to work! Thank you.
render(){
const { places } = this.state;
return(
<div>
<p>
{
places.map((place) => {
if(place.rating >= 4){
return <p key={place.author_name}>{place.author_name}{place.rating}{place.text}</p>
}
})
}
</p>
</div>
)
}

How can I remove an attribute from a React component's state object

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.
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>
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>
);
}
});
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
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 });
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
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}
>×</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>
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;
});
Use dot-prop-immutable
import dotPropImmutable from "dot-prop-immutable";
onClick() {
this.setState(
dotPropImmutable.delete(this.state, 'foo')
);
}
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).
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 });
}
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.

Resources