pass a useReducer state to another useReducer - javascript

I am trying to create a search for a simple todo-app, instead of trying to manage the filtered states throught the reducer that handles the todos, I was told to set another useReducer instead and then manage the filters throught that reducer, tho, on Redux (which is my reference based of) they use mapStateToProps.
The problem is that when I pass my state that comes from the todos useReducer down to my filter reducer state, it wont update as I change the reducer. And it is expected. My initial value is my todo's state (whose an empty array) and it will always be even when new todos are added to the list.
well, whats the ideal aproach in this case?
code:

Shaping your state is up to you -- shape it in a way that works best. Both Redux and React are unopinionated about that. If it makes sense to put those slices of state together, do that. With useReducer, think about storing the entire state for a component in it vs breaking it up. I look at useReducer as the alternative for a bunch of useState statements.
In the case of Redux, if a filter in a reducer needs to filter data in another reducer, _do that in the component, e.g.,
const filterdData = rawDataState.filter(x => x.someProperty === filterState)

Related

Should we compare new state with old state in the reducer

I am using spread operator in the reducer function, so what is happening when i am dispatching the action then it is creating new state (obj) even old state is same as like new state so it is re-rendering react component.
Is it good approach to compare new state with old state as object could be complex ? Deep comparison ?
const initialState = {};
export const reducer = (state = initialState, action: any = {}): any => {
if (action.type === ActionTypes.RC) {
return {
...state,
...action.config,
};
}
return state;
};
Or redux does it out of the box for you in mapStateToProps ?
There a few things worth noting here.
React Redux uses strict equality (reference equality) as the default method for comparisons. The immutable nature of Redux state means that any change to a deeply-nested property requires that all of its ancestor must be new object references.
If you update a.b.c then a and a.b must be new objects. Let's say that you have a sibling property tree like a.d.e. When you update a.b.c it causes a to be a new object reference, but a.d and a.d.e are the same. If you have a component that only needs to know about a.d then it should not need to re-render in response to your a.b.c change.
This is why it's important that each component should only select the minimal information that it needs from the state. You should connect more individual components rather than selecting a lot of information from state and passing it down through props. You can use multiple useSelector hooks in the same component to select small, granular pieces of state.
You do have the ability to customize the equality comparison by passing a comparison function as the second argument to the useSelector hook.
If you have a component that subscribes to the piece of state that's controlled by this reducer then you can use the shallowEqual function which is included in the react-redux package. I'm not sure if the reducer in your question is your root reducer or something that you are passing to combineReducers. I'm assuming it's just one piece.
Let's say it's for a property settings and you have some updateSettings action that merges the new settings with the existing ones. Then when you have a component that needs to access the settings, you would do this:
import { shallowEqual, useSelector } from 'react-redux'
const settings = useSelector(state => state.settings, shallowEqual);
This would mean that the component will only re-render if some of the settings have actually changed, even if state.settings is a new object. It is doing a shallow comparison between the current value of state.settings and the previous one.
It is possible to use a deep equals comparison like lodash's isEqual but I'm not seeing why you would need it as this reducer is making a shallow copy. If you need to do that expensive check anywhere then it seems more logical to do it before you dispatch the action in the component or before you return a new object from the reducer rather than on every call of the useSelector hook.
You asked about whether you should check for changes in the reducer before returning a new state object. That's a good idea and it's actually what a combined reducer created by combineReducers does, though it only checks for shallow equality. See lines 177-201 of the source code. If all of the individual property reducers returned the same object as before then it will return the previously combined state in order to maintain object references.
personal opinion:
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]);// <= re-render state diff here
The page should be re-render or not is decided by react, or says: will be better.
There's dom-diff algorithm in react.
You dispatch action from multiple pages, these pages pick their own state from newState, and diff themselves, trigger re-render or not.
So i think you don't have to run another diff algorithm in reducer.
consider case:
You have PageA & PageAB, state { a:1, b:1 }
PageA re-render by a. PageAB re-render by a,b
Now your state become {a:1, b:2}. How could you make PageA not re-render in reducer? you will diff it in PageA.

React useReducer Hook fires twice / how to pass props to reducer?

FOREWORD / DESCRIPTION
I am trying to use React's new hooks feature for an e-commerce website that I am building, and have been having an issue working a bug out of my shopping cart component.
I think it is relevant to preface the discussion with the fact that I am trying to keep my global state modular by using multiple Context components. I have a separate context component for the types of items that I offer, and a separate context component for the items in a person's shopping cart.
PROBLEM
The issue I am having is that when I dispatch an action to add a component to my cart, the reducer will run twice as if I had added the item to my cart twice. But only when it is initially rendered, or for weird reasons such as the display is set to hidden and then back to block or for a change in the z-index and potentially other similar changes.
I know this is kind of verbose, but it is rather knit picky issue so I have created two codepens that showcase the issue:
full example
minimum example
You will see that I have included a button to toggle the display of the components. This will help showcase the correlation of the css to the issue.
Finally please monitor the console in the code pens, this will show all button clicks and which part of each reducer has been run. The issues are most evident in the full example, but the console statements display the issue is also present in the minimum example.
PROBLEM AREA
I have pinpointed the problem to be related to the fact that I am using the state of a useContext hook to get the items list. A function is called to generate the reducer for my useReducer hook, but only arises when a different hook is used AKA I could use a function that wouldn't be subject to re-eval like hook is and not have the issue, but I also need the info from my previous Context so that workaround doesn't really fix my issue.
Relevant Links
I have determined the issue is NOT an HTML issue so I will not include the links to the HTML fixes I have tried. The issue, while triggered by css, is not rooted in css so I will not include css links either.
useReducer Action dispatched twice
As you indicated, the cause is the same as the related answer of mine that you linked to. You are re-creating your reducer whenever Provider is re-rendered, so in some cases React will execute the reducer in order to determine whether or not it needs to re-render Provider and if it does need to re-render it will detect that the reducer is changed, so React needs to execute the new reducer and use the new state produced by it rather than what was returned by the previous version of the reducer.
When you can't just move the reducer out of your function component due to dependencies on props or context or other state, the solution is to memoize your reducer using useCallback, so that you only create a new reducer when its dependencies change (e.g. productsList in your case).
The other thing to keep in mind is that you shouldn't worry too much about your reducer executing twice for a single dispatch. The assumption React is making is that reducers are generally going to be fast enough (they can't do anything with side effects, make API calls, etc.) that it is worth the risk of needing to re-execute them in certain scenarios in order to try to avoid unnecessary re-renders (which could be much more expensive than the reducer if there is a large element hierarchy underneath the element with the reducer).
Here's a modified version of Provider using useCallback:
const Context = React.createContext();
const Provider = props => {
const memoizedReducer = React.useCallback(createReducer(productsList), [productsList])
const [state, dispatch] = React.useReducer(memoizedReducer, []);
return (
<Context.Provider value={{ state, dispatch }}>
{props.children}
</Context.Provider>
);
}
Here is a modified version of your codepen: https://codepen.io/anon/pen/xBdVMp?editors=0011
Here are a couple answers related to useCallback that might be helpful if you aren't familiar with how to use this hook:
Trouble with simple example of React Hooks useCallback
React Hooks useCallback causes child to re-render
Seperate the Reducer from the functional component that helped me solve mine
An example based on Ryans excellent answer.
const memoizedReducer = React.useCallback((state, action) => {
switch (action.type) {
case "addRow":
return [...state, 1];
case "deleteRow":
return [];
default:
throw new Error();
}
}, []) // <--- if you have vars/deps inside the reducer that changes, they need to go here
const [data, dispatch] = React.useReducer(memoizedReducer, _data);
When I read some useContext source code, i found
const useContext = hook(class extends Hook {
call() {
if(!this._ranEffect) {
this._ranEffect = true;
if(this._unsubscribe) this._unsubscribe();
this._subscribe(this.Context);
this.el.update();
}
}
After the first time update, a effect like is called after the update. After the value is subscribed to the right context, for instance, resolving the value from Provider, it requests another update. This is not a loop, thanks to _ranEffect flag.
Seems to me if above is true for React, the render engine are called twice.

Update react context outside of a consumer?

I am trying to understand how the new react context API works.
In redux, it is possible for a component to have knowledge of dispatch actions without knowing state. This allows updates to redux state without causing a rerender of components that don't care about that state.
For example I could have
<Updater onClick={updateCount}/>
and
<Consumer value={count}/>
Updater is connected to dispatch(updateCount()) and Consumer is connected to count's current value via state.count. When state.count is updated, only the Consumer rerenders. To me, that's a crucial behavior.
In react context, it seems very difficult to duplicate this behavior. I'd like to be able to update state without causing unnecessary rerenders of components that want to alter the context but don't actually care about the state.
How would it be possible for components to trigger updates to context if they are not inside a consumer? And I definitely don't want to trigger an update to the entire tree by setting state at the provider level.
interesting question. Not sure you can without at least an extra layer (but happy to be shown wrong).
Maybe using Memo or PureComponent to minimise the re-rendering?
import React, { memo } from 'react';
function Widget({ setContext }) {
return <button onClick={setContext}/>Click Me</button>;
}
export default memo(Widget);
...
function Wrap() {
const { setSession } = useContext(SessionContext);
return <Widget setSession={setSession} />;
}
One possible solution is to transform your consumer components into pure components and check against the values each component really cares about.
This can be easily done using the onlyUpdateForKeys HOC from recompose.
you can try this library react-hooks-in-callback to isolate the context from your component and pick only desired state values from it,
check this example

react, redux - Modifying parent components with redux

Okay so I had a problem when programming in react, and I've found that it's a common one. If I have multiple nested components, in my case I have:
<AppView>
<Navigation/> // this is a navbar
<ViewHandler currentTab={props.currentTab}/>
<Footer/>
</AppView>
And then in <ViewHandler/> I have other dumb presentational components, which also have nested components as well. If I have a button in a deeply nested component within <ViewHandler>, and I want to respond to onClick from that button by changing something many parent components above the component that I am in, how would I do so? In my case I would be reacting to the button being clicked in that deeply nested component, and then I want to change the selected tab on <Navigation>. I don't want to pass a bunch of callback functions down as properties, because that feels very scotch-tape-ish.
I learned redux because I read that it solved this problem. But for me it hasn't. I am giving <AppView> access to my redux store using react-redux's <Provider>, and I can access the store through props (props.currentTab). But for all the components nested within <AppView>, they don't have access to the store or any of my action creators. How can modify my store from within a deeply nested component so that I may change a parent component without passing a ton of callback functions down? Or is this just incorrect architecture? I thought redux would solve this problem but it hasn't.
Yes I have connected my component. I just don't like the idea of passing down store.state information as props because it gets very redundant with many nested components.
I don't know why you think you have to send props all the way down your component tree. That's what connect and mapStateToProps help you avoid: they let you turn bits of app state into props only for the components which need it.
in your button's onClick handler, create and dispatch a Redux action:
// button.js
onClick={() => {
dispatch({
payload: 1 // or whatever value
type: 'SET_SELECTED_TAB'
});
}}
next, have your reducer function watch for this action and modify a bit of Redux app state:
// reducer.js
if (action.type === 'SET_SELECTED_TAB') {
return {
...currentAppState,
selectedTab: action.payload
};
}
finally, in the render function of your <Navigation> component, you decide which tab to show based on the current values in that bit of app state:
// Navigation.js
render() {
return (
<div>
current tab: {this.props.selectedTab}
</div>
);
}
access to that state is via connect and mapStateToProps:
// Navigation.js still
const mapStateToProps = (appState) => {
return {
selectedTab: appState.selectedTab
};
};
export default connect(mapStateToProps)(Navigation);
Hoc (higher order components) is a wrapper that is serving methods and data to the children components, usually it's a good idea to use it , but it enforces some 'discipline'.
Example: if your HOC is at level 0 and you have a deeply nested button component at level 4 that calls a method in this same HOC , What should you do ? pass it down the to all 4 levels? the answer is NO WAY !
Because doing so will bring the spaghetti to it , Everytime you click this button , and assuming the method binded to it will mess with the state (internal or the store itself) it will rerender all the 4 levels , and you could avoid that by using the shouldComponentUpdate() but this is way too much work for nothing useful.
So the solution would be to connect every component with mapStateToProps and mapDispatchToProps , right ?
well kind of , in fact after using extensively react and redux , you will notice that for every component , there is a sweet spot in terms of size , childrens , and what you should put in it and what you should not.
Example: you have a button inside a form that controls the send mechanism , there's no need to make a component for the button , it will add up complexity without any benefit. just put it on the form component and you will have both ready to use.
If you really need to call actions or to pass props between a deeply nested component and an HOC then use the connect module at the component level (for your case the button) , but not much because it will make your components heavier (to load and to display).Here are some tips to help :
you need to be as specfic as possible when you use mapStateToProps , don't return the whole store , just the piece of data needed , same for mapDispatchToprops , just bind the method that you will be using nothing else.
in your case the button doesn't have to know which tab is selected , so a mapDispatchToProps is enough.
avoid deep nesting components that handles some kind of logic ,refactor your structure or create A HOC for that component , logic less components in contrary can be nested deeply
If you are writing a huge app with a lot of reducers and states , consider using selectors , and some libraries like reselect.
I know that this is not the answer you were expecting but following this guideline will saves you countless hours of refactoring.
Hope it helps

Bind react component to part of redux state

I have redux store that looks something like this:
{
user: {},
alerts: [],
reports: [],
sourses: []
}
For each one of this parts of state i have a bunch of React Components wrapped in a container wich connected via react-redux. And has mapStateToProps like this
(state) => {alerts: state.alerts}
(state, ownProps) => {alert: _.filter(state, {id: ownProps.curId})}
Problem that when i for example launch some action for Alerts like CREATE_ALERT or EDIT_ALERT and redux state updated, ALL REACT COMPONENTS WILL RESPOND TO THIS CHANGE even ones that works with different parts like sources or reports.
My question: how to "bind" certain components to certain parts of a tree. So each container component WILL UPDATE ONLY WHEN APROPRIATE PART OF REDUX STATE UPDATED and ignore other changes.
Expected behavior
Dispatch CREATE_ALERT -> Alert reducer -> Redux store update -> ONLY Alert container component re-rendering.
When you are changing state in redux the whole state becomes just a new object.
Then your component is given by this new object (new reference) and re-renderes itself.
To fix this behaviour you need to add some logic to compare if your component got props with different value (not reference).
The easiest and fastest way is to use React.PureComponent. You can also override shouldComponentUpdate function and handle changes by yourself. But note that PureComponent works only with primitives (it does a shallow compare).
Check also Immutable.js which helps you with intelligent way of changing references of props.
if you use connect method, then pass only selected redux state to the component, this will prevent rendering of other components
example:
User Component:
const mapStateToProps = state =>({
users: state.users
});
export default connect(mapStateToProps)(User)
Alert Component:
const mapStateToProps = state =>({
alerts: state.alerts
});
export default connect(mapStateToProps)(Alert)
Check this out: Avoid Reconciliation
There explains what Neciu says.
Container components created with connect will always receive notifications of all updates to the store.
The responsibility for consuming these updates falls on the receiving connect component. It should contain the logic to extract the data relevant to it.

Categories

Resources