State. That’s one word behind Redux’s existence in the React framework. But wait a minute what is the state of an application, and why does it matter in single-page applications(SPA). Oh, single-page applications? What are those too? Let’s back it up a bit and take it one step at a time.
This guide assumes you already have a fundamental understanding of React and Redux architecture and API. However, if the opposite is the case you can check out the Redux documentation here and the React documentation here, as we would focus more on understanding what happens during React-Redux data flow.
In the course of this article, we would discuss what single-applications are and what state means in a React context, as sort of a refresher course. Next, we would dive into explaining how Redux plays a role in react, breaking down the various parts that make up the React-Redux flow, then cementing this explanation with various code examples.
If you consider the introductory sections of the article quite long and want to quickly head over to the juicy part, you can simply jump over to the subheading titled — Understanding the way Redux works, else stick with me to get a full understanding.
A single-page application is an app that works inside a browser and doesn’t require the user to reload the page because they interact with the user dynamically, rewriting the content of a web page with the help of JavaScript.
They are faster than the traditional web applications because they execute logic directly in the web browser rather than the server. After the initial page load, only data is sent back and forth rather than the whole HTML, this helps to reduce bandwidth. Single-page apps are generally created with javascript frameworks like React.JS, Angular.JS, Vue.JS, Ember.js, and many more.
Despite the afore-mentioned cons the benefits of a Single Page Application can’t be overlooked especially when one takes cognizance of the fact that it is used by the likes of Facebook, Twitter, GitHub, Google, Gmail, Maps and a host of others.
That being said React is one of the most popular SPA frameworks out there. Which brings us to our first question. What is the state in React, and why does it matter?
The state can also be referred to as the store in terms of SPAs as seen in the picture above. However, we can define the state of an application as the representation of every piece of data that changes in an application. The state comprises of immutable objects.
The immutability of an application’s state comes with several benefits. An immutable value or object cannot be changed, so every update will create a new value, leaving the old one untouched. For example, if an application’s state is immutable, one can simply save all the state’s objects in a single store to easily implement undo/redo functionality. Think Git version control system. Here is a code example of immutability:
//Here we can't assign the named instance to a new object,
//but we can change the object it is assigned to. This object is mutable.
const obj = {a: 1, b: 2}
obj.a = 3
console.log(obj.a) // 3
//But if we use Javascript's Object.freeze() method we gain immutablity.
const obj = {a: 1, b: 2}
Object.freeze(obj) // This makes the object immutable.
obj.a = 3 //
console.log(obj.a) // 1
Immutability also has other advantages which include avoiding unexpected side effects or reducing coupling.
Going forward, simply keep in mind that one essential quality of state is immutability.
In the context of React, the state of a component is the “heart” of that component. Similar to the role of the literal heart of an organism, it is what determines how the component will behave and what it renders. It is what makes a component dynamic, and as the word; dynamic implies it simply means the component can take on different shapes as dictated by the user.
Now let’s not get bored down with many explanations let’s use an analogy. We will use a common object — water, pretty well known right? Now to a quick question — what is the difference between water, ice, and steam? If you answered — state, you are right!
Another question, what gives water its state?
Temperature? Exactly!
It is a unit that determines the state at which water exists. So knowing temperature can be measured with a thermometer, the difference between ice and water is to get the thermometer reading at or below 0°C, probably by putting a container of water in a deep freezer then you have a block of ice.
Want to change it to water? Simply get it out of the deep freezer and put it on a heater, you get back your water. Want to take it a step further, leave it on the heater a little while longer and you would have steam escaping the heating container.
What happened was as the temperature of the water changed the state also changed. Pretty straightforward analogy right?
Here is a code example below to drive home the point:
Notice that as we toggle the slider from zero(0) unit to its maximum value which is one hundred(100) units, the state of water changed from solid, then to liquid, and finally gaseous above 100 units of temperature.
This is what happened to the state of the React component as seen in the code snippet. This shows that by changing the state of a component you change the way it behaves and ultimately what it returns or renders, pretty cool right?
In its own words, Redux can be defined as:
…a predictable state container for JavaScript applications. It helps you write applications that behave consistently, run in different environments (client, server, and native), and are easy to test.
Hence as stated at the beginning of this article, the management of the state is the role Redux plays in React. Just state? One could ask. Oh, true it’s simply state management, but don’t get quickly disappointed because the scope and dimension state could take can go from simple to complex as the application grows in magnitude.
It’s like planting a seed by the roadside and a few years later when it becomes a large tree you regret not planting it in a park where it would be able to grow to whatever size.
Hence, it’s good practice to start a project as you mean to go on, by using the right state management tool so however complex your application gets you will have it under control, under one state. Oh, what is one state?
One state means a single source of truth for the entire application. It sounds intriguing, doesn’t it? Just one source of truth would make an application no matter how complex more manageable. Hence the state must be immutable and if there is to be a change it should completely remove the previous state and bring in a new state.
So the Redux store is the single source of truth for every React component. This makes things way easier than letting each component manage its state. Hey but to appreciate Redux better, let’s analyze what could happen if each component maintains its state, we’ll do that with an example.
In React to share data among siblings, a state has to live in the parent component, and then a method to update the state will exist in the parent component and be passed down to the sibling components as props.
So even if the parent component doesn’t need the state it is passing down it would still hold this state. Doesn’t that sound rather superfluous and redundant? Let’s use a simple authentication example to illustrate further.
// Auth is a parent component.
const Auth = () => {
const [isLogged, setLogin] = useState(false)
// It holds the state for it's two children components despite not needing the state itself.
// This is the method used to update the state when an action is carried out in a sibling component.
const setLogin = (username, password) => {
if (username === user && password === pass) {
setLogin(true)
}
}
return (
<div>
// Here the state is passed down to the child component as a props and
//updated when an action is carried out in the second child component.
<Status status={isLogged}/>
<Login handleLogin={setLogin}/>
// This child commponent carries out the action that changes the state,
// receiving setLogin method as a props.
</div>
)
}
However, if you recall, the parent component doesn’t need the state. But because the children components need to share data it has to hold the state for them.
This might not be a big deal for small applications but like we mentioned before if the application grows in size you begin to face challenges like what to do when components are far apart in the component tree and a state has to be shared between them.
You would have to pass the state from one component to the next until it gets to where it is needed. Phew, that got terrifyingly complex pretty fast. Hence it is clear that state management could get messy as the application grows. Enter Redux.
Redux eliminates the need to pass data through components that don’t need them, by connecting a component directly to the state shared by the entire application.
It’s pretty straightforward the way Redux works. There is a central store that serves as the heart of the entire application. Each component has access to the store hence no need to trickle state down from component to component as props.
Redux consists of three parts — actions, store, and reducers. We will briefly highlight each of these parts explaining the role each plays in Redux. This would prove to be important because you would get a firm understanding of the way Redux works and how to use it.
They are created via action creators and are simply events. They are the only way you can change the data in the Redux store. These could be data resulting from API calls, user interactions with your application, or form submissions.
Actions are sent or dispatched using the store.dispatch() method. They are plain javascript objects, containing two properties; a type property and a payload property. The type indicates the type of action to be carried out, while the payload contains the data to be stored.
Let’s use an analogy of an action that can be carried out when changing the state of water to solid, funny analogy but walk with me:
{
type: 'CHANGE TO SOLID',
payload: {
state: solid
}
}
Now here is the action creator that created the above action:
const changeToIce = (solid) => ({
type: 'CHANGE TO SOLID',
payload: {
state: solid
}
})
As stated you only have two properties in the action returned by the creator — type and payload.
Put simply reducers are pure functions that take the previous state of the application then perform an action and finally return a new state. Being pure functions they can be trusted to return the same result when passed the same input without any associated side effects.
So we could say reducers are more like the engine room of Redux, like the regular engine of a car, they take fuel(previous state), perform a combustion action(reduction) and return motion(a new state). Funny analogy but you get the point.
Here is a reducer function to change the state of water:
const waterReducer = (state = initialState, action) => {
switch (action.type) {
case 'CHANGE TO SOLID':
return {...state, action.payload }; //Here action.payload will be solid.
case 'CHANGE TO LIQUID':
return {...state, action.payload}; //Here action.payload will be liquid.
default:
return state // This will return the default state which we would set as gas,
// if any of the above cases don't checkout.
}
}
As seen here the reducer has two parameters, an initial or default state as the first parameter and the action object as the second parameter.
The store is responsible for holding the state of the application. There is just one store in Redux, hence one source of truth. Apart from holding the application state, the store has other responsibilities, which include; allowing access to the state via ‘store.getState()’, updating the state via ‘store.dispatch(action)’, registering and registering from listeners via ‘store.subscribe()’.
Every action that gets dispatched to the store must return a new state, making the store predictable. To create a store from our water reducer is just a single line:
const store = createStore(waterReducer)
But what if we have multiple reducers? Then we could combine them using the ‘combineReducers()’ method in Redux.
Now we have analyzed the three parts of Redux, let’s understand the data flow by building a project. In this project, we would create a library app using React and Redux. Here is the final state of what we would build:
We would go over every step in the building process, in the course of it analyze how data flows from React components to the various parts of Redux and before it finally evokes a change in the state of the Redux store.
We start by structuring our app, arranging components properly in the right directory structure ensures proper management and clean modular code structure. We create four folders — components, containers, actions, and reducers. The components folder would house our child components. The containers folder will house our parent components.
While the actions folder will contain our ‘index.js’ file which has all the action creators our application would need. Then finally the reducers folder would contain all our Redux reducers.
First, we define the actions we would like our users to have. We want a user to be able to create a book, then filter books, and finally also can delete a book if they so desire, bringing us to a total of three actions with corresponding action creators. Next, we define our reducers, we can dispatch our create and delete book actions to a single reducer, so we would call the reducer; book.
Finally, for components, we would have book and filter components, and for containers, we would have ‘book list’ and ‘book form’ containers.
We start by defining the actions in the ‘index.js’ file under the actions folder:
// actions/index.js
const ADDBOOK = book => ({
type: "ADD BOOK",
book
});
const REMOVEBOOK = book => ({
type: "DELETE BOOK",
book
});
const FILTER = filter => ({
type: "FILTER",
filter
});
export { ADDBOOK, REMOVEBOOK, FILTER };
Next, we define our reducers:
//reducers/book.js
const initialState = [
{
name: "The Intelligent Investor",
author: "Ben Graham"
},
{
name: "Harry Potter and the Philosopher's Stone",
author: "J. K. Rowling"
}
];
const addBook = (state = initialState, action) => {
switch (action.type) {
case "ADD BOOK":
return [...state, action.book];
case "DELETE BOOK":
return [...state].filter(bk => bk !== action.book);
default:
return state;
}
};
export default addBook;
//reducers/filter.js
const filter = (state = "", action) => {
switch (action.type) {
case "FILTER":
return action.filter;
default:
return state;
}
};
export default filter;
Then wrap ‘App.js’ in a Provider component wrapper and create the store:
//src/index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore } from "redux";
import rootReducer from "./reducers/index";
import App from "./components/App";
const store = createStore(rootReducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
Now you will notice that after creating the store and logging ‘
store.getState()
’ to the console, it outputs a single object that contains what we would call ‘pieces of our one state’.We would add our components and containers next. First, we add the book component:
//components/book.js
import React from "react";
import PropTypes from "prop-types";
const Book = ({ bk, handleRemove }) => (
<div style={{ border: "1px solid green", padding: "5px" }}>
<h2>{bk.name}</h2>
<h3>{bk.author}</h3>
<button type="button" onClick={() => handleRemove(bk)}>
Delete
</button>
</div>
);
Book.propTypes = {
bk: PropTypes.shape({
name: PropTypes.string,
author: PropTypes.string
}).isRequired
};
export default Book;
Then add the ‘
book list
’ container afterward://containers/bookList.js
import React from "react";
import { CREATEBOOK, REMOVEBOOK, FILTER } from "../actions/index";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import Filter from "../components/filterBook";
import Book from "../components/book";
const mapStateToProps = state => ({
books: state.book,
filter: state.filter
});
const mapDispatchToProps = dispatch => ({
addBook: book => dispatch(CREATEBOOK(book)),
deleteBook: book => dispatch(REMOVEBOOK(book)),
filterInput: string => dispatch(FILTER(string))
});
const BookList = ({ filter, filterInput, books, deleteBook }) => {
const handleRemove = bk => deleteBook(bk);
const handleFiltering = books => {
if (filter !== "" && filter.split("")[filter.length - 1] !== "\\") {
const pattern = new RegExp(`${filter}`, "i");
const filtered = books.filter(book => pattern.test(book.name));
return filtered.map((bk, i) => (
<Book bk={bk} key={bk.name + i} handleRemove={handleRemove} />
));
}
return books.map((bk, i) => (
<Book bk={bk} key={bk.name + i} handleRemove={handleRemove} />
));
};
const handleFilterChange = e => {
filterInput(e.target.value);
};
return (
<div>
<Filter filter={filter} handleFilterChange={handleFilterChange} />
<h1>Books</h1>
{handleFiltering(books)}
</div>
);
};
BookList.propTypes = {
filter: PropTypes.string.isRequired,
filterInput: PropTypes.func.isRequired,
books: PropTypes.arrayOf(Object).isRequired,
deleteBook: PropTypes.func.isRequired
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(BookList);
Pretty long right? Let’s break it down. We imported the needed files and dependencies not forgetting to import the needed connect method which will help us connect our mapStateToProps and mapDispatchToProps methods because that’s the only way we can connect a container to the store. The mapStateToProps maps us to the redux store and the mapDispatchToProps connects our dispatched actions to the reducers.
Don’t get confused with the ‘handle filtering’ function it helps us ensure that when the user searches for a book we only return matching books. Hence we call the function in the return value of the booklist component.
We also have a ‘handle remove’ function, that’s for removing books and we pass it down as a prop to the book component so that clicking the delete book button dispatches an action to remove the book from the store, we will go over how this happens subsequently. We don’t forget to validate our props, to ensure we are receiving the right props.
Now let’s add our ‘
book form
’ container://containers/bookForm.js
import React from "react";
import { connect } from "react-redux";
import { ADDBOOK } from "../actions/index";
import PropTypes from "prop-types";
const mapDispatchToProps = dispatch => ({
createBook: book => dispatch(ADDBOOK(book))
});
const BookForm = ({ createBook }) => {
const handleSubmit = e => {
e.preventDefault();
const formElems = e.target.elements;
const bookName = formElems.bookName.value;
const author = formElems.author.value;
formElems.bookName.value = "";
formElems.author.value = "";
return createBook({ name: bookName, author });
};
return (
<div>
<form onSubmit={handleSubmit}>
<h1>Kindly input the book details</h1>
<label htmlFor="bookName">
Book Name
<input type="text" name="bookName" id="bookName" required />
</label>
<label htmlFor="author">
Book Author
<input type="text" name="author" id="author" required />
</label>
<input type="submit" value="Create Book" />
</form>
</div>
);
};
BookForm.propTypes = {
createBook: PropTypes.func.isRequired
};
export default connect(
null,
mapDispatchToProps
)(BookForm);
It simply helps us to add new books to the store. But how does it do this you might ask. Well, this is what happens, after submitting the form to create a new book it triggers the ‘handle submit’ function in the ‘book form’ container that dispatches the ‘ADD BOOK’ function to the book reducer as seen on the above code block, in line seven(7).
Next, we add our component — ‘filter.js’:
//components/filter.js
import React from "react";
import PropTypes from "prop-types";
const Filter = ({ filter, handleFilterChange }) => (
<div>
<h3>Search Available Books</h3>
<input value={filter} type="text" onChange={handleFilterChange} />
</div>
);
Filter.propTypes = {
filter: PropTypes.string.isRequired,
handleFilterChange: PropTypes.func.isRequired
};
export default Filter;
Filter component contains an input element which on change of its value triggers the ‘handle filter change’ function which is different from the ‘handle filtering’ function because it receives the input value and carries out a dispatch action that updates the value of filter in the Redux store, which then triggers a re-rendering of the 'book list' container because in the first line of our ‘handle filtering’ function[take note of the difference in name from ‘handle filter change’] we said that when there is a change in the value of filter it should filter the books gotten from the store and return the filtered books.
Hoping you weren’t confused, that pretty much sums up what happens during the input filtering.
Finally, let’s look into what happens when we click the ‘delete book’ button like we said before this would trigger the ‘handle delete’ function but what next happens?
Well, it next dispatches a ‘REMOVE BOOK’ action to the book reducer, which then triggers the ‘DELETE BOOK’ condition in the switch statement and then ends with filtering out the book to be removed from the state and returning a new state. This about sums up what happens in our library app.
To summarize the data life-cycle in our library app, let’s highlight the process that happens right from when you trigger an event on the component connected to the Redux store:
We’ve discussed the major features and flow in a React-Redux app, we’ve also seen how important the state of an application is and how Redux helps to protect the state of a React app by upholding certain principles that enable it to integrate seamlessly with the application and provide stable state management mechanism where the state becomes easily predictable.
One major benefit of Redux is that it gives us a way to trace the action that triggered a change in state and how it also happened. However, this doesn’t mean all your apps must use Redux. If it’s a lightweight app that you don’t plan to scale you could decide to run it with the default React state, but if the opposite is the case, at least now you hopefully have a better understanding of the way Redux works.
Want to have a better tool in your arsenal to deal with rails' vulnerability? Consider checking out my previous article; Rails Security-Eliminating CSRF and XSS vulnerabilities.
Kindly reach out to me if you have better tips or if you feel I made a mistake here i would be glad to edit this. Thanks a bunch!!!
Previously published at https://medium.com/@michgoldennathan17/a-firm-grasp-on-react-redux-flow-7cc8215d7815