javascript/redux: how to avoid null when fetching state? - javascript

say I have a reducer like this:
export const fetchStuff = (state, action) => {
const s = state || someDefaultState
switch (action.type) {
case FETCH_STUFF:
return {...s, isFetching: true}
case SET_STUFF:
return {...s, isFetching: false, stuff: action.values}
default:
return s
}
}
In this case if actions.values has objects that are null they will be very hard to deal with in my components because I will manually have to ensure that the component is not passed a null prop and then also manually have to deal with null fields in the component somewhere.
const component = ({ prop }) => {
return {
<div>
<span>{prop.testnull ? '' : prop.testnull}</span>
<div
}
}
const mapStateToProps = (state) => {
const p = prop || someDefaultProp
return {
prop: state.prop
}
}
It might seem easy enough in this example, but I have found it a pain to manage a bigger component/component-set. What is the idiomatic way of doing this? DO I have to bite the bullet and manage it in the component and mapStateToProps? or is there a better way to manage it in the reducer?
EDIT:
I should clarify that I am not trying to take care of the case where state is null I am trying to take care of the case where , on fetching of state, some attribute if state is set to null

We have worked with a few similar solutions for a while, and found this to be a nice way of handling this use case in our reducers. Lodash's _.get is a nice solution to provide a default value for multi-level get in your reducer:
https://lodash.com/docs/4.17.4#get
_.get(object, path, [defaultValue])
object (Object): The object to query.
path (Array|string): The path of the property to get.
[defaultValue] (*): The value returned for undefined resolved values.
For example:
const testValue = get(action, 'values.test', {});

Related

How to prevent useSelector from causing unnecessary renders?

I'm using useSelector hook to retrieve the values of my reducer, but it is causing a lot of unnecessary renders on my application.
It doesn't matter which property I'm using on a component, since they are all getting the same state object from the reducer, every time one property changes, all components that use useSelector are rendered.
This is the reducer:
const initialState = {
productsDataState: [], // => this is populated by multiple objects
searchProducts: [],
isSearchOn: false,
inputValue: '',
listOrder: [],
toastify: ['green', ''],
toastifyOpen: false
}
const reducer = ((state = initialState, action) => {
switch (action.type) {
case actionTypes.UPDATE_PRODUCT:
return {
...state,
productsDataState: action.products,
listOrder: action.listOrder
}
case actionTypes.SET_TOASTIFY:
return {
...state,
toastify: action.toastify,
toastifyOpen: action.open
}
case actionTypes.SET_SEARCH:
return {
...state,
searchProducts: action.searchProducts,
isSearchOn: action.isSearchOn,
inputValue: action.inputValue
}
default:
return state
}
})
One of the components is using isSearchOn, which is a boolean, so I solved the problem checking if the value is true before rendering it:
const { isSearchOn } = useSelector(state => state.isSearchOn && state)
But that's not the case for all components. The one I'm stuck right now uses the productsDataState property, which is an array of objects. I can't just make a simple validation before returning state. I thought about storing the initial value in a useState, make a deep comparison between the current value and the past one before returning the state, which would work similarly to what I did in the other component, but I can't see how this would be a good approach.
const { productsDataState } = useSelector(state => state)
Is there a way where I could take advantage of useSelector without comprimising the performance of the application?
I've being reading a lot and making a lot of tests, but I couldn't find a good way to do that so far.
I'd like to keep useSelector, but I'm open to suggestions, even if it involves other libraries.
What you should be doing is not selecting whole state, just the part you need :)
const productsDataState = useSelector(state => state.productsDataState)
#edit
If you want to select multiple data with one selector you will cause it to change reference if you would try to use an object for example.
const { productsDataState, other } = useSelector(state => ({ productsDataState: state.productsDataState, other: state.other }))
this will cause rerender on any state change as redux use strict equality check by default.
You should listen to official documentation and select each state separately
Call useSelector() multiple times, with each call returning a single field value

How can I simplify my reducer function or how long should a simple reducer function be?

I have a reducer function whose sole purpose it to toggle a style on and off. This is a global style and this is why it is in my Redux store.
However, the code looks overly obtuse for such a simple toggle.
const Style = (state = {current: true}, action) => {
if(action.type === "toggleAppStyle"){
const newState = { ...state };
newState.current = !state.current;
return newState;
}
return state;
};
I recently realized that redux runs all reducer functions for each single action, which I find strange, so the returned state must equal the initial state if the action.type is not called for that particular reducer.
Here is the one place I use it:
// ... snip
const mapStateToProps = state => {
return {
Style: state.Style
}
}
// ... snip
render() {
return (
<img className = 'icon_all'
id = {this.props.Style.current === true ? 'icon_10' : 'icon_90'}
onClick = {this.clickHandler} src='/images/favicon-optimized.svg'/>
)
}
the code looks overly obtuse for such a simple toggle.
Overly obtuse is a bit much, this is a pretty standard immutable update pattern, i.e. shallow copy into new object reference and update the necessary properties.
It can't get much simpler than what you've already got, but here is an example returning the new object directly.
const Style = (state = {current: true}, action) => {
if (action.type === "toggleAppStyle") {
return {
...state,
current: !state.current,
};
}
return state;
};
I recently realized that redux runs all reducer functions for each
single action, which I find strange, so the returned state must equal
the initial state if the action.type is not called for that particular
reducer.
Nothing strange here really. The reducer function either acts on the action and operates on the state, thus returning a new state reference and value, otherwise it simply returns the current state value. Other than the action part this is exactly how React reconciliation works. When you update state you are creating new object references or primitive values. This is what triggers rerenders.
Of course, if your objective is to be as short and succinct as possible, you could reduce the state slice to just the boolean value and use a ternary operator to return the toggled state or current state value. Adjust your redux selectors accordingly.
const Style = (state = true, action) => action.type === "toggleAppStyle"
? !state
: state;

In redux, preloading state using with combineReducers

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.

Why is it possible to return Immutable.js Map as a Redux state?

I am trying to learn how to use Immutable.js properly. I found this article: http://frontendinsights.com/immutablejs-with-redux-his-best-friend/.
It has a following code snippet:
const initialState = Immutable.Map({ counter: 0 });
const reducer = (state, action) => {
switch (action.type) {
case 'INCREMENT':
return state.set('counter', state.get('counter') + 1);
case 'DECREMENT':
return state.set('counter', state.get('counter') - 1);
default:
return state;
}
};
const store = createStore(reducer, initialState);
What confuses me is that for me it looks like the switch is returning Map as a state.
I tried the immutable with following:
var obj = {
lol: 'trol',
}
var immu = Immutable.Map(obj)
var immu2 = immu
immu2 = immu2.set('lol', 'trollz')
console.log(immu2) // Map {size: 1, _root: ArrayMapNode, __ownerID: undefined, __hash: undefined, __altered: false}
console.log(immu2.toObject()) // Object {lol: "tral"}
I thought that Redux states needs to always be objects.
So, what is Map() actually returning and why is it possible to set that as a Redux state?
It is possible, however some of the default middleware in RTK claims it to be 'non-serializable'...
More here: Getting an error "A non-serializable value was detected in the state" when using redux toolkit - but NOT with normal redux

Redux and immutable js, I lost my state after trying to use immutable

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.

Categories

Resources