React Native Chat Tutorial #2 - Following the Redux pattern
This article reorganizes the previous tutorial so that we follow the Redux pattern. This may seem redundant, but Redux will help us organize our app's data flow at scale and make debugging significantly easier.
You can build great applications with React Native's various features, components and navigators.
However, as your application gets bigger and more complex, it also becomes more difficult to manage shared data across many screens.
Redux is the popular concept that helps to mitigate this problem.
We'll not deeply cover Redux in this article but you can learn from the Redux Official Documentation.
If you're not familiar with Redux, this blog article (A cartoon intro to Redux) will give you a good introduction to its improvements on Flux and give you a sense of how it organizes a data flow.
We'll use a pattern based on Redux in this tutorial.
To do this, we suggest using a folder structure based on each module's role.
There are a total of 6 modules and so we'll have 6 folders (actions, components, reducers, screens, sendbirdActions, store).
1.1. Actions ( Reference )
The 'Actions' folder has Actions and Action creators and types.
Note that the format of our Action creators follow redux-thunk to create asynchrous dispatching.
In a simple project, you can just maintain one file for all Actions. As the project scales, it will be better to have one file per screen.
- index.js: combined Actions and Action creators
- loginActions.js: Actions and Action creators for login screen.
- types.js: all types used in actions
1.2. Reducers ( Reference )
Reducers get states from Actions and return the next states. Note that Reducers don't mutate previous states.
Like Actions, we will also create one Reducer file per screen.
- index.js: combined reducers are here.
- loginReducers.js: reducers for login screen.
1.3. Store ( Reference )
- index.js: store configuration file
1.4. Screens & Components
Screens & components files render UI screens together.
- screens/*.js: layout of UI and objects, data for UI per screen
- components/*.js: shared UI components can be reused
Some actions are shared across various screens. The SendBird SDK is a great example. It shares many actions across various screens
Let's put them in one place so we can manage modules related to the SendBird SDK together.
2. Sample Application
Let's install the libraries related to redux first.
npm install --save redux react-redux redux-thunk
New the overall folder structure should look like the following image.
Let's create the sendbirdActions files first to manage the features related to the SendBird SDK.
In sendbirdActions/user.js, we will create the function, sbConnect(userId, nickname), using the SendBird functions connect() and updateCurrentUserInfo()
Then let's make an Action creator, sendbirdLogin, that calls sbConnect in loginAction.js.
Based on the result that sbConnect returns, sendbirdLogin returns different actions.
2.3. Reducers & Store
It's time to write the Reducer to pass the next state based on the user's actions on the UI containers. In reducers/index.js, we'll combine separate reducers into one as we did with actions in actions/index.js. Creating a reducer for each screen makes it easier to manage many reducers.
We should modify App.js to use the Provider component in the react-redux library which passes Store to screen containers. In store/index.js, we create Store and setup redux-thunk as middleware.
2.4. Screens & Components
Let's modify the screen container to work with redux.
In components/index.js, you can define all shared UI components to easily manage them.
In Login.js, one of the most critical changes is linking the props of the containter with redux state. Using connect from react-redux in the last line transfers the state to the props.
Now, whenever the propagated props change, the componentWillReceiveProps function is called. We've added the code that transitions the login screen to the main screen when a 'user' exists in props. This infers the login's success.
Rather than managing 'error' inside of screen containers, we instead manage 'error' as a state in Reducers because the value of 'error' can change constantly.
Now, it's time to run the new code. You should be able to get the exactly same result as we did without the redux pattern as pictured below.
2.5 Overall Flow
Let's recap the overall flow of events and actions of the sample we built
- Touch event is triggered in the Login screen.
- sendbirdLogin action creator is called to run sbConnect and returns the action.
- Store's dispatch is called with the action.
- Reducer is called and returns the next state.
- New state is mapped to props through react-redux's connect.
Understanding this flow should make it easier to plan out your work and debug issues.
In this article, we've applied the redux pattern to Login screen we built in the previous article. Folder and file structures need not be the same but the general idea should be helpful to use the redux in React Native.