How to pass props from parent to grandchild component in react - reactjs

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)

Related

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

React - Hooks + Context - Is this a good way to do global state management?

I am trying to find a good, clean, with little boilerplate, way to handle React's global state
The idea here is to have a HOC, taking advantage of React's new Hooks & Context APIs, that returns a Context provider with the value bound to its state. I use rxjs for triggering a state update on store change.
I also export a few more objects from my store (notably : the raw rxjs subject object and a Proxy of the store that always returns the latest value).
This works. When I change something in my global store, I get updates anywhere in the app (be it a React component, or outside React). However, to achieve this, the HOC component re-renders.
Is this a no-op ?
The piece of code / logic I think could be problematic is the HOC component:
const Provider = ({ children }) => {
const [store, setStore] = useState(GlobalStore.value)
useEffect(() => {
GlobalStore.subscribe(setStore)
}, [])
return <Context.Provider value={store}>{children}</Context.Provider>
}
GlobalStore is a rxjs BehaviorSubject. Every time the subject is updated, the state of the Provider component gets updated which triggers a re-render.
Full demo is available there: https://codesandbox.io/s/qzkqrm698q
The real question is: isn't that a poor way of doing global state management ? I feel it might be because I basically re-render everything on state update...
EDIT: I think I have written a more performant version that's not as lightweight (depends on MobX), but I think it generates a lot less overhead (demo at: https://codesandbox.io/s/7oxko37rq) - Now what would be cool would be to have the same end result, but dropping MobX - The question makes no sense anymore
I understand your need to handle a global state. I already found myself in the same situation. We have adopted similar solutions, but in my case, I've decided to completelly drop from ContextAPI.
The ContextAPI really sucks to me. It seems to pretend to be a controller based pattern, but you end up wrapping the code inside an non-sense HOC. Maybe I've missed he point here, but in my opinion the ContextAPI is just a complicated way to offer scoped based data flow.
So, I decided to implement my own global state manager, using React Hooks and RxJS. Mainly because I do not use to work on really huge projects (where Redux would fit perfectly).
My solution is very simple. So lets read some codes because they say more than words:
1. Store
I've created an class only to dar nome aos bois (it's a popular brazilian expression, google it 😊) and to have a easy way to use partial update on BehaviorSubject value:
import { BehaviorSubject } from "rxjs";
export default class Store<T extends Object> extends BehaviorSubject<T> {
update(value: Partial<T>) {
this.next({ ...this.value, ...value });
}
}
2. createSharedStore
An function to instantiate the Store class (yes it is just because I don't like to type new ¯\(ツ)/¯):
import Store from "./store";
export default function <T>(initialValue: T) {
return new Store<T>(initialValue);
}
3. useSharedStore
I created an hook to easily use an local state connected with the Store:
import Store from "./store";
import { useCallback, useEffect, useState } from "react";
import { skip } from "rxjs/operators";
import createSharedStore from "./createSharedStore";
const globalStore = createSharedStore<any>({});
type SetPartialSharedStateAction<S> = (state: S) => S;
type SetSharedStateAction<S> = (
state: S | SetPartialSharedStateAction<S>
) => void;
export default function <T>(
store: Store<T> = globalStore
): [T, SetSharedStateAction<T>] {
const [state, setState] = useState(store.value);
useEffect(() => {
const subscription = store
.pipe(skip(1))
.subscribe((data) => setState(data));
return () => subscription.unsubscribe();
});
const setStateProxy = useCallback(
(state: T | SetPartialSharedStateAction<T>) => {
if (typeof state === "function") {
const partialUpdate: any = state;
store.next(partialUpdate(store.value));
} else {
store.next(state);
}
},
[store]
);
return [state, setStateProxy];
}
4. ExampleStore
Then I export individual stores for each feature that needs shared state:
import { createSharedStore } from "hooks/SharedState";
export default createSharedStore<Models.Example | undefined>(undefined);
5. ExampleComponent
Finally, this is how to use in the component (just like a regular React state):
import React from "react";
import { useSharedState } from "hooks/SharedState";
import ExampleStore from "stores/ExampleStore";
export default function () {
// ...
const [state, setState] = useSharedState(ExampleStore);
// ...
function handleChanges(event) {
setState(event.currentTarget.value);
}
return (
<>
<h1>{state.foo}</h1>
<input onChange={handleChange} />
</>
);
}
GlobalStore subject is redundant. RxJS observables and React context API both implement pub-sub pattern, there are no benefits in using them together this way. If GlobalStore.subscribe is supposed to be used in children to update the state, this will result in unnecessary tight coupling.
Updating glubal state with new object will result in re-rendering the entire component hierarchy. A common way to avoid performance issues in children is to pick necessary state parts and make them pure components to prevent unnecessary updates:
<Context.Consumer>
({ foo: { bar }, setState }) => <PureFoo bar={bar} setState={setState}/>
</Context.Provider>
PureFoo won't be re-rendered on state updates as long as bar and setState are the same.

React pre-processing props

I'd to know if there's any component or helper function to pre-process/transform properties before delivering to the component itself.
I've using the redux's connect function to achieve this behaviour but for components that doesn`t connect to the redux store doesn't make much sense. The following illustrates the ideal solution:
const MyComponent = (props) => { ... }
const propsProcessor = (props) => {
//do something here and return the processed props
}
export default processingProps(propsProcessor)(MyComponent);
This way I could pass an array to the component, group into an json and use it inside the component.

How is this dispatch function coming from props?

I'm trying to learn from reading this app's code, and I am confused how you get dispatch from the props in this line of code:
_handleSubmit(e) {
e.preventDefault();
const { email, password } = this.refs;
const { dispatch } = this.props;
dispatch(Actions.signIn(email.value, password.value));
}
https://github.com/bigardone/phoenix-trello/blob/master/web/static/js/views/sessions/new.js#L17
Hoping someone can explain how calling this.props will return a dispatch?
react-redux is a library that helps components get values from the Redux store in a predictable and performant way. The main tool it provides is a function called connect, which wraps Redux components providing them with store values as props. The key part of the code you link to is at the bottom: https://github.com/bigardone/phoenix-trello/blob/master/web/static/js/views/sessions/new.js#L70-L74.
Say you have a value in the Redux store named counter. You want your component CounterDisplay to know about this value, and update when it changes:
class CounterDisplay extends Component {
render () {
const { counter, dispatch } = this.props
return (
<div>{counter}</div>
)
}
}
Those variables are going to be undefined unless you've explicitly put the values into props the 'old fashioned way':
<CounterDisplay counter={1} dispatch={() => {}} />
That's where connect comes in. It knows about the Redux store (often using another component called Provider) and can place values from it into the props of the component it's wrapping. It returns what's called a Higher Order Component (HOC): one that wraps another to perform a specific function, in this case connection to the store.
Here's how we'd get the counter value into props:
function mapStateToProps (state) {
// Slightly confusingly, here `state` means the entire application
// state being tracked by Redux... *not* CounterDisplay's state
return {
counter: state.counter
}
}
export default connect(mapStateToProps)(CounterDisplay)
So instead of exporting CounterDisplay itself, we export the HOC. In addition to counter, connect will also automatically insert the dispatch function into props so we can make use of it in the component. That's the behaviour you're seeing in the source you're reviewing.
const { dispatch } = this.props; is just deconstructing this.props.dispatch into a dispatch variable so it's used from props and where do they come to props? From react-redux connect:
export default connect(mapStateToProps)(SessionsNew);
connect is just Higher Order Component which basically connects your component with the store. As part of this process it puts dispatch into component's props
Edit:
The main idea is that connect is a function that takes whatever components and extends it's props with dispatch property (it returns another react components that wraps your component). You can also map some properties from state to your component and bind actions with dispatch using mapDispatchToProps and mapStateToProps
Just an example of destructuring assignment. See more here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment

Redux: Child receiving props before parent

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).

Resources