Redux: Child receiving props before parent - reactjs

My app has a state which may or may not have a user object.
I have two containers and two components:
ParentContainer:
const mapStateToProps = (state) => ({
showUserDetails: Boolean(state.user),
})
export default connect(mapStateToProps)(ParentComponent)
ParentComponent:
const ParentComponent = ({ showUserDetails }) => (
<div>
{showUserDetails && <ChildContainer />}
</div>
)
ChildContainer:
const mapStateToProps = (state) => ({
name: state.user.name,
})
export default connect(mapStateToProps)(ChildComponent)
ChildComponent:
const ChildComponent = ({ name }) => (
<h1>{name}></h1>
)
However I am running into scenarios where, after an action which sets state.user = null, ChildContainer tries to render before ParentContainer and hence throws an exception as it can't access null.name.
Is this expected behaviour of Redux, that a child container can re-render before the parent? In the classic React flow, this error would not happen.
I appreciate in this contrived example one could just call <ChildComponent name={user.name} /> in the ParentComponent instead of using a container. But in the real world I have deeply nested components accessing a lot of different parts of state, so can't just pass props down easily.

Are you batching your Redux dispatches? If not read v3.0.0 release notes carefully:
https://github.com/reactjs/react-redux/releases/tag/v3.0.0
It suggests redux-batched-updates module but it's more or less deprecated in favor of redux-batched-subscribe
Use it like this:
import { unstable_batchedUpdates as batchedUpdates } from 'react-dom';
import { batchedSubscribe } from 'redux-batched-subscribe';
const store = createStore(reducer, intialState, batchedSubscribe(batchedUpdates));

The update will happen regardless of the child component being contained in a parent or not. Since it is in the DOM at the time of the update, the connect will try to update your component.
The easiest workaround for this would be to insert a safety check when pulling the user's name (state.user.name,) and replace it with (state.user && state.user.name,).
Or, of course - the alternative. Passing it down from the parent.
To further explain, the props that are being sent down from the parent will first be evaluated in the parent and then the updates would propagate downwards in the hierarchical tree. However, since your prop of name is actually coming directly from the state tree, you will need some safety checks around it.
Even if the parent after a subsequent update of it's own will decide that the child should no longer be rendered (since showUserDetails will now evaluate to false).

Related

How to pass props from parent to grandchild component in react

I have tried pass value from parent to grandchild component, and it works. While I am thinking if there is another simpler or other way of passing props in shorter path.
What I did is quite cumbersome in codesandbox
There may be a common problem in react world called prop drilling by passing data to children only using props.
I would recommend only 2-level passing, if you need pass data deeper then you probably doing something wrong.
Use one of popular state management library (if your project is big) or React context (which is awesome)
Create a folder called /contexts and put contexts there. The structure of files can be like shown below:
First you need to create a context itself
type ClientContextState = {
data: User;
set: (data: User) => void;
logout: () => void;
};
// empty object as a default value
export const UserContext = createContext<UserContextState>({} as UserContextState);
Then create a Provider wrapper component
export const UserProvider = ({ children }: Props) => {
const [data, setData] = useState<User>({});
const sharedState = {
data,
set: setData
logout: () => setData(null)
}
return <UserContext.Provider value={sharedState}>{children}</UserContext.Provider>
});
You may also want to have an alias of useContext hook:
export const useUser = () => {
return useContext(UserContext);
};
After all this whenever you wrap your components or app to <UserProvider>...</UserProvider> you can use our hook to access data and methods form sharedState from any place you want:
export LogoutButton = () => {
const {data, logout} = useUser();
return <Button onClick={() => logout()}>Logout from {data.name}</Button>
}
Whenever you want to pass props or data from Grandparent to child component, always use react-redux. This is useful to maintain the state and access the data from anywhere/any component.
Another way is to use useContext hooks which you can use to pass the props
Following are the steps to use useContext hooks
Creating the context
The built-in factory function createContext(default) creates a context instance:
import { createContext } from 'react';
const Context = createContext('Default Value');
The factory function accepts one optional argument: the default value.
Providing the context
Context.Provider component available on the context instance is used to provide the context to its child components, no matter how deep they are.
To set the value of context use the value prop available on the
<Context.Provider value={value} />:
function Main() {
const value = 'My Context Value';
return (
<Context.Provider value={value}>
<MyComponent />
</Context.Provider>
);
}
Again, what’s important here is that all the components that’d like later to consume the context have to be wrapped inside the provider component.
If you want to change the context value, simply update the value prop.
Consuming the context: Consuming the context can be performed in 2 ways.
The first way, the one I recommend, is to use the useContext(Context) React hook:
import { useContext } from 'react';
function MyComponent() {
const value = useContext(Context);
return <span>{value}</span>;
}
Generally it's helpful to consider whether moving state down the hierarchy would be the simplest route. That means lifting the component instantiation to a place closer to the state being used. In your example, that could mean Component_data is used inside Component and passed to its children there, removing one step in the nested data flow. Even better, would be that Child.a accesses Component_data.A directly.
In a real app with cases where accessing the data directly is less feasible, a solution I lean towards is using Context to set data in the parent that retrieves it, and then I can access it however deeply nested the component might be that needs it.
i.e. in App I would create the Context provider, and in ChildA I access it via useContext hook.
Further reading
https://reactjs.org/docs/context.html
https://overreacted.io/before-you-memo/#solution-1-move-state-down (this post is about an alternative to using useMemo but has an illustrative example of why moving state down is a good thing)

How can I prevent unnecessary re-renders on the child components in React with Hooks & Context?

I'm working on some code, and this code is huge. We have so many child components(nearly 300) and each one of them are using & manipulating values from the parent component's state via React Context.
Note: I didn't wrote this code from scratch. Which also means the design I'm about to show is not what I would come up with.
The problem: Since every component is using the same state, there are so many unnecessary re-renders happening. Every small state change is causing every component to re-render. And it makes the web app laggy. Literally, there is lag when you enter some input in a field.
I think, when state change happens the functions get rebuilt and that's why every child gets updated, because the value provided by context is changed after state change happened.
Additionally, I tried to use useReducer instead of useState that didn't went well. Besides that, I tried to use React.memo on every child component but the compare function didn't get triggered no matter what I tried. compare function only got triggered on the parent component which has the state as props. At this point, I'm not even really sure what is the problem :D
To give more specific details on the design, here is how we define and pass the callbacks to child components.
Definitions:
const [formItemState, setFormState] = React.useState<FormItemState>({} as FormItemState);
const getAppState = useCallback(() => ({ state: props.state as AppState }), [props.state]);
const getAppAction = useCallback(() => ({ action: props.action as AppAction }), [props.action]);
const getFormItemError = useCallback((key: string) => formItemErrorState[key], [
formItemErrorState,
]);
const getFormItem = useCallback((key: string) => formItemState[key], [formItemState]);
const updateFormItem = useCallback(
(name: string, formItemData: FormItemData): void => {
const previousState = getFormItem(name);
if (!isEqual(previousState, formItemData)) {
formItemState[name] = formItemData;
setFormState((state) => ({
...state,
...formItemState,
}));
}
},
[formItemState, setFormState]
);
Passing them to Context.Provider:
return (
<FormContext.Provider
value={{
getAppAction,
getAppState,
getFormItem,
updateFormItem
}}
>
<SomeComponent>
{props.children} // This children contains more than 250 nested components, and each one of them are using these provided functions to interact with the state.
</SomeComponent>
</FormContext.Provider>
);
Last note: Please ask me if more info is needed. Thanks!
Why dont you just rewrite your state management to redux and pull only the necessary state to be used on each component. React.memo only picks up changes from props

How do I pass things between components on the same level?

I've got a React app of the form:
<App>
<ParentComponent>
<FormComponent>
<AnotherComponent>
</ParentComponent>
</App>
I want to be able to update some state values of <FormComponent> by clicking on elements in <AnotherComponent>.
I don't want to place them inside each other, but keep them side-by-side. I don't want to lift up <FormComponent> state as it's nicely encapsulated.
What's the best way to achieve this? Can I do it with just react or do I need RxJS/something else?
The data in React Flows Down.
If you don't want to lift the state up, what's left is Context API or any state management library like Redux and MobX (both implement Context API with different strategy).
But still, the state is "above" FormComponent (you still lifting state up).
const Context = React.createContext();
const ParentComponent = () => {
const contextState = useState(DEFAULT_STATE);
return (
<Context.Provider value={contextState}>
<FormComponent />
<AnotherComponent />
</Context.Provider>
);
};
const FormComponent = () => {
const [, setState] = useContext(Context);
// use setState
};
const AnotherComponent = () => {
const [state] = useContext(Context);
// valid state updated from FormComponent
};
As far as I can tell the "right thing" to do in these instances is move the state up one level, into your Parent component.
If you have a look at the Intro to React:
To collect data from multiple children, or to have two child components communicate with each other, you need to declare the shared state in their parent component instead.
"Lifting state up" is a common thing in React applications and doesn't require introducing a state management solution like Redux or RxJS.
Apart from the ways mentioned above you can pass down a function as prop from the Parent component to AnotherComponent. And when clicking any element in Another component, pass the intended value in that function, which will in turn be transferred to ParentComponent. And you can then pass the value as props to the FormComponent from the ParentComponent.
You can check this example for better understanding https://react-m9skpu.stackblitz.io

Does React renders Component due to props?

According to that link: http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
render() may be triggered with new props. Could someone give me a code example for that? I cannot see how props change invoke rendering! Please not by changing the props via the state; then it is setState() that invokes render()...
Look at shouldComponentUpdate() - this is it's signature - it returns a boolean. Props is there so you can compare and manually say whether the component should update.
shouldComponentUpdate(nextProps, nextState)
For function components React.memo is the replacement for shouldComponentUpdate which is used with class components.
const myComponent = React.memo(props => {
...
code of React.FunctionComponent
...
},
(prevProps, nextProps) => prevProps.propA === nextProps.propA
);
React.memo gets two arguments shown above: a React.FunctionComponent which will be wrapped around by memo and an optional function that returns a boolean.
When the function returns true, the component will not be re-rendered. If the function is omitted then its default implementation in React.memo works like the implementation of shouldComponentUpdate in React.PureComponent. E.g. it does shallow comparison of props, the difference is that only props are taken into account because state doesn’t exist for functional components.
Using hooks is neater to show. The new props data passed to ComponentB causes a re-rendering of ComponentB:
import React, { useState } from 'react'
import ComponentB from '...'
const ComponentA = props => {
const [data, setData] = useState(0) // data = 0
handleChangeProp = item => setData(item) // data = 1
return(
<div>
<button onClick{() => handleChangeProp(1)}
<ComponentB props={data} />
</div>
)
}
Yes, when you do a setState(newState) or when you pass in changed props the component will re render, this is why you can't mutate. The following will not work because you set state with mutated state.
export default function Parent() {
const [c, setC] = useState({ c: 0 });
console.log('in render:', c);
return (
<div>
<button
onClick={() =>
setC(state => {
state.c++;
console.log('state is:', state);
return state;
})
}
>
+
</button>
<Child c={c.c} />
</div>
);
}
That code "won't work" because pressing + will not cause a re render, you mutated state and then set state with the same object reference so React doesn't know you changed anything.
This is how React detects changes, you may think that comparing {c:0} to {c:1} is a change but because you mutated there actually is no change:
const a = {c:1};
a.c++;//you "changed" a but a still is a
To indicate a change in React you have to create a new reference:
const a = {c:1};
const b = {...a};//b is shallow copy of a
a===b;//this is false, even though both a and b have same internal values
This means you can also have unintended renders because you create an object prop that may have the same value but still is a different reference than the last time you created it.
Note that even <Child prop={1} will cause Child to render if Child is not a pure component (see links at the end).
What you want to avoid is doing <Child prop={{c:value}} because every time you pass prop it'll force Child to render and React to do a virtual DOM compare even if value didn't change. The virtual DOM compare will probably still detect that Child virtual DOM is the same as last time and won't do an actual DOM update.
The most expensive thing you can do is <Child onEvent={()=>someAction(value)}. This is because now the virtual DOM compare will fail even if value and someAction did't change. That's because you create a new function every time.
Usually you want to memoize creating props in a container, here is an example of doing this with react-redux hooks. Here is an example with stateful components passing handlers.

Prevent react component from rendering twice when using redux with componentWillMount

I have a React component that dispatches a redux state change in its componentWillMount function. The reason is that when the component is loaded, it needs to get the id from the url (powered by react-router), and trigger an action that sets up the state with that id's data.
Here is the component:
class Editor extends React.Component {
componentWillMount() {
const { dispatch, params } = this.props
dispatch(editItem(params.id))
}
render() {
const item = this.props.item
console.log("Editing", item)
}
}
export default connect(state => ({item: state.item}))(Editor)
Here's the catch: render is getting called twice. item is undefined on the first call, and valid on the second. Ideally, it should only be called once this.props.item actually exists (after the editItem action has been dispatched and run).
According to the React docs: "If you call setState within this method, render() will see the updated state and will be executed only once despite the state change."
In redux, dispatch is the equivalent of calling setState, as it results in a state change. However, I'm guessing something in the way connect works is still causing render to be called twice.
Is there a way around this besides adding a line like if (!item) return; ?
One thing you might do is create a higher order component that handles the basic pattern of loading a different component (or no component) before the required props are loaded.
export const LoaderWrapper = function(hasLoaded, Component, LoaderComponent, onLoad) {
return props => {
if (hasLoaded(props)) {
return <Component {...props} />
}
else {
if (onLoad) onLoad(props)
return { LoaderComponent ? <LoaderComponent /> : null }
}
}
}
Then you can wrap your component before connecting it to get the desired behaviour.
export default connect(state => ({item: state.item}))(LoaderWrapper(
((props) => !!props.item),
Editor,
null,
(props) => props.dispatch(editItem(props.params.id))
))
You might want to add some currying magic to make sure you can compose these kinds of wrapper functions more nicely. Take a look at recompose for more info.
It looks like there's already an issue in the react-redux library.
https://github.com/rackt/react-redux/issues/210
What does editItem do? Does it add item to the redux state or is it there already?
If it is adding I imagine what is happening is that a render cycle happens with the current props, ie item being blank.
Then it gets rendered again when the props have changed, via setting the item.
One approach to fixing this sort of thing is to create a higher order component that wraps Editor and calls the dispatch action the rendering though is set either to a loading screen or and empty div until item is set. That way you can be assured that Editor will have an item.
But without knowing what editItem does it's sort of hard to know. Maybe you could paste the code for that?

Resources