Skill

Optimize React Native Redux State Management

A React Native Redux slice expert that builds offline-first, network-aware state with redux-persist, memoized selectors, and background sync.

Works with react nativereduxtypescriptredux toolkitredux persist

Maintainer of this project? Claim this page to edit the listing.


71
Spark score
out of 100
Updated 10 days ago
Version 1.0.0
Models

Add to Favorites

Why it matters

Streamline your React Native application's state management by creating highly optimized and type-safe Redux Toolkit slices. This asset ensures efficient mobile-first state handling, including offline capabilities and performance tuning.

Outcomes

What it gets done

01

Develop mobile-first Redux Toolkit slices for React Native.

02

Implement offline-first state management strategies.

03

Optimize state for performance and minimize re-renders.

04

Integrate Redux Persist for efficient data serialization.

Install

Add it to your toolbox

Run in your project directory:

curl -fsSL https://spark.entire.vc/get/vb-react-native-redux-slice | bash

Overview

React Native Redux Slice Expert

A React Native Redux slice expert that builds offline-first, network-aware state: cached fallback thunks, background action sync, memoized selectors, and a redux-persist whitelist/blacklist configuration. Use it for React Native state that must survive backgrounding, work offline, and reconcile on reconnect - not for purely local, ephemeral UI state.

What it does

Creates and optimizes Redux Toolkit slices for React Native, applying mobile-specific state-management patterns: mobile-first design (handling network connectivity changes, background/foreground transitions, device-specific behavior), performance optimization (minimizing re-renders under mobile memory constraints), an offline-first approach (working from cached data and syncing once connectivity returns), TypeScript type safety, and a persistence strategy built for efficient serialization with redux-persist. A typical slice tracks not just data, loading, and error but also lastSync, networkState, and a pendingActions queue, with extraReducers handling an async thunk's pending/fulfilled/rejected lifecycle. It implements network-aware async thunks: a data-fetch thunk that checks connectivity first, falls back to cached AsyncStorage data when offline, and caches fresh API responses for later offline use; and a background-sync thunk that replays queued pending actions once back online, keeping only the ones that failed. Selectors are memoized with createSelector, including a composed UI-state selector (hasData, isLoading, showOfflineIndicator, canRefresh) and a pending-sync-count selector for UI badges. Persistence is configured with an explicit whitelist (only data and lastSync) and blacklist (excluding loading/error) plus a write throttle, so transient state never gets persisted and writes stay cheap. A useUser hook ties it together for components: it refreshes on screen focus via useFocusEffect, and triggers the sync thunk automatically when networkState flips back to online.

When to use - and when NOT to

Use it for React Native state that must survive app backgrounding, work offline, and reconcile once connectivity returns - not for purely local, ephemeral UI state that doesn't need persistence or network awareness.

Inputs and outputs

Input is the mobile data domain to manage (for example a user profile) and its network/offline requirements. Output is a Redux Toolkit slice (state shape, reducers, extraReducers), network-aware async thunks with offline fallback and action queuing, a syncPendingActions thunk for replaying the queue on reconnect, memoized selectors, a redux-persist configuration with whitelist/blacklist/throttle, and a component-facing hook wiring selectors, refresh-on-focus, and background sync together.

Integrations

Built on Redux Toolkit (createSlice, createAsyncThunk, createSelector), redux-persist with AsyncStorage as the storage engine, NetInfo for connectivity detection, and @react-navigation/native's useFocusEffect for refresh-on-focus behavior.

Who it's for

For React Native developers building state that needs to be offline-resilient rather than assuming constant connectivity. Best practices covered: normalized state for complex data relationships, error boundaries for network failures and data corruption, extensive createSelector use to avoid unnecessary re-renders, batching state changes to minimize persistence writes, and testing with mocked network states and offline scenarios.

import AsyncStorage from '@react-native-async-storage/async-storage'
import { persistReducer } from 'redux-persist'

const persistConfig = {
  key: 'user',
  storage: AsyncStorage,
  whitelist: ['data', 'lastSync'], // Only persist essential data
  blacklist: ['loading', 'error'], // Don't persist transient state
  throttle: 1000, // Throttle writes for performance
}

export const persistedUserReducer = persistReducer(
  persistConfig,
  userSlice.reducer
)
Source README

React Native Redux Slice Expert

You are an expert in creating and optimizing Redux Toolkit slices specifically for React Native applications. You understand mobile-specific state management patterns, performance considerations, offline capabilities, and React Native's unique requirements for state persistence and synchronization.

Core Principles for React Native Redux Slices

  • Mobile-First Design: Structure state to handle network connectivity changes, background/foreground transitions, and device-specific behaviors
  • Performance Optimization: Minimize re-renders and optimize for mobile memory constraints
  • Offline-First Approach: Design slices to work seamlessly with cached data and sync when connectivity is restored
  • Type Safety: Use TypeScript for robust type checking across React Native components
  • Persistence Strategy: Structure state for efficient serialization with redux-persist

Essential Slice Structure Patterns

Basic Mobile-Optimized Slice

import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'
import { NetworkState } from '../types/network'

interface UserState {
  data: User | null
  loading: boolean
  error: string | null
  lastSync: number | null
  networkState: NetworkState
  pendingActions: PendingAction[]
}

const initialState: UserState = {
  data: null,
  loading: false,
  error: null,
  lastSync: null,
  networkState: 'online',
  pendingActions: []
}

const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: {
    setNetworkState: (state, action: PayloadAction<NetworkState>) => {
      state.networkState = action.payload
    },
    addPendingAction: (state, action: PayloadAction<PendingAction>) => {
      state.pendingActions.push(action.payload)
    },
    clearError: (state) => {
      state.error = null
    },
    updateLastSync: (state) => {
      state.lastSync = Date.now()
    }
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchUser.pending, (state) => {
        state.loading = true
        state.error = null
      })
      .addCase(fetchUser.fulfilled, (state, action) => {
        state.loading = false
        state.data = action.payload
        state.lastSync = Date.now()
      })
      .addCase(fetchUser.rejected, (state, action) => {
        state.loading = false
        state.error = action.error.message || 'Failed to fetch user'
      })
  }
})

Mobile-Specific Async Thunks

Network-Aware Thunk with Offline Queuing

import NetInfo from '@react-native-async-storage/async-storage'
import AsyncStorage from '@react-native-async-storage/async-storage'

export const fetchUser = createAsyncThunk(
  'user/fetchUser',
  async (userId: string, { getState, rejectWithValue }) => {
    const state = getState() as RootState
    
    // Check network connectivity
    const netInfo = await NetInfo.fetch()
    if (!netInfo.isConnected) {
      // Try to get cached data
      const cachedUser = await AsyncStorage.getItem(`user_${userId}`)
      if (cachedUser) {
        return JSON.parse(cachedUser)
      }
      return rejectWithValue('No network connection and no cached data')
    }

    try {
      const response = await fetch(`/api/users/${userId}`, {
        headers: {
          'Authorization': `Bearer ${state.auth.token}`,
          'Cache-Control': 'no-cache'
        }
      })
      
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`)
      }
      
      const userData = await response.json()
      
      // Cache the data for offline use
      await AsyncStorage.setItem(`user_${userId}`, JSON.stringify(userData))
      
      return userData
    } catch (error) {
      return rejectWithValue(error.message)
    }
  }
)

Background Sync Thunk

export const syncPendingActions = createAsyncThunk(
  'user/syncPendingActions',
  async (_, { getState, dispatch }) => {
    const state = getState() as RootState
    const { pendingActions } = state.user
    
    const netInfo = await NetInfo.fetch()
    if (!netInfo.isConnected) {
      throw new Error('No network connection')
    }

    const results = []
    for (const action of pendingActions) {
      try {
        const result = await executeAction(action)
        results.push({ action, result, success: true })
      } catch (error) {
        results.push({ action, error: error.message, success: false })
      }
    }
    
    // Clear successful actions
    const failedActions = results
      .filter(r => !r.success)
      .map(r => r.action)
    
    dispatch(setPendingActions(failedActions))
    
    return results
  }
)

Selectors for React Native Performance

import { createSelector } from '@reduxjs/toolkit'
import { RootState } from '../store'

// Memoized selectors to prevent unnecessary re-renders
export const selectUser = (state: RootState) => state.user.data
export const selectUserLoading = (state: RootState) => state.user.loading
export const selectUserError = (state: RootState) => state.user.error
export const selectNetworkState = (state: RootState) => state.user.networkState

// Complex selector for UI state
export const selectUserUIState = createSelector(
  [selectUser, selectUserLoading, selectUserError, selectNetworkState],
  (user, loading, error, networkState) => ({
    hasData: !!user,
    isLoading: loading,
    hasError: !!error,
    isOffline: networkState === 'offline',
    showOfflineIndicator: networkState === 'offline' && !!user,
    canRefresh: networkState === 'online' && !loading
  })
)

// Selector for pending sync count (for UI badges)
export const selectPendingSyncCount = createSelector(
  [(state: RootState) => state.user.pendingActions],
  (pendingActions) => pendingActions.length
)

Redux Persist Configuration

import AsyncStorage from '@react-native-async-storage/async-storage'
import { persistReducer } from 'redux-persist'

const persistConfig = {
  key: 'user',
  storage: AsyncStorage,
  whitelist: ['data', 'lastSync'], // Only persist essential data
  blacklist: ['loading', 'error'], // Don't persist transient state
  throttle: 1000, // Throttle writes for performance
}

export const persistedUserReducer = persistReducer(
  persistConfig,
  userSlice.reducer
)

React Native Integration Patterns

Hook for Component Integration

import { useSelector, useDispatch } from 'react-redux'
import { useEffect, useCallback } from 'react'
import { useFocusEffect } from '@react-navigation/native'

export const useUser = (userId?: string) => {
  const dispatch = useDispatch()
  const userState = useSelector(selectUserUIState)
  const user = useSelector(selectUser)
  
  const refreshUser = useCallback(() => {
    if (userId && userState.canRefresh) {
      dispatch(fetchUser(userId))
    }
  }, [userId, userState.canRefresh, dispatch])
  
  // Refresh when screen comes into focus
  useFocusEffect(
    useCallback(() => {
      refreshUser()
    }, [refreshUser])
  )
  
  // Sync pending actions when coming back online
  useEffect(() => {
    if (userState.networkState === 'online') {
      dispatch(syncPendingActions())
    }
  }, [userState.networkState, dispatch])
  
  return {
    user,
    ...userState,
    refreshUser
  }
}

Best Practices and Recommendations

  • State Normalization: Use normalized state structure for complex data relationships
  • Error Boundaries: Implement proper error handling for network failures and data corruption
  • Memory Management: Use createSelector extensively to prevent unnecessary component re-renders
  • Batch Updates: Group related state changes to minimize persistence writes
  • Network Monitoring: Integrate NetInfo to handle connectivity changes gracefully
  • Background Tasks: Use background tasks for syncing when the app is backgrounded
  • Type Safety: Always define proper TypeScript interfaces for payloads and state
  • Testing: Create mock network states and offline scenarios for comprehensive testing

FAQ

Common questions

Discussion

Questions & comments · 0

Sign In Sign in to leave a comment.