I have a packageReducer which keeps the packageType and packageList related details. once the details are fetched from the server I need to replace the initial state values with the new values that are been fetched. As an example, if the packageLists are been fetched I need to replace only the "packageList"
Below is my PackageState reducer,
const initialState = {
packageList: packageListInitialState,
packageTypes: [{title: 'Select Package Type', value: ''}],
};
export default function packageState( state = initialState, action ) {
switch ( action.type ) {
case FETCH_PACKAGE_LIST_SUCCESS:{
return Object.assign( {}, state, action.payload );
}
case FETCH_PACKAGE_TYPES_SUCCESS:{
return Object.assign( {}, state, action.payload );
}
default:
return state;
}
}
The way I have implemented I think im replacing the entire state, Can someone let me know how I can achieve it?
Thank you.
You are not:
var state = {a: 1, b: 2, c: 3}
var newData = {a: 4, b: 5}
console.log(Object.assign( {}, state, newData )) // { a: 4, b: 5, c: 3 }
Properties in the target object will be overwritten by properties in the sources if they have the same key. Later sources' properties will similarly overwrite earlier ones. (docs)
So, as long as your payload contains the keys that you really want to update, you are safe. You can also do it in a simpler way if you use ES6's spread syntax (I'm assuming your payload looks like {packageList: data}):
const initialState = {
packageList: packageListInitialState,
packageTypes: [{title: 'Select Package Type', value: ''}],
};
export default function packageState( state = initialState, action ) {
switch ( action.type ) {
case FETCH_PACKAGE_LIST_SUCCESS:{
return {...state, ...action.payload};
}
case FETCH_PACKAGE_SETTINGS_SUCCESS:{
return {...state, ...action.payload};
}
default:
return state;
}
}
Under the assumption that the action you're trying to achieve this in is only the FETCH_PACKAGE_LIST_SUCCESS action, and the payload is the updated/fetched list, then you just need to return an object as shown below.
Since you're trying to return an object with only one of the two properties changed, then you can use the previous state's value for the unchanged property and update the other.
const initialState = {
packageList: packageListInitialState,
packageTypes: [{title: 'Select Package Type', value: ''}],
};
export default function packageState( state = initialState, action ) {
switch ( action.type ) {
case FETCH_PACKAGE_LIST_SUCCESS:{
return { packageList: action.payload, packageTypes: state.packageTypes }
}
case FETCH_PACKAGE_SETTINGS_SUCCESS:{
return Object.assign( {}, state, action.payload );
}
default:
return state;
}
}
Related
I'm kind of new to React.js & Redux, so I have encountered a problem with Reducers.
I am creating a site that have a main "Articles" page, "Question & Answers" page, I created for each one a separate Reducer that both work just fine.
The problem is in "Main Page" which contains a lot of small different pieces of information, and I don't want to create each little different piece of information its on Reducer, so I am trying to create one Reducer which will handle a lot of very small different pieces of information, and I can't make that work, inside the main "Content" object, I put 2 Key Value Pairs that each have an array, one for each different information, one is "Features" info, and one for the "Header" info.
This is the error that I'm getting:
Uncaught TypeError: Cannot read property 'headerContent' of undefined
at push../src/reducers/ContentReducer.js.__webpack_exports__.default (ContentReducer.js:15)
I am not sure what's the problem, maybe my code is wrong or maybe my use of the spread operator, any solution?
I have added the necessary pages from my code:
ACTIONS FILE
export const addFeatureAction = (
{
title = 'Default feature title',
feature = 'Default feature',
} = {}) => ({
type: 'ADD_FEATURE',
features: {
id: uuid(),
title,
feature
}
})
export const addHeaderAction = (
{
title = 'Default header title',
head = 'Default header',
} = {}) => ({
type: 'ADD_HEADER',
header: {
id: uuid(),
title,
head
}
})
REDUCER FILE:
const defaultContentReducer = {
content: {
featuresContent: [],
headerContent: [],
}
}
export default (state = defaultContentReducer, action) => {
switch(action.type) {
case 'ADD_FEATURE':
return [
...state.content.featuresContent,
action.features
]
case 'ADD_HEADER':
return [
...state.content.headerContent,
action.header
]
default:
return state
}
}
STORE FILE:
export default () => {
const store = createStore(
combineReducers({
articles: ArticleReducer,
qnaList: QnaReducer,
content: ContentReducer
})
);
return store;
}
The reducer function is supposed to return the next state of your application, but you are doing a few things wrong here, you are returning an array, a piece of the state and not the state object, I would suggest you look into immer to prevent this sort of errors.
Simple fix:
export default (state = defaultContentReducer, action) => {
switch(action.type) {
case 'ADD_FEATURE':
return {...state, content: {...state.content. featuresContent: [...action.features, ...state.content.featuresContent]}}
// More actions are handled here
default:
return state
}
}
If you use immer, you should have something like this
export default (state = defaultContentReducer, action) => {
const nextState = produce(state, draftState => {
switch(action.type) {
case 'ADD_FEATURE':
draftState.content.featuresContent = [...draftState.content.featuresContent, ...action.features]
});
break;
default:
break;
return nextState
}
Do not use push while updating your state. Use concat
I'm facing this really strange issue, consider this reducer:
export default function(state = null, action) {
console.log("Current State: ",state);
// on performing actions, it gives me:
// Current State: null
// Current State: Array [{}]
// Current State: Array [{}] -- all good
if(state === null) {
state = [
{id: 1, title: "Java"}
];
}
// UPDATED PART. FORGOT TO MENTION IT BEFORE
if(Action.type == "UPDATE_LIST") {
state.push( Action.payload ); // don't do that, this'll mutate your array and states are immutable
}
/////////////
return state; // this is the main problem
}
The above code does not invoke mapStateToProps inside my component. However, modifying the above reducer like down below does invoke mapStateToProps:
return []; // instead of return state;
OR
return [ {id: 1, title: "Python"} ]; // instead of return state;
I'm returning instanceof Array in both cases [ state & [] ], but only the latter one is invoking mapStateToProps in my component.
This is strange, and I've no idea what am I suppose to do to fix this.
The point of redux is to ensure that your state is not directly mutable. Since Arrays and objects are passed by reference in Javascript, your code is attempting to mutate the state object directly..which is incorrect.
Always mutate the state by returning the new state. Like this:
export default function(state = null, action) {
let newState = [...state];
if(state === null) {
newstate = [
{id: 1, title: "Java"}
];
}
return newState;
}
do like this way :
if(state === null) {
state = [
{id: 1, title: "Java"}
];
return state;
}
return state;
Redux State:
let initialState = Immutable.fromJS({
budgetItems: [],
editingBudget: [[]]
});
Trying to update items in the budgetItems section, which is an array of objects.
The structure of the objects in the array is like:
let initBudget = {budgetCategory: '', budgetCost: '', budgetDate: ''};
My attempt:
case types.UPDATE_EXISTING_BUDGET:
return state.getIn(['budgetItems']).update(
state.getIn(['budgetItems']).findIndex(function(item) {
return item.get("budgetCategory") === action.payload.budgetCategory;
}), function(item) {
return item.set(action.payload.budgetCategory);
}
);
Trying to literally replace the entire object that I've found. The above code is working if I set a single key with a value, but not the entire object
To update an object or an array you'll need the updateIn method.
import { fromJS, List, Map } from immutable;
const initialState = fromJS({
data: List([Map({ foo: { bar: baz } } })],
});
functionFooReducer(state = initialState, action) {
switch (action.type) {
case UPDATE:
return state
.updateIn(['data.0.foo.bar'.split('.')], (), action.value);
// ...
}
}
I am looking at redux and adding names to an array. The code below works (kind of!).
I have a few issues.
I know that it is advised to create a new state tree object each time the state is passed through the reducer, however I thought it should still work even if I change the state object passed in.
In my code below the console.log(store.getState()); works if I use var newArr = state.names.concat(action.name); but not if I use state.names.push(action.name);
If I add another store.dispatch(action) the code doesn't work.
store.dispatch({type: 'ADD_NAME',name: 'PhantomTwo'});
Can anyone explain why this is so?
Finally, do I need to return state again outside the switch statement?
Here is the code I currently have below.
const initialState = {
names: []
}
function namesApp(state = initialState, action) {
switch(action.type) {
case 'ADD_NAME':
var newArr = state.names.concat(action.name);
return newArr;
default:
return state;
}
}
let store = createStore(namesApp);
store.dispatch({
type: 'ADD_NAME',
name: 'Phantom'
});
console.log(store.getState()); //returns `["Phantom"]`
This is the behavior of array object mutability
Since React highly cares about state change for re-rendering, so we need to take care of mutability.
The below snippet explains the array mutability.
let x = [];
let y = x;
console.log(x);
console.log(y);
y.push("First");
console.log(x);
console.log(y);
let z = [...x]; //creating new reference
console.log(z);
x.push("Second");
console.log(x); //updated
console.log(y); //updated
console.log(z); //not updated
So for better functionality your reducer will be like
function namesApp(state = initialState, action) {
switch(action.type) {
case 'ADD_NAME':
return {
...state, //optional, necessary if state have other data than names
...{
names: [...state.names, action.name]
}
};
default:
return state;
}
}
[].concat returns a new array. But your state was { name: [] }. Inspite of returning newly build object with new names, the code above returned the new names array.
Vanilla solution
const initialState = { names: [] };
function namesApp(state = initialState, action) {
switch(action.type) {
case 'ADD_NAME':
var newArr = state.names.concat(action.name);
return {
...state,
names: newArr
};
default:
return state;
}
}
immutability-helper
For this type of work I would use immutability-helper
import u from 'immutability-helper';
function namesApp(state = initialState, action) {
switch(action.type) {
case 'ADD_NAME':
return u(state, {
names: {
$push: action.name
}
});
default:
return state;
}
}
learn how to use immutability-helper https://facebook.github.io/react/docs/update.html
I'm new to Redux so please bear with me. I am wondering if something like the following is possible and/or optimal and if so, how do you update the nested object values in the reducer?
const initialState = {
banner: {
backgroundColor: 'black',
text: 'Some Title',
textColor: 'white',
image: null
},
nav: {
mainOpen: false,
authOpen: false,
}
...
}
And then in a reducer something like this does not seem to work...
export default function reducer(state = initialState, action = {}) {
const {type, data} = action;
switch (type) {
case SET_BANNER_TEXT:
return {
...state,
banner.text: data //<-- ****THIS FAILS WITH UNEXPECTED TOKEN!!!****
}
...
}
Or is it better to to have a 'banner' reducer, a 'nav' reducer and so on??
TIA!
I'm a bit new to redux too, so I can't speak too much to 'best practice'. I would lean towards a new reducer personally, since you have have specific actions like SET_BANNER_TEXT (color, img, etc?) that modify a specific portion of your state tree and nothing else. Making your reducers simple by breaking them apart, (even if there are a lot of them), will make things easier to trace down the road.
Syntactically you could achieve what you're trying to do with something like:
export default function reducer(state = initialState, action = {}) {
const {type, data} = action;
switch (type) {
case SET_BANNER_TEXT:
const newBannerState = Object.assign({}, state.banner, {text: data});
return Object.assign({}, state, {banner: newBannerState});
}
Since you're updating a key of an object, try using this to update the key
const state = {
...initialState,
banner: {
...initialState.banner, // extend
text: 'newText'
}
}
which translates to
var state = _extends({}, initialState, {
banner: _extends({}, initialState.banner, {
text: 'newText'
})
});
in ES5
check this jsbin
edit: as stated in the comment below, it will overwrite the whole banner object if the code is used above. You can try the other answer in this thread using the Object.assign() and clone the banner object. If you still want to use spread operators, I updated my answer.
I think it is also better to have specific reducers for deeply nested state.And I will write it something like this
export function rootReducer(state = initialState, action) {
switch (action.type) {
case SET_BANNER_TEXT:
case SET_BANNER_BG:
return Object.assign({}, state,
{
banner: bannerReducer(state.banner, action)
}
);
// ...
default:
return state;
}
}
function bannerReducer(state, action) {
switch (action.type) {
case SET_BANNER_TEXT:
return Object.assign({}, state, {text: action.payload});
case SET_BANNER_BG:
return Object.assign({}, state, {backgroundColor: action.payload});
// ...
default:
return state;
}
}