I can return values from the Redux State, and when I make an API call, I see those being logged out but the state is not updating. I setup a basic App with a Redux Store to prove this out. And I've also include the Thunk library, which I read is needed for asynchronous State actions (like API). My guess is I am not returning the state properly at the end of the reducer, because again, if I log the values, there is detail being returned.
To consolidate the code as best as possible, I am only including the necessary pieces:
export default function App() {
const OtherVal = useSelector((state) => state.reducer2.WorkoutDetails);
const dispatch = useDispatch();
React.useEffect (() =>{
dispatch({ type: 'Test_API' })
})
return (
<View style={styles.container}>
<Text>Open up App.js to start rking on your app!</Text>
<Text>Value is: {OtherVal}</Text>
<StatusBar style="auto" />
</View>
);
}
My Store:
import {createStore, applyMiddleware } from 'redux';
import rootReducer from './index';
import thunk from 'redux-thunk'
const store = createStore(rootReducer, applyMiddleware(thunk) );
export default store;
My Root Reducer (could be in the Store):
import { combineReducers } from 'redux'
import reducer2 from './FirstReducer'
export default combineReducers({
reducer2,
})
My Reducer:
import axios from 'axios';
const initialState = {
WorkoutDetails:null,
}
const reducer2 = (state = initialState, action) => {
if(action.type == 'Test_API'){
axios.post('https://xxxx',{},
{
headers:{
'X-Requested-With':'XMLHttpRequest'
, 'Content-Type': 'application/json',
}
}
).then(function (response) {
// handle success
console.log('API Call 2')
const val = JSON.parse(response.data.recordset[0].EndPoint)
console.log('JSON Val', val)
return {...state,WorkoutDetails: val}
})
}
console.log('default Red',state)
return state
}
export default reducer2;
Instead of calling your API in reducer , you can call it in Action creator and then dispatch your action with payload as response. Based on the dispatch action type you can return your updated state to store through reducer.
Component dispatch an action -> apply your thunk (api call)->dispatch an action with API response data ->reducer update the store with correct state based on the action type.
Please refer the below URL:
https://www.freecodecamp.org/news/redux-thunk-explained-with-examples/
Related
Im using react native with expo, I have redux persist and im able to use it fine to get the state from the store however when i reload the app, the state does not persist.
Im wondering if there is some other step to have your state sync with the async storage state.
I am using a dispatch method to change the state, Then i print the updated state - all good.
But then i print what is stored in the asyc storage and only the old state is stored.
I have also done this on web browser and seen the same thing, The state updates fine in the application, Then when i check the storage nothing has changed and it still has the 'initialState' variable as state and the updates are not being added to the storage.
heres my outer component:
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<ThemeProvider theme={theme}>
<AppGateway />
</ThemeProvider>
<StatusBar style="auto" />
</PersistGate>
</Provider>
The dispatch method to update the state:
async function handleSubmit() {
dispatch(createUser(fakeuser)).....
The reducer/duck
const CREATEUSER = 'createuser'
export const createUser = ({name, id, email}) => ({
type: CREATEUSER,
payload: {
id,
name,
email,
created_account: Math.floor(Date.now() / 1000),
last_login: Math.floor(Date.now() / 1000),
}
})
function userReducer (state = initialState, action) {
switch (action.type) {
case CREATEUSER:
return {...state, account:{...state.account, ...action.payload} }
break;
default:
return state
}
}
export default userReducer;
and my config
import {createStore , combineReducers} from 'redux'
import userReducer from './ducks/user'
import { persistStore, persistReducer } from 'redux-persist'
// import AsyncStorage from 'redux-persist/lib/storage'
import AsyncStorage from '#react-native-async-storage/async-storage';
const reducer = combineReducers({
user: userReducer
})
const persistConfig = {
key: 'root',
storage: AsyncStorage,
}
const persistedReducer = persistReducer(persistConfig, reducer)
export default () => {
let store = createStore(persistedReducer)
let persistor = persistStore(store)
return { store, persistor }
}
I understand this kind of question was already asked several times here at StackOverflow. But I tried all the recommended solutions and nothing works for me. I'm running out of ideas.
The problem is with a React Native application for Android. Basically, the app provides a search bar to search an underlying database. The search results should be put into the store.
I use Redux v4.0.5, React-Redux v7.1.3, React v16.12.0 and React Native v0.61.5. For debugging, I use React Native Debugger in the latest version.
Now the simplified code. First, the component with the search bar. Here, mapStateToProps() is called. User makes an input and useEffect() immediately runs the database query, which should result in immediately calling mapStateToProps().
import React, {useEffect, useRef, useState} from 'react';
import {connect} from 'react-redux';
import {RootState} from '../../../rootReducer/rootReducer';
import {setResultValueSearchBar} from '../../../store/searchBar/actions';
imports ...
type Props = {};
const SearchBar: React.FC<Props> = () => {
const [returnValue, setReturnValue] = useState('');
const [inputValue, setInputValue] = useState('');
useEffect(() => {
// get query results
// logic to finally get a result string that should be put into the store
const resultNames: string = resultNamesArray.toString();
// method to set local and Redux state
const sendReturnValueToReduxStore = (resultNames: string) => {
setReturnValue(resultNames);
setResultValueSearchBar({resultValue: resultNames});
console.log('result value sent to store ', resultNames);
};
// call above method
sendReturnValueToReduxStore(resultNames);
}, [inputValue, returnValue]);
return (
<View>
<ScrollView>
<Header searchBar>
<Item>
<Input
placeholder="Search"
onChangeText={text => setInputValue(text)}
value={inputValue}
/>
</Item>
</Header>
</ScrollView>
</View>
);
};
function mapStateToProps(state: RootState) {
console.log("map state to props!", state); // is only called one time, initially
return {
resultValue: state.searchBarResult.resultValue,
};
}
const mapDispatchToProps = {
setResultValueSearchBar,
};
export default connect(mapStateToProps, mapDispatchToProps)(SearchBar);
Here is the rootReducer:
import {combineReducers} from 'redux';
import searchBarResultReducer from '../store/searchBar/reducers';
import reducer2 from '../store/reducer2example/reducers';
const rootReducer = combineReducers({
searchBarResult: searchBarResultReducer,
reducer2Result: reducer2,
});
export type RootState = ReturnType<typeof rootReducer>;
Here is the searchBarResultReducer in reducers.ts file:
import {
SearchBarResultState,
SET_RESULT_VALUE_SEARCHBAR,
ResultValueType,
} from './types';
const initialState: SearchBarResultState = {
resultValue: 'No results',
};
// take state and action and then return a new state
function searchBarResultReducer(
state = initialState,
action: ResultValueType,
): SearchBarResultState {
console.log('invoked result: ', action.type); // called only initially
if (action.type === 'SET_RESULT_VALUE_SEARCHBAR') {
return {
...state,
...action.payload,
};
} else {
return state;
}
}
export default searchBarResultReducer;
And the corresponding types.ts ...
export const SET_RESULT_VALUE_SEARCHBAR = 'SET_RESULT_VALUE_SEARCHBAR';
export interface SearchBarResultState {
resultValue: string;
}
interface ResultValueAction {
type: typeof SET_RESULT_VALUE_SEARCHBAR;
payload: SearchBarResultState;
}
export type ResultValueType = ResultValueAction
... and the actions.ts:
import {SET_RESULT_VALUE_SEARCHBAR, ResultValueType, SearchBarResultState} from './types'
export const setResultValueSearchBar = (resultValue: SearchBarResultState): ResultValueType => ({
type: SET_RESULT_VALUE_SEARCHBAR,
payload: resultValue,
});
And index.js:
import React from 'react';
import {AppRegistry} from 'react-native';
import {createStore, applyMiddleware, compose} from 'redux';
import {Provider} from 'react-redux';
import App from './App';
import {name as appName} from './app.json';
import rootReducer from './src/rootReducer/rootReducer';
import Realm from 'realm';
import { composeWithDevTools } from 'redux-devtools-extension';
import invariant from 'redux-immutable-state-invariant';
const composeEnhancers = composeWithDevTools({});
const store = createStore(
rootReducer,
composeEnhancers(applyMiddleware(invariant()))
);
const Root = () => {
Realm.copyBundledRealmFiles();
return (
<Provider store={store}>
<App />
</Provider>
);
};
AppRegistry.registerComponent(appName, () => Root);
To summarize: Whenever the database query succeeds, the result value should be sent to the store. But in the React Native Debugger/Redux Devtools, the reducer/mapStateToProps() is called only once and only, as shown by the console.log s in the code.
What is going on here?
Solved! As stated by Hemant in this Thread, you also have to pass the action that you import as props into the component. Works like a charm now :)
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 am using redux-persist to store the data in my react-native app.
This is the code:
store.js
import { createStore, compose, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import {
persistStore,
persistCombineReducers,
} from 'redux-persist';
import { AsyncStorage } from 'react-native';
import { composeWithDevTools } from 'redux-devtools-extension';
import user from './reducers/user';
import auth from './reducers/auth';
const config = {
key: 'root',
storage: AsyncStorage,
};
const reducers = persistCombineReducers(config, {
user,
auth
});
export const configureStore = () => {
const store = createStore(
reducers,
compose(
applyMiddleware(thunk),
)
);
const persistor = persistStore(store);
return { persistor, store };
};
Then in the App.js I have this :
const { persistor, store } = configureStore();
const onBeforeLift = () => {
// take some action before the gate lifts
store.dispatch(startingApp());
}
return (
<Provider store={store}>
<PersistGate
loading={<HomeLoader />}
onBeforeLift={onBeforeLift}
persistor={persistor}>
<RootNav />
</PersistGate>
</Provider>
Everything works fine when I dispatch and action from the App.js componentDidMount.
The problem is that when I fire the action from component, for example, the state is not stored, so when I restart the app the state is gone.
What I do in is just calling the action and passing the data:
this.props.onSetAuthData(data.credentials);
The state is updated as I can see in the console, but if I restart the app, only the state created by the action in App.js is saved, not the one in
Maybe this has to do with the RootNav component ?
maybe I am exporting wrong the reducers?
I have
const user = (state = initialState, action = {}) => {}
export default user.
Same for the other reducer:
const auth = (state = initialState, action = {}) => {}
export default auth.
Then I export with
combineReducers({auth, user})
Is this wrong?
Use tool like Reacttotron to see if your store is persisted or not.
https://github.com/infinitered/reactotron
If it's already persisted your component should wait until the store rehydrated on app launch. Sometimes I can't use the redux persist using persistgate to wait for the persisted store to be rehydrated. So I set the store and persistor into state on async componentWillMount then in your render, check if the store is not empty (null) and already rehydrated then load your app.
constructor(){
super();
this.state = {store: null, persistor: null}
}
async componentWillMount () {
const store = configureStore();
this.setState({ store: store.store })
this.setState({ persistor: store.persistor })
}
render(){
return (
if (this.state.store === null) {
return (
<View>
<Text>Loading...</Text>
</View>
);
}
<Provider store={this.state.store} persistor={this.state.persistor}>
<RootNav />
</Provider>
Also try to change your storage from AsyncStorage to storage.
const config = {
key: 'root',
storage,
};
First import the storage import storage from 'redux-persist/es/storage';
sometimes it calls an error with the key in persistConfig. try key: 'primary'
const primary = {
key: 'root',
storage: AsyncStorage,
blacklist: [],
whitelist: ['user'],
};
I'm getting the error:
Actions may not have an undefined "type" property.
But I'm sure I defined it and spelled it right.
App:
import React, {Component} from 'react';
import { createStore, applyMiddleware, combineReducers } from 'redux';
import { Provider } from 'react-redux';
import { AsyncStorage } from 'react-native';
import thunk from 'redux-thunk';
import {persistStore, autoRehydrate} from 'redux-persist';
import FBLoginView from '../components/FBLoginView'
import * as reducers from '../reducers';
import Routing from './Routing';
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const reducer = combineReducers(reducers);
const store = createStoreWithMiddleware(reducer, undefined, autoRehydrate());
persistStore(store, {
storage: AsyncStorage,
}, () => {
})
export default class App extends Component {
render() {
return (
<Provider store={store}>
<Routing />
</Provider>
);
}
}
Actions:
import * as types from './actionTypes';
export function getFacebookUser(user) {
return {
type: types.GET_FACEBOOK_USER,
user: user,
};
}
Types:
export const GET_FACEBOOK_USER = 'GET_FACEBOOK_USER';
Reducer:
import * as types from '../actions/actionTypes';
const initialState = {
user: {},
};
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case types.GET_FACEBOOK_USER:
return {
...state,
user: action.user
};
default:
return state;
}
}
Edit (My home.js page)
import React, { Component } from 'react'
import { View, Text, StyleSheet, Image, TouchableHighlight } from 'react-native'
import { Actions } from 'react-native-router-flux'
import {FBLogin, FBLoginManager} from 'react-native-facebook-login'
import FBLoginView from '../components/FBLoginView'
import * as facebookActions from '../actions/facebookActions';
import { connect } from 'react-redux'
import {bindActionCreators} from 'redux'
class Home extends Component {
constructor(props) {
super(props);
this.state = {
login: false
};
console.log(this.props)
}
render() {
let { facebook, actions } = this.props
_onLogin = (e) => {
actions.getFacebookUser(e.profile)
console.log(facebook)
}
_onLogout = (e) => {
console.log(e)
}
return (
<View style={styles.background}>
<Text>{this.state.login ? "Logged in" : "Logged out"}</Text>
<FBLogin
buttonView={<FBLoginView />}
ref={(fbLogin) => { this.fbLogin = fbLogin }}
loginBehavior={FBLoginManager.LoginBehaviors.Native}
permissions={["email","user_friends"]}
onLogin={function(e){_onLogin(e)}}
onLoginFound={function (e){console.log(e)}}
onLoginNotFound={function(e){console.log(e)}}
onLogout={function(e){_onLogin(e)}}
onCancel={function(e){console.log(e)}}
onError={function(e){console.log(e)}}
onPermissionsMissing={function(e){console.log(e)}}
style={styles.fbButton}
passProps={true}
/>
</View>
)
}
}
export default connect(store => ({
facebook: store.facebook.user,
}),
(dispatch) => ({
actions: bindActionCreators(facebookActions, dispatch)
})
)(Home);
const styles = StyleSheet.create({
background: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#00796B',
},
});
I don't think that you're dispatching the action correctly:
actions.getFacebookUser(e.profile)
is an action creator and will just return the action, not dispatch it.
I can't see your Home component that you're hooking up with Connect but I'd guess this is the source of events that you will want to dispatch as actions. Why not try dispatching directly against the store, and then move to use connect to hook up with mapDispatchToProps? Finally you can use bindActionCreators if this is necessary.
There are two very good (free) egghead.io courses that will help here, both by Dan Abramov:
https://egghead.io/courses/getting-started-with-redux
https://egghead.io/courses/building-react-applications-with-idiomatic-redux
and the docs are also very good, but I guess you've seen them.
After seeing more of the code, I can't see how the component you're connecting (Home) is linking its events (for example onLogin) to a dispatch property. I can see it caling its own internal function called _onLogin, but this just in turn call the action creator, it won't dispatch.
The connect function allows you connect properties on a component (here, Home) with the redux store; it effectively links, in your example, the 'onLogin' property of your Home component with a particular action and can then dispatch that action to the store.
So,your Home component needs to accept a property like 'onLogin' that it can then call; mapDispatchToProps is a function you write to marry up your child component's properties to dispatch actions. bindActionCreators is just a further helper to bind to action creators; it may be overkill in your current use case.
Dan Abramov explains this so much better than I can, so see the docs, but also see his answer here:
How to get simple dispatch from this.props using connect w/ Redux?