Reputation: 1238
I have a React Native app (using TypeScript and Redux-Toolkit (RTK) Query) in which I want to show an intro slider only for the first app launch and never show it again unless the app data is cleared. I have created a slice for its visibility
state and am using redux-persist
to keep the data.
Upon logout I'm reseting the store and persisted state by following Dan Abramov's and Lenz Weber-Tronic's recommendations.
// Root reducer (resettable)
const rootReducer = (
state: ReturnType<typeof appReducer> | undefined,
action: AnyAction
) => {
// If the `logout` action is dispatched, clean the `Redux Persist` storage and reset the state
if (action.type === "auth/logout") {
storage.removeItem("persist:root");
return appReducer(undefined, action);
}
return appReducer(state, action);
};
I also have separated the persistence
config of my intro
slice so that I can keep it into storage.
// Root persistence config
const rootPersistConfig = {
key: "root",
version: 1,
storage,
blacklist: ["intro", api.reducerPath],
};
// Intro slides persistence config
const introPersistConfig = {
key: "intro",
version: 1,
storage,
blacklist: [api.reducerPath],
};
// Top-level reducers
const appReducer = combineReducers({
[api.reducerPath]: api.reducer,
auth: authSliceReducer,
intro: persistReducer(introPersistConfig, introSliceReducer),
});
And this is the intro
slice:
interface IntroSliceState {
/** Whether to show the app intro slides or not */
showIntro: boolean;
}
const initialState: IntroSliceState = {
showIntro: true,
};
const introSlice = createSlice({
name: "intro",
initialState,
reducers: {
hideIntro: (state) => {
state.showIntro = false;
},
},
});
The problem I'm trying to solve now is to find a way to keep the intro
slice from being cleared on logout and I know I have to modify this part: return appReducer(undefined, action);
but I have no idea how to do it.
Upvotes: 2
Views: 1061
Reputation: 21
Have you tried clearing specific persistors?
storage.removeItem("persist:intro");
EDIT: Sorry just saw the blacklist. You should still try to single it out to rule out other possibilities
But I would probably recommend using a local storage for saving first time launch. There used to be AsyncStorage for react native but here are alternatives:
https://reactnative.directory/?search=storage
If you use expo, you could use:
@react-native-async-storage/async-storage
If you need an example of a first time launch hook + async storage helpers setup send a reply!
EDIT2:
useAppHasLaunched
import { useEffect, useState } from 'react'
import { getAsyncStorageItem, storeAsyncStorageItem } from '../utils/asyncStorageHelper'
export const useAppHasLaunched = () => {
const HAS_LAUNCHED = 'HAS_LAUNCHED'
const [hasLaunched, setHasLaunched] = useState<boolean>(false)
useEffect(() => {
getData()
}, [])
const getData = async () => {
try {
const hasLaunched = await getAsyncStorageItem(HAS_LAUNCHED)
if (hasLaunched) {
setHasLaunched(true)
} else {
await storeAsyncStorageItem(HAS_LAUNCHED, 'true')
}
} catch (error) {
console.log('ERROR_GET_LAUNCH_DATA', error)
}
}
return { hasLaunched, setHasLaunched }
}
asyncStorageHelpers
import AsyncStorage from '@react-native-async-storage/async-storage'
export const getAsyncStorageItem = async (key: string) => {
try {
const value = await AsyncStorage.getItem(key)
if (value) return value
} catch (error) {
console.log('Error getting data', error)
}
return false
}
export const storeAsyncStorageItem = async (key: string, value: string) => {
try {
await AsyncStorage.setItem(key, value)
} catch (error) {
console.log('Error storing data', error)
}
}
Then I use it in my logged out screen:
const { hasLaunched, setHasLaunched } = useAppHasLaunched()
Render onboarding screen, pass in setHasLaunched and use it once your onboarding slider finishes or when you dismiss the slider
{!hasLaunched && <Onboarding setFinishedOnboarding={setHasLaunched} />}
Upvotes: 0
Reputation: 203323
Instead of passing undefined state to the reducer so the entire state tree is recomputed from initial state, compute a new state object with only what you want to keep retained.
Example:
// Root reducer (resettable)
const rootReducer = (
state: ReturnType<typeof appReducer> | undefined,
action: AnyAction
) => {
// If the `logout` action is dispatched, clean the `Redux Persist`
// storage and reset the state
if (action.type === "auth/logout") {
storage.removeItem("persist:root"); // *
// Keep intro state from previous state
const { intro } = state;
// Create new state with retain previous state values
const nextState = {
intro,
};
return appReducer(nextState, action);
}
return appReducer(state, action);
};
*Note: storage.removeItem("persist:root");
is one way to delete the persisted state. Another is the persistor.purge
method to "clear out the stored state".
Note also that there may actually be no need to manually clear/purge the storage since upon logout and subsequent state update, the new state will be immediately persisted back out to storage.
Upvotes: 0