Think this question is best answered by the docs:
If you have not programmed several apps with React, your first inclination is usually going to be to try to use refs to "make things happen" in your app. If this is the case, take a moment and think more critically about where state should be owned in the component hierarchy. Often, it becomes clear that the proper place to "own" that state is at a higher level in the hierarchy. Placing the state there often eliminates any desire to use refs to "make things happen" – instead, the data flow will usually accomplish your goal.
Not sure exactly what you are trying to do, but my hunch is a parent component and passing props is what you really want here.
Answer from Mark McKelvy on Stack OverflowThink this question is best answered by the docs:
If you have not programmed several apps with React, your first inclination is usually going to be to try to use refs to "make things happen" in your app. If this is the case, take a moment and think more critically about where state should be owned in the component hierarchy. Often, it becomes clear that the proper place to "own" that state is at a higher level in the hierarchy. Placing the state there often eliminates any desire to use refs to "make things happen" – instead, the data flow will usually accomplish your goal.
Not sure exactly what you are trying to do, but my hunch is a parent component and passing props is what you really want here.
I completely agree with the quote Mark McKelvy has provided. What you are trying to achieve is considered an anti-pattern in React.
I'll add that creating a parent component doesn't necessarily means it has to be a direct parent, you can create a parent component further up the chain, in which you can render an array of all your children components together, having the logic to coordinate between all the children (or pairs of children according to your example) sit inside your parent.
I created a rough example of the concept which should do the trick:
class A extends React.Component {
onClick(key) {
alert(this.refs[key].refs.main.innerText);
}
render() {
var children = [];
for (var i = 0; i < 5; i++)
children.push.apply(children, this.renderPair(i));
return (
<div>
{children}
</div>
);
}
renderPair(key) {
return [
<B ref={'B' + key} key={'B' + key} onClick={this.onClick.bind(this, 'C' + key)}/>,
<C ref={'C' + key} key={'C' + key} onClick={this.onClick.bind(this, 'B' + key)}/>
];
}
}
class B extends React.Component {
render() {
return <p ref="main" onClick={this.props.onClick}>B</p>;
}
}
class C extends React.Component {
render() {
return <p ref="main" onClick={this.props.onClick}>C</p>;
}
}
React.render(<A/>, document.getElementById('container'));
And any state you need to save for all your children, you do in the common parent. I really hope this helps.
Hi everybody. I'm trying to figure out what is the correct way of accessing a ref method of a child component in a sibling of that child
< Parent>
Const childRef = useRef()
Return ( <> <Child1 sibRef={ref}/> <Child2 ref={childRef}/> </> )
</Parent>
So the problem is ref will be undefined in child1, and cannot use an effect with ref as dependency because ref won't trigger it. Also tried setting the ref in child2 with usecallback, didn't work either
So what's the correct way of access a sibling method through ref? Thanks in advance!
React.js - Communicating between sibling components
Pass state between sibling components in React
Passing data between two sibling React.js components
ReactJS - Pass references between siblings
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>
);
}
}
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
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...
I know that one could use something such as Flux/Redux/MobX for this, but how would one communicate between sibling components using the React's suggestion? https://facebook.github.io/react/tips/communicate-between-components.html: says "Subscribe to events in componentDidMount(), unsubscribe in componentWillUnmount(), and call setState() when you receive an event." However, I am having a hard time conceptualizing what that might look like. For example, if I wanted to build a simple counter app that could display the count within the Display component and increment in the Counter component. How could I do that if there was no parent/child relationship between the two components? In Vue I could create a global event bus to handle such communication. Is this possible in React?
I 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?