Hi i am new to react native and redux.
I am using redux persist to store data locally, but there are some keys that i don't want to persist. For that i am using blacklist, which is not working. It persist all the keys and not ignoring the keys that i want. Here is the code
I dont want to persist data and tabbar.
store.js
import rootReducer from './reducers'
// import AsyncStorage from '#react-native-community/async-storage';
import { AsyncStorage } from 'react-native';
import { persistStore, persistReducer } from 'redux-persist'
const persistConfig = {
key: 'root',
storage: AsyncStorage,
blacklist: ['data', 'tabbar'],
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
let store = createStore(persistedReducer)
let persistor = persistStore(store)
export { store, persistor }.
reducers/product.js
const initialState = {
products: [],
favs: [],
data: [],
tabbar: false
}
const products = (state = initialState, action) => {
switch (action.type) {
case SET_PRODUCTS:
return {
...state,
products: action.products
}
case ADD_TO_FAV:
return {
...state,
favs: [...state.favs, action.product]
}
case REMOVE_FROM_FAV:
return {
...state,
favs: state.favs.slice().filter(f => f._id != action.product._id)
}
case SET_DATA:
return {
...state,
data: [...state.data, action.data]
}
case TABBAR:
return {
...state,
tabbar: action.tabbar
}
default:
return state;
}
}
export default products;
reducers/index.js
import prodReducer from './products';
export default combineReducers({
prodReducer
})
Action/product.js
export const SET_PRODUCTS = 'SET_PRODUCTS';
export const ADD_TO_FAV = 'ADD_TO_FAV';
export const REMOVE_FROM_FAV = 'REMOVE_FROM_FAV';
export const SET_DATA = 'SET_DATA';
export const TABBAR = 'TABBAR';
export const setProducts = (products) => {
return {
type: SET_PRODUCTS,
products
};
}
export const addToFav = (product) => {
return {
type: ADD_TO_FAV,
product
};
}
export const removeFromFav = (product) => {
return {
type: REMOVE_FROM_FAV,
product
};
}
export const tabbar = (tabbar) => {
return {
type: TABBAR,
tabbar
};
}
export const setData = (data) => {
return {
type: SET_DATA,
data
};
}
app.js
import React, { useEffect } from 'react';
import Navigator from './navigation/Navigator'
import { Provider } from 'react-redux';
import { store, persistor } from './redux/store';
import { PersistGate } from 'redux-persist/integration/react'
import { firebase } from '#react-native-firebase/messaging'
import AsyncStorage from '#react-native-community/async-storage';
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<StatusBar backgroundColor={'#AD1457'} />
<Navigator />
</PersistGate>
</Provider>
)
}
Your persist config seems alright to me. Can you add the whitelist key too?
const persistConfig = {
key: 'root',
storage: AsyncStorage,
blacklist: ['data', 'tabbar'],
whitelist:['products','favs']
}
On closer inspection, the persist config is not entirely correct. In order for blacklist and whitelist to work, they have to match keys of a reducer to which you apply the persist config - in this case, rootReducer, which only has one key - prodReducer.
What you want is configure persistence of your products reducer specifically in addition to root. The docs call this nested persists. You can do this in your reducers/index.js:
import AsyncStorage from '#react-native-community/async-storage';
import { persistStore, persistReducer } from 'redux-persist'
import prodReducer from './products';
const productsPersistConfig = {
key: 'products',
storage: AsyncStorage,
blacklist: ['data', 'tabbar'],
}
export default combineReducers({
prodReducer: persistReducer(productsPersistConfig, prodReducer),
})
You can then remove the blacklist from your main persistConfig.
Have you tried removing the app completely and doing a fresh install after the persist config has been added? Without that, data persisted previously is still there.
I have fixed this issue
Now, i have created two reducers . One for blacklist and one for whitelish.
Then combine it
export default combineReducers({
whitelistReducer, blackListReducer
})
and in store create a persistConfig in which i gave the respected reducer in blacklist and whitelist
const persistConfig = {
key: 'root',
storage: AsyncStorage,
whitelist: ['whitelistReducer'],
blacklist: ['blackListReducer']
}
Related
I am new to redux , redux toolkit and I got an error message that I really struggle to deal with but i need some help. Any help will be appreciated. Thanks in advance
My error :
ERROR TypeError: undefined is not an object (evaluating
'state.favorites.favorites')
my code :
reducer.ts
import { createSlice, PayloadAction } from "#reduxjs/toolkit";
import { IFavorite, MEATPROPS, IFavoriteState } from "../../../types/meat";
const initialState = {
favorites: [],
};
const FavoriteData = createSlice({
name: "favoriteData",
initialState,
reducers: {
addNewFavoriteCharacter: (
state: IFavorite,
action: PayloadAction<MEATPROPS>
) => {
state.favorites = [...state.favorites, action.payload];
},
removeFavoriteCharacter: (
state: IFavorite,
action: PayloadAction<number>
) => {
state.favorites = state.favorites.filter(
(item) => item.id !== action.payload
);
},
},
});
export const { addNewFavoriteCharacter, removeFavoriteCharacter } =
FavoriteData.actions;
export const favoriteStateData = (state: IFavoriteState) =>
state.favorites.favorites;
export default FavoriteData.reducer;
app.tsx
import React, { useCallback } from "react";
import { SafeAreaProvider } from "react-native-safe-area-context";
import { AppNavigation } from "./src/appNavigation";
import { useFonts, Poppins_500Medium } from "#expo-google-fonts/poppins";
import * as SplashScreen from "expo-splash-screen";
import { Provider } from "react-redux";
import {PersistGate} from 'redux-persist/es/integration/react';
import { persistor, store } from "./src/store";
export default function App() {
const [fontsLoaded] = useFonts({
Sangharia: require("./src/fonts/Sangharia.ttf"),
Poppins_500Medium: Poppins_500Medium,
});
const onLayoutRootView = useCallback(async () => {
if (fontsLoaded) {
await SplashScreen.hideAsync();
}
}, [fontsLoaded]);
if (!fontsLoaded) {
return null;
}
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}></PersistGate>
<SafeAreaProvider onLayout={onLayoutRootView}>
<AppNavigation />
</SafeAreaProvider>
</Provider>
);
}
types.ts
export interface MEATPROPS {
id: number;
meatType: "Beef" | "Pork" | "Sheep" | "Chicken";
image?: any;
name: string;
simplifiedDescription?: string;
fullDescription?: string;
countriesNaming?: countries;
bestTo: string[];
popularity?: "baixa" | "tradicional" | "alta";
nutriFact?: Nutrition;
}
export interface IFavorite{
favorites: MEATPROPS[]
}
export interface IFavoriteState{
favorites:{
favorites: MEATPROPS[]
}
}
store
import { configureStore } from "#reduxjs/toolkit";
import {
persistStore,
persistReducer,
FLUSH,
REHYDRATE,
PAUSE,
PERSIST,
PURGE,
REGISTER,
} from "redux-persist";
import AsyncStorage from "#react-native-async-storage/async-storage";
import favoriteData from "./modules/Favorites/reducer";
const persistConfig = {
key: "root",
storage: AsyncStorage,
};
const persistedReducer = persistReducer(persistConfig, favoriteData);
const store = configureStore({
reducer: {
favoriteData: persistedReducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}),
});
const persistor = persistStore(store);
export { store, persistor };
I'm trying to persist data on local storage
Your slice name is "favoriteData". When you call favorites from the state you should use state.favoriteData.favorites not state.favorites.favorites
Fix this code:
export const favoriteStateData = (state: IFavoriteState) =>
state.favoriteData.favorites // use this here
I hope it helps
I have a React Native app. I am storing username and uid in AsyncStorage so they don't have to log in every time. How do I populate the initialState with these values. There are some packages that do it for you but it seems like this should be doable without the overhead of another package. Right now initial state is just empty values.
const initialState = {
uid: "",
username: "",
};
Here is the solution I came up with. Just create an action that gets the AsyncStorage properties and dispatch the array of properties to the reducer where they are assigned to the state. And you call the action directly on the store. Much lighter than adding a whole other library. For simplicity I'll assume all the Redux code is in one file called myRedux.js:
// Imports:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { AsyncStorage, } from "react-native";
// Set initial state to empty values:
const initialState = {
uid: "",
username: "",
};
// Reducer:
const reducer = (state = initialState, action) => {
switch(action.type) {
case "setInit":
return {
...state,
uid: action.uid,
username: action.username,
}
default:
return state;
}
};
// Store
const store = createStore(reducer, applyMiddleware(thunk));
export { store };
// Action
const setInit = (result) => {
return {
type: "setInit",
uid: result[0][1],
username: result[1][1],
};
}
const getAsyncStorage = () => {
return (dispatch) => {
AsyncStorage.multiGet(['uid', 'username'])
.then((result) => {dispatch(setInit(result))});
};
};
// Dispatch the getAsyncStorage() action directly on the store.
store.dispatch(getAsyncStorage());
Then in the Screen files you can access them with mapStateToProps:
const mapStateToProps = (state) => {
return {
uid: state.uid,
username: state.username,
};
}
// Access the prop values in the render:
render() {
return (
<View>
<Text>Uid: {this.props.uid}</Text>
<Text>Username: {this.props.username}</Text>
</View>
);
}
// Connect mapStateToProps to the component class
export default connect(mapStateToProps)(MyScreen);
Aman Mittal provides an excellent guide for persisting state to AsyncStorage and populating the initial state using the redux-persist package.
https://blog.jscrambler.com/how-to-use-redux-persist-in-react-native-with-asyncstorage/
Just make sure when you get to the config part, that you use AsyncStorage as the storage value:
import { persistReducer } from 'redux-persist';
import rootReducer from './reducers';
...
export const config = {
key: 'my-root-key',
storage: AsyncStorage,
blacklist: [],
};
const store = createStore(
persistReducer(
config,
rootReducer
),
compose(...activeEnhancers),
);
I'm trying to get data from App.js for network connection availability. I'm getting data from App.js to action and reducer but the reducer is not updating the state for my component. The console log in the reducer is working but I'm not able to get data in the mapStateToProps of myComponent.
My App.js file contains this code.
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { NetInfo } from 'react-native';
import { createStore, applyMiddleware } from 'redux';
import ReduxThunk from 'redux-thunk';
import reducers from './src/reducers';
import Router from './src/Router';
import { internetConnectionChanged } from './src/actions/';
class App extends Component {
componentWillMount() {
NetInfo.isConnected.addEventListener('connectionChange', this.handleConnectionChange);
}
componentWillUnmount() {
NetInfo.isConnected.removeEventListener('connectionChange', this.handleConnectionChange);
}
handleConnectionChange = (isConnected) => {
NetInfo.isConnected.fetch().done(
(isConnecteds) => {
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk));
store.dispatch(internetConnectionChanged(isConnecteds));
});
};
render() {
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk));
return (
<Provider store={store}>
<Router />
</Provider>
);
}
}
export default App;
My code in action file is
import { CONNECTION_CHANGE } from '../actions/types';
export const internetConnectionChanged = (isConnected) => {
return {
type: CONNECTION_CHANGE,
payload: isConnected
};
};
That is exported via the index.js of actions file
through export * from './AppActions';
Code for the reducer is
import { CONNECTION_CHANGE } from '../actions/types';
const INITIAL_STATE = { isConnected: false };
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case CONNECTION_CHANGE:
console.log(action.payload);
return { ...state, isConnected: action.payload };
default:
return state;
}
};
Under my component, this is the code to get the info is
const mapStateToProps = ({ auth, app }) => {
const { email, password, error, loading } = auth;
const { isConnected } = app;
return { email, password, error, loading, isConnected };
};
export default connect(mapStateToProps, {
emailChanged,
passwordChanged,
loginUser,
forgotPasswordAction,
})(LoginForm);
Create store outside the App class. This might be causing the store to always have initial reducer values. Just paste the below line before Class App extends Component line
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk));
Also remove the same above line of code from the following function
handleConnectionChange = (isConnected) => {
NetInfo.isConnected.fetch().done(
(isConnecteds) => {
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk)); //remove this line
store.dispatch(internetConnectionChanged(isConnecteds));
});
};
i'm developing an android application whereby my screen has to depends on whether the user is logged in or not. The login data is stored inside the AsyncStorage.
By the time when the apps start, it should get the data from AsyncStorage and make it as the default state. How can i achieve that ?
Below is my redux structure
index.android.js
import {
AppRegistry,
} from 'react-native';
import Root from './src/scripts/Root.js';
AppRegistry.registerComponent('reduxReactNavigation', () => Root);
Root.js
import React, { Component } from "react";
import { Text, AsyncStorage } from "react-native";
import { Provider, connect } from "react-redux";
import { addNavigationHelpers } from 'react-navigation';
import getStore from "./store";
import { AppNavigator } from './routers';
const navReducer = (state, action) => {
const newState = AppNavigator.router.getStateForAction(action, state);
return newState || state;
};
const mapStateToProps = (state) => ({
nav: state.nav
});
const user = {};
class App extends Component {
constructor(){
super();
}
render() {
return (
<AppNavigator
navigation={addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav
})}
/>
);
}
}
const AppWithNavigationState = connect(mapStateToProps)(App);
const store = getStore(navReducer);
export default function Root() {
return (
<Provider store={store}>
<AppWithNavigationState />
</Provider>
);
}
user reducers
import {
REGISTER_USER,
UPDATE_USER,
LOGIN_USER
} from '../../actions/actionTypes';
import { handleActions } from 'redux-actions';
**// here is the default state, i would like to get from asyncstorage**
const defaultState = {
isLoggedIn: false,
user:{},
};
export const user = handleActions({
REGISTER_USER: {
next(state, action){
return { ...state, user: action.payload, isLoggedIn: true }
}
},
LOGIN_USER: {
next(state, action){
return { ...state, user: action.payload, isLoggedIn: true }
}
},
}, defaultState);
You can use the component lifecycle method componentWillMount to fetch the data from AsyncStorage and when the data arrive you can change the state.
I made a reducer called 'group' but when I call the store I see it's saved inside store.default.group. Why isn't it in store.group instead? How come I have a default inside it?
index reducer:
import { combineReducers } from 'redux'
import counter from './counter'
import group from './groupReducer'
export default combineReducers({
counter,
group,
})
Group reducer:
import * as types from '../actions/actionTypes';
const initialState = {
name: null,
route: null,
grade: null,
coins: 0,
image: null,
};
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case types.SET_GROUP_NAME:
return {
...state,
name: action.name
};
case types.SET_GROUP_ROUTE:
return {
...state,
route: action.route
};
case types.ADD_GROUP_COINS:
return {
...state,
coins: state.count + action.coins
};
case types.REMOVE_GROUP_COINS:
return {
...state,
coins: state.count - action.coins
};
case types.SET_GROUP_IMAGE:
return {
...state,
image: actions.image
};
case types.SET_GROUP_grade:
return {
...state,
grade: action.grade
};
default:
return state;
}
}
My connect function:
export default connect(store => ({
group: store.default.group
}),
(dispatch) => ({
actions: bindActionCreators(groupActions, dispatch)
})
)(StartGame);
EDIT:
Creating store:
import React, {Component} from 'react';
import { createStore, applyMiddleware, combineReducers } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import * as reducers from '../reducers';
import Routing from './Routing';
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const reducer = combineReducers(reducers);
const store = createStoreWithMiddleware(reducer);
export default class App extends Component {
render() {
return (
<Provider store={store}>
<Routing />
</Provider>
);
}
}
You are using combineReducers twice in your application while creating the store object. One inside reducer index.js and another while creating store object. Remove one combineReducers then your store should work as expected.