useReducer inside context provider is only returning the initial state instead of updated state after dispatching and running reducer function.
When I console log the action inside reducer function, developer console logs the action object that I passed but it is not updated in the return of if statement.
GlobalContext.js
import { createContext } from 'react';
const GlobalContext = createContext({
auth: {
user: Object,
token: String,
addUserData: Function,
},
});
export default GlobalContext;
GlobalProvider.js
import { useReducer } from 'react';
import GlobalContext from './GlobalContext';
const initState = {
auth: {
user: {},
token: null,
},
};
const reducer = (state, action) => {
if (action.type === 'ADD_USER_DATA') {
console.log(action);
return {
...state,
auth: {
user: JSON.parse(action.payload.user),
token: action.payload.token,
},
};
}
};
const GlobalProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initState);
const value = {
auth: {
user: state.auth.user,
token: state.auth.token,
addUserData: (user, token) => {
dispatch({ type: 'ADD_USER_DATA', payload: { user, token } });
},
},
};
return (
<GlobalContext.Provider value={value}>{children}</GlobalContext.Provider>
);
};
export default GlobalProvider;
Related
I am learning how to create a React reducer to be used in a React context.
Can someone explain why the instructor created the {currentUser: null} object twice? Once in the context (line 7), once as an INITIAL_STATE to be used in the reducer (lines 26 and 29). Is any of it redundant?
import { createContext, useReducer, useEffect } from "react";
import {
onAuthStateChangedListener,
createUserDocumentFromAuth,
} from "../utils/firebase/firebase.utils";
export const UserContext = createContext({
currentUser: null,
setCurrentUser: () => null,
});
export const USER_ACTION_TYPES = {
SET_CURRENT_USER: "SET_CURRENT_USER",
};
const userReducer = (state, action) => {
const { type, payload } = action;
switch (type) {
case "SET_CURRENT_USER":
return {
...state,
currentUser: payload,
};
default:
throw new Error(`Unhandled type ${type} in userReducer`);
}
};
const INITIAL_STATE = {
currentUser: null,
};
export const UserProvider = ({ children }) => {
const [state, dispatch] = useReducer(userReducer, INITIAL_STATE);
const { currentUser } = state;
const setCurrentUser = (user) => {
dispatch({ type: USER_ACTION_TYPES.SET_CURRENT_USER, payload: user });
};
const value = { currentUser, setCurrentUser };
useEffect(() => {
const unsubscribe = onAuthStateChangedListener((user) => {
if (user) {
createUserDocumentFromAuth(user);
}
setCurrentUser(user);
});
return unsubscribe;
}, []);
return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
};
Thanks!
No none is redundant. The currentUser property in the INITIAL_STATE object is the initial state for your useReducer hook while the other currentUser is the initial state for your useContext hook.
I have created an app using react native typescript and redux. I have successfully configured the store and everything. But the problem is when I tried to login, I dispatch payload to authReducer and it returns undefined.
store.tsx
import AsyncStorage from "#react-native-async-storage/async-storage";
import { createStore, applyMiddleware } from "redux";
import { createLogger } from 'redux-logger';
import { persistStore, persistReducer } from "redux-persist";
import rootReducer from "../_redux/reducers/index";
const persistConfig = {
key: "root",
storage: AsyncStorage,
whitelist: ["authReducer"],
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
const store = createStore(persistedReducer, applyMiddleware(createLogger()));
let persistor = persistStore(store);
export { store, persistor };
authReducer.tsx
export const initState = {
isAuthenticated: false,
user: {},
token: "No Token",
};
const rootReducer = (state = initState, action: any) => {
if (action.type === "LOG_IN") {
const { isAuthenticated, user, token } = action.payload;
return {
...state,
isAuthenticated,
user,
token,
};
}
if (action.type === "LOG_OUT") {
const { isAuthenticated, user, token } = action.payload;
return {
...state,
isAuthenticated,
user,
token,
};
}
if (action.type === "UPDATE_PROFILE") {
return {
...state,
user: {
...state.user,
name: action.name,
email: action.email,
phone: action.phone,
},
};
}
return state;
};
export default rootReducer;
Login.tsx
axios
.post(`${BASE_URL}/login`, {
email: email,
password: password,
})
.then((response) => {
setLoading(false);
if (response.data.success) {
// response.data.user_data = {user_id: 1, user_name: "Jithin Varghese", user_email: "jithin#gmail.com", user_phone: "1234567890"}
// response.data.token = 1
logIn(true, response.data.user_data, response.data.token);
navigation.navigate("Home");
}
})
.catch((error) => {
console.log(error);
setLoading(false);
});
};
const mapDispatchToProps = (dispatch: any) => ({
logIn: ({ isAuthenticated, user, token }: any) => {
dispatch({
type: "LOG_IN",
payload: {
isAuthenticated,
user,
token,
},
});
},
});
const mapStateToProps = (state: any) => ({
isAuthenticated: state.authReducer.isAuthenticated,
user: state.authReducer.user,
token: state.authReducer.token,
});
export default connect(mapStateToProps, mapDispatchToProps)(Login);
The isAuthenticated, user, token is always undefined after dispatch. You can check the below screenshot for more details.
Initial load
After login axios call dispatch (logIn(true, response.data.user_data, response.data.token))
I have tried a lot to find a solution and I couldn't find any. What is the problem. I cannot figure this out.
I think prev state is causing the problem. It is empty initially.
I think you're passing 3 args to "logIn" instead of 1 arg as an object
instead of calling it like: logIn(true, response.data.user_data, response.data.token);
it should be like
logIn({isAuthenticated:true, user:response.data.user_data, token:response.data.token});
You are not passing data correctly in Action. You need to pass one argument and you are passing three arguments.
You need to pass data like this
logIn(isAuthenticated:true , user: response.data.user_data, token: response.data.token)
So my reducers are not updating the state for my app. The initial state in user shows up with no changes made. The initial state from uiReducer doesn't come up at all. emphasized textI can log in okay and receive a token, so I think it is a problem with getUserData. I have no idea why the ui initial state isn't working however. This is my first time trying to use react redux so I probably have a dumb mistake somewhere. Any help appreciated!
EDIT: Without changing code, I just restarted my app and now both of the initial states are loading, but user is still not populated with data.
login
import React, { Component } from 'react';
import PropTypes from 'prop-types'
// redux
import {connect} from 'react-redux'
import {loginUser} from '../redux/actions/userActions'
class login extends Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
errors: [],
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.UI.errors) {
this.setState({
errors: nextProps.UI.errors
});
}
}
handleChange = (event) => {
this.setState({
[event.target.name]: event.target.value
});
};
handleSubmit = (event) => {
event.preventDefault();
const userData = {
email: this.state.email,
password: this.state.password
};
this.props.loginUser(userData, this.props.history)
};
render() {
const { classes, UI: {loading} } = this.props;
const { errors } = this.state;
return (
..............
);
}
}
login.propTypes = {
classes: PropTypes.object.isRequired,
loginUser: PropTypes.func.isRequired,
user: PropTypes.object.isRequired,
UI:PropTypes.object.isRequired
}
const mapStateToProps = (state) => ({
user: state.user,
UI: state.UI
})
const mapActionsToProps = {
loginUser
}
export default connect(mapStateToProps, mapActionsToProps)(withStyles(styles)(login));
userActions
import axios from 'axios';
export const loginUser = (userData, history) => (dispatch) => {
dispatch({ type: 'LOADING_UI' });
axios
.post('/login', userData)
.then((res) => {
setAuthorizationHeader(res.data.token);
dispatch(getUserData())
dispatch({type: 'CLEAR_ERRORS'})
history.push('/');
})
.catch((error) => {
dispatch({
type: 'SET_ERRORS',
payload: error.response.data
});
});
}
export const getUserData = ()=> (dispatch) => {
axios
.get('/user')
.then(res => {
dispatch({
type: 'SET_USER',
payload: res.data
})
})
}
const setAuthorizationHeader = (token) => {
const FBIdToken = `Bearer ${token}`;
localStorage.setItem('FBIdToken', FBIdToken);
axios.defaults.headers.common['Authorization'] = FBIdToken;
};
userReducer
const initialState = {
authenticated: false,
credentials: {},
approves: []
}
export default function(state = initialState, action){
switch(action.type){
case 'SET_AUTHENTICATED':
return{
...state,
authenticated: true
}
case 'SET_UNAUTHENTICATED':
return initialState
case 'SET_USER':
return{
authenticated: true,
loading: false,
...action.payload
}
default:
return state
}
}
uiReducer
const initialState = {
loading: false,
errors: null
}
export default function(state = initialState, action) {
switch (action.type) {
case 'SET_ERRORS':
return {
...state,
loading: false,
errors: action.payload
};
case 'CLEAR_ERRORS':
return {
...state,
loading: false,
errors: null
};
case 'LOADING_UI':
return {
...state,
loading: true
};
default:
return state
}
}
I'm really confused that I can log in fine, but the state isnt updated, and only the user state is initialized..
EDIT:
store.js
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import userReducer from './reducers/userReducer';
import dataReducer from './reducers/dataReducer';
import uiReducer from './reducers/uiReducer';
const initialState = {};
const middleware = [thunk];
const reducers = combineReducers({
user: userReducer,
data: dataReducer,
UI: uiReducer
});
const store = createStore(reducers,
initialState,
compose(
applyMiddleware(...middleware),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
);
export default store;
Changing ...action.payload to user: action.payload seems to have fixed it. I got the code for this section from a tutorial but the syntax must have been slightly outdated or something, who knows
export default function(state = initialState, action){
switch(action.type){
case 'SET_AUTHENTICATED':
return{
...state,
authenticated: true
}
case 'SET_UNAUTHENTICATED':
return initialState
case 'SET_USER':
return{
authenticated: true,
loading: false,
user: action.payload
}
default:
return state
}
}
I have been trying to connect my Redux Action and Reducer to my component. But it doesn't seem to work properly.
Currently, when I call my Action, it does get to that Action but it does not move onto my reducer. I think I am missing something here but having a hard time finding out what is the issue.
Could anyone please help me with this issue?
Thank you.
Here is my Action:
export const getItem = () => {
return (dispatch, getState) => {
debugger;
dispatch({
type: 'API_REQUEST',
options: {
method: 'GET',
endpoint: `18.222.137.195:3000/v1/item?offset=0`,
actionTypes: {
success: types.GET_ITEM_SUCCESS,
loading: types.GET_ITEM_LOADING,
error: types.GET_ITEM_SUCCESS
}
}
});
};
};
Here is my Reducer:
export const initialState = {
getItem: {}
};
const registerItemReducer = (state = initialState, action) => {
switch (action.type) {
case types.GET_ITEM_LOADING:
debugger;
return { ...state, loading: true, data: null };
case types.GET_ITEM_SUCCESS:
debugger;
return { ...state, loading: false, getItem: action.data};
case types.GET_ITEM_ERROR:
debugger;
return { ...state, loading: false, error: action.data};
default: {
return state;
}
}
}
export default registerItemReducer;
Here is my store:
/* global window */
import { createStore, applyMiddleware, compose } from 'redux';
import { persistStore, persistCombineReducers } from 'redux-persist';
import storage from 'redux-persist/es/storage'; // default:
localStorage if web, AsyncStorage if react-native
import thunk from 'redux-thunk';
import reducers from '../reducers';
// Redux Persist config
const config = {
key: 'root',
storage,
blacklist: ['status'],
};
const reducer = persistCombineReducers(config, reducers);
const middleware = [thunk];
const configureStore = () => {
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__(),
compose(applyMiddleware(...middleware)),
);
const persistor = persistStore(
store,
null,
() => { store.getState(); },
);
return { persistor, store };
};
export default configureStore;
Lastly here is my component that has "connect" part & componentDidMount:
componentDidMount() {
this.props.getItem();
}
const mapStateToProps = state => ({
registerItem: state.registerItem || {},
});
const mapDispatchToProps = {
getItem: getItem
};
export default connect(mapStateToProps, mapDispatchToProps)(RegisterItemComponent);
Is registerItem name of your reducer? Your reducer has two state getItem and loading. But in the below code you are calling state.registerItem. Looks like there is some mismatch between the actual state and the mapped state.
In the code below, try to print the state value, it will help you to navigate to the exact parameter you are looking for.
Add the below line in your existing code to debug:
const mapStateToProps = state => ({
console.log("State of reducer" + JSON.stringify(state));
registerItem: state.registerItem || {},
});
I have read through 100's of these threads on here, and I can't seem to understand why my component isn't updating. I am pretty sure it has something to do with the Immutability, but I can't figure it out.
The call is being made, and is returning from the server. The state is changing (based on the redux-Dev-Tools that I have installed).I have made sure to not mutate the state in any instance, but the symptoms seem to point that direction.
Code Sandbox of whole app https://codesandbox.io/s/rl7n2pmpj4
Here is the component.
class RetailLocationSelector extends Component {
componentWillMount() {
this.getData();
}
getData = () => {
this.props.getRetailLocations()
}
render() {
const {data, loading} = this.props;
return (
<div>
{loading
? <LinearProgress/>
: null}
<DefaultSelector
options={data}
placeholder="Retail Location"/>
</div>
);
}
}
function mapStateToProps(state) {
return {
loading: state.retaillocations.loading,
data: state.retaillocations.data,
osv: state.osv};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
getRetailLocations,
selectRetailLocation,
nextStep
}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(RetailLocationSelector);
And here is my reducer :
import {REQUEST_RETAIL_LOCATIONS, SUCCESS_RETAIL_LOCATIONS,
ERR_RETAIL_LOCATIONS, SELECT_RETAIL_LOCATION} from
'../actions/RetailLocationsAction'
const initialState = {
data: [],
loading: false,
success: true,
selectedRetailLocation: undefined
}
function retailLocation(state = initialState, action) {
switch (action.type) {
case REQUEST_RETAIL_LOCATIONS:
return Object.assign({}, state, {
loading: true
}, {success: true})
case SUCCESS_RETAIL_LOCATIONS:
return Object.assign({}, state, {
loading: false
}, {
success: true
}, {
data: Object.assign([], action.payload.data)
})
case ERR_RETAIL_LOCATIONS:
return Object.assign({}, state, {
loading: false
}, {
success: false
}, {errorMsg: action.payload.message})
case SELECT_RETAIL_LOCATION:
return Object.assign({}, state, {
selectedRetailLocation: state
.data
.find((rec) => {
return rec.id === action.payload.id
})
})
default:
return state;
}
}
export default retailLocation
And finally, my Action file:
import axios from 'axios';
//import {api} from './APIURL'
export const REQUEST_RETAIL_LOCATIONS = 'REQUEST_RETAIL_LOCATIONS'
export const SUCCESS_RETAIL_LOCATIONS = 'SUCCESS_RETAIL_LOCATIONS'
export const ERR_RETAIL_LOCATIONS = 'ERR_RETAIL_LOCATIONS'
export const SELECT_RETAIL_LOCATION = 'SELECT_RETAIL_LOCATION'
const URL = 'localhost/api/v1/retail/locations?BusStatus=O&LocType=C'
export const getRetailLocations = () => (dispatch) => {
dispatch({ type: 'REQUEST_RETAIL_LOCATIONS' });
return axios.get(URL)
.then(data => dispatch({ type: 'SUCCESS_RETAIL_LOCATIONS', payload: data }))
.catch(error => dispatch({type : 'ERR_RETAIL_LOCATIONS', payload: error}));
}
Combined Reducer
import { combineReducers } from "redux";
import retailLocations from './RetailLocationsReducer'
import vendors from './VendorsReducer'
import receiptInformation from './ReceiptInfoReducer'
import osv from './OSVReducer'
import receiptDetail from './ReceiptDetailReducer'
const allReducers = combineReducers({
retaillocations: retailLocations,
vendors: vendors,
receiptInformation: receiptInformation,
receiptDetail: receiptDetail,
osv: osv
});
export default allReducers;
This answer doesn't solve your issue totally but provides some hints about what is not working. The broken part is your store definition. I don't have much experience with redux-devtools-extension or redux-batched-subscribe but if you define your store like that your thunk function works:
const store = createStore(reducer, applyMiddleware(thunk));
One of the configuration for the mentioned packages above brokes your thunk middleware.