Designing your application using a component-based architecture is a first step towards a nice decoupled design. But with great power comes great complexity! Components might need to interact in different ways in order to achieve some common goals. How can components communicate with each other? In this post, I am going to show how to make components communicate by using the redux pattern.
Code outlined in this article can be found on GitHub.
State in React's components
In the previous post, I blogged about React's components and how to manage state internally in a component. Although working with state within a component is great, it is limiting as well, as this state is only local, it does not travel to other components within the same application. In some cases this is fine, but in other scenarios, like in more complex applications, communication is vital. Components might want to react on actions that are sourced from other component state changes.
There are various ways to support component communication, but I would like to talk about the Redux pattern in this post.
Redux is pattern, based on the Flux pattern that Facebook introduced. Let me quickly introduce you to the gist of these patterns by first exploring quickly the Flux and then the Redux patterns. (disclaimer: image on Flux comes from the official Facebook docs page).
Intent: A unidirectional data flow pattern to notify state changes in component-based architecture applications.
[caption id="" align="alignnone" width="1300"] Image 1 - Flux pattern[/caption]
- Dispatcher. Receives and emits actions to stores.
- Store. Receives actions, which mutate state, which in turn update the controller view.
- Action. Indicates data/state change.
- View. Receives state/data changes that render in UI, but also emits actions which update data/state based on UI interactions.
There is a central dispatcher for the application. That dispatcher receives actions, which might come from a view or might come from other entities like a service or a timer. Dispatcher is then broadcasting actions to the stores, which might be more than one, and each store updates itself based on the incoming action. Finally, each store is updating its related controller view by emitting a change event to that view.
Redux is based on the Flux pattern I mentioned above, but it has three extra principles.
- Store is now a singleton, the single source of truth.
- State is immutable.
- Changes are made with pure functions.
[caption id="attachment_1071" align="alignnone" width="750"] Image 2 - Redux pattern[/caption]
- Store. The singular source of truth, it receives actions from dispatchers, uses reducers internally to change state and also emits data/state changes to controller views.
- Action dispatchers. This is a dispatcher that listens to view events or receives actions outside a view, like a push notification or the timer API, etc. It dispatches events to the store, which contain data & action types, with the latter helping in identifying the state change that should occur.
- Reducer. It is a pure function, which receives the state and the current action and provides as output the new state or if the action type does not match the reducer type, the current state, untouched.
- View. Receives state/data changes that render in UI, but also uses action dispatchers to dispatch events back to the store to indicate data/state changes.
Store now is the single source of truth, only one store exists for the application. This store can receive various actions which are dispatched by action creators. One or more reducers are used by the store to create a new state, while finally, the view gets its state updated via the store.
State cannot be mutated by views or anything else except the store itself. For this reason, in order to update state you need to dispatch a new event to the store and never assign to the state stored in store directly.
Finally, state is updated within the store by a reducer, which in turn is a pure function. Be advised that pure functions do not cause any side effect, which in simple words means that it will produce an output based on the input it gets without changing the input.
For React, Dan Abramov created the Redux library that provides an implementation on the Redux pattern along with a set of helpful APIs. Please be advised, that this approach is not lean and involves complexity, so be careful and evaluate your application complexity on how redux will be able to improve the situation. If your application is relatively simple, redux might not be the way to go. Check this section from the official redux website for more information on where redux might be useful.
First I will start by downloading the redux npm package
npm i react-redux --save
That's it, I don't need to change webpack, or anything else, only my React code. Let's deep dive into the changes, by first exploring the store.
Let's first define the state that the store is going to manage and the action types that it is going to allow. If you have been following along, in the previous post, I had created a single component, that had one textbox. Based on user's input in that textbox, another area within the view was being updated, by receiving state changes and rendering state in the view. So, pretty much the application's state here is just a string, the input that the user types in the textbox. I can name the action type whatever I want, in this case I will name it
As discussed earlier about the Redux pattern, I need a reducer for my store. I am going to create a reducer that is going to receive a state in a string form (the textbox content) and the action type I mentioned above. Then I will create my store using this reducer.
Pretty much what I described earlier. When the action type matches the state changes to this action value and when it does not match, state returns untouched.
I will use that store throughout my application components.
Now that I have the store, it is time to actually use it. I import the store in the
main.js file, in which I subscribe the main render function with the store. I am also passing the store reference to the
App component, in order for the underlying components to be able to get the state or dispatch a new action.
Compared to the previous post, this application design is more granular as I have separated the concerns by defining more components. But now that I have defined different components for the form and for the input content that is displayed, I have to find a way to make these components to communicate.
Separating concerns with components
The App component is the root component for the application. But it gets more granular as App uses the
FormContainer, as the name implies, it serves as a container component, which in turn is encapsulating logic within the child
Remember that in
main.js, I passed a store reference to the
App component in order to pass this further to child components that might depend on it. I am getting the store reference from the props object and I pass it to the
Greet components respectively. First is using the store to dispatch state changes, while the latter is retrieving the state from the store to render in the view.
This is a very simple component, it just receives a string as input and it renders it within a bootstrap jumbotron.
This is a simple container component, it is used to contain the NameForm component, which does all the heavy lifting.
A container is what it says, it is a component that contains other components.
Why you might want to have a container component?
- Separate rendering and business/data concerns.
- Being more granular, so child components can be reused.
This is the place where I am using a dispatcher to dispatch the
NAME_CHANGE action type to the store.
Take a look in the following code. I have an
<input> element, which sets its value by using the value fetched from the store.
When the input content changes by some user interaction, the
onChange event handler fires. In this case, I have defined a simple method that calls the
dispatch method of the store and I pass a simple object with type as
NAME_CHANGE and value as the input current text value. The
type property is required.
So every time a user enters or deletes any character in the textbox, store will be notified for this action and it will change the state. This is going to affect other components that are interested in that particular state, like the
This component renders the textbox contents, so it is technically listening for changes over the store's state.
I am just fetching the state from the store and passing it to the view to render, as you might notice in the
render method, using the
Below you can see everything in action, it is working exactly like before in the previous post.
[video width="1920" height="1080" mp4="https://georgedyrra.files.wordpress.com/2018/05/20180505_202931trim.mp4" autoplay="true" loop="true"][/video]
Although this code seems to work and the problem of component communication seems to be weathered, I really don't like the fact that component's know about the store. They shouldn't even care, yet it is passed as dependency. In next post I will try to find a way to eliminate this dependency from the component's and make code less cluttered.
In this post you saw what is the Redux pattern and how you can use the React Redux library in your application to make components communicate.
You might need to take a bit more in depth look in Redux, learn more about the library and the use cases that it can be incorporated into. Redux is not recommended to be used in simple scenarios. Take a look at Dan Abramov's Egghead free course for more information on react redux.
If you liked this blog post and found it useful, please like, share & subscribe for more! Also, if not yet a follower, follow me on Twitter!
This post is part of the Learning React series
Comments powered by Disqus.