Hola amigos! In this guide, we'll learn to use Redux—the popular state management library to React—to pass data from a parent component to a grandchild component.
When building web apps, you must have come across a situation where you have to pass data as props from component to component, and a few minutes later you see yourself giving props to a 99th successor component (a bit of an exaggeration, but you get the point). Well, Redux was built precisely for scenarios like these.
The first step is to wrap the root component of the application with Redux's <Provider />
component.
The <Provider />
component requires the store object. The store object holds the entire state of the application.
1// ..
2import { Provider } from "react-redux";
3
4const App = () => {
5 return (
6 <div className="App">
7 <Provider store={store}>// ..</Provider>
8 </div>
9 );
10};
11
12// ...
Now the question is, how do we create this store?
Answer: createStore()
.
The createStore()
function accepts a reducer
function and returns a Redux store object that contains the complete state tree of the application.
A reducer
is a function that returns the current state of the application.
1// ..
2import { Provider } from "react-redux";
3import { createStore } from "redux";
4
5function reducer() {
6 // ..
7 return {
8 count: 0
9 };
10}
11
12const store = createStore(reducer);
13
14const App = () => {
15 return (
16 <div className="App">
17 <Provider store={store}>// ..</Provider>
18 </div>
19 );
20};
21
22// ...
Right now, the reducer
is only returning the initial state of the application.
1import React from "react";
2import ReactDOM from "react-dom";
3import { Provider } from "react-redux";
4import { createStore } from "redux";
5
6import "./styles.css";
7
8import Parent from "./Parent";
9
10const initialState = {
11 count: 0
12};
13
14function reducer(state = initialState) {
15 // ..
16 return {
17 count: state.count
18 };
19}
20
21const store = createStore(reducer);
22
23const App = () => {
24 return (
25 <div className="App">
26 <Provider store={store}>
27 <Parent />
28 </Provider>
29 </div>
30 );
31};
32
33// ...
In a later section of this guide, we'll see how to instruct the reducer to return the current or the changed state.
For this example, we will have 3 components: <Parent />
, <Child />
and <GrandChild />
. We will create a button in the <Parent />
component and increment the counter value on button click, which will be passed to the <GrandChild />
component.
So, let's begin with the <GrandChild />
component.
1import React from "react";
2import { connect } from "react-redux";
3
4const GrandChild = props => (
5 <div className="grandchild-component">
6 <div>This is the grandchild component</div>
7 <div class="emp">Count: {props.count}</div>
8 </div>
9);
10
11const mapStateToProps = state => ({
12 count: state.count
13});
14
15export default connect(mapStateToProps)(GrandChild);
What's this weird looking connect
function that we see above?
connect()
is a higher-order function that connects the component with the Redux store. The store is where the global state of the whole application lives.
The connect()
function returns a function when we call it, and to the returned function we pass the React component, which then returns a connected component; that's why the connect
function call looks so weird.
We then export the connected <GrandChild />
component with all the necessary props.
The mapStateToProps()
function is a custom function and not something specific to Redux. Its purpose, as the name of the function suggests, is to return only the necessary data from the global state as props to the component.
The <Child />
is going to be a simple component that is only going to wrap the <GrandChild />
component.
1const Child = () => (
2 <div className="child-component">
3 <div>This is the child component</div>
4 <Grandchild />
5 </div>
6);
We are going to connect the <Parent />
component with Redux, not for accessing the store but for dispatching actions
.
An action is simply an object that contains a type
property whose value must be a string, and it signifies the purpose of the action. It can contain other optional properties like the payload or data associated with that action.
We will dispatch the INCREMENT
action on button click.
1// ...
2import { connect } from "react-redux";
3
4class Parent extends Component {
5 increment = () => {
6 // dispatch the action
7 };
8 render() {
9 return (
10 <div className="parent-component">
11 <div>This is the parent component</div>
12 <button onClick={this.increment}>Click Me!!</button>
13 <Child />
14 </div>
15 );
16 }
17}
18
19export default connect(null)(Parent);
Since this component does not need the state, we are passing null
to the connect()
function.
Along with the state, the connected components has access to the dispatch()
function, which takes the action object as an argument.
1increment = () => {
2 this.props.dispatch({ type: "INCREMENT" });
3};
We can pass action
as the second parameter to the reducer
, and based on the action type, we can return the state from the function.
1const initialState = {
2 count: 0
3};
4
5function reducer(state = initialState, action) {
6 switch (action.type) {
7 case "INCREMENT":
8 return { count: state.count + 1 };
9 default:
10 return {
11 count: state.count
12 };
13 }
14}
1import React from "react";
2import ReactDOM from "react-dom";
3import { Provider } from "react-redux";
4import { createStore } from "redux";
5
6import "./styles.css";
7
8import Parent from "./Parent";
9
10const initialState = {
11 count: 0
12};
13
14function reducer(state = initialState, action) {
15 switch (action.type) {
16 case "INCREMENT":
17 return { count: state.count + 1 };
18 default:
19 return {
20 count: state.count
21 };
22 }
23}
24
25const store = createStore(reducer);
26
27const App = () => {
28 return (
29 <div className="App">
30 <Provider store={store}>
31 <Parent />
32 </Provider>
33 </div>
34 );
35};
36
37const rootElement = document.getElementById("root");
38ReactDOM.render(<App />, rootElement);
1import React, { Component } from "react";
2import { connect } from "react-redux";
3
4import Child from "./Child";
5
6class Parent extends Component {
7 increment = () => {
8 this.props.dispatch({ type: "INCREMENT" });
9 };
10 render() {
11 return (
12 <div className="parent-component">
13 <div>This is the parent component</div>
14 <button onClick={this.increment}>Click Me!!</button>
15 <Child />
16 </div>
17 );
18 }
19}
20
21export default connect(null)(Parent);
1import React from "react";
2import Grandchild from "./GrandChild";
3
4const Child = () => (
5 <div className="child-component">
6 <div>This is the child component</div>
7 <Grandchild />
8 </div>
9);
10
11export default Child;
1import React from "react";
2import { connect } from "react-redux";
3
4const GrandChild = props => (
5 <div className="grandchild-component">
6 <div>This is the grandchild component</div>
7 <div class="emp">Count: {props.count}</div>
8 </div>
9);
10
11const mapStateToProps = state => ({
12 count: state.count
13});
14
15export default connect(mapStateToProps)(GrandChild);
1.App {
2 font-family: sans-serif;
3 text-align: center;
4}
5
6.parent-component {
7 padding: 15px;
8 border: 1px solid #fc3;
9}
10
11.parent-component button {
12 margin-top: 10px;
13 background: #fc3;
14 border: 0;
15 padding: 10px 18px;
16 color: #000;
17}
18
19.child-component {
20 padding: 15px;
21 border: 1px solid #007bff;
22 margin-top: 10px;
23}
24
25.grandchild-component {
26 padding: 15px;
27 border: 1px solid #f39;
28 margin-top: 10px;
29}
30
31.emp {
32 font-weight: 600;
33 margin-top: 15px;
34 color: #f39;
35}
The purpose of this guide was to familiarize you with Redux and the basics of state management in React applications. Complex applications won't have just one reducer or action, so the fundamentals of Redux are essential. I hope you liked this guide. Don't forget to check out my other guides on React.