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
Related
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)
Because of the architecture of the application I'm working on, I need to have access to the states of one child component in another child component and I can't take these states to the parent or use react-redux. So I use a technique where I store pointers to the states of the target child in the parent.
In parent I create state for pointers:
export default function App() {
const [compOneStatePointers, setCompOneStatePointers] = useState({});
return (
<div className="App">
<CompOne setCompOneStatePointers={setCompOneStatePointers}></CompOne>
<CompTwo compOneStatePointers={compOneStatePointers}></CompTwo>
</div>
In CompOne I create state and save pointer for this state in parent's 'compOneStatePointers' state.
const [state, setState] = useState(0);
useEffect(() => {
setCompOneStatePointers((prev) => {
const temp = { ...prev };
temp.state = state;
temp.setState = setState;
return temp;
});
}, [setCompOneStatePointers, state]);
After that I can use state from CompOne in CompTwo. Like this:
<button className="button" onClick={() => {
props.compOneStatePointers.setState(prev=> prev+1)
}}>Change state of Comp One</button>
Example of this technique: https://codesandbox.io/s/state-pointers-object-62dkl?file=/src/CompTwo.js
The question is, can I do this or are there pitfalls?
There is a concept of Context API in react js which provides a way to pass data to all the components(called Component Tree) in your react application without passing the props deep to all the levels of component explicitly.
You can read the same concept from the official Documentation of Reactjs https://reactjs.org/docs/context.html#when-to-use-context.
From production perspective, it would be better to use Context API for passing date from one component to one on more component. In real life react application, there would be 100 or more components, and using the context they will interact.
When you keep your app architecture as simple as this, I would say, you have a good approach there. Nothing wrong with passing setState and state to different components from a parent.
Once you get a little more complex, I recommend using something structured like Redux or somehting more native and flexible like rx-global (its only 13kb large) or build your own state management library (like rx-global is).
I'm not creating a react app from scratch, but adding interactive components to an existing webpage. I'm mounting two components, disconnected to each other like this:
ReactDOM.render(<Component1 />, document.getElementById('comp1-root'));
ReactDOM.render(<Component2 />, document.getElementById('comp2-root'));
They lie far from each other on the page.
How do I have them to communicate their states with each other?
Thank you.
React Portals is what I was looking for.
As per my question, I wanted to have same context for all the components mounted at different locations (dom nodes).
Portals solved exactly this issue. Now, I can have one context component housing all the components that exist on that page. Like this:
const dashboardContextDom = document.getElementById('dashboard-root');
const comp1DOM = document.getElementById('comp1-root');
const comp2DOM = document.getElementById('comp2-root');
const Dashboard = () => {
return (
<>
{ReactDOM.createPortal(<Component1 />, comp1DOM)}
{ReactDOM.createPortal(<Component2 />, comp2DOM)}
</>
);
}
if(dashboardContextDom) {
ReactDOM.render(<Dashboard />, dashboardContextDom);
}
With these components housed in one context allows to easily pass state from one component to another via prop drilling and lifting state up.
You have two options with React:
Move Component 1 and Component 2 and their state into a parent component, and pass the parent's update state function to them:
ComponentWrapper {
...
const updateState = () => {
//update state
}
return (
<Component 1 updateParentState={updateState}/>
<Component 2 updateParentState={updateState}/>
)
...
}
You cannot update the state of a component that is not a parent or a child without using an external state management solution, such as Redux or the new useContext api/hook. These libraries involve moving all individual component's states to a larger centralized global state, which can be updated and accessed by all components.
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).
I have a listview component which consists of a number of child listitem components.
Each child listitem have a showSubMenu boolean state, which display a few extra buttons next to the list item.
This state should update in response to a user event, say, a click on the component DOM node.
childcomponent:
_handleClick() {
... mutate state
this.props.onClick() // call the onClick handler provided by the parent to update the state in parent
}
However, it feels somewhat wrong to update state like, as it mutates state in different places.
The other way i figured i could accomplish it was to call the this.props.onClick directly, and move the child state into the parent as a prop instead, and then do change the state there, and trickle it down as props.
Which, if any, of these approaches is idiomatic or preferable?
First of all, I think that the question's title doesn't describe very well what's your doubt. Is more an issue about where the state should go.
The theory of React says that you should put your state in the higher component that you can find for being the single source of truth for a set of components.
For each piece of state in your application:
Identify every component that renders something based on that state.
Find a common owner component (a single component above all the
components that need the state in the hierarchy).
Either the common
owner or another component higher up in the hierarchy should own the
state.
If you can't find a component where it makes sense to own the
state, create a new component simply for holding the state and add it
somewhere in the hierarchy above the common owner component.
However, a Software Engineer at Facebook said:
We started with large top level components which pull all the data
needed for their children, and pass it down through props. This leads
to a lot of cruft and irrelevant code in the intermediate components.
What we settled on, for the most part, is components declaring and
fetching the data they need themselves...
Sure, is talking about data fetched from stores but what im traying to say is that in some cases the theory is not the best option.
In this case i would say that the showSubMenu state only have sense for the list item to show a couple of buttons so its a good option put that state in the child component. I say is a good option because is a simple solution for a simple problem, the other option that you propose means having something like this:
var GroceryList = React.createClass({
handleClick: function(i) {
console.log('You clicked: ' + this.props.items[i]);
},
render: function() {
return (
<div>
{this.props.items.map(function(item, i) {
return (
<div onClick={this.handleClick.bind(this, i)} key={i}>{item} </div>
);
}, this)}
</div>
);
}
});
If, in a future, the list view has to get acknowledge of that state to show something for example, the state should be in the parent component.
However, i think it's a thin line and you can do wathever makes sense in your specific case, I have a very similar case in my app and it's a simple case so i put the state in the child. Tomorrow maybe i must change it and put the state in his parent.
With many components depending on same state and its mutation you will encounter two issues.
They are placed in component tree so far away that your state will have to be stored in a parent component very high up in the render tree.
Placing the state very high far away from children components you will have to pass them down through many components that should not be aware of this state.
THERE ARE TWO SOLUTIONS FOR THIS ISSUE!
Use React.createContext and user context provider to pass the data to child elements.
Use redux, and react-redux libraries to save your state in store and connect it to different components in your app. For your information react-redux library uses React.createContext methods under the hood.
EXAMPLES:
Create Context
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// Use a Provider to pass the current theme to the tree below.
// Any component can read it, no matter how deep it is.
// In this example, we're passing "dark" as the current value.
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
class ThemedButton extends React.Component {
// Assign a contextType to read the current theme context.
// React will find the closest theme Provider above and use its value.
// In this example, the current theme is "dark".
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
}
// A component in the middle doesn't have to
// pass the theme down explicitly anymore.
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// Assign a contextType to read the current theme context.
// React will find the closest theme Provider above and use its value.
// In this example, the current theme is "dark".
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
REDUX AND REACT-REDUX
import { connect } from 'react-redux'
const App = props => {
return <div>{props.user}</div>
}
const mapStateToProps = state => {
return state
}
export default connect(mapStateToProps)(App)
For more information about redux and react-redux check out this link:
https://redux.js.org/recipes/writing-tests#connected-components