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)