Component not re-rendering on state change - Redux - reactjs

I cannot get my component to re-render on a state change. I am fairly confident that I am not mutating the state.
component (Domain)
...
<Add
onClick={() => {
domain.routes.push({
from: 'foo',
to: 'bar'
});
this.props.setDomain(this.props.domainIndex, domain);
}} />
...
reducer
case 'SET_DOMAIN':
let tmpState = {...state}
tmpState.domains[action.index] = action.value;
return {...tmpState}
action
export const setDomain = (index, domain) => ({
type: 'SET_DOMAIN',
value: domain,
index: index
});
container
import { connect } from 'react-redux';
import { setDomain, setRoute } from '../actions';
import Domain from '../components/pages/domain';
const mapStateToProps = state => ({
domains: state.domains
});
const mapDispatchToProps = dispatch => ({
setDomain: (index, domain) => dispatch(setDomain(index, domain)),
setRoute: (domainIndex, routeIndex, route) =>
dispatch(setRoute(domainIndex, routeIndex, route))
});
export default connect(mapStateToProps, mapDispatchToProps)(Domain);
From the redux chrome extension I can see that setDomain is being called and the state is being returned correctly:

React Redux tries to improve performance by doing shallow equality
reference checks on incoming props in shouldComponentUpdate, and if
all references are the same, shouldComponentUpdate returns false to
skip actually updating your original component.
It's important to remember that whenever you update a nested value,
you must also return new copies of anything above it in your state
tree. If you have state.a.b.c.d, and you want to make an update to d,
you would also need to return new copies of c, b, a, and state.
From https://redux.js.org/faq/react-redux#react-not-rerendering
{...object} is only a shallow clone. The object's child object will still have the same reference. Therefore, to Redux, the child object is not updated.
I think the quickest way to solve this is to use Lodash cloneDeep.
let tmpState = _.cloneDeep(state);
or
tmpState.domains = _.cloneDeep(tmpState.domains);
But this does come with a performance cost.
Alternatively, you can shallow clone the affected child 1 by 1.

Related

React usEffect firing when it shouldn't be

I have a react component with a prop that is passed by a redux connect method. There is a useEffect linked specifically to that prop that is supposed to perform an async call when it changes. The problem is the useEffect fires any time I change the redux state anywhere else in that app, despite the prop I have the useEffect attached to not changing.
The useEffect method looks like this
useEffect(() => {
if (userPhoneNumber) {
myAsyncFunction()
.then(() => {
showData()
})
}
}, [userPhoneNumber])
And the userPhoneNumber prop is passed via the react-redux connect method like so:
const mapStateToProps = state => {
return {
userPhoneNumber: state.appState.userPhoneNumber
}
}
export default connect(mapStateToProps)(MyComponent)
From what I understand this could be breaking in two potential places. for one, useEffect should not be firing if the userPhoneNumber prop doesn't change. Also, the mapStateToProps method is not returning a new value so it should not be triggering any sort of a rerender.
The redux state changes that are leading to the unexpected useEffect call are coming from sibling components that have a similar react-redux connect setup to this component, with different state to prop mappings.
When a reducer runs you'll have an entirely new state object, thus a new userPhoneNumber prop reference. You should memoize the userPhoneNumber value. I use reselect to create memoized state selectors, but you could probably use the useMemo hook and use that memoized value in the dependency for the effect.
const memoizedUserPhoneNumber = useMemo(() => userPhoneNumber, [userPhoneNumber]);
useEffect(() => {
if (memoizedUserPhoneNumber) {
myAsyncFunction()
.then(() => {
showData()
})
}
}, [memoizedUserPhoneNumber]);
Using Reselect
import { createSelector } from 'reselect';
const appState = state => state.appState || {};
const userPhoneNumber = createSelector(
appState,
appState => appState.userPhoneNumber;
);
...
const mapStateToProps = state => {
return {
userPhoneNumber: userPhoneNumber(state),
}
}
None of this helps keep the entire functional component from re-rendering when the parent re-renders though, so to help with this you'll need to help "memoize" the props being fed into the component with the memo HOC.
export default memo(connect(mapStateToProps)(MyComponent))

Avoid re render with react hooks

I have some value that comes from Redux through props and I want to NOT render the component again when this value changes.
I found some answers saying that I can use Memo but I don't know if this is the best option for my case?
My "code":
const MyComponent = () => {
return ...;
}
const mapStateToProps = state => ({
myVar: state.myVar
});
export default connect(mapStateToProps)(MyComponent);
myVar changing shouldn't re render the component in this case.
React.memo can do the job, you can pass a custom equality check function to perform a rerender only when it returns a falsy value. I never faced a case where you want to completely ignore a value update from your Redux store, maybe it shouldn't be stored there ?
Memo API
eg: React.memo(Component, [areEqual(prevProps, nextProps)])
UseSelector API
Another way would be to use useSelector with a custom equality check function:
useSelector Redux API Reference
Connect API
If you still want to stick with mapStateToProps, you can also pass a custom equality check function as a parameter of the connect function:
areStatePropsEqual Redux API Reference
Edit: useRef solution
By using useRef, you store a mutable variable that will be kept as it is for the whole lifetime of the component.
Example based on yours:
const StoreMyVar = (WrappedComponent) => ({myVar, ...props}) => {
const myRefVar = useRef(myVar)
return <WrappedComponent myVar={myRefVar} {...props} />
}
const MyComponentWithImmutableVar = StoreMyVar(MyComponent)
The fastest way is React.memo, but you can use it just with functional components. Be careful, it is not tested.
const MyComponent(props) {
return ...;
}
const areEqual(prevProps, nextProps) {
return prevProps.myVar === nextProps.myvar
}
const mapStateToProps = state => ({
myVar: state.myVar
});
export default React.memo(MyComponent, areEqual);

Using component prop inside function body of reselect?

Honestly, after hours of research, I totally don't understand reselect, so I just ask why I need it, and if it can help.
Selectors are efficient. A selector is not recomputed unless one of its arguments changes.
It's a bit unclear, what do we mean as argument here, but I assume that not the redux state, because otherwise there wouldn't be any point in reselect.
My goal is to not calculate the whole list every time something happens, because it can contain thousands of elements.
First question is that if for an example the value of state["2"] changes to 4 for an example, it will run through the whole list?
//for an example, in the next list, the key is the child,
//and the value is it's parent
const state = {
1: 5,
2: 5,
3: 2,
4: 1,
5: 10,
//...
1000: 342
};
//and we have to find all children of a specific element,
//what we get from the component's own props
const getState = (
state,
props //okay, I can access it here
) => state;
const getChildren = createSelector(
[getState],
state => Object.keys(state).filter(child => {
const parent = state[child];
return parent === props.id //but I need it here
})
);
const mapStateToProps = (state, props) = ({ children: getChildren(state, props) });
And the main question: how can I access the props inside the function body?
You can pass the props argument directly to the other selector getChildren and you don't need the first getState like this:
const getChildren = createSelector(
[
state => state,
props => props
], (state, props) => {...}
About clarifying the use cases for reselect:
it does recompute if the state or the props changes (any argument indeed). So why using it? I use it for 2 reasons
you can combine parts of state coming from multiple reducers and build up what we can call a 'meta-reducer' and pass that to your component. In that way you place that code only in one place (the selector) and you can reuse it across different components. Imagine each reducer like a database table and the selector like a query result. You can query anything from your state and you want to keep the result cached for performance.
instead of running this logic on the mapStateToProps which is run every time that a component renders (no matter if the state has changed), you run it only 1 time per state change and you get the cached version if the component rerenders. This happens for example if a child component renders only because its parent rendered but the state portion related to its selector didn't change. So I like to use selectors all the times instead of accessing the redux state directly.
Here's the typical flow.
You'll have some ConnectedComponent that's hooked into connect, and its mapStateToProps calls out to a selector with both state and ownProps.
You have individual selectors for both the getting of id off of props and your objects from state.
Using ConnectedComponent
<span>
<ConnectedComponent id="123" />
</span>
mapStateToProps (ConnectedComponent)
import {connect} from 'react-redux'
import {getMyObjectSelector} from './selectors';
const mapStateToProps = (state, ownProps) => ({
myObject: getMyObjectSelector(state, ownProps)
});
export default connect(mapStateToProps)(Component)
selectors
const getIdFromProps = (state, props) => props.id
const getMyObjectsFromState= state => state.myObjects;
export getMyObjectSelector = createSelector(
getMyObjectsFromState,
getIdFromProps,
(objects, id) => objects[id]
);
Component
export const Component = ({myObject}) => (
<span>
// Do stuff
</span>
)

react redux mapStateToProps - correct props not making it to the component

I have a component OnePayrunGridContainer which is wrapped in react-redux's connect Component.
There are two distinct async actions that update the redux store -
FETCH_PAYRUN_TS_DETAILS - to retrieve all the data after the component has loaded
FETCH_TIMESLIP_DATA - that updates the state.timeslipInModal
When there is an object in timeslipInModal, my render function displays it in a modal.
In the react tools in chrome and firefox, I can see the console.log output demonstrating that the redux store has updated, the mapStateToProps has been called, but when I inspect the component in the react console, the prop timeslipInModal remains false - it's initial value when the component was first rendered.
The react-redux documentation for mapStateToProps says -
The new component will subscribe to Redux store updates. This means that any time the store is updated, mapStateToProps will be called.
which IS happening (I know this from a console.log in the mapStateToProps).
The documentation also says -
The results of mapStateToProps must be a plain object, which will be merged into the component's props.
This part is what is NOT happening. I console.log the redux store newState just before I return it - and I can see the same output in the console when I have the axios debug feature turned on.
The result of the mapStateToProps function is a plain object, but this object is NOT being merged into the child component props - this is illustrated by this screen shot:
Here is the code to show that I console.log(props.timeslipInModal), which is non-false in the console but the props of the wrapped component does not change.
const mapStateToProps = (state) => {
let props = {
userIdBy: false,
apiToken: false
};
props.timeslipInModal = state.timeslipInModal ? state.timeslipInModal : false;
if (state && state.myBus) {
if (state.myBus.payrun) {
// state.myBus.payrun.loading, loaded and errors copied by this
props = Object.assign(state.myBus.payrun, props);
}
if (state.myBus.common && state.myBus.common.auth) {
props.userIdBy = state.myBus.common.auth.userId;
props.apiToken = state.myBus.common.auth.apiToken;
}
}
console.log('timeslipInModal at the end of mapStateToProps:', props.timeslipInModal);
return props;
};
const mapDispatchToProps = (dispatch, ownProps) => ({
dispatch,
fetchPayrunDetails:
dispatch(fetchPayrunDetails(ownProps.userIdBy, ownProps.apiToken, ownProps.entityId))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(OnePayrunGridContainer);
My Reducer for the fetchPayrunDetails looks like this:
case 'FETCH_PAYRUN_TS_DETAILS_FULFILLED': {
const newState = Object.assign({}, state);
newState.myBus = Object.assign({}, state.myBus);
newState.myBus.payrun = action.payload.data.data;
newState.myBus.payrun.loading = false;
newState.myBus.payrun.loaded = true;
newState.myBus.payrun.errors = false;
return newState;
}
My Reducer to update the reactStore.timeslipInModal is a separate case in the reducer.js:
case 'FETCH_TIMESLIP_DATA_FULFILLED': {
if (action.payload.data.errors) {
// caputure errors - not relevant here
}
const newState = Object.assign({}, state, {
modalFetchErrors: false,
modalFetched: true,
modalFetching: false,
modalValidating: false,
timeslipInModal: action.payload.data.timeslip
});
return newState;
}
One thing that is a little 'different' and hence may be a problem - in my entry.js script, I have a loop where I look for html entities by their ID, and based on their id, I call the correct component to render.
This allows me to have one entry.js script called at the end of my common template view.
import OnePayrunGridContainer from './components/payrun/OnePayrunGridContainer.js';
import {Provider} from 'react-redux';
import store from './store.js';
// put these in here so they match code searches - these container components
// are included below by Alias so explicitly list them here: <OnePayrunGridContainer>
let componentNamesByRootElementIds = {
onePayrunGridMyBus: OnePayrunGridContainer
};
let htmlElement = false;
for (let onereactElementId in componentNamesByRootElementIds) {
htmlElement = document.getElementById(onereactElementId);
if (htmlElement) {
let Component = componentNamesByRootElementIds[onereactElementId];
ReactDOM.render(
<Provider store={store}>
<Component
userIdBy={userIdBy}
apiToken={apiToken}
entityId={entityId}
onereactElementId={onereactElementId}
/>
</Provider>,
htmlElement
);
}
}
Also, I do export the connect in my OnePayrunGridContainer.js:
export default connect(
mapStateToProps,
mapDispatchToProps
)(OnePayrunGridContainer)
The other props all update when the FETCH_PAYRUN_TS_DETAILS_FULFILLED calls are made that update the redux store - what am I missing? How do I make the props on the wrapped component update correctly when the redux state is updated by the FETCH_TIMESLIP_DATA_FULFILLED action?

Reusable component using React Redux mapStateToProps

A React component OilBarrel connected my redux store to create a container OilBarrelContainer:
// ---- component
class OilBarrel extends Component {
render() {
let data = this.props.data;
...
}
}
// ---- container
function mapStateToProps(state) {
let data = state.oilbarrel.data;
...
}
const OilBarrelContainer = connect(mapStateToProps)(OilBarrel)
// ---- reducer
const oilbarrel = (state = {}, action) => {
let data = state.data;
}
const storeFactory = (server = false, initialState = {}) => {
return applyMiddleware(...middleware(server))(createStore)(
combineReducers({oilbarrel, otherReducer1, otherReducer2}),
initialState
)
}
I find it strange that mapStateToProps() receives the top level state object (the entire state of the application), requiring me to traverse state.oilbarrel.data, when the reducer (conveniently) only receives the branch of the state that belongs to this component.
This limits the ability to reuse this container without knowing where it fits into the state hierarchy. Am I doing something wrong that my mapStateToProps() is receiving the full state?
That is the mapStateToProps behavior. You have to think redux state as a single source of truth (by the way, that is what it really is) independently of the components you have in project. There is no way out, you have to know the exactly hierarchy of you especific data in the state to pass it to your container component.
No this is intentional, because you may want to use other parts of the state inside your component. One option is to keep the selector (mapStateToProps) in a separate file from your component, which will help you reuse the selector, if you app is very large and complex you can also checkout libraries such as reselect which helps you make your selectors more efficient.
Dan Abramov offers a solution for this in his advanced redux course under Colocating Selectors with Reducers.
The idea is that for every reducer, there is a selector, and the selector is only aware of it's reducer structure. The selectors for higher level reducers, wrap the lower level reducer, with their part of the state, and so on.
The example was taken from the course's github:
In the todos reducer file:
export const getVisibleTodos = (state, filter) => {
switch (filter) {
case 'all':
return state;
case 'completed':
return state.filter(t => t.completed);
case 'active':
return state.filter(t => !t.completed);
default:
throw new Error(`Unknown filter: ${filter}.`);
}
};
In the main reducer file:
export const getVisibleTodos = (state, filter) =>
fromTodos.getVisibleTodos(state.todos, filter);
Now you can get every part of your state without knowing the structure. However, it adds a lot of boilerplate.

Resources