I created a jsfiddle with an example of how to share a variable between two components using a parent component.
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {shared_var: "init"};
}
updateShared(shared_value) {
this.setState({shared_var: shared_value});
}
render() {
return (
<div>
<CardSearch shared_var={this.state.shared_var} updateShared={this.updateShared} />
<RunOnServer shared_var={this.state.shared_var} updateShared={this.updateShared} />
<div> The shared value is {this.state.shared_var} </div>
</div>
);
}
}
class CardSearch extends React.Component {
updateShared() {
this.props.updateShared('card');
}
render() {
return (
<button onClick={this.updateShared} style={this.props.shared_var == 'card' ? {backgroundColor: "green"} : null} >
card
</button>
);
}
}
class RunOnServer extends React.Component {
updateShared() {
this.props.updateShared('run');
}
render() {
return (
<button onClick={this.updateShared} style={this.props.shared_var == 'run' ? {backgroundColor: "green"} : null}>
run
</button>
);
}
}
ReactDOM.render(
<Parent/>,
document.getElementById('container')
);
Answer from Mark on Stack OverflowI have been pulling my hair why such an easy thing to do in other frameworks like asp, or asp.net (hello session variable),is soooo difficult in react. Ok so how do I share state from one component to another component. I tried callback but no do. Now I am trying context. But is redux is the one that I should use?
How can I pass data between sibling components in React?
Pass state between sibling components in React
React.js - Communicating between sibling components
How can I pass data between sibling components in React?
I created a jsfiddle with an example of how to share a variable between two components using a parent component.
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {shared_var: "init"};
}
updateShared(shared_value) {
this.setState({shared_var: shared_value});
}
render() {
return (
<div>
<CardSearch shared_var={this.state.shared_var} updateShared={this.updateShared} />
<RunOnServer shared_var={this.state.shared_var} updateShared={this.updateShared} />
<div> The shared value is {this.state.shared_var} </div>
</div>
);
}
}
class CardSearch extends React.Component {
updateShared() {
this.props.updateShared('card');
}
render() {
return (
<button onClick={this.updateShared} style={this.props.shared_var == 'card' ? {backgroundColor: "green"} : null} >
card
</button>
);
}
}
class RunOnServer extends React.Component {
updateShared() {
this.props.updateShared('run');
}
render() {
return (
<button onClick={this.updateShared} style={this.props.shared_var == 'run' ? {backgroundColor: "green"} : null}>
run
</button>
);
}
}
ReactDOM.render(
<Parent/>,
document.getElementById('container')
);
As of 2020, Feb; Context API is the way to handle this:
// First you need to create the TodoContext
// Todo.jsx
//...
export default () => {
return(
<>
<TodoContextProvider>
<TodoList />
<TodoCalendar />
</TodoContextProvider>
</>
)
}
// Now in your TodoList.jsx and TodoCalendar.jsx; you can access the TodoContext with:
//...
const todoContext = React.useContext(TodoContext);
console.log(todoContext)
//...
//...
Check this video tutorial by The Net Ninja for Hooks & Context API
Good Luck...
In React, state goes top to bottom. A nested component can update the state of a parent if a function defined in the parent has been passed to it as prop. Hence, what you wanna do is not possible (exchanging state between List and Form, two sibling components).
For this to work, you should have the state for the list in a parent component, App.js for example, this way:
import Form from "./Components/Form";
import List from "./Components/List";
import {useState} from "react";
function App() {
const [list, setList] = useState([])
return (
<div className="App">
<header>
<h1>Shopping List</h1>
</header>
<Form list = {list} setList = {setList}/>
<List list = {list} />
</div>
)
}
import React, {useState} from "react";
const List = ({list}) =>{
return(
<div>
<ul>{list.map(item => <li>"test"</li>)}</ul>
</div>
)
}
export default List
const Form = ({list, setList}) => {
const [inputText,setInputText] = useState("")
const submitBtn = (e) =>{
e.preventDefault()
setList([
...list,{inputText}
])
}
return (
<form>
<input
value={inputText}
type="text"
onChange={
(e) => setInputText(e.target.value)
}
/>
<button type="submit" onClick={submitBtn}>
<i>Add</i>
</button>
</form>
)
}
export default Form
You can't really do what you're asking as React only allows child components to accept state from parent components, it is a top-down process. I would recommend using a React "Context"; A context will allow you to have one component that can share state throughout the entire component tree without having to pass props down through child components. It's basically a store for the whole react app, that can be pulled wherever and whenever needed.
https://reactjs.org/docs/context.html
TLDR: Yes, you should use a props-from-top-to-bottom and change-handlers-from-bottom-to-top approach. But this can get unwieldy in a larger application, so you can use design patterns like Flux or Redux to reduce your complexity.
Simple React approach
React components receive their "inputs" as props; and they communicate their "output" by calling functions that were passed to them as props. A canonical example:
<input value={value} onChange={changeHandler}>
You pass the initial value in one prop; and a change handler in another prop.
Who can pass values and change handlers to a component? Only their parent. (Well, there is an exception: you can use the context to share information between components, but that's a more advanced concept, and will be leveraged in the next example.)
So, in any case, it's the parent component of your selects that should manage the input for your selects. Here is an example:
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
// keep track of what is selected in each select
selected: [ null, null, null ]
};
}
changeValue(index, value) {
// update selected option
this.setState({ selected: this.state.selected.map((v, i) => i === index ? value : v)})
}
getOptionList(index) {
// return a list of options, with anything selected in the other controls disabled
return this.props.options.map(({value, label}) => {
const selectedIndex = this.state.selected.indexOf(value);
const disabled = selectedIndex >= 0 && selectedIndex !== index;
return {value, label, disabled};
});
}
render() {
return (<div>
<Select value={this.state.selected[0]} options={this.getOptionList(0)} onChange={v => this.changeValue(0, v)} />
<Select value={this.state.selected[1]} options={this.getOptionList(1)} onChange={v => this.changeValue(1, v)} />
<Select value={this.state.selected[2]} options={this.getOptionList(2)} onChange={v => this.changeValue(2, v)} />
</div>)
}
}
Redux
The main drawback of the above approach is that you have to pass a lot of information from the top to the bottom; as your application grows, this becomes difficult to manage. React-Redux leverages React's context feature to enable child components to access your Store directly, thus simplifying your architecture.
Example (just some key pieces of your redux application - see the react-redux documentation how to wire these together, e.g. createStore, Provider...):
// reducer.js
// Your Store is made of two reducers:
// 'dropdowns' manages the current state of your three dropdown;
// 'options' manages the list of available options.
const dropdowns = (state = [null, null, null], action = {}) => {
switch (action.type) {
case 'CHANGE_DROPDOWN_VALUE':
return state.map((v, i) => i === action.index ? action.value : v);
default:
return state;
}
};
const options = (state = [], action = {}) => {
// reducer code for option list omitted for sake of simplicity
};
// actionCreators.js
export const changeDropdownValue = (index, value) => ({
type: 'CHANGE_DROPDOWN_VALUE',
index,
value
});
// helpers.js
export const selectOptionsForDropdown = (state, index) => {
return state.options.map(({value, label}) => {
const selectedIndex = state.dropdowns.indexOf(value);
const disabled = selectedIndex >= 0 && selectedIndex !== index;
return {value, label, disabled};
});
};
// components.js
import React from 'react';
import { connect } from 'react-redux';
import { changeDropdownValue } from './actionCreators';
import { selectOptionsForDropdown } from './helpers';
import { Select } from './myOtherComponents';
const mapStateToProps = (state, ownProps) => ({
value: state.dropdowns[ownProps.index],
options: selectOptionsForDropdown(state, ownProps.index)
}};
const mapDispatchToProps = (dispatch, ownProps) => ({
onChange: value => dispatch(changeDropdownValue(ownProps.index, value));
});
const ConnectedSelect = connect(mapStateToProps, mapDispatchToProps)(Select);
export const Example = () => (
<div>
<ConnectedSelect index={0} />
<ConnectedSelect index={1} />
<ConnectedSelect index={2} />
</div>
);
As you can see, the logic in the Redux example is the same as the vanilla React code. But it is not contained in the parent component, but in reducers and helper functions (selectors). An instead of top-down passing of props, React-Redux connects each individual component to the state, resulting in a simpler, more modular, easier-to-maintain code.
The following help me to setup communication between two siblings. The setup is done in their parent during render() and componentDidMount() calls.
class App extends React.Component<IAppProps, IAppState> {
private _navigationPanel: NavigationPanel;
private _mapPanel: MapPanel;
constructor() {
super();
this.state = {};
}
// `componentDidMount()` is called by ReactJS after `render()`
componentDidMount() {
// Pass _mapPanel to _navigationPanel
// It will allow _navigationPanel to call _mapPanel directly
this._navigationPanel.setMapPanel(this._mapPanel);
}
render() {
return (
<div id="appDiv" style={divStyle}>
// `ref=` helps to get reference to a child during rendering
<NavigationPanel ref={(child) => { this._navigationPanel = child; }} />
<MapPanel ref={(child) => { this._mapPanel = child; }} />
</div>
);
}
}
you can't pass data through siblings, you need to lift up state or use other strategies. you need to find a common ancestor (in your case App.js) to place your state, and pass down your state and your setState function as props to Shop and Pdp. this is the simplest approach.
with a deep nested tree you would need to pass down to each component until it finds the final consumer. in this case instead you could use the Context API where you create a provider and you can consume directly.
for more complex state management (a shopping cart would fit the case) you have options like redux where you have a redux state, with reducers that handle state properly, and connect with components.
about setState:
setState is async, to print an updated state you can console.log on componentDidUpdate life cycle. also setState can take a second argument, a callback function that will run after your state's update so you can console.log your updated state.
setState is an async function, so that's why the console.log is not printing the correct productId.
handleClick(id, name, price, description, image) {
console.log(id) //the id here is correct
this.setState((prevState) => {
return {
productId: prevState.productId + 1
}
})
console.log(this.state.productId) //setState hasn't happened
}
if you want to see the changes, add it to componentWillReceiveProps
componentWillReceiveProps(nextProps) {
console.log(nextProps.productId);
}
I have a requirement where data changes in one sibling component must be sent to another sibling component. I have implemented state lifting, where the parent component holds the state, and changes in one sibling component are passed to the other sibling component.
However, I've observed that all child components within the parent component re-render when the state in the parent component changes, even if the data in other child components hasn't changed.
One possible solution I've come across involves using memo, useMemo, and useCallback. However, in my application, this would result in a large number of these hooks, which isn't ideal.
Are there any other approaches to efficiently manage this scenario without causing excessive re-renders of child components?