I have this Redux store and reducer
const INITIAL_WORK = {
departments: []
}
const works = (state = INITIal_WORK, action) => {
switch(action.type){
case 'ADD_DEPARTMENT':
return {
...state,
departments: [...state.department, action.item]
}
default:
return state
}
}
In departments work people so I want to this people was inside a single department works people. So after fetch data from db I want my store look like this:
const INITIAL_WORK = {
departments: [
{
id: 1,
name: "First department",
people: [
{
id: 1,
name: "Johna Wayne"
},
{
id: 2,
name: "Jessica Biel"
},
{
id: 3,
name: "Bratt Pitt"
}
]
},
{
id: 2,
name: "second department",
people: [
{
id: 4,
name: "Salma Hayek"
},
{
id: 5,
name: "Sylvester Stallone"
}
]
}
]
}
Is it possible create case in reducer which will be added people inside people array inside single department? How can I do that?
Yes, absolutely.
Let us assume your API returns payload as following.
{
departmentId: `id of the department`
id: `person id`
name: `person name`
}
const INITIAL_WORK = {
departments: []
}
const works = (state = INITIal_WORK, action) => {
switch(action.type){
case 'ADD_DEPARTMENT':
return {
...state,
departments: [...state.department, action.item]
}
case 'ADD_PERSON':
const { departmentId, id, name } = action.item
const departments = [...state.departments];
departments[departmentId].people.push({id, name})
return {
...state,
departments
}
default:
return state
}
}
Combining Immer.js and Redux will be helpful for this case.
Here is a simple example of the difference that Immer could make in practice.
// Reducer with inital state
const INITAL_STATE = {};
// Shortened, based on: https://github.com/reactjs/redux/blob/master/examples/shopping-cart/src/reducers/products.js
const byId = (state = INITAL_STATE, action) => {
switch (action.type) {
case RECEIVE_PRODUCTS:
return {
...state,
...action.products.reduce((obj, product) => {
obj[product.id] = product
return obj
}, {})
}
default:
return state
}
}
After using Immer, our reducer can be expressed as:
import produce from "immer"
// Reducer with inital state
const INITAL_STATE = {};
const byId = produce((draft, action) => {
switch (action.type) {
case RECEIVE_PRODUCTS:
action.products.forEach(product => {
draft[product.id] = product
})
}
}, INITAL_STATE)
let mergePeople=[];
INITIAL_WORK.departments.filter((cur,index)=>{
cur.people.filter((person)=>{
mergePeople.push(person);
})
}
)
console.log(mergePeople);
Related
I have a problem with redux. I am working on an app where you can save a note about your youtube video. Everything is working fine, but when I started adding the 'ADD_ITEM' action I have the same mistake every time with this action in my reducer. The error I have is: TypeError: can't access property Symbol.iterator, state[action.payload.savedVideos] is undefined. I am using connect method and function mapDispatchToProps seems fine, so maybe the problem is in reducer or action creator. Here is my add item function in my reducer:
`
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'ADD_ITEM':
return {
...state,
[action.payload.savedVideos]: [...state[action.payload.savedVideos], action.payload.item],
};
here is my action creator:
export const addItem = (itemContent, savedVideos) => {
const getId = () => `_${Math.random().toString(36).substr(2, 9)}`;
return {
type: 'ADD_ITEM',
payload: {
savedVideos,
item: {
id: getId(),
...itemContent,
},
},
};
};
and my initial state:
const initialState = {
saves: [
{
id: 1,
title: 'Hello meow',
created: '18 may 2018',
link: 'https://www.youtube.com/watch?v=-QmyosHh-kU',
content: 'Nowy film gargamela!',
},
{
id: 2,
title: 'Hello meow',
created: '18 may 2018',
link: 'https://www.youtube.com/watch?v=-QmyosHa-kU',
content: 'Nowy film!',
},
{
id: 3,
title: 'Hello meow',
created: '18 may 2018',
link: 'https://www.youtube.com/watch?v=-QmyosHy-kU',
content: 'Nowy film gorgonzoli!',
},
{
id: 4,
title: 'Hello meow',
created: '18 may 2018',
link: 'https://www.youtube.com/watch?v=-QmyosHy-kU',
content: 'Nowy film gorgonzoli!',
},
{
id: 5,
title: 'Hello meow',
created: '18 may 2018',
link: 'https://www.youtube.com/watch?v=-QmyosHy-kU',
content: 'Nowy film gorgonzoli!',
},
],
};
https://redux.js.org/tutorials/fundamentals/part-3-state-actions-reducers#creating-the-root-reducer
Assuming savedVideos is from the initialState, when u add video, you are adding the following object, and you want to add on to your initialState (aka initial store), you do not need to pass savedVideos to actions and reducer.
{
id: 1,
title: 'Hello meow',
created: '18 may 2018',
link: 'https://www.youtube.com/watch?v=-QmyosHh-kU',
content: 'Nowy film gargamela!',
},
Your action can be..
export const addItem = (itemContent, savedVideos) => {
const getId = () => `_${Math.random().toString(36).substr(2, 9)}`;
return {
type: 'ADD_ITEM',
payload: {
id: getId(),
...itemContent,
},
};
};
Your reducer can be
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'ADD_ITEM':
return { ...state, saves: [...state.saves, action.payload] }
//or whatever new item u need to add to the state (store).
case 'DELETE_ITEM': //bonus
return { ...state, saves: state.saves.filter( x => x.id !== action.payload.id) }
}
I want to explain the error
"TypeError: can't access property Symbol.iterator, state[action.payload.savedVideos] is undefined"
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'ADD_ITEM':
return {
...state,
[action.payload.savedVideos]: [...state[action.payload.savedVideos], action.payload.item],
};
What you are actually doing in your reducer is updating a property name from a variable.
Square brackets around a property name means that it's a variable.
The property name is the value of action.payload.savedVideos. But that's not a valid property name because action.payload.savedVideos is an array (a Symbol.iterator).
You don't need a dynamic property name because you are always updating the property saves. As explained by #Someone Special, you don't need to include the existing savedVideos in your action either because you can already access them from the state.
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'ADD_ITEM':
return {
...state,
saves: [...state.saves, action.payload.item],
};
I have a scenario
{
data:'',
skus: [
{ id: 1, ......}
{ id: 2, ......}
{ id: 3, ......}
]
api_first:'',
}
I have that schema and want to setState in somewhere skus on selected sku item and return changed item to original array
this.setState(produce(prevstate =>
prevstate.data.sku.obj="change"
))
this works for me
I'd recommend to use functional setState and map:
const updateSku = (skuId, data) => {
this.setState(prevState => ({
...prevState,
skus: prevState.skus.map(sku => {
if (sku.id === skuId) {
return {...sku, ...data}
} // else
return sku
})
}))
}
State immutability is important sometimes devs mutate states those are complex with multiple nested levels. You can always update state with simple javascript object update stratigy but I would suggest you to use immerjs. It reduces the code and makes it much more cleaner and easy to understand what is going to change. It helps a lot in redux reducers where a complex state needs to be updated with mutation
Here is example
https://immerjs.github.io/immer/docs/example-setstate
/**
* Classic React.setState with a deep merge
*/
onBirthDayClick1 = () => {
this.setState(prevState => ({
user: {
...prevState.user,
age: prevState.user.age + 1
}
}))
}
/**
* ...But, since setState accepts functions,
* we can just create a curried producer and further simplify!
*/
onBirthDayClick2 = () => {
this.setState(
produce(draft => {
draft.user.age += 1
})
)
}
Using immerjs, it will be
const updateSku = (skuId, data) => {
this.setState(produce(draft => {
const sku = draft.skus.find(s => s.id === skusId);
Object.assign(sku, data);
}));
}
What I have understood from your explanation is that when the SKU item gets changed you want to update the state Skus.
Here I've provided a solution for the same please try to relate with your example.
let's assume you have the following react component.
import React, { Component } from "react";
export class Sku extends Component {
constructor(props) {
super(props);
this.state = {
data: "",
skus: [
{ key: "key1", value: "value1" },
{ key: "key2", value: "value2" },
{ key: "key3", value: "value3" },
],
APIFirst: "",
};
}
handleSkuChange = (data) => {
this.setState(({ skus }) => {
const newSkus = skus.map(sku => (sku.key === data.key ? { ...sku, ...data } : sku));
return { skus: newSkus };
});
};
render() {
const { data, skus, APIFirst } = this.state;
const newSku = { key: 'key2', value: 'newSku' };
console.log("states =>", data, skus, APIFirst);
return (
<button type="button" onClick={() => this.handleSkuChange(newSku)}>'Change sku'</button>
);
}
}
The handleSkuChange function will work like it,
const skus = [
{ key: "key1", value: "value1" },
{ key: "key2", value: "value2" },
{ key: "key3", value: "value3" },
];
const handleSkuChange = (data) => (
skus.map(sku => (sku.key === data.key) ? { ...sku, value: "newValue" } : sku));
const newSku = { key: 'key2', value: 'newSku' };
console.log('old skus', skus);
console.log('new skus', handleSkuChange(newSku));
I have the following reducer in React Redux:
export const reducer = (state = initialStateData, action) => {
switch (action.type) {
case Action.TOGGLE_ARR_FILTER:
{
const subArr = state.jobOffers.filters[action.key];
const filterIdx = subArr.indexOf(action.value[0]);
const newArr = { ...state.jobOffers.filters
};
if (filterIdx !== -1) {
newArr[action.key].splice(filterIdx, 1);
} else {
newArr[action.key].push(action.value[0]);
}
return {
...state,
jobOffers: {
...state.jobOffers,
filters: {
...newArr,
},
},
};
}
And this is my object:
const initialStateData = {
jobOffers: {
filters: {
employments: [],
careerLevels: [],
jobTypeProfiles: [],
cities: [],
countries: [],
},
configs: {
searchTerm: '',
currentPage: 1,
pageSize: 5,
},
},
};
The reducer as such seems to work, it toggles the values correctly.
But: Redux always shows "states are equal", which is bad as it won't recognize changes.
Can someone help ? I assume that I am returning a new object..
you can use Immer , redux also uses this for immutable updates for nested stuffs.
Because of this, you can write reducers that appear to "mutate" state, but the updates are actually applied immutably.
const initialStateData = {
jobOffers: {
filters: {
employments: [],
careerLevels: [],
jobTypeProfiles: [],
cities: [],
countries: [],
},
configs: {
searchTerm: '',
currentPage: 1,
pageSize: 5,
},
},
};
export const reducer = immer.produce((state = initialStateData, action) => {
switch (action.type) {
case Action.TOGGLE_ARR_FILTER:
const subArr = state.jobOffers.filters[action.key];
const filterIdx = subArr.indexOf(action.value[0]);
const newArr = state.jobOffers.filters;
if (filterIdx !== -1)
newArr[action.key].splice(filterIdx, 1);
else
newArr[action.key].push(action.value[0]);
return state;
}
})
Although you take a copy of state.jobOffers.filters, this still holds references to original child arrays like employments. So when you mutate newArr[action.key] with splice or push, Redux will not see that change, as it is still the same array reference.
You could replace this:
if (filterIdx !== -1) {
newArr[action.key].splice(filterIdx, 1);
} else {
newArr[action.key].push(action.value[0]);
}
with:
newArr[action.key] = filterIdx !== -1
? [...newArr[action.key].slice(0, filterIdx), ...newArr[action.key].slice(filterIdx+1)]
: [...newArr[action.key], action.value[0]]);
BTW, you don't have to copy newArr again, as it already is a copy of filters. You can replace:
filters: {
...newArr,
},
by just:
filters: newArr,
I'm trying some app in react redux and i have a problem with updating (push, remove, update) the nested array in state.
I have some object called service like this:
{
name: 'xzy',
properties: [
{ id: 1, sName: 'xxx'},
{ id: 2, sName: 'zzz'},
]
}
Whatever I did (in case of adding property to collection) in the reducer with the properties collection generate problem that all properties got same values as the last I had recently added -> Added property object is in service properties collection but the action replace all values in all properties in this collection.
My reducer:
export function service(state = {}, action) {
switch (action.type) {
case 'ADD_NEW_PROPERTY':
console.log(action.property) // correct new property
const service = {
...state, properties: [
...state.properties, action.property
]
}
console.log(service); // new property is pushed in collection but all properties get same values
return service
default:
return state;
}
}
I have tried some solution with immutability-helper library and it generate the same problem:
export function service(state = {}, action) {
case 'ADD_NEW_PROPERTY':
return update(state, {properties: {$push: [action.property]}})
default:
return state;
}
For example when I add new property { id: 1, sName: 'NEW'} to example above I will get this state:
{
name: 'xzy',
properties: [
{ id: 1, sName: 'NEW'},
{ id: 1, sName: 'NEW'},
{ id: 1, sName: 'NEW'}
]
}
Can someone help? :)
Make a copy of action.property as well. Whatever is dispatching this action, it could be reusing the same object.
export function service(state = {}, action) {
switch (action.type) {
case 'ADD_NEW_PROPERTY':
console.log(action.property) // correct new property
const service = {
...state,
properties: [
...state.properties,
{ ...action.property }
]
}
console.log(service); // new property is pushed in collection but all properties get same values
return service
default:
return state;
}
}
I'd recommend you to use Immutable data https://facebook.github.io/immutable-js/docs/#/List
import { fromJS, List } from 'immutable';
const initialState = fromJS({
propeties: List([{ id: 1, sName: 'xyz' }]
}
function reducer(state = initialState, action) {
case ADD_NEW_PROPERTY:
return state
.update('properties', list => list.push(action.property));
// ...
}
Your service reducer should probably look somewhat like this:
// Copy the state, because we're not allowed to overwrite the original argument
const service = { ...state };
service.properties.append(action.property)
return service
You should always copy the state before returning it.
export default function(state = {}, action) {
switch(action.type) {
case 'GET_DATA_RECEIVE_COMPLETE': {
const data = action.firebaseData;
const newState = Object.assign({}, state, {
data
});
return newState
}
default:
return state;
}
}
In my application i am displaying person data.
I have a scenario where I can input new person. So far I have got this:
Reducer:
export default function (state = [], action) {
console.log("Reducing");
switch (action.type) {
case 'ADD':
console.log("ADDING!");
return action.payload;
break;
}
return state;
}
Action:
export const addPerson = (person) => {
console.log("addPerson Action Fired! ", person);
return {
type: 'ADD',
payload: person
}
};
I have a reducer with few person data which i am showing in my application but i am blocked at this point of adding new person. So far i can see my input in console log but can't figure out how can i add it to the state.
Help would be very much appreciated.
This is the reducer where i am showing the data from.
export default function () {
return ["abc","def","ghi","jkl"]
}
Now i am displaying a list of these elements from the array(each are one person). When i add one it will be added with these persons and show me the updated list.
You should do the following in your reducer:
export default function (state = [], action) {
console.log("Reducing");
switch (action.type) {
case 'ADD':
return [
...state,
action.payload,
]
}
return state;
}
In this way, you add the person to your existing array. Otherwise you changes your current state value from an array to an object.
Let's consider your person object value is the following and you dispatch it:
{
type: 'ADD',
payload: {
id: 1,
name: 'John Doe',
}
}
With your previous code, you would have the current value:
// First step
return action.payload;
// Second step
return { id: 1, name: 'John Doe' };
With the corrected code:
// First step
return [
...state,
action.payload,
]
// Second step
return [
...[],
{ id: 1, name: 'John Doe' },
]
// Third step
return [{ id: 1, name: 'John Doe' }];