How to Connect Redux to a Grandchild Component
Nov 25, 2019 • 9 Minute Read
Introduction
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.
Wrapping Root Component with
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.
// ..
import { Provider } from "react-redux";
const App = () => {
return (
<div className="App">
<Provider store={store}>// ..</Provider>
</div>
);
};
// ...
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.
// ..
import { Provider } from "react-redux";
import { createStore } from "redux";
function reducer() {
// ..
return {
count: 0
};
}
const store = createStore(reducer);
const App = () => {
return (
<div className="App">
<Provider store={store}>// ..</Provider>
</div>
);
};
// ...
Right now, the reducer is only returning the initial state of the application.
index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore } from "redux";
import "./styles.css";
import Parent from "./Parent";
const initialState = {
count: 0
};
function reducer(state = initialState) {
// ..
return {
count: state.count
};
}
const store = createStore(reducer);
const App = () => {
return (
<div className="App">
<Provider store={store}>
<Parent />
</Provider>
</div>
);
};
// ...
In a later section of this guide, we'll see how to instruct the reducer to return the current or the changed state.
Building our Components
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.
GrandChild.js
import React from "react";
import { connect } from "react-redux";
const GrandChild = props => (
<div className="grandchild-component">
<div>This is the grandchild component</div>
<div class="emp">Count: {props.count}</div>
</div>
);
const mapStateToProps = state => ({
count: state.count
});
export 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.
Child.js
The <Child /> is going to be a simple component that is only going to wrap the <GrandChild /> component.
const Child = () => (
<div className="child-component">
<div>This is the child component</div>
<Grandchild />
</div>
);
Parent.js
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.
// ...
import { connect } from "react-redux";
class Parent extends Component {
increment = () => {
// dispatch the action
};
render() {
return (
<div className="parent-component">
<div>This is the parent component</div>
<button onClick={this.increment}>Click Me!!</button>
<Child />
</div>
);
}
}
export 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.
increment = () => {
this.props.dispatch({ type: "INCREMENT" });
};
Finishing Up the Reducer
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.
const initialState = {
count: 0
};
function reducer(state = initialState, action) {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 };
default:
return {
count: state.count
};
}
}
Complete Source Code
index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore } from "redux";
import "./styles.css";
import Parent from "./Parent";
const initialState = {
count: 0
};
function reducer(state = initialState, action) {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 };
default:
return {
count: state.count
};
}
}
const store = createStore(reducer);
const App = () => {
return (
<div className="App">
<Provider store={store}>
<Parent />
</Provider>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Parent.js
import React, { Component } from "react";
import { connect } from "react-redux";
import Child from "./Child";
class Parent extends Component {
increment = () => {
this.props.dispatch({ type: "INCREMENT" });
};
render() {
return (
<div className="parent-component">
<div>This is the parent component</div>
<button onClick={this.increment}>Click Me!!</button>
<Child />
</div>
);
}
}
export default connect(null)(Parent);
Child.js
import React from "react";
import Grandchild from "./GrandChild";
const Child = () => (
<div className="child-component">
<div>This is the child component</div>
<Grandchild />
</div>
);
export default Child;
GrandChild.js
import React from "react";
import { connect } from "react-redux";
const GrandChild = props => (
<div className="grandchild-component">
<div>This is the grandchild component</div>
<div class="emp">Count: {props.count}</div>
</div>
);
const mapStateToProps = state => ({
count: state.count
});
export default connect(mapStateToProps)(GrandChild);
style.css
.App {
font-family: sans-serif;
text-align: center;
}
.parent-component {
padding: 15px;
border: 1px solid #fc3;
}
.parent-component button {
margin-top: 10px;
background: #fc3;
border: 0;
padding: 10px 18px;
color: #000;
}
.child-component {
padding: 15px;
border: 1px solid #007bff;
margin-top: 10px;
}
.grandchild-component {
padding: 15px;
border: 1px solid #f39;
margin-top: 10px;
}
.emp {
font-weight: 600;
margin-top: 15px;
color: #f39;
}
Conclusion
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.