How to Communicate Between Related React Components

How can I communicate between related react components?

The best approach would depend on how you plan to arrange those components. A few example scenarios that come to mind right now:

  1. <Filters /> is a child component of <List />
  2. Both <Filters /> and <List /> are children of a parent component
  3. <Filters /> and <List /> live in separate root components entirely.

There may be other scenarios that I'm not thinking of. If yours doesn't fit within these, then let me know. Here are some very rough examples of how I've been handling the first two scenarios:

Scenario #1

You could pass a handler from <List /> to <Filters />, which could then be called on the onChange event to filter the list with the current value.

JSFiddle for #1 →

/** @jsx React.DOM */

var Filters = React.createClass({
handleFilterChange: function() {
var value = this.refs.filterInput.getDOMNode().value;
this.props.updateFilter(value);
},
render: function() {
return <input type="text" ref="filterInput" onChange={this.handleFilterChange} placeholder="Filter" />;
}
});

var List = React.createClass({
getInitialState: function() {
return {
listItems: ['Chicago', 'New York', 'Tokyo', 'London', 'San Francisco', 'Amsterdam', 'Hong Kong'],
nameFilter: ''
};
},
handleFilterUpdate: function(filterValue) {
this.setState({
nameFilter: filterValue
});
},
render: function() {
var displayedItems = this.state.listItems.filter(function(item) {
var match = item.toLowerCase().indexOf(this.state.nameFilter.toLowerCase());
return (match !== -1);
}.bind(this));

var content;
if (displayedItems.length > 0) {
var items = displayedItems.map(function(item) {
return <li>{item}</li>;
});
content = <ul>{items}</ul>
} else {
content = <p>No items matching this filter</p>;
}

return (
<div>
<Filters updateFilter={this.handleFilterUpdate} />
<h4>Results</h4>
{content}
</div>
);
}
});

React.renderComponent(<List />, document.body);


Scenario #2

Similar to scenario #1, but the parent component will be the one passing down the handler function to <Filters />, and will pass the filtered list to <List />. I like this method better since it decouples the <List /> from the <Filters />.

JSFiddle for #2 →

/** @jsx React.DOM */

var Filters = React.createClass({
handleFilterChange: function() {
var value = this.refs.filterInput.getDOMNode().value;
this.props.updateFilter(value);
},
render: function() {
return <input type="text" ref="filterInput" onChange={this.handleFilterChange} placeholder="Filter" />;
}
});

var List = React.createClass({
render: function() {
var content;
if (this.props.items.length > 0) {
var items = this.props.items.map(function(item) {
return <li>{item}</li>;
});
content = <ul>{items}</ul>
} else {
content = <p>No items matching this filter</p>;
}
return (
<div className="results">
<h4>Results</h4>
{content}
</div>
);
}
});

var ListContainer = React.createClass({
getInitialState: function() {
return {
listItems: ['Chicago', 'New York', 'Tokyo', 'London', 'San Francisco', 'Amsterdam', 'Hong Kong'],
nameFilter: ''
};
},
handleFilterUpdate: function(filterValue) {
this.setState({
nameFilter: filterValue
});
},
render: function() {
var displayedItems = this.state.listItems.filter(function(item) {
var match = item.toLowerCase().indexOf(this.state.nameFilter.toLowerCase());
return (match !== -1);
}.bind(this));

return (
<div>
<Filters updateFilter={this.handleFilterUpdate} />
<List items={displayedItems} />
</div>
);
}
});

React.renderComponent(<ListContainer />, document.body);


Scenario #3

When the components can't communicate between any sort of parent-child relationship, the documentation recommends setting up a global event system.

How to communicate between React components which do not share a parent?

In React, data is supposed to flow in only one direction, from parent component to child component. Without getting into context/redux, this means keeping common state in a common ancestor of the components that need it and passing it down through props.

Your useEffect() idea isn't horrible as a kind of ad hoc solution, but I would not make PageTitle a react component, because setting the value imperatively from another component really breaks the react model.

I've used useEffect() to set things on elements that aren't in react, like the document title and body classes, as in the following code:

const siteVersion = /*value from somewhere else*/;
//...

useEffect(() => {

//put a class on body that identifies the site version
const $ = window.jQuery;
if(siteVersion && !$('body').hasClass(`site-version-${siteVersion}`)) {
$('body').addClass(`site-version-${siteVersion}`);
}

document.title = `Current Site: ${siteVersion}`;

}, [siteVersion]);

In your case, you can treat the span in a similar way, as something outside the scope of react.

Note that the second argument to useEffect() is a list of dependencies, so that useEffect() only runs whenever one or more changes.

Another side issue is that you need to guard against XSS (cross site scripting) attacks in code like this:

//setting innerHTML to an unencoded user value is dangerous
document.getElementsByClassName('page-title')[0].innerHTML = inputProps.value;

Edit:

If you want to be even more tidy and react-y, you could pass a function to your input component that sets the PageTitle:

const setPageTitle = (newTitle) => {
//TODO: fix XSS problem
document.getElementsByClassName('page-title')[0].innerHTML = newTitle;
};

ReactDOM.render(
<Main setPageTitle={setPageTitle} />,
document.getElementById('react-app')
);

//inside Main:
function Input({setPageTitle}) {
const inputProps = useInput("");
useEffect(() => {
setPageTitle(inputProps.value);
})
return (
<div>
<h3>React asks what shall we name this product?</h3>
<input
placeholder="Type here"
{...inputProps}
/>
</div>
);
}

How can I make two components communicate in React?

You can do that via 2 ways

1 - props to communicate between them

class Calculator extends React.Component {
this.state ={
num1:1,
num2:2
}
render() {
return <Sum num1={this.state.num1} num2={this.state.num2}>;
}
}

const sum =(props)=>{
return (
<div>
{props.num1+props.num2}
</div>
);
}

2 - You can use any State management solution for react (but only if its a complex app)

Communicate between React Components

You need then the state in a parent component which will control your buttons. You can do something like :

Button component, where you pass onClick prop and active prop

class Button extends React.Component {
render(){
let disabled = this.props.active ? "disabled" : "";
return(
<div><button onClick={this.props.onClick} disabled={disabled}>Button</button></div>
)
}
}

Then in your parent you need to have a state which you will pass to the button components and onClick function:

class Test extends React.Component {
constructor(props){
super(props);

this.state = {
active: false
}
}

handleClick(event){
this.setState({active: !this.state.active});
}

render(){
return (
<div>
<Button onClick={this.handleClick.bind(this)}/>
<Button active={this.state.active}/>
</div>
)
}
}

React.render(<Test />, document.getElementById('container'));

Here is a fiddle.

Hope this helps.

In React, Is there a way to communicate between two components in both sides (duplex) using React Context API?

Yes, you can do that. If you were using props, two way communication would be achieved by a parent component passing down both some data and a function. The child component uses the function to communicate back to the parent. The same thing can be done with context, only now the components aren't direct parent and child.

export const ExampleContext = React.createContext();

const ExampleProvider = (props) => {
const state = useState('something');

return (
<ExampleContext.Provider value={state}>
{props.children}
</ExampleContext.Provider>
)
}

const ExampleConsumer = (props) => {
const [value, setValue] = useContext(ExampleContext);

// Do something with the value, or call setValue to let the provider know it needs to update.
}

Two way communication between two functional React JS components

In React, the data flows down, so you'd better hold state data in a stateful component that renders presentation components.

function ListItem({ description, price, selected, select }) {
return (
<li className={"ListItem" + (selected ? " selected" : "")}>
<span>{description}</span>
<span>${price}</span>
<button onClick={select}>{selected ? "Selected" : "Select"}</button>
</li>
);
}

function List({ children }) {
return <ul className="List">{children}</ul>;
}

function Content({ items }) {
const [selectedId, setSelectedId] = React.useState("");
const createClickHandler = React.useCallback(
id => () => setSelectedId(id),
[]
);

return (
<List>
{items
.sort(({ price: a }, { price: b }) => a - b)
.map(item => (
<ListItem
key={item.id}
{...item}
selected={item.id === selectedId}
select={createClickHandler(item.id)}
/>
))}
</List>
);
}

function App() {
const items = [
{ id: 1, description: "#1 Description", price: 17 },
{ id: 2, description: "#2 Description", price: 13 },
{ id: 3, description: "#3 Description", price: 19 }
];

return (
<div className="App">
<Content items={items} />
</div>
);
}

ReactDOM.render(
<App />,
document.getElementById("root")
);
.App {
font-family: sans-serif;
}
.List > .ListItem {
margin: 5px;
}
.ListItem {
padding: 10px;
}
.ListItem > * {
margin: 0 5px;
}
.ListItem:hover {
background-color: lightgray;
}
.ListItem.selected {
background-color: darkgray;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>

Is it possible to directly communicate between 2 React child components instead of going over their common parent

Using pure React, your request is not possible to achieve. You can try to use Context API. However Context works the same as a state but without sending value node by node. I think you can use Redux for your purpose since it only updates when mapDispatchToProps changes.

How to communicate between sibling components in React

Keep the state in the App component and give setter to Input and state to Preview component.

App.js

import Input from "./Input";
import Preview from "./Preview";

export default function App() {
const [state, setState] = useState({ name: "", job: "" });
return (
<div className="App">
<Input setState={setState} />
<Preview state={state} />
</div>
);
}

Input.js

import React from "react";

const Input = ({ setState }) => {
const onChangeHandler = (e) => {
setState((prevState) => ({
...prevState,
[e.target.id]: e.target.value
}));
};

return (
<>
<label htmlFor="name">Full Name</label>
<input
type="text"
id="name"
placeholder="TheDareback"
onChange={onChangeHandler}
/>
<label htmlFor="job">Job Title</label>
<input
type="text"
id="job"
placeholder="Frontend Developer"
onChange={onChangeHandler}
/>
</>
);
};

export default Input;

Preview.js

import React from "react";

const Preview = ({ state: { name, job } }) => {
return (
<>
<table>
<thead>
<tr>
<th>{name}</th>
<th>{job}</th>
</tr>
</thead>
</table>
</>
);
};

export default Preview;

Code Sandbox

How to manage communication between to independent react components?

Thinking in React is a great tutorial explaining state ownership in React and how components communicate. Once you get comfortable with it, you can try to implement this in vanilla React.

When you are comfortable in React but notice that some of your components get bloated because of all the state handling logic, you might want to check out a Flux implementation like Redux to offload the state handling to it. Getting Started with Redux is a set of free introductory videos to using Redux with React, and they are complemented well by the official basics tutorial.

But don’t rush into Flux or Redux until you understand React.



Related Topics



Leave a reply



Submit