As we've seen, React provides patterns for communicating up and down the component hierarchy. Since all components exist in this hierarchy, this is natural and effective. There are some cases, however, when data sent through props might not be the best option for communicating between components. For these cases, React provides a mechanism called context. The simplest data flow direction is down the hierarchy, from parent to child. React's mechanism for accomplishing this is called props. A React component is a function that receives a parameter called props. Props is a bag of data, an object that can contain any number of fields.
1function BookList() {
2 const list = [
3 { title: 'A Christmas Carol', author: 'Charles Dickens' },
4 { title: 'The Mansion', author: 'Henry Van Dyke' },
5 // ...
6 ]
7
8 return (
9 <ul>
10 {list.map((book, i) => <Book title={book.title} author={book.author} key={i} />)}
11 </ul>
12 )
13}
1function Book(props) {
2 return (
3 <li>
4 <h2>{props.title</h2>
5 <div>{props.author}</div>
6 </li>
7 )
8}
1function BookTitle(props) {
2 return (
3 <label>
4 Title:
5 <input onChange={props.onTitleChange} value={props.title} />
6 </label>
7 )
8}
1const ThemeContext = React.createContext('dark')
2
3function BookPage() {
4 return (
5 <ThemeContext.Provider value="light">
6 <BookList />
7 </ThemeContext.Provider>
8 )
9}
1import React, { useContext } from 'react'
2
3function Book(props) {
4 const theme = useContext(ThemeContext)
5 const styles = {
6 dark: { background: 'black', color: 'white' },
7 light: { background: 'white', color: 'black' }
8 }
9 return (
10 <li style={styles[theme]}>
11 <h2>{props.title</h2>
12 <div>{props.author}</div>
13 </li>
14 )
15}
To communicate between two independent components in React, you have the flexibility to set up a global event-driven system, or a PubSub system. An event bus implements the PubSub pattern and allows you to listen and dispatch events from components. The event bus helps you to decouple your components and pass data using events fired from other components so that they don't have direct dependencies between each other. React uses props to communicate between dependent components. Still, there are instances when you might want to communicate between two independent components that don't have a common functionality to share data. In this guide, you will learn how to communicate between independent components using an event bus. Now that you have created a custom event bus, it's time to use it to pass data between the components.
1
const eventBus = {
2 on(event, callback) {
3 // ...
4
},
5 dispatch(event, data) {
6 // ...
7
},
8 remove(event, callback) {
9 // ...
10
},
11
};
1 // ...
2 on(event, callback) {
3 document.addEventListener(event, (e) => callback(e.detail));
4
},
5 // ...
1 // ...
2 dispatch(event, data) {
3 document.dispatchEvent(new CustomEvent(event, {
detail: data
}));
4
},
5 // ...
1// Coupon Component
2
3import eventBus from "./EventBus";
4
5class Coupon extends Component {
6 constructor(props) {
7 super(props);
8 this.state = {
9 couponCode: "",
10 };
11 }
12
13 applyCoupon = () => {
14 console.log("applying");
15 eventBus.dispatch("couponApply", { message: "coupone applied" });
16 };
17
18 render() {
19 return (
20 <div>
21 <input
22 value={this.state.couponCode}
23 onChange={(e) => this.setState({ couponCode: e.target.value })}
24 />
25 <button onClick={this.applyCoupon}>Apply Coupon</button>
26 </div>
27 );
28 }
29}
1// Message Component
2
3import eventBus from "./EventBus";
4
5class Message extends Component {
6 constructor(props) {
7 super(props);
8 this.state = {
9 message: "",
10 };
11 }
12
13 componentDidMount() {
14 eventBus.on("couponApply", (data) =>
15 this.setState({ message: data.message })
16 );
17 }
18
19 componentWillUnmount() {
20 eventBus.remove("couponApply");
21 }
22
23 render() {
24 return <div>{this.state.message}</div>;
25 }
26}
When the components can't communicate between any sort of parent-child relationship, the documentation recommends setting up a global event system. Extending answer of @MichaelLaCroix when a scenario is that the components can't communicate between any sort of parent-child relationship, the documentation recommends setting up a global event system. For communication between two components that don't have a parent-child relationship, you can set up your own global event system. Subscribe to events in componentDidMount(), unsubscribe in componentWillUnmount(), and call setState() when you receive an event.
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.
/** @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);
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 />
.
/** @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);
const Child = ({fromChildToParentCallback}) => (
<div onClick={() => fromChildToParentCallback(42)}>
Click me
</div>
);
class Parent extends React.Component {
receiveChildValue = (value) => {
console.log("Parent received value from child: " + value); // value is 42
};
render() {
return (
<Child fromChildToParentCallback={this.receiveChildValue}/>
)
}
}
In this case you can use a "portal". There are different react libraries using portals, usually used for modals, popups, tooltips...
Consider the following:
<div className="a">
a content
<Portal target="body">
<div className="b">
b content
</div>
</Portal>
</div>
Could produce the following DOM when rendered inside reactAppContainer
:
<body>
<div id="reactAppContainer">
<div className="a">
a content
</div>
</div>
<div className="b">
b content
</div>
</body>
For child-parent communication: Say your GroceryList component has a list of items generated through an array. When a list item is clicked, you want to display its name: For parent-child communication, simply pass props. For communication between two components that don't have a parent-child relationship, you can set up your own global event system. Subscribe to events in componentDidMount(), unsubscribe in componentWillUnmount(), and call setState() when you receive an event. Flux pattern is one of the possible ways to arrange this. Communicate Between Components
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>
);
}
});
ReactDOM.render(
<GroceryList items={['Apple', 'Banana', 'Cranberry']} />, mountNode
);
React Native is inspired by React, so the basic idea of the information flow is similar. The flow in React is one-directional. We maintain a hierarchy of components, in which each component depends only on its parent and its own internal state. We do this with properties: data is passed from a parent to its children in a top-down manner. If an ancestor component relies on the state of its descendant, one should pass down a callback to be used by the descendant to update the ancestor. You can pass properties down to the React Native app by providing a custom implementation of ReactActivityDelegate in your main activity. This implementation should override getLaunchOptions to return a Bundle with the desired properties. The problem exposing properties of native components is covered in detail in this article. In short, properties that are to be reflected in JavaScript needs to be exposed as setter method annotated with @ReactProp, then use them in React Native as if the component was an ordinary React Native component.
public class MainActivity extends ReactActivity { @Override protected ReactActivityDelegate createReactActivityDelegate() { return new ReactActivityDelegate(this, getMainComponentName()) { @Override protected Bundle getLaunchOptions() { Bundle initialProperties = new Bundle(); ArrayList<String> imageList = new ArrayList<String>(Arrays.asList( "http://foo.com/bar1.png", "http://foo.com/bar2.png" )); initialProperties.putStringArrayList("images", imageList); return initialProperties; } }; }}
class MainActivity: ReactActivity() {
override fun createReactActivityDelegate(): ReactActivityDelegate {
return object: ReactActivityDelegate(this, mainComponentName) {
override fun getLaunchOptions(): Bundle {
val imageList = arrayListOf("http://foo.com/bar1.png", "http://foo.com/bar2.png") val initialProperties = Bundle().apply {
putStringArrayList("images", imageList)
}
return initialProperties
}
}
}
}
import React from 'react';import { View, Image } from 'react-native';export default class ImageBrowserApp extends React.Component { renderImage(imgURI) { return <Image source={{ uri: imgURI }} />; } render() { return <View>{this.props.images.map(this.renderImage)}</View>; }}
var updatedProps: Bundle = reactRootView.getAppProperties() var imageList = arrayListOf("http://foo.com/bar3.png", "http://foo.com/bar4.png")