in the reactjs I am not understand the below code please help me in this
...todo, complete: !todo.complete
according to me first ...todo is the spread array, and after comma the condition has been implemented on it.
It's vanilla JavaScript, nothing related to react here
This is not a condition
!false -> true
!true -> false
Doing this:
const todo = {
name: 'Something to do',
complete: false
}
const newTodo = {
...todo,
complete: !todo.complete
}
Results in:
{
name: 'Something to do',
complete: true
}
this is spread operator.
The JavaScript spread operator (...) allows us to quickly copy all or
part of an existing array or object into another array or object.
you can read more in this link
This is the Spread Operator, just plain JS, nothing related to React.
When you use the spread operator:
const todo = {
completed: false,
title: "Test"
};
const sameTodo = { ...todo };
The sameTodo variable will be the same as the todo variable.
// sameTodo
{
completed: false,
title: "Test"
}
In the example you gave in the question, it will first create a new object and then add the values of the todo object to it
Eg:
const todo = {
completed: false,
title: "Something"
}
/*
Value of `changedTodo`:
{
completed: false,
title: "Something"
}
*/
const changedTodo = {
...todo
}
And then it says
complete: !todo.complete
The ! Mark is a logical 'Not' operator, it will change a value to its opposite.
Eg:
// True will become false & false will become true
!true --> false
!false --> true
So when we get back to changedTodo, we can add the operator to the completed property:
const changedTodo = {
...todo,
completed: !todo.completed
}
And when you assign a property that already exists in an object that you are spreading, it will overwrite it.
So, here the todo object already has a completed property, so when we use
completed: !todo.completed, it will overwrite what was on the todo object and change it to it's opposite.
Since here the todo object has the completed property as false, it will turn to true
So in the end, we get this:
const changedTodo = {
title: "Something",
completed: true
}
By the way, this has nothing to do with React or useReducer. This is plain vanilla Javascript.
Hope this helps.
Related
I have the following object which is my initial state in my reducer:
const INITIAL_STATE = {
campaign_dates: {
dt_start: '',
dt_end: '',
},
campaign_target: {
target_number: '',
gender: '',
age_level: {
age_start: '',
age_end: '',
},
interest_area: [],
geolocation: {},
},
campaign_products: {
survey: {
name: 'Survey',
id_product: 1,
quantity: 0,
price: 125.0,
surveys: {},
},
reward: {
name: 'Reward',
id_product: 2,
quantity: 0,
price: 125.0,
rewards: {},
},
},
}
And my reducer is listening for an action to add a reward to my object of rewards:
case ADD_REWARD:
return {
...state, campaign_products: {
...state.campaign_products,
reward: {
...state.campaign_products.reward,
rewards: {
...state.campaign_products.reward.rewards,
question: action.payload
}
}
}
}
So far so good (despite the fact that every object added is named "question")... its working but its quite messy. I've tried to replace the reducer above using the immutability-helper, to something like this but the newObh is being added to the root of my state
case ADD_REWARD:
const newObj = update(state.campaign_products.reward.rewards, { $merge: action.payload });
return { ...state, newObj }
return { ...state, newObj }
First, you must understand how the object shorthand works. If you're familiar with the syntax before ES2015, the above code translates to:
return Object.assign({}, state, {
newObj: newObj
});
Note how the newObj becomes a key and a value at the same time, which is probably not what you want.
I assume the mentioned immutability-helper is this library: https://www.npmjs.com/package/immutability-helper. Given the documentation, it returns a copy of the state with updated property based on the second argument.
You're using it on a deep property so that it will return a new value for that deep property. Therefore you still have to merge it in the state, so you have to keep the approach you've labelled as messy.
What you want instead is something like:
const nextState = update(state, {
$merge: {
campaign_products: {
reward: {
rewards: action.payload
}
}
}
});
return nextState;
Note how the first argument is the current state object, and $merge object is a whole object structure where you want to update the property. The return value of update is state with updated values based on the second argument, i.e. the next state.
Side note: Working with deep state structure is difficult, as you've discovered. I suggest you look into normalizing the state shape. If applicable, you can also split the reducers into sub-trees which are responsible only for the part of the state, so the state updates are smaller.
I have a component in which I create a new post. I have a state used to configure and create a form. The main object in the state is called formControls and inside each element looks something like this:
title: {
elementType: "input",
elementConfig: {
type: "text",
placeholder: "Title"
},
value: "",
validation: {
required: true
},
valid: false,
isTouched: false
}
Then I have a submit handler in which I create a new post and I try to clear the inputs. I m using 2 way binding so I try to clear by looping through state, make copies and update the values for each elements in the formControls : title, author and content like this:
for (let key in this.state.formControls) {
const updatedState = { ...this.state.formControls };
const updatedInput = { ...this.state.formControls[key] };
updatedInput.value = "";
updatedState[key] = updatedInput;
console.log(updatedState);
this.setState({
formControls: updatedState
});
}
The things is that it only clears the last element in the form (textarea). I console logged updatedState and in each iteration it clears the current input but in the next iteration the previous cleared input has again the value before clearing so only the last element is cleared in the end. If i move const updatedState = { ...this.state.formControls };
outside the for loop is behaves as it should. Does this happen because of async operation of setState() and it doesn t give me the right previous state when I try to update in each iteration?
I was hoping that maybe someone could help me understand why is like this. I would post more code but is quite long.
The data available to you in the closure is stale after the first call to setState. All iterations in the for .. in block will be run with the "old" data, so your last iteration is the one which is actually setting the fields to the values as they were when the for .. in loop began.
Try calling setState only once and put all your required changes into that.
const updatedFormControls = { ...this.state.formControls };
for (let key in this.state.formControls) {
const updatedInput = { ...updatedFormControls[key] };
updatedInput.value = "";
updatedFormControls[key] = updatedInput;
}
console.log(updatedFormControls);
this.setState({
formControls: updatedFormControls
});
Another way to do the same thing might look like this:
this.setState(state => ({
...state,
formControls: Object.keys(state.formControls).reduce(
(acc, key) => ({
...acc,
[key]: { ...state.formControls[key], value: '' }
}),
{}
)
});
I'm trying to set new values for several fields in nested object in state using spread operator in loop, but it works only for last field.
I have an array "formFields" with names of fields which values I want to overwrite. I use map() to compare each element in array with field in state and switch it's value to "true". But it change value only for the last field in array - "comment".
constructor() {
super();
this.state = {
fields: {
time: false,
date: false,
quantity: false,
comment: false,
},
}
}
getFormFields() {
const formFields = ["time", "quantity", "comment"];
formFields.map(item => {
this.setState({
...this.state.fields,
[item]: true
})
});
}
What should I do to overwrite values for all the fields I want?
Since you are changing state in a loop, and each state you set contains the original item with only changed, the latest change overrides the previous one. Instead, create a new state object with the change, and then setState the object once:
getFormFields() {
const formFields = ["time", "quantity", "comment"];
this.setState(formFields.reduce((r, item) => ({
...r,
[item]: true
}), {}));
}
btw - If the fields you want to set to true are always the same, you can create the object manually, and set it:
getFormFields() {
this.setState({
time: true,
quantity: true,
comment: true,
});
}
I want to set state of this form :
this.state = {
filter: {
search: null,
brands: null,
price: null
}
}
How to set value for search / brands / price ?
Do the following:
this.setState({
filter: {
search: 'value',
brands: 'value',
price: 'value'
}
})
The key is that you don't want to ever mutate a value in state. As a result, you must copy the filter object before passing it to setState. Example:
onSearchChange(value) {
this.setState((state) => {
return {
filter: {
...state.filter,
search: value
}
})
}
Note that I am passing a function to setState. Since the next value of state relies on the previous value, you want to use an updater functions, as the setState docs recommend.
In general, it is nicer if you can keep your state flat. So rather than having a filter object in state, your shape could just be
this.state = {
search: null,
brands: null,
price: null,
}
In which case the above onSearchChange function would just be
onSearchChange(value) {
this.setState({search: value})
}
Definitely a lot easier on the eyes.
I recommend avoiding nested objects and keeping your state flat. e.g.
this.state = {
brandsFilter: null,
priceFilter: null,
searchFilter: null,
};
Component state in react is really nothing more than simple key-value pairs; that's what setState supports. Sure you can have nested objects if you really want. But as the other answers demonstrate, supporting such an approach can be needlessly complex.
you should use the setState function, you can set the filter with updated data like so
const newFilter = {...};
this.setState({filter: newFilter});
You should avoid to mutate React state directly, there are some functions can do immutable jobs for you (ex Object.assign):
const currentFilter = this.state.filter;
const newFilter = Object.assign({}, currentFilter, {search: "new search", brands: [], price: 100});
this.setState({filter: newFilter});
ES 6:
const currentFilter = this.state.filter;
this.setState({
filter: {
...currentFilter,
search: "new search",
brands: []
}
});
this.setState({
filter: {
...this.state.filter,
search: "new search",
brands: 'value',
price: 'value'
}
});
You may try this with Spread Operator.
If you need to preserve the previous filter value.
other wise you can,
this.setState({
filter: {
search: "new search",
brands: 'value',
price: 'value'
}
});
let newfilter = Object.assign({}, this.state.filter)
newfilter.search ="value";
newfilter.brands ="value";
newfilter.price ="value";
this.setState({
filter:newfilter
})
You can access the search, brands, price by using:
this.setState({
filter.search = true,
filter.brands = 'stackoverflow',
filter.price = 1400
})
and to access it, just like usual state access (this.state.filter.search).
I have a feeling I'm so close to this... I have a to do list application I'm building.
A user can click on a button labeled "complete" to mark that specific item as complete.
My thinking on this is, when the user clicks that button to only update "completed" state to true.
For some reason the text within the state changed to "undefined" from the selected item and another item adds to the state when clicking "complete"
Here is my action:
export function completeTodo() {
return {
type: "COMPLETE_TODO",
completed: true
}
}
Here is my reducer:
case "COMPLETE_TODO": {
return Object.assign({}, state, {
todos: [{
text: action.text,
completed: action.completed,
id: getId(state)
}, ...state.todos]
});
}
The following code creates new array with one new todo object and all the previous todos (so basically you are adding one todo to the beginning of the array from previous state):
[
{
text: action.text,
completed: action.completed,
id: getId(state)
},
...state
]
You should filter out old todo object:
[
{
text: action.text,
completed: action.completed,
id: getId(state)
},
...state.todos.filter(todo => todo.id !== getId(state))
]
Your COMPLETE_TODO action doesn't have a text field so the reducer assigns undefined to the state.
As far as I understand, you dont want to change the items' text property when completed. You can remove the text assignment from the reducer.