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)