Managing state in React Native can get tricky as apps grow. Counters, inputs, API calls — they all add complexity.
That’s where Redux Toolkit (RTK) shines. It removes boilerplate, enforces best practices, and keeps your app state predictable.
In this post, we’ll build a React Native app with Redux Toolkit that covers three common use cases:
- A counter state
- An input field state
- Fetching API data
🗂 Project Setup
Here’s the folder structure we’ll be working with:
src/ ┣ screens/ ┃ ┣ HomeScreen/ ┃ ┃ ┗ index.js ┃ ┗ ProfileScreen/ ┣ store/ ┃ ┣ slice/ ┃ ┃ ┣ apiSlice.js ┃ ┃ ┣ counterSlice.js ┃ ┃ ┣ InputSlice.js ┃ ┗ store.js ┗ vendor/
This structure clearly separates UI (screens) and state management (store). Each feature gets its own slice inside store/slice/
, keeping logic modular and easy to maintain.
⚡ Configuring Redux Store
In store.js
, we configure Redux Toolkit with three slices:
import { configureStore } from '@reduxjs/toolkit'; import counterReducer from './slice/counterSlice'; import inputReducer from './slice/InputSlice'; import apiReducer from './slice/apiSlice'; export const store = configureStore({ reducer: { counter: counterReducer, input: inputReducer, api: apiReducer, }, });
Now, Redux knows how to handle counter, input, and API states independently.
Counter Slice
Let’s start with a simple counter.
import { createSlice } from '@reduxjs/toolkit'; const initialState = { counterValue: 0 }; export const counterSlice = createSlice({ name: 'counter', initialState, reducers: { increment: state => { state.counterValue += 1; }, decrement: state => { state.counterValue -= 1; }, reset: state => { state.counterValue = 0; }, }, }); export const { increment, decrement, reset } = counterSlice.actions; export default counterSlice.reducer;
With just a few lines, we now have three actions: increment, decrement, and reset.
Input Slice
Now let’s handle user input:
import { createSlice } from '@reduxjs/toolkit'; const initialState = { inputValue: "Default Value" }; export const inputSlice = createSlice({ name: 'input', initialState, reducers: { changeInputValue: (state, action) => { state.inputValue = action.payload; }, }, }); export const { changeInputValue } = inputSlice.actions; export default inputSlice.reducer;
Anytime the user types something, this slice updates the Redux store with the latest value.
API Slice
Finally, let’s fetch some data from an API.
import { createSlice } from '@reduxjs/toolkit'; const initialState = { macbook: null }; const apiSlice = createSlice({ name: 'api', initialState, reducers: { setMacbook: (state, action) => { state.macbook = action.payload; }, }, }); export const { setMacbook } = apiSlice.actions; export const fetchMacbook = () => async dispatch => { const response = await fetch('https://api.restful-api.dev/objects/7'); const data = await response.json(); dispatch(setMacbook(data)); }; export default apiSlice.reducer;
When dispatched, this slice calls the API, retrieves MacBook details, and stores them in the Redux state.
Connecting Redux to React Native
In App.js
, wrap the root component with Provider to make Redux available across the app:
import React from 'react'; import HomeScreen from './src/screens/HomeScreen'; import { Provider } from 'react-redux'; import { store } from './src/store/store'; const App = () => ( <Provider store={store}> <HomeScreen /> </Provider> ); export default App;
🏡 HomeScreen Implementation
Here’s how the slices come together in the UI:
- Counter Section → Increment, decrement, reset
- Input Section → Save and display text
- API Section → Fetch and render MacBook details
HomeScreen Code
import { StyleSheet, Text, TextInput, View, ScrollView, TouchableOpacity } from 'react-native'; import React, { useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { decrement, increment, reset } from '../../store/slice/counterSlice'; import { changeInputValue } from '../../store/slice/InputSlice'; import { fetchMacbook } from '../../store/slice/apiSlice'; const HomeScreen = () => { const [value, setValue] = useState(''); const { counterValue } = useSelector(state => state.counter); const { inputValue } = useSelector(state => state.input); const { macbook } = useSelector(state => state.api); const dispatch = useDispatch(); return ( <ScrollView style={styles.scrollContainer}> <View style={styles.container}> <Text style={styles.appTitle}>🚀 Redux Toolkit Demo</Text> {/* Counter Section */} <View style={styles.smallCard}> <Text style={styles.smallCardTitle}>🔢 Counter: {counterValue}</Text> <View style={styles.buttonRow}> <TouchableOpacity style={[styles.smallButton, styles.incrementButton]} onPress={() => dispatch(increment())}> <Text style={styles.smallButtonText}>+</Text> </TouchableOpacity> <TouchableOpacity style={[styles.smallButton, styles.decrementButton]} onPress={() => dispatch(decrement())}> <Text style={styles.smallButtonText}>-</Text> </TouchableOpacity> <TouchableOpacity style={[styles.smallButton, styles.resetButton]} onPress={() => dispatch(reset())}> <Text style={styles.smallButtonText}>Reset</Text> </TouchableOpacity> </View> </View> {/* Input Section */} <View style={styles.smallCard}> <Text style={styles.smallCardTitle}>📝 Stored: {inputValue || 'None'}</Text> <View style={styles.inputRow}> <TextInput style={styles.compactInputStyle} value={value} onChangeText={setValue} placeholder="Enter text..." placeholderTextColor="#999" /> <TouchableOpacity style={[styles.smallButton, styles.saveButton]} onPress={() => { dispatch(changeInputValue(value)); setValue(''); }} > <Text style={styles.smallButtonText}>Save</Text> </TouchableOpacity> </View> </View> {/* API Section */} <View style={styles.card}> <Text style={styles.cardTitle}>🌐 API Fetch</Text> <TouchableOpacity style={[styles.button, styles.apiButton]} onPress={() => dispatch(fetchMacbook())} > <Text style={styles.buttonText}>📡 Fetch MacBook Data</Text> </TouchableOpacity> {macbook && ( <View style={styles.macbookContainer}> <Text style={styles.macbookTitle}>💻 {macbook.name}</Text> <View style={styles.specRow}> <Text style={styles.specLabel}>📅 Year:</Text> <Text style={styles.specValue}>{macbook.data.year}</Text> </View> <View style={styles.specRow}> <Text style={styles.specLabel}>💰 Price:</Text> <Text style={styles.specValue}>${macbook.data.price}</Text> </View> <View style={styles.specRow}> <Text style={styles.specLabel}>⚡ CPU:</Text> <Text style={styles.specValue}>{macbook.data['CPU model']}</Text> </View> <View style={styles.specRow}> <Text style={styles.specLabel}>💾 Storage:</Text> <Text style={styles.specValue}>{macbook.data['Hard disk size']}</Text> </View> </View> )} </View> </View> </ScrollView> ); }; export default HomeScreen; const styles = StyleSheet.create({ scrollContainer: { flex: 1, backgroundColor: '#f8f9fa', }, container: { flex: 1, padding: 20, paddingTop: 60, }, appTitle: { fontSize: 32, fontWeight: 'bold', textAlign: 'center', marginBottom: 30, color: '#2c3e50', textShadowColor: 'rgba(0,0,0,0.1)', textShadowOffset: { width: 1, height: 1 }, textShadowRadius: 2, }, card: { backgroundColor: 'white', borderRadius: 16, padding: 20, marginBottom: 20, shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.1, shadowRadius: 8, elevation: 5, }, smallCard: { backgroundColor: 'white', borderRadius: 12, padding: 15, marginBottom: 15, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.08, shadowRadius: 4, elevation: 3, }, cardTitle: { fontSize: 20, fontWeight: '700', marginBottom: 15, color: '#34495e', textAlign: 'center', }, smallCardTitle: { fontSize: 16, fontWeight: '600', marginBottom: 10, color: '#34495e', }, counterDisplay: { alignItems: 'center', marginBottom: 20, backgroundColor: '#ecf0f1', borderRadius: 50, width: 100, height: 100, justifyContent: 'center', alignSelf: 'center', }, counterValue: { fontSize: 36, fontWeight: 'bold', color: '#2c3e50', }, buttonRow: { flexDirection: 'row', justifyContent: 'space-around', marginTop: 10, }, button: { paddingVertical: 12, paddingHorizontal: 20, borderRadius: 25, alignItems: 'center', justifyContent: 'center', minWidth: 80, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.2, shadowRadius: 4, elevation: 3, }, incrementButton: { backgroundColor: '#27ae60', }, decrementButton: { backgroundColor: '#e74c3c', }, resetButton: { backgroundColor: '#f39c12', }, saveButton: { backgroundColor: '#3498db', marginTop: 15, }, apiButton: { backgroundColor: '#9b59b6', }, buttonText: { color: 'white', fontWeight: '600', fontSize: 16, }, smallButton: { paddingVertical: 8, paddingHorizontal: 15, borderRadius: 20, alignItems: 'center', justifyContent: 'center', minWidth: 60, shadowColor: '#000', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.15, shadowRadius: 2, elevation: 2, }, smallButtonText: { color: 'white', fontWeight: '600', fontSize: 14, }, inputRow: { flexDirection: 'row', alignItems: 'center', gap: 10, }, compactInputStyle: { flex: 1, borderWidth: 1, borderColor: '#bdc3c7', borderRadius: 8, padding: 10, fontSize: 14, backgroundColor: '#fff', }, storedValueContainer: { backgroundColor: '#ecf0f1', padding: 15, borderRadius: 10, marginBottom: 15, }, label: { fontSize: 14, color: '#7f8c8d', marginBottom: 5, }, storedValue: { fontSize: 16, fontWeight: '600', color: '#2c3e50', }, inputStyle: { borderWidth: 2, borderColor: '#bdc3c7', borderRadius: 12, padding: 15, fontSize: 16, backgroundColor: '#fff', marginBottom: 5, }, macbookContainer: { backgroundColor: '#f8f9fa', borderRadius: 12, padding: 20, marginTop: 20, borderLeftWidth: 5, borderLeftColor: '#3498db', }, macbookTitle: { fontSize: 18, fontWeight: 'bold', color: '#2c3e50', marginBottom: 15, textAlign: 'center', }, specRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 8, borderBottomWidth: 1, borderBottomColor: '#ecf0f1', }, specLabel: { fontSize: 16, color: '#7f8c8d', fontWeight: '500', }, specValue: { fontSize: 16, color: '#2c3e50', fontWeight: '600', }, });
👉 The UI reacts automatically when the Redux state updates.
🎯 Final Output
- Tap +/- buttons → Counter updates
- Enter text → Stored in Redux
- Press Fetch button → MacBook data appears with name, year, price, CPU, and storage
✅ Why Redux Toolkit?
- Less boilerplate than traditional Redux
- Built-in Immer for immutability
- Cleaner async thunks for API calls
- Scalable for large apps
🚀 Conclusion
Redux Toolkit makes state management in React Native easier, cleaner, and scalable.
With just a few slices, we built a counter, input manager, and API fetcher — all in a structured, maintainable way.
If you’re starting a new project or refactoring an old one, Redux Toolkit is the way to go!
Top comments (0)