I wrote last week about my first dive into React Native, so this will be a quasi-continuation of that project. Shortly after starting the project, I realized that I would need some kind of global state solution, particularly for authentication purposes. So for this article, I will document how to add Redux to your React Native project. When it comes to Redux, there are a few important differences with React Native as opposed to a regular React project, so let’s jump into it!
First, we’ll need to install the Redux libraries. In your project’s root directory, run npm install redux react-redux.
To implement Redux, we will need to set up a few new files. I like to keep the Redux store in a separate file for clarity. We will also need to wrap our App component in Redux’s <Provider> component, so that we can pass the store into it. For this, we will need to set up a new entry point for our app, in a file called index.js. Lastly, we will create a reducer and an action for currentUser, so that we can store the current, logged-in user to the Redux store.
Create a reducer
The reason I decided to use Redux was for authentication, so we’ll need to use the store to hold a currentUser. So our reducer will be named currentUserReducer, and stored in a file called auth.js in ./src/reducers. If we write the code we wish to have, we can say that when this reducer receives the action SET_CURRENT_USER, the payload should be a User object.
const currentUserReducer = (state = {}, action) => {
if (action.type === 'SET_CURRENT_USER') {
return (action.payload)
}
return state
}
export default currentUserReducer
Now we can use this reducer in creating our store.
Create the Redux store
For this, I made a new file called store.js in the root of my project. Note that I’m importing Thunk because I will need to make asynchronous requests, although that’s beyond the scope of this article (maybe next week!).
import {createStore, applyMiddleware, combineReducers, compose} from 'redux'
import thunk from 'redux-thunk'
import currentUserReducer from './src/reducers/auth.js'
const reducer = combineReducers({
currentUserReducer
})
const store = createStore(reducer, applyMiddleware(thunk) )
export default store
Changing Your App’s Entry Point
This is arguably the trickiest part of setting up Redux in React Native, since it works a little differently than in a normal React project.
To use Redux, you will need to wrap your application in Redux’s <Provider>, to grant it access to the store. Using a managed Expo project, the project’s structure requires you to make some changes.
In your package.json, there is a key/value pair that tells Expo where to start your app. We’re going to change it to a new file, index.js, which will live in the root directory:
// Old:
"main": "node_modules/expo/AppEntry.js",
//New:
"main": "index.js",
Now let’s create our index.js.
import React from 'react'
import App from './App.js'
import store from './store.js'
import { Provider } from 'react-redux'
import { registerRootComponent } from 'expo';
const NewRootComponent = () => {
return (
<Provider store={store}>
<App />
</Provider>
)
}
export default registerRootComponent(NewRootComponent);
You can see that we import the store so that we can pass it to the app. registerRootComponent is imported from Expo so that we can tell it what we want the root of our application to be. Then, we create a new root component so that we can wrap our entire <App> component in Redux’s <Provider>, passing the store as a prop.
Setting up an action
We’ll create one action for now, which represents the user logging in. The payload will carry a User object.
export const setCurrentUser = userObject => {
return dispatch => {
dispatch({type: 'SET_CURRENT_USER', payload: userObject})
}
}
Conditionally Rendering a Login Screen
Allowing User to log in:
In my <LoginScreen> component, I will attach this action to the Login button. The goal is that when a User is not logged in, we will display the LoginScreen, and when a User is logged in, we will display the HomeScreen.
First, let’s look at the <LoginScreen> component to see the action added to the Login button. For test purposes, we’ll hard code this action to the button press event handler handleLoginPress, so that we can be sure the rendering is working as intended.
// src/components/LoginScreen.js
import {connect} from 'react-redux'
import {setCurrentUser} from '../../../actions/currentUser.js'
export const Login = (props) => {
const handleLoginPress = () => {
props.login({first_name: 'test', email: 'test@email.com'})
}
return (
<View>
<TouchableOpacity onPress={handleLoginPress}>
<Text>log in</Text>
</TouchableOpacity>
</View>
)
}
const mapDispatchToProps = (dispatch) => {
return {
login: userObject => dispatch(setCurrentUser(userObject))
}
}
export default connect(null, mapDispatchToProps)(Login)
To conditionally render content:
Now in our <App> component, we’ll add conditional rendering based on whether or not the User is logged in:
function App(props) {
const userIsLoggedIn = !!props.currentUser
return (
<View>
{userIsLoggedIn == false ? (
<>
<LoginScreen />
</>
) : (
<>
<HomeScreen />
</>
)}
</View>
);
}
const mapStateToProps = state => {
return {
currentUser: state.currentUser
}
}
export default connect(mapStateToProps)(App)
Now when you click the Log in button, the view will change to display the HomeScreen!
For me, it was a little tricky to change my App’s entry point, but once I did, the workflow returned to the familiarity of Redux as it would be in a web app. The next step will be to connect my React Native app to a Rails back end, so stay tuned for that š
Thanks for reading, and please comment any thoughts or feedback!