Topic 11: Redux
📖 5 min read · 🎯 advanced · 🧭 Prerequisites: navigation, libraries-image-picker-react-elements
Why this matters
Up until now, every screen in your app has been managing its own data — you pass props down, lift state up, and suddenly you're doing mental gymnastics just to share one piece of information between two components. It works for small apps, but the moment your app grows, that juggling act becomes a nightmare. Redux gives your entire app a single, central place to store state — one source of truth that any screen can read from or write to. That's what we're setting up today with Redux in React Native.
What You'll Learn
- Understand the five core concepts of Redux: Store, Action, Reducer, Dispatch, and Provider
- Install and configure
reduxandreact-reduxin a React Native project - Build a Redux store with action creators and a reducer
- Connect a React Native component to the store using
useSelectoranduseDispatch
The Analogy
Think of Redux as a city hall records office. Every department (component) that needs information about the city's budget (state) must request it from the single records office (the store) — they cannot keep their own private ledger. When a department wants to change the budget, it submits an official change request (an action) to a clerk (the reducer), who applies the change and files the updated record. Every department that asked for the budget automatically sees the new number. No department holds stale data, no two departments can contradict each other, and the entire history of requests is auditable.
Chapter 1: Introduction to Redux
Redux is a predictable state container for JavaScript applications. It centralizes your entire app's state in one place, making it easy to trace how and why the state changed at any point in time.
The five key concepts:
- Store — The centralized state of the application. There is exactly one store per app.
- Action — A plain JavaScript object that describes a change in state. It must have a
typefield. - Reducer — A pure function that receives the current state and an action, and returns the next state.
- Dispatch — The method used to send an action to the store, triggering the reducer.
- Provider — A React component (from
react-redux) that makes the store available to every component in the tree.
flowchart LR
UI["UI Component"] -->|dispatch(action)| Store
Store -->|current state + action| Reducer
Reducer -->|new state| Store
Store -->|useSelector| UI
Chapter 2: Setting Up Redux in a React Native Project
Step 1: Install Redux and React-Redux
npm install redux react-redux
Step 2: Create the Redux Store
Create a new file store.js at the root of your project. This file defines the initial state, the action types, the action creators, the reducer, and finally creates the store.
store.js:
import { createStore } from 'redux';
// Initial state
const initialState = {
count: 0,
};
// Action types
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
// Action creators
export const increment = () => ({ type: INCREMENT });
export const decrement = () => ({ type: DECREMENT });
// Reducer
const reducer = (state = initialState, action) => {
switch (action.type) {
case INCREMENT:
return { ...state, count: state.count + 1 };
case DECREMENT:
return { ...state, count: state.count - 1 };
default:
return state;
}
};
// Create store
const store = createStore(reducer);
export default store;
Key observations:
- The reducer uses spread syntax (
...state) to return a new state object rather than mutating the existing one — this is mandatory in Redux. - The
defaultcase returns the current state unchanged, so the store always has a valid value. - Action creators (
increment,decrement) are exported so connected components can import them directly.
Step 3: Integrate the Store with the App
Wrap the component tree in <Provider store={store}> so every child can access the Redux store.
App.js:
import React from 'react';
import { Provider } from 'react-redux';
import { StyleSheet, View } from 'react-native';
import store from './store';
import Counter from './Counter';
export default function App() {
return (
<Provider store={store}>
<View style={styles.container}>
<Counter />
</View>
</Provider>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#fff',
},
});
Provider must wrap the entire component tree — or at least every subtree that needs store access. Placing it at the root of App.js satisfies this for most apps.
Chapter 3: Creating a Redux-Connected Component
With the store live, the class built a Counter component that reads state from the store and dispatches actions to change it.
Step 1: Create the Counter Component
Create a new file Counter.js.
Counter.js:
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { View, Text, Button, StyleSheet } from 'react-native';
import { increment, decrement } from './store';
const Counter = () => {
const count = useSelector((state) => state.count);
const dispatch = useDispatch();
return (
<View style={styles.container}>
<Text style={styles.count}>{count}</Text>
<Button title="Increment" onPress={() => dispatch(increment())} />
<Button title="Decrement" onPress={() => dispatch(decrement())} />
</View>
);
};
const styles = StyleSheet.create({
container: {
alignItems: 'center',
},
count: {
fontSize: 48,
marginBottom: 20,
},
});
export default Counter;
Two hooks do all the work:
useSelector(selector)— subscribes the component to the store. Whenever the selected slice of state changes, React re-renders the component. The selector(state) => state.countextracts just thecountfield.useDispatch()— returns the store'sdispatchfunction. Callingdispatch(increment())sends{ type: 'INCREMENT' }to the reducer.
Chapter 4: Testing the Redux Integration
Start the Expo development server and open the app in Expo Go.
expo start
Navigating to the app in Expo Go on a mobile device displays the Counter component. The Increment button increases the displayed number; the Decrement button decreases it. Both changes flow through the Redux store: dispatch → reducer → new state → useSelector re-render.
🧪 Try It Yourself
Task: Extend the counter to also track a step value in the Redux store. Add an action SET_STEP that updates a step field in state (default 1). Change the INCREMENT and DECREMENT reducers so they add/subtract step instead of 1. In Counter.js, read step from the store and display it alongside two new buttons: Step +5 and Step -5 (which dispatch setStep(step + 5) and setStep(step - 5)).
Starter snippet — add to store.js:
const SET_STEP = 'SET_STEP';
export const setStep = (value) => ({ type: SET_STEP, payload: value });
// Updated initialState
const initialState = { count: 0, step: 1 };
// Updated reducer cases
case INCREMENT:
return { ...state, count: state.count + state.step };
case DECREMENT:
return { ...state, count: state.count - state.step };
case SET_STEP:
return { ...state, step: action.payload };
Success criterion: The displayed count changes by step on each button press. Pressing Step +5 raises the step value; subsequent increments now jump by the new step. Verify in the Expo Go app.
🔍 Checkpoint Quiz
Q1. In Redux, what is the role of a reducer?
A) It sends actions to the store
B) It receives the current state and an action, and returns the next state
C) It wraps the component tree to provide store access
D) It subscribes a component to store updates
Q2. Given the following Counter.js snippet, what is rendered on screen when the component first mounts?
const count = useSelector((state) => state.count);
// initialState = { count: 0 }
<Text style={styles.count}>{count}</Text>
A) Nothing — count is undefined until an action is dispatched
B) 0
C) null
D) A runtime error because the store is not yet populated
Q3. A developer wraps only a single screen component in <Provider store={store}> instead of the entire app. What is the consequence?
A) Redux will throw an error on startup
B) Components outside the wrapped subtree cannot access the store via useSelector or useDispatch
C) There is no consequence; Provider can be placed anywhere
D) The store will be duplicated for each screen
Q4. You have a Redux store with { items: [], loading: false }. How would you write a useSelector call that returns only the items array?
A1. B — The reducer is a pure function: given the current state and an action, it computes and returns the next state without mutating the original.
A2. B — initialState sets count: 0, so on first render useSelector returns 0 and <Text> displays 0.
A3. B — Provider makes the store available only to its React subtree. Components rendered outside that subtree will throw an error when they call useSelector or useDispatch because no store context is found. Always place Provider at the root.
A4. const items = useSelector((state) => state.items); — the selector function receives the full state object and returns whichever slice you need; here state.items gives the array.
🪞 Recap
- Redux centralizes app state in a single store, eliminating scattered local state across components.
- Actions describe what happened; reducers decide how state changes in response — always returning a new object, never mutating.
<Provider store={store}>must wrap the component tree so hooks can reach the store.useSelectorsubscribes a component to a slice of state;useDispatchreturns the function to fire actions.- The
reduxandreact-reduxpackages are installed together and work as a pair.
📚 Further Reading
- Redux Official Docs — the source of truth on store setup, middleware, and Redux Toolkit
- React-Redux Hooks API — complete reference for
useSelector,useDispatch, anduseStore - Redux Toolkit — the modern, opinionated Redux setup that reduces boilerplate (PLACEHOLDER for a recommended intro article)
- ⬅️ Previous: Libraries: Image Picker & React Elements
- ➡️ Next: Firebase