How to persist the store values in react native? - react-native

In my application,the home page fetches the Json response from my rest API.Then I add the products into the cart array.Initially,my store values are..
const DEFAULT_STATE_TRENDING = {
data:{},
specialoffdata:[],
banner:[],
offerstitle:[],
cart:[],
cartcount:0,
isFetching:false,
dataFetched:false,
error:false,
}
After i added the products the cart array and cart count becomes..
cart:[{...},{...}],
cartcount:cart.length
Now, i close my app.After reloading the app,the cart array becomes empty.How to persist the store values here?

The best way to persist the store values is by using this awesome library. Redux Persist
It contains various methods and levels of persistance, with the ease of use.
Installation and basic usage is well documented on their github docs.

The simplest approach is to use AsyncStorage
let response = await AsyncStorage.getItem('count'); //Read
AsyncStorage.setItem('count', this.state.count + '');
This way, you can access the 'count' in every component.
The most modern way is to use react's new context api, you define the context provider and consume it everywhere:
const ThemeContext = React.createContext('light')
class ThemeProvider extends React.Component {
state = {theme: 'light'}
render() {
return (
<ThemeContext.Provider value={this.state.theme}> //<--- 'light' is exposed here
{this.props.children}
</ThemeContext.Provider>
)
}
}
class App extends React.Component {
render() {
return (
<ThemeProvider>
<ThemeContext.Consumer>
{item => <div>{item}</div>} //<-- you consume the value ('light') here
</ThemeContext.Consumer>
</ThemeProvider>
)
}
}
Either way, it is much lighter and easier than Redux or MobX.

I used the AsyncStorage.In this way i persisted my store state values even after reloading,closing and opening app.
configureStore.js:
import {createStore, applyMiddleware,compose} from 'redux';
import thunk from 'redux-thunk';
import { autoRehydrate } from 'redux-persist';
import allReducers from './reducers';
import { AsyncStorage } from 'react-native';
const middleware = [ thunk,
];
export default function configureStore() {
let store= createStore(
allReducers,
{},
compose(applyMiddleware(...middleware),autoRehydrate())
);
console.log("Created store is"+store);debugger
return store;
}
App.js:
const store = configureStore();
export default class App extends Component {
componentDidMount() {
console.log("App.js started and its store value is"+store);debugger
SplashScreen.hide()
persistStore(
store,
{
storage : AsyncStorage,
whitelist : ['trending']
}
);
}
render() {
return (
<Provider store={store}>
<MyStack/>
</Provider>
);
}
}

Related

Context: Cannot read properties of undefined

I'm trying to keep a live state of either the user is signed-in or not, so I can either show him or not, specific elements in the components. Using this code it's supposed to console.log(0) and then console.log(1), but it actually throws an error Cannot read properties of undefined.
./addons/Signed.js:
import { useState, createContext } from "react";
export const SignedContext = createContext();
export default function SignedProvider(props) {
const [SignedIn, setSignedIn] = useState(0);
return (
<SignedContext.Provider value={{ SignedIn, setSignedIn }}>
{props.children}
</SignedContext.Provider>
);
}
./screens/Profile.js:
import { useContext } from "react";
import SignedContext from "../addons/Signed";
...
const ProfileScreen = () => {
const { SignedIn, setSignedIn } = useContext(SignedContext);
console.log(SignedIn);
setSignedIn(1);
console.log(SignedIn);
...
}
...
You are using a named export for SignedContext but using a default import in Profile. Thus, you must use curly braces for your import. The following should change your issue.
import { SignedContext } from ".../addons/Signed"
Edit: If ProfileScreen is not a child of SignedContext.Provider, then this will not work. The general workflow is documented here. Hence, if ProfileScreen is not a child of the Provider, the context won't be available to it.
The ususal way to do this, is to define the context provider as a top level element in your app, if you want the context to be available at a global level in your application.
function App = () => {
const [signedIn, setSignedIn] = useState(0)
const contextValue = React.useMemo(() => ({signedIn, setSignedIn}), [singedIn])
// your application structure must be wrapped inside here.
// as an example I have only used ProfileScreen.
// Usually this is your root stack.
return (
<SignedContext.Provider value ={contextValue}>
<ProfileScreen />
</SignedContext.Provider>
)
}

Initialize redux store after load asynchronous data

I am using Redux to manage the state of my app and AsyncStorage to persist some data of the store.
I'm using the following way to initialize the store data because I want to access the store without any initialization function but directly from store variable. I know that store's data is read-only but it works fine for me.
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
import store from './store';
export default App = () => {
const [ready, setReady] = React.useState(false);
React.useEffect(() => {
AsyncStorage.getItem('reduxData').then((data) => {
store.getState().ReducerA.sampleItem1 = data.sampleItem1;
store.getState().ReducerB.sampleItem2 = data.sampleItem2;
store.getState().ReducerC.sampleItem3 = data.sampleItem3;
setReady(true);
});
}, [setReady]);
if (!ready) return null;
return (
<Provider store={store}>
...
</Provider>
)
}'
What is the best way to initialize store correctly? ( Without external library such us Redux Persist and without wrapping the store in a function )

react-redux useSelector() hook not working

I am new to React Native Programming. So, please tell me in detail. thank you.
calling use Selector
I am calling use Selector inside my functional component like this:
import { useDispatch, useSelector } from 'react-redux';
const AddAddressScreen = ({ navigation }) => {
const dispatch = useDispatch();
const data = useSelector(state => state);
console.log(data + "happy Coding");
return (
<View style={styles.container}>
<View>
);
}
export default AddAddressScreen;
My reducer looks like this
case types.API_LOGIN_SUCCESS:
if (action.result.result.mobile_verified === false) {
return {
...state,
onLoad: false,
result: action.result,
status: action.status,
error: null,
navigation: action.navigation.navigate("VerifyMNO")
};
} else {
return {
...state,
onLoad: false,
result: action.result,
status: action.status,
error: null,
navigation: action.navigation.navigate("AddAddress")
};
}
here my mobile number is verified so I move to the address screen.
where I use Use Selector which gives me an error. while I remove above two lines my code runs successfully.
My saga looks like this
export function* watchLoginUserInfo() {
yield takeLatest(types.LOGIN_USER, loginApiSaga)
}
My root saga
import { all, fork } from 'redux-saga/effects';
import { watchLoginUserInfo, } from './authenticationSagas';
function* rootSaga() {
yield all([
watchLoginUserInfo(),
])
}
export default rootSaga;
My Store looks like this
import {createStore, applyMiddleware} from 'redux';
import rootReducer from '../redux/reducers/root-reducer.js'
import createSagaMiddleware from 'redux-saga';
import rootSaga from '../redux/sagas/rootSaga';
const sagaMiddleware = createSagaMiddleware();
const store = createStore(rootReducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(rootSaga);
export {store};
when ever I use use Selector hook in my code it gives me the following error.
error 1
error 2, 3, 4
Use the select effect from redux-saga inside of a reducer: https://redux-saga.js.org/docs/api/#selectselector-args
For example const selectedState = yield select(state => state);.
The useSelector hook is for use inside of a function component.
EDIT: since the above doesn't seem to be the issue, I think the issue is that you're calling navigation functions from within your reducer. Reducer code can have no side effects, so you can't call navigation.navigate(...) from within the reducer. This will need to happen in the saga code instead. It might be able to be done in the loginApiSaga or in a dedicated saga that is triggered by API_LOGIN_SUCCESS.

Redux-Thunk unable to get props in component

I have an application with firebase connected. I am able to at each step console log and get see the data from firebase being passed around however when im in the component level it always returns empty.
import * as firebase from "firebase";
import { GET_LIST } from './types';
export const getListThunk = () => {
return (dispatch) => {
const teams = [];
const teamsObj = {};
var that = this;
var ref = firebase.database().ref('SignUp/' + "577545cf-c266-4b2e-9a7d-d24e7f8e23a5");
//var query = ref.orderByChild("uuid");
console.log("uuid thunk ");
ref.on('value', function (snapshot) {
console.log("snap ", snapshot.val())
snapshot.forEach(function (child) {
let currentlike = child.val()
console.log("schedas ", currentlike)
teams.push(currentlike);
console.log("teams ",teams);
});
dispatch({ type: GET_LIST, payload: teams})
})
}
}
At all the console log here I am able to receive the information from firebase. The console displays:
Now I checked my reducer to see if I can see my information there.
import { GET_LIST } from '../actions/types';
const INITIAL_STATE = {
jello: 'hello'
};
const listReducer = (state = INITIAL_STATE, action) => {
switch (action.type){
case GET_LIST:
console.log("action ", action.payload);
return action.payload;
default:
console.log("default ");
return state;
}
};
export default listReducer;
This console log labeled action shows the payload as well
So I am again able to see the data in the reducer payload.
Now checking my component I would assume calling this.props would show the data again however it shows up empty.
Component:
mport React, { Component } from "react";
import {
View,
StyleSheet,
Button,
SafeAreaView,
ScrollView,
Image,
TouchableOpacity,
Alert,
Animated,
FlatList
} from "react-native";
import {connect} from 'react-redux';
import { getListThunk } from '../actions';
import Form from '../components/Form';
import firebase from "firebase";
import * as theme from '../theme';
import Block from '../components/Block';
import Text from '../components/Text';
import App from "../../App";
class RenderRequests extends Component {
constructor(props) {
super(props);
this.params = this.props;
uuid2 = this.props.uuid;
}
componentWillMount(){
this.props.getListThunk();
}
componentDidMount(){
console.log("did mount " , this.params.uuid)
var uuid = this.params.uuid;
//this.props.getListThunk({uuid});
}
render() {
console.log("Component level array " ,this.props)
return (
<View>
<Text> {this.params.uuid} </Text>
</View>
);
}
}
export default connect(null, { getListThunk })(RenderRequests);
Now console login this shows an empty array:
NOTE the UUID in that log file is a UUID I passed as a prop from the previous screen. As you can see the "getListThunk" is empty.
--------EDIT------------------------ I have added code based on what Vinicius Cleves has said. However I had to make it {list: state} instead of {list: state.listReducer}
I now see it in my console. However, it seems like it shows up then runs the default action and my state gets reset to nothing. Below is a screenshot of my console:
If you see my reducer code I am logging when the default action is being called. Why is it being called so many times after the initial 'GET_LIST' action gets called. This keeps replacing my state with the default state.
getListThunk is a function, as expected. To access the information as you want in this.props, you should provide a mapStateToProps function to connect.
export default connect(
state=>({someVariableName: state.listReducer}),
{ getListThunk }
)(RenderRequests);
Now, the information you were missing on this.props will show under this.props.someVariableName

Changing state in React native App.js from another component

I'm making authentication in an app, and I'm kind of stuck. I have 2 different navigations. One shows if the user is logged in and another one if not. Basically, a Sign in screen. It's working fine if I change the value manually upon the start. But I can't find a way to change a state when a user signs in, for example. Even though the value in auth module changes, it doesn't update in App.js So how can I update the App.js's state from Sign in screen, for example?
import React, { Component } from 'react';
import { AppRegistry, Platform, StyleSheet, Text, View } from 'react-native';
import DrawerNavigator from './components/DrawerNavigator'
import SignedOutNavigator from './components/SignedOutNavigator'
import auth from './auth'
type Props = {};
export default class App extends Component<Props> {
constructor(props) {
super(props)
this.state = {
isLoggedIn: auth.isLoggedIn
}
}
render() {
return (
(this.state.isLoggedIn) ? <DrawerNavigator /> : <SignedOutNavigator />
);
}
}
AppRegistry.registerComponent('App', () => App)
and my auth module, which is very simple
import { AsyncStorage } from 'react-native';
// try to read from a local file
let api_key
let isLoggedIn = false
function save_user_settings(settings) {
AsyncStorage.mergeItem('user', JSON.stringify(settings), () => {
AsyncStorage.getItem('user', (err, result) => {
isLoggedIn = result.isLoggedIn
api_key = result.api_key
});
isLoggedIn = true
});
}
module.exports.save_user_settings = save_user_settings
module.exports.api_key = api_key
module.exports.isLoggedIn = isLoggedIn
First off, there are loads of ways to approach this problem. Because of this I'm going to try explain to you why what you have now isn't working.
The reason this is happening is because when you assign auth.isLoggedIn to your isLoggedIn state, you are assigning the value once, kind of as a copy. It's not a reference that is stored.
In addition to this, remember, React state is generally only updated with setState(), and that is never being called here, so your state will not update.
The way I would approach this problem without bringing in elements like Redux, which is overkill for this problem by itself, is to look into building an authentication higher order component which handles all the authentication logic and wraps your entire application. From there you can control if you should render the children, or do a redirect.
Auth Component
componentDidMount() {
this._saveUserSettings(settings);
}
_saveUserSettings(settings) {
AsyncStorage.mergeItem('user', JSON.stringify(settings), () => {
AsyncStorage.getItem('user', (err, result) => {
isLoggedIn = result.isLoggedIn
api_key = result.api_key
});
this.setState({isLoggedIn: true});
});
}
render() {
const { isLoggedIn } = this.state;
return isLoggedIn ? this.props.children : null;
}
App.js
render() {
<AuthComponent>
//the rest of authenticated app goes here
</AuthComponent>
}
Here's a really quick, incomplete example. But it should showcase to you how you may want to lay your authentication out. You'll also want to consider error handling and such, however.