How does spread operator work in an array vs. obj? - javascript

I'm learning Redux from this tutorial and I don't get how the spread operator below works in both the object and array. If ...state returns the same thing, how can it work in both situations? I thought it will just return an array, so it will work inside the SHUTTER_VIDEO_SUCCESS because it'll just spread whatever is inside the state into the new array in addition to the action.videos, but how will this work inside the SELECTED_VIDEO case? There is no key to place it in. The spread operator grabs the array not the key value pair from the default initialState right?
initialState.js
export default {
images: [],
videos: []
};
someComponent.js
import initialState from './initialState';
import * as types from 'constants/actionTypes';
export default function ( state = initialState.videos, action ) {
switch (action.type) {
case types.SELECTED_VIDEO:
return { ...state, selectedVideo: action.video }
case types.SHUTTER_VIDEO_SUCCESS:
return [...state, action.videos];
default:
return state;
}
}

UPDATE
Spread syntax allows you to spread an array into an object (arrays are technically objects, as is mostly everything in js). When you spread an array into an object, it will add a key: value pair to the object for each array item, where the key is the index and the value is the value stored at that index in the array. For example:
const arr = [1,2,3,4,5]
const obj = { ...arr } // { 0: 1, 1: 2, 2: 3, 3: 4, 4: 5 }
const arr2 = [{ name: 'x' }, { name: 'y' }]
const obj2 = { ...arr2 } // { 0: { name: 'x' }, 1: { name: 'y' } }
You can also spread strings into arrays and objects as well. For arrays, it will behave similarly as String.prototype.split:
const txt = 'abcdefg'
const arr = [...txt] // ['a','b','c','d','e','f', 'g']
For objects, it will split the string by character and assign keys by index:
const obj = { ...txt } // { 0:'a',1:'b',2:'c',3:'d',4:'e',5:'f',6:'g' }
So you may be getting data that sort of works when you spread an array into an object. However, if the example you gave is what you're actually using, you're going to run into problems. See below.
=============
In the case of reducers in redux, when you use the spread syntax with an array it spreads each item from your array into a new array. It's basically the same as using concat:
const arr = [1,2,3]
const arr2 = [4,5,6]
const arr3 = [...arr, ...arr2] // [1,2,3,4,5,6]
// same as arr.concat(arr2)
With an object, the spread syntax spreads key: value pairs from one object into another:
const obj = { a: 1, b: 2, c: 3 }
const newObj = { ...obj, x: 4, y: 5, z: 6 }
// { a: 1, b: 2, c: 3, x: 4, y: 5, z: 6 }
These are two ways to help keep your data immutable in your reducers. The spread syntax copies array items or object keys/values rather than referencing them. If you do any changes in nested objects or objects in arrays, you'll have to take that into account to make sure you get new copies instead of mutated data.
If you have arrays as object keys then you can spread the entire object into a new one and then override individual keys as needed, including keys that are arrays that need updating with spread syntax. For example, an update to your example code:
const initialState = {
images: [],
videos: [],
selectedVideo: ''
}
// you need all of your initialState here, not just one of the keys
export default function ( state = initialState, action ) {
switch (action.type) {
case types.SELECTED_VIDEO:
// spread all the existing data into your new state, replacing only the selectedVideo key
return {
...state,
selectedVideo: action.video
}
case types.SHUTTER_VIDEO_SUCCESS:
// spread current state into new state, replacing videos with the current state videos and the action videos
return {
...state,
videos: [...state.videos, ...action.videos]
}
default:
return state;
}
}
This shows updating a state object and specific keys of that object that are arrays.
In the example you give, you're changing the structure of your state on the fly. It starts as an array, then sometimes returns an array (when SHUTTER_VIDEO_SUCCESS) and sometimes returns an object (when SELECTED_VIDEO). If you want to have a single reducer function, you would not isolate your initialState to just the videos array. You would need to manage all of your state tree manually as shown above. But your reducer should probably not switch the type of data it's sending back depending on an action. That would be an unpredictable mess.
If you want to break each key into a separate reducer, you would have 3 (images, videos and selectedVideo) and use combineReducers to create your state object.
import { combineReducers } from 'redux'
// import your separate reducer functions
export default combineReucers({
images,
videos,
selectedVideos
})
In that case each reducer will be run whenever you dispatch an action to generate the complete state object. But each reducer will only deal with its specific key, not the whole state object. So you would only need array update logic for keys that are arrays, etc.

According to the tutorial:
create-react-app comes preinstalled with babel-plugin-transform-object-rest-spread that lets you use the spread (…) operator to copy enumerable properties from one object to another in a succinct way. For context, { …state, videos: action.videos } evaluates to Object.assign({}, state, action.videos).
So, that's not a feature of ES6. It uses a plugin to let you use that feature.
Link: https://babeljs.io/docs/plugins/transform-object-rest-spread/

An array is also a key/value-pair but the key is an index. It's using ES6 destructuring and the spread syntax.
Redux docs on the subject
You may also want to read up on ES6 property value shorthand (or whatever it is called):
ES6 Object Literal in Depth
Whenever you find yourself assigning a property value that matches a property name, you can omit the property value, it’s implicit in ES6.

Related

redux react update array after copying array

I have a question I'm facing a problem that I can't update array after copying it.
If I copy an array and don't update the id, when I type something on input the text will appear in the same way where I copy it.
here is my Initialstate
const initialState = [
{
id: random numbers,
options: [{ id: random numbers , value: '' },
],
},
];
it will have a lot of option
and I just would like to update options id
this is what i tried
case COPY_QUESTION: {
const newArray = [...state];
const copyQuestion = newArray[action.payload];
copyQuestion.options.map((option) =>
Object.assign({}, option, {
id: random number,
}),
);
return [...state, copyQuestion];
}
thanks for reading my question.
it's caused due to call-by-reference.
As I can see in your code,
You are copying the reference of an array which might have the reason to overwrite details of the original array when you are typing. You can copy the values of the original array by using Javascript Object Prototype
so in that, you need to destruct your array or break your reference in the duplicate array.
example
let A = [a,b,c] //original Array
let B = JSON.parse(JSON.strigify(A)) // duplicate Array

How can I store items by id in Redux Store

I am using Redux in my React application. However items are always stored by index, like that =>
I want to store them by ids, like instead of 0 first item's index should be 41. How can I do that?
reducer.js
export function ratedPosts(state=[], action) {
enableES5()
return (
produce(state, draft => {
const rate = action.Rate
switch (action.type) {
case RATE_POST:
draft.unshift({postId: action.postId, rate: rate})
break
case RATE_POST_UPDATE:
draft.map(post => post.postId === action.postId).rate = rate
break
default:
return draft
}
})
)
}
You can't do that with arrays, but you can do that with objects. I see also that you are using Array.unshift to add new posts, keep in mind that arrays do not guarantee the sequence of the items, even though it works most of the time.
You'll need to convert your data structure to use objects instead of array, but in the getter function you could convert to an array so it can be more easily used in the frontend.
You can set an object ID programmatically using [ ]
let myObject = {}
const idOne = 'abc'
const idTwo = 'def'
draft[idOne] = "Hello" // draft.abc === "Hello"
draft[idTwo] = "World" // draft.def === "World"
draft === {
abc: "Hello",
def: "World"
}

When using the spread syntax, should I copy the whole object, level by level?

When using Redux, I have my initial state:
const initialState = {
foo: {
bar: {
foobar: 1,
barfoo: 2
}
},
xyz: true,
abc: {
jkl: [1,2,3,4]
}
}
Then I have the reducer, and inside a switch. Suppose I have a case X in which I want to change xyz to false.
Is this enough?
return {
...state,
xyz: false
}
or should I do?
return {
foo: {
bar: {
...state.foo.bar
}
},
xyz: false,
abc: {
jkl: [...state.abc.jkl]
}
}
It is enough if you are aware that the spread operator only does a shallow clone:
const a = {x: {y: 10}};
const b = {...a};
b.x.y = 42;
console.log(b.x.y); //=> 42
console.log(a.x.y); //=> 42
I understand that with Redux you want to return new state. Just know that this potentially opens up the gate to unwanted mutations and side effects.
With this simple example, I have shown how a simple reducer can potentially tamper with the history of your Redux states.
No need to do your second implementation! That would create a catastrophe of reducers lol.
Just use:
return {
...state,
xyz: false
}
Not only is this the way that is advised in Redux apps, imagine having to debug reducers where you use your second implementation, and you make typos.
On the case of nested properties, you would need to do something like this:
return {
...state,
foo: {
bar: { ...state.foo.bar, roflWaffle: 3 }
}
}
Also, recommend checking out other answers here too, very helpful in depth information that will greatly help you understand how JavaScript and Redux works under the hood.
Dan's answer gives you a practical and working syntax (do that! it's cleaner), but let's understand why.
Each spread copy is shallow, not deep. Only the first level is duplicated. For example, let's say you attempt to clone an object this way:
> let original = { someNumber: 1, someArray: [1, 2] }
> let copy = { ...original }
The objects original and copy are distinct. If you set properties, they won't reflect each other.
> x2.someNumber = 2
> x2.newProperty = "hello"
> console.log(x1)
{ someNumber: 1, someArray: [1,2] } // same someNumber, no newProperty!
But the value of each individual key is not duplicated, it's just a reference to the original. The someArray property references the very same array instance in both objects. So:
> console.log(x1.someArray)
[1, 2]
> x2.someArray.push(3)
> console.log(x1.someArray)
[1, 2, 3]
Both original.someArray and copy.someArray are referencing the same array instance. Two references in two objects, but only one underlying array.
There's no easy, 100% foolproof way to actually clone an object, because not all objects are simple JSON-like dictionaries. But you have some options in this other answer.
When working with React and Redux, many problems can be avoided by using a library like ImmutableJS, which ensures each object is distinct and every modification produces a different object. The performance is good and the syntax more comfortable in many cases, as well.

React Redux - Reducer with CRUD - best practices?

I wrote simple reducer for User entity, and now I want to apply best practices for it, when switching action types and returning state. Just to mention, I extracted actions types in separate file, actionsTypes.js.
Content of actionsTypes.js :
export const GET_USERS_SUCCESS = 'GET_USERS_SUCCESS';
export const GET_USER_SUCCESS = 'GET_USER_SUCCESS';
export const ADD_USER_SUCCESS = 'ADD_USER_SUCCESS';
export const EDIT_USER_SUCCESS = 'EDIT_USER_SUCCESS';
export const DELETE_USER_SUCCESS = 'DELETE_USER_SUCCESS';
First question, is it mandatory to have actions types for FAILED case? For example, to add GET_USERS_FAILED and so on and handle them inside usersReducer?
Root reducer is:
const rootReducer = combineReducers({
users
});
There is code of usersReducer, and I put comments/questions inside code, and ask for answers (what are best practices to handle action types):
export default function usersReducer(state = initialState.users, action) {
switch (action.type) {
case actionsTypes.GET_USERS_SUCCESS:
// state of usersReducer is 'users' array, so I just return action.payload where it is array of users. Will it automatically update users array on initial state?
return action.payload;
case actionsTypes.GET_USER_SUCCESS:
// What to return here? Just action.payload where it is just single user object?
return ;
case actionsTypes.ADD_USER_SUCCESS:
// what does this mean? Can someone explain this code? It returns new array, but what about spread operator, and object.assign?
return [...state.filter(user => user.id !== action.payload.id),
Object.assign({}, action.payload)];
case actionsTypes.EDIT_USER_SUCCESS:
// is this ok?
const indexOfUser = state.findIndex(user => user.id === action.payload.id);
let newState = [...state];
newState[indexOfUser] = action.payload;
return newState;
case actionsTypes.DELETE_USER_SUCCESS:
// I'm not sure about this delete part, is this ok or there is best practice to return state without deleted user?
return [...state.filter(user => user.id !== action.user.id)];
default:
return state;
}
}
I'm not an experienced developer but let me answer your questions what I've learned and encountered up to now.
First question, is it mandatory to have actions types for FAILED case?
For example, to add GET_USERS_FAILED and so on and handle them inside
usersReducer?
This is not mandatory but if you intend to give a feedback to your clients it would be good. For example, you initiated the GET_USERS process and it failed somehow. Nothing happens on client side, nothing updated etc. So, your client does not know it failed and wonders why nothing happened. But, if you have a failure case and you catch the error, you can inform your client that there was an error.
To do this, you can consume GET_USERS_FAILED action type in two pleases for example. One in your userReducers and one for, lets say, an error or feedback reducer. First one returns state since your process failed and you can't get the desired data, hence does not want to mutate the state anyhow. Second one updates your feedback reducer and can change a state, lets say error and you catch this state in your component and if error state is true you show a nice message to your client.
state of usersReducer is 'users' array, so I just return
action.payload where it is array of users. Will it automatically
update users array on initial state?
case actionsTypes.GET_USERS_SUCCESS:
return action.payload;
This is ok if you are fetching whole users with a single request. This means your action.payload which is an array becomes your state. But, if you don't want to fetch all the users with a single request, like pagination, this would be not enough. You need to concat your state with the fetched ones.
case actionsTypes.GET_USERS_SUCCESS:
return [...state, ...action.payload];
Here, we are using spread syntax.
It, obviously, spread what is given to it :) You can use it in a multiple ways for arrays and also objects. You can check the documentation. But here is some simple examples.
const arr = [ 1, 2, 3 ];
const newArr = [ ...arr, 4 ];
// newArr is now [ 1, 2, 3, 4 ]
We spread arr in a new array and add 4 to it.
const obj = { id: 1, name: "foo, age: 25 };
const newObj = { ...obj, age: 30 };
// newObj is now { id: 1, name: "foo", age: 30 }
Here, we spread our obj in a new object and changed its age property. In both examples, we never mutate our original data.
What to return here? Just action.payload where it is just single user
object?
case actionsTypes.GET_USER_SUCCESS:
return ;
Probably you can't use this action in this reducer directly. Because your state here holds your users as an array. What do you want to do the user you got somehow? Lets say you want to hold a "selected" user. Either you can create a separate reducer for that or change your state here, make it an object and hold a selectedUser property and update it with this. But if you change your state's shape, all the other reducer parts need to be changed since your state will be something like this:
{
users: [],
selectedUser,
}
Now, your state is not an array anymore, it is an object. All your code must be changed according to that.
what does this mean? Can someone explain this code? It returns new
array, but what about spread operator, and object.assign?
case actionsTypes.ADD_USER_SUCCESS:
return [...state.filter(user => user.id !== action.payload.id), Object.assign({}, action.payload)];
I've already tried to explain spread syntax. Object.assign copies some values to a target or updates it or merges two of them. What does this code do?
First it takes your state, filters it and returns the users not equal to your action.payload one, which is the user is being added. This returns an array, so it spreads it and merges it with the Object.assign part. In Object.assign part it takes an empty object and merges it with the user. An all those values creates a new array which is your new state. Let's say your state is like:
[
{ id: 1, name: "foo" },
{ id: 2, name: "bar" },
]
and your new user is:
{
id: 3, name: "baz"
}
Here what this code does. First it filters all the user and since filter criteria does not match it returns all your users (state) then spread it (don't forget, filter returns an array and we spread this array into another one):
[ { id: 1, name: "foo"}, { id: 2, name: "bar" } ]
Now the Object.assign part does its job and merges an empty object with action.payload, a user object. Now our final array will be like this:
[ { id: 1, name: "foo"}, { id: 2, name: "bar" }, { id: 3, name: "baz" } ]
But, actually Object.assign is not needed here. Why do we bother merging our object with an empty one again? So, this code does the same job:
case actionsTypes.ADD_USER_SUCCESS:
return [...state.filter(user => user.id !== action.payload.id), action.payload ];
is this ok?
case actionsTypes.EDIT_USER_SUCCESS:
const indexOfUser = state.findIndex(user => user.id === action.payload.id);
let newState = [...state];
newState[indexOfUser] = action.payload;
return newState;
It seems ok to me. You don't mutate the state directly, use spread syntax to create a new one, update the related part and finally set your state with this new one.
I'm not sure about this delete part, is this ok or there is best
practice to return state without deleted user?
case actionsTypes.DELETE_USER_SUCCESS:
return [...state.filter(user => user.id !== action.user.id)];
Again, it seems ok to me. You filter the deleted user and update your state according to that. Of course there are other situations you should take into considiration . For example do you have a backend process for those? Do you add or delete users to a database? If yes for all the parts you need to sure about the backend process success and after that you need to update your state. But this is a different topic I guess.

Replace object in array on react state

This question might fall a little on the side of a "best practice" question, but please bear with me.
Here is a portion of my state:
this.state = {
typeElements: {
headers: [
{
name: "h1",
size: 70,
lineHeight: 1.25,
kearning: 0,
marginAfter: 0
}, {
name: "h2",
size: 70,
lineHeight: 1.25,
kearning: 0,
marginAfter: 0
}, {
name: "h3",
size: 70,
lineHeight: 1.25,
kearning: 0,
marginAfter: 0
}...
What I need to do is REPLACE the object at a given index on the headers array.
I don't know how to do that with the setState method as in this.setState(headers[1] = {obj}) - but that's obviously invalid. My current method is creating a new array and clobbering the old one like this:
_updateStyle(props) {
let newState = Object.assign({}, this.state)
newState.typeElements.headers[props.index] = props
this.setState(newState)
};
For my small hacky project I guess it's OK but I feel like this is super heavy handed and would quickly lead to performance issues at any kind of scale.
Updated: since this answer still gets upvotes, be aware that the previous answer below is outdated with modern JavaScript and React. The "update" addon is now legacy and "immutability-helper" can be used instead.
The React docs also mention why immutability is important so avoid mutating state. For immutable updates you can use Object.assign() or spread syntax which needs to be done for every level of nesting, like in this example the nested headers object and its array elements. In this particular example we can use the array index as key so it's possible to also use the spread operator to make a shallow clone of the array and assign a new object as value at given index in the cloned array.
_updateStyle (props) {
const { typeElements } = this.state;
const updatedHeaders = [...typeElements.headers];
updatedHeaders[props.index] = props;
this.setState({
...this.state,
typeElements: {
...typeElements,
headers: updatedHeaders
}
));
}
Another solution which doesn't require the spread syntax and is needed if we are not using the array index to find the object we want to replace, is using array.map to create a new array and returning the new object instead of the old one at given index.
const updatedHeaders = typeElements.headers.map((obj, index) => {
return index === props.index ? props : obj;
});
Similar examples in the Redux docs also explain "immutable update patterns".
React has some immutability helpers for this, which is explained in
the docs: https://facebook.github.io/react/docs/update.html
In your case you could use the $splice command to remove one item and
add the new one at given index, for example:
_updateStyle (props) {
this.setState(update(this.state.typeElements,
{ $splice: [[props.index, 1, props]] }
));
}
Offering a better explanation of how to accomplish this.
First, find the index of the element you're replacing in the state array.
Second, update the element at that index
Third, call setState with the new collection
import update from 'immutability-helper';
// this.state = { employees: [{id: 1, name: 'Obama'}, {id: 2, name: 'Trump'}] }
updateEmployee(employee) {
const index = this.state.employees.findIndex((emp) => emp.id === employee.id);
const updatedEmployees = update(this.state.employees, {$splice: [[index, 1, employee]]}); // array.splice(start, deleteCount, item1)
this.setState({employees: updatedEmployees});
}
use immutability-helper
you can find nice examples there
Object.Assign uses shallow copy, not deep copy.
Please be aware that in your example Object.assign({}, this.state) copies only links to the nearest children of the state, i.e. to typeElements, but headers array is not copied.
There is a syntactic sugar ... in ES6 for Object.Assign, Babel has the special addon transform-object-rest-spread.
let newHeaders = { ...this.state.typeElements.headers};
newHeaders[index] = props.Something;
let newState = {...state, typeElements: { newHeaders }};

Categories

Resources