I'm using Redux, but NOT React to provide a bit of context. I have a situation where I would like to store some data about pages a user has visited, but it seems like I need to set my schema prior to setting any new state in a reducer.
function reducer(state, action) {
switch(action.type) {
case STORE_CATEGORY:
return Object.assign(
{}, state,{
[action.category] : {
pages: [],
filters: {}
}
}
);
}
}
The above function works, but I want to translate updating the state with an undetermined set of keys using immutable.js where the keys will be named after the category visited. Thanks.
As I understand, the Map from Immutable would be good for you issue
const initalState = Immutable.Map();
function reducer(state = initalState, action) {
switch(action.type) {
case STORE_CATEGORY:
return state.set(action.category, {
pages: [],
filters: {}
});
}
}
Related
I have 2 apps inside my CRA that use the same UI but do slightly different things. at the moment I am using lots of similar reducers for the 2 apps and am wondering the best way to make them reusable and not duplicate the code?
app1:
export default (state: App1State = initialState, action: ReducerAction) => {
switch (action.type) {
case APP_1.ACTION:
return {
...state,
id: 123,
app1SpecificState: 'app1'
}
default:
return state
}
}
app2:
export default (state: App2State = initialState, action: ReducerAction) => {
switch (action.type) {
case APP_2.ACTION:
return {
...state,
id: 123,
app2SpecificState: 'app1'
}
default:
return state
}
}
so to combine them I'm thinking of doing something like this:
export default (state: App1State = initialState, action: ReducerAction) => {
if (process.env.APP_NAME === 'app2') {
(state as App2State) = App2State
}
switch (action.type) {
case APP_1.ACTION:
return {
...state,
id: 123,
app1SpecificState: 'app1'
}
case APP_2.ACTION:
return {
...state,
id: 123,
app2SpecificState: 'app2'
}
default:
return state
}
}
I'm wondering if a) this makes sense b) this is a good idea c) there is a better way to achieve this?
I also don't want to repeat the if statement across all my "shared" reducers so would be good to abstract this if possible. probably a HOC, if that's even possible here?
The redux documentation has recipes for that, check it out:
Reusing reducer logic
If you need to create new parts of redux store dynamically, you can also add more reducers later:
Add reducer dynamically
In order to update your store dynamically you should dispatch your data with its property name like this:
dispatch({
type: APP.ACTION,
payload: "app1",
specificState: "app1SpecificState"
})
dispatch({
type: APP.ACTION,
payload: "app2",
specificState: "app2SpecificState"
})
and then in your reducer you can use like this:
case APP.ACTION:
return {
...state,
id: 123,
[action.specificState]: action.payload
}
It is the same principle as updating the state dynamically.
I can't get my reducer to return updated state.
The action (confirmed with debugger) is an array of objects - something like: [{name: "test"}, {name: "second_test"}]
I think something must be wrong with my spread operator, although I've also tried Object.assign() in the debugger and that seems to return the expected result.
My components is apparently just getting the default state. Here's my reducer code. Any help would be appreciated. Thank you!
const initialState = {
current: {},
all: []
}
export default function deckReducer(state = initialState, action) {
switch(action.type) {
case 'CREATE_DECK':
return {...state, all: [...state.all, action.payload]}
case 'FETCH_DECKS':
debugger;
return {...state, all: action.payload}
default: return state
}
}
I had this very issue recently (exactly as you describe), and I resolved the problem by using the immutability-helper package which is listed here on the ReactJS docs.
This approach allows you to delegate the heavy lifting of state immutability and nested state updates. You should find this resolves your issue:
import update from 'immutability-helper';
const initialState = {
current: {},
all: []
}
export default function deckReducer(state = initialState, action) {
switch(action.type) {
case 'CREATE_DECK': {
/* Add payload to nested state.all list */
return update(state, { all : { $push : [action.payload] }});
}
case 'FETCH_DECKS': {
/* Replace state.all with incoming payload */
return update(state, { all : { $set : action.payload }});
}
default: return state
}
}
Both updateUiActive and updateData return new copy of state.
I am using updateData(updateUiActive(state, action), action) to return a new version of state which contain first the properties updated with updateUiActive and after with updateData.
I would like to know if there is a more elegant and efficient way to do so.
Maybe using JS carry?
const updateUiActive = (state, action) => {
return dotProp.set(state, 'navigation.ui.active', action.payload)
}
const updateData = (state, action) => {
const updatedData = state.navigation.data.map((x) => {
x.isActive = x.id === action.payload
return x
})
return dotProp.set(state, 'navigation.data', updatedData)
}
function navigationReducer (state = initialState, action) {
switch (action.type) {
case types.SET_ACTIVE:
return updateData(updateUiActive(state, action), action)
default:
return state
}
}
first of all, redux relies heavily on the state being immutable. You should not modify it, but return a new modified copy of it.
One option is to do it yourself (the spread operator from ES is usually very helpful for this).
Another option is using immutability-helper. The syntax might be hard to get in the beginning, but for more complex state it helps you getting a better structure.
I am just getting introduced to redux, and am stucked with a problem of preloading the state for some time.
When using a single reducer, I was using the following code, and it used to work fine. Relevant snippets::
const head = (state = {}, action) => {
switch (action.type) {
case 'TOGGLE_VISIBLITY':
if (state.head.content !== action.id) {
return state
}
state.body.visible = !state.body.visible;
return state;
default:
return state
}
}
const heads = (state = [], action) => {
switch (action.type) {
case 'TOGGLE_VISIBLITY':
state.body = state.body.map(t =>
head(t, action)
);
}
return state;
}
export const store = createStore(heads, config);
But instead this I just changed to combinerReducers, and it started thowing JS errors.
Unexpected keys "head", "body" found in preloadedState argument passed to createStore. Expected to find one of the known reducer keys instead: "heads". Unexpected keys will be ignored.
My change was::
const plannerApp = combineReducers({
heads
});
export const store = createStore(plannerApp, config);
In case you wanna check the full code,please visit here.
Any help is highly appreciable. Thanks a lot in advance.. I appreciate your time and efforts...
In a nutshell, the preloaded state needs to match the structure of your reducers. Since you switched to using combineReducers, your state tree structure has changed. You now have a top level key of heads that has a child key of body, so you probably need to update your config to look like:
export default {
heads: {
body: {
...
As it is now, the config object contains top level keys of head and body, which do not have entries at the top level of your state tree.
After starting my development with reactjs and redux I was thinking that it would be better to work with immutable.js while using redux.
But... Maybe am I retarded or one need some practice before using it properly, everything crashed.
And if you can help understand what's wrong here, it would be awesome!
So, here was my first code:
export function posts(state = {
isFetching: true,
didInvalidate: false,
items: []
}, action) {
switch (action.type) {
case INVALIDATE_REQ:
return Object.assign({}, state, {
didInvalidate: true
});
case REQUEST_POSTS:
return Object.assign({}, state, {
isFetching: true,
didInvalidate: false
});
case RECEIVE_POSTS:
return Object.assign({}, state, {
isFetching: false,
didInvalidate: false,
items: action.posts
});
default:
return state;
};
};
That I transformed this way:
const initPostState = Map({
isFetching: true,
didInvalidate: false,
items: []
});
export function posts(state = initPostState, action) {
switch (action.type) {
case INVALIDATE_REQ:
return state.set('didInvalidate', true);
case REQUEST_POSTS:
return state.set({
isFetching: true,
didInvalidate: false
});
case RECEIVE_POSTS:
return state.set({
isFetching: false,
didInvalidate: false,
items: action.posts
});
default:
return state;
};
};
And my container MapStateToProps:
function mapStateToProps(state) {
const {
posts: isFetching,
posts
} = state.posts;
console.log(state);
...
So the problem is, how do I access my states?
The console of state report that:
I'm lost! Help!
Never use toJS() in mapStateToProps. From the Redux docs:
Converting an Immutable.JS object to a JavaScript object using toJS() will return a new object every time. If you do this in mapSateToProps, you will cause the component to believe that the object has changed every time the state tree changes, and so trigger an unnecessary re-render.
If your app requires high performance, you have to use Immutable.js in your Dumb components with their get() and getIn() helpers.
Also since ImmutableJS has versatile API, in most cases it removes the
need for helper libraries like lodash.
But it mosts cases, you can use the code they proposed to separate Immutable.js from your components by sacrificing performance.
A HOC component (with the latest immutable.js using the isIterable predicate):
import React from 'react';
import { Iterable } from 'immutable';
export const toJS = (WrappedComponent) =>
(wrappedComponentProps) => {
const KEY = 0;
const VALUE = 1;
const propsJS = Object.entries(wrappedComponentProps)
.reduce((newProps, wrappedComponentProp) => {
newProps[wrappedComponentProp[KEY]] =
Iterable.isIterable(wrappedComponentProp[VALUE])
? wrappedComponentProp[VALUE].toJS()
: wrappedComponentProp[VALUE];
return newProps;
}, {});
return <WrappedComponent {...propsJS} />
};
A HOC component (with the latest immutable.js using the isImmutable predicate):
import React from 'react';
import { isImmutable } from 'immutable';
export const toJS = (WrappedComponent) =>
(wrappedComponentProps) => {
const KEY = 0;
const VALUE = 1;
const propsJS = Object.entries(wrappedComponentProps)
.reduce((newProps, wrappedComponentProp) => {
newProps[wrappedComponentProp[KEY]] =
isImmutable(wrappedComponentProp[VALUE])
? wrappedComponentProp[VALUE].toJS()
: wrappedComponentProp[VALUE];
return newProps;
}, {});
return <WrappedComponent {...propsJS} />
};
How to use:
import { connect } from 'react-redux';
import { toJS } from './to-js';
import DumbComponent from './dumb.component';
const mapStateToProps = (state) => {
return {
/**
obj is an Immutable object in Smart Component, but it’s converted to a plain
JavaScript object by toJS, and so passed to DumbComponent as a pure JavaScript
object. Because it’s still an Immutable.JS object here in mapStateToProps, though,
there is no issue with errant re-renderings.
*/
obj: getImmutableObjectFromStateTree(state)
}
};
export default connect(mapStateToProps)(toJS(DumbComponent));
There are a lot of links to move on in the Redux immutable-js-best-practices documentation section.
You need to use get method from immutableJS
Use state.get('didInvalidate') to access the value of didInvalidate, similarly for other values.
If you're using a javascript object, then you can get it like state.get('something').toJS()
Doing this should give you the idea
function mapStateToProps(state){
const isFetching = state.get('isFetching'),
const items = state.get('items').toJS();
}
If you use ImmutableJS with redux, you're whole app state is an immutable. In the connect function, use state.get("posts") to access to the posts state. Then you will have to use get() to access the posts state properties. Or you can use toJs() to avoid having to manipulate immutable inside your component.