6.1 KiB
6.1 KiB
Redux Toolkit Setup
Installation
yarn add @reduxjs/toolkit react-redux redux-persist
yarn add -D @types/react-redux
Store Configuration
// src/redux/store/index.ts
import {configureStore} from '@reduxjs/toolkit';
import {persistStore, persistReducer} from 'redux-persist';
import AsyncStorage from '@react-native-async-storage/async-storage';
import {combineReducers} from '@reduxjs/toolkit';
import authSlice from '../slices/authSlice';
import userSlice from '../slices/userSlice';
const persistConfig = {
key: 'root',
storage: AsyncStorage,
whitelist: ['auth'],
};
const rootReducer = combineReducers({
auth: authSlice,
user: userSlice,
});
const persistedReducer = persistReducer(persistConfig, rootReducer);
export const store = configureStore({
reducer: persistedReducer,
middleware: getDefaultMiddleware =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: ['persist/PERSIST', 'persist/REHYDRATE'],
},
}),
});
export const persistor = persistStore(store);
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
Auth Slice
// src/redux/slices/authSlice.ts
import {createSlice, PayloadAction} from '@reduxjs/toolkit';
interface User {
id: string;
email: string;
firstName: string;
lastName: string;
avatar?: string;
}
interface AuthState {
isAuthenticated: boolean;
user: User | null;
token: string | null;
refreshToken: string | null;
loading: boolean;
error: string | null;
}
const initialState: AuthState = {
isAuthenticated: false,
user: null,
token: null,
refreshToken: null,
loading: false,
error: null,
};
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
loginStart: (state) => {
state.loading = true;
state.error = null;
},
loginSuccess: (state, action: PayloadAction<{user: User; token: string; refreshToken: string}>) => {
state.loading = false;
state.isAuthenticated = true;
state.user = action.payload.user;
state.token = action.payload.token;
state.refreshToken = action.payload.refreshToken;
state.error = null;
},
loginFailure: (state, action: PayloadAction<string>) => {
state.loading = false;
state.isAuthenticated = false;
state.user = null;
state.token = null;
state.refreshToken = null;
state.error = action.payload;
},
logout: (state) => {
state.isAuthenticated = false;
state.user = null;
state.token = null;
state.refreshToken = null;
state.error = null;
},
updateUser: (state, action: PayloadAction<Partial<User>>) => {
if (state.user) {
state.user = {...state.user, ...action.payload};
}
},
clearError: (state) => {
state.error = null;
},
},
});
export const {
loginStart,
loginSuccess,
loginFailure,
logout,
updateUser,
clearError,
} = authSlice.actions;
export default authSlice.reducer;
Typed Hooks
// src/redux/hooks.ts
import {useDispatch, useSelector, TypedUseSelectorHook} from 'react-redux';
import type {RootState, AppDispatch} from './store';
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
Async Thunks
// src/redux/thunks/authThunks.ts
import {createAsyncThunk} from '@reduxjs/toolkit';
import {authAPI} from '@services/authAPI';
interface LoginCredentials {
email: string;
password: string;
}
export const loginUser = createAsyncThunk(
'auth/loginUser',
async (credentials: LoginCredentials, {rejectWithValue}) => {
try {
const response = await authAPI.login(credentials);
return response.data;
} catch (error: any) {
return rejectWithValue(error.response?.data?.message || 'Login failed');
}
}
);
export const refreshToken = createAsyncThunk(
'auth/refreshToken',
async (_, {getState, rejectWithValue}) => {
try {
const state = getState() as any;
const refreshToken = state.auth.refreshToken;
if (!refreshToken) {
throw new Error('No refresh token available');
}
const response = await authAPI.refreshToken(refreshToken);
return response.data;
} catch (error: any) {
return rejectWithValue(error.response?.data?.message || 'Token refresh failed');
}
}
);
Provider Setup
// App.tsx
import React from 'react';
import {Provider} from 'react-redux';
import {PersistGate} from 'redux-persist/integration/react';
import {store, persistor} from './src/redux/store';
import {AppNavigator} from './src/navigation/AppNavigator';
import {LoadingScreen} from './src/components/common/LoadingScreen';
const App: React.FC = () => {
return (
<Provider store={store}>
<PersistGate loading={<LoadingScreen />} persistor={persistor}>
<AppNavigator />
</PersistGate>
</Provider>
);
};
export default App;
Middleware Configuration
// src/redux/middleware/authMiddleware.ts
import {Middleware} from '@reduxjs/toolkit';
import {logout} from '../slices/authSlice';
export const authMiddleware: Middleware = (store) => (next) => (action) => {
// Handle token expiration
if (action.type.endsWith('/rejected') && action.payload?.status === 401) {
store.dispatch(logout());
}
return next(action);
};
Selectors
// src/redux/selectors/authSelectors.ts
import {createSelector} from '@reduxjs/toolkit';
import {RootState} from '../store';
export const selectAuth = (state: RootState) => state.auth;
export const selectIsAuthenticated = createSelector(
[selectAuth],
(auth) => auth.isAuthenticated
);
export const selectUser = createSelector(
[selectAuth],
(auth) => auth.user
);
export const selectAuthLoading = createSelector(
[selectAuth],
(auth) => auth.loading
);
export const selectAuthError = createSelector(
[selectAuth],
(auth) => auth.error
);
export const selectUserFullName = createSelector(
[selectUser],
(user) => user ? `${user.firstName} ${user.lastName}` : ''
);