Professional Documents
Culture Documents
with Redux
Daniel Merrill Follow
Jan 29, 2018 · 10 min read
. . .
Initialization
I’ll start a new project from scratch:
We’ll add the two main libraries we’ll be working with: React
Navigation and Redux. We’ll also add a few helper libraries.
I’m going to breeze over the part where I create a basic app with a
StackNavigator , a few routes with corresponding screen
Our default export will be a navigator wrapped in a component. We’ll also export the bare
StackNavigator as a named export. It’ll be clear why we’re doing this in a bit.
Our app has a feed and each item has a detail screen
What is Redux?
Redux is a library used to maintain a single “source of truth” for an
application’s data. Understanding its inner workings is beyond the
scope of this post, and if you’re already comfortable with Redux feel
free to skip this section, but I’ll give an overview for those who could
use a refresher.
The concept isn’t too complicated but requires the coordination of a few
elements. A single “store” object is created to hold whatever data we
want. To ensure that our store’s data is predictable, we aren’t allowed to
directly alter or even access it.
store.dispatch(action)
Then, we listen for these actions in a reducer function and return a new
state object based on the type of the action, payload data, and/or
previous state of the store:
Note: Never mutate old state. Always return a new object instead.
Once our store is set up, we can inject up-to-date state from the store
into any component we like using the the connect function from react-
redux. Maintaining this “single source of truth” solves many headaches
that arise in passing state around an application.
To recap:
• reducer : A function that receives the current Redux state and the
action dispatched, and returns a new state object that replaces the
current state in the store.
• connect : A higher-order function from react-redux that can wrap
one of our components. connect takes up to two arguments,
usually named mapStateToProps and mapDispatchToProps . We
use these functions to tell Redux which pieces of state to pass to
our component as props , and also to give us a convenient
reference to the store’s dispatch method. See examples here.
Check out the cartoon guides to Flux and Redux for a friendly
walkthrough of this pattern.
Dummy reducer
We’ll import our reducer into App.js and set up our Redux store:
For those using React Navigation 1.0.0 and above please follow the
additional setup steps:
https://github.com/computerjazz/ReactNavigationRedux/issues/1#issue
comment-372718115
Our navigation stack is initialized at index 0, which is the route with name ‘Feed’
Now, we can refresh our app and it won’t crash. However, tapping on a
list item no longer routes to the correct screen:
This is because React Navigation now expects us to explicitly provide
navigation state, and our current dummy reducer only returns the state
that’s already in the store. Since we defaulted our state.navigation to
initialState we’re stuck on the initial screen. Let’s go back and fix
that now.
Let’s update our reducer to return the correct navigation state. Since we
have the handy getStateForAction function that returns a navigation
state object based on the current state and the passed-in action, all we
have to do is change one line,—the return value:
1 import { Navigator } from './Navigator'
2 import { NavigationActions } from 'react-navigation'
3
4 const initialAction = { type: NavigationActions.Init }
5 const initialState = Navigator.router.getStateForAction(ini
6
7 export default (state = initialState, action) => {
8 // Our Navigator's router is now responsible for
. . .
The Payoff
So…did we just spend a lot of effort to end up back where we began?
Nope! Let’s explore some benefits of manual state management. I
would argue the main win is the ability to store state with redux-persist
and load the app in the same state we left it, but there are other
benefits too.
The following customizations feel slightly hacky to me, but I have found
various uses for them. The main takeaway here is that navigation
behavior can be thought of as the result of changes in navigation state.
Therefore, by modifying state, we modify behavior. Sounds pretty
React-y, no?
1 goToRandomItem = () => {
2 const items = ['one', 'two', 'three', 'four', 'five']
3 const rand = Math.floor(Math.random() * 5)
4 this.props.navigation.navigate('ItemDetail', { item: ite
5 }
Since the index and stack size remain the same, we no longer see an animation to a new screen.
Active check
What if we want a screen that does some work every few seconds
(make a network request, etc), but only if the user is actively viewing
that screen? It can be nice to know if a screen is active and
unfortunately React Navigation doesn’t ship with this functionality*.
We can add it in our reducer:
1 _interval = null
2
3 componentDidMount() {
4 this._interval = setInterval(this.periodicUpdate, 1000)
5 }
6
7 periodicUpdate = () => {
8 if (this.props.navigation.state.params.active) {
*At the time of writing, a PR has just been merged into React Navigation
that adds event listening and may solve this issue. Stay tuned.
. . .
Persistence
I’ve mentioned redux-persist a few times in this post, which is a
package used to save the contents of our Redux store to local storage.
Let’s go ahead and add it to our app:
to enable persistence.
Now, we can navigate around as much as we like, close the app, and
when we open it we will be right where we left off. Plus, all of our other
Redux state will be persisted too!
• With redux-persist:
https://github.com/computerjazz/ReactNavigationRedux/tree/d
m-reduxPersist
• Without redux-persist:
https://github.com/computerjazz/ReactNavigationRedux
Notes:
• If you’re getting a redbox error regarding undefined
addListeners , follow the instructions here:
https://github.com/computerjazz/ReactNavigationRedux/issues/
1#issuecomment-372718115
. . .