I am a beginner in React.js. So, I am using theme from keenthemes. And it uses redux-persist and redux-saga for authentication with axios mock samples. It's original store initialState (persisted) is like this:
const initialAuthState = {
user: undefined,
authToken: undefined
};
But I want to implement authentication in my own style. So I changed the store into this format:
const initialAuthState= {
user: {
id: undefined,
username: undefined,
fullname: undefined,
email: undefined,
pic: undefined
},
authToken: undefined,
isAuthorized: false,
error: undefined
}
Then, I implemented some other codes required for authentication such as API calls, add some more actions, reducers, etc. After that, I ran the project and I noticed that the store remains still like the original.
Store
So, I searched the reasons and ways to solve that case, and tried to add migrations into persistconfig to inform the change of store structure. But the problem still remains. What should I do to change the store structure into the one that I want?
Migrations and PersistConfig from auth.duck.js:
const migrations = {
2: (state) => {
return {
...state,
user: {
id: undefined,
username: undefined,
fullname: undefined,
email: undefined,
pic: undefined
},
authToken: undefined,
isAuthorized: false,
error: undefined
}
}
}
const persistConfig = {
storage,
version: 2,
key: "demo1-auth",
whitelist: ['user', 'authToken', 'isAuthorized'],
migrate: createMigrate(migrations, { debug: true })
}
Reducer from auth.duck.js:
export const reducer = persistReducer(
// { storage, key: "demo1-auth", whitelist: ['user', 'authToken', 'isAuthorized']/*["user", "authToken"]*/ },
persistConfig,
(state = initialAuthState, action) => {
switch (action.type) {
// case actionTypes.Login: {
// return {...state, error: ""}
// }
case actionTypes.LOGIN_SUCCESS: {
//const { data } = action.payload;
//return action.payload.data//data//{...state, data}
return {...state, user: action.payload.data, authToken: action.payload.authToken, isAuthorized: true}
}
case actionTypes.LOGIN_FAIL: {
return {...state, error: action.payload.error}
}
// case actionTypes.register: {
// return {...state, error: ""}
// }
case actionTypes.REGISTER_SUCCESS: {
//const { data } = action.payload;
//return action.payload.data//data
return {...state, user: action.payload.data}
}
case actionTypes.REGISTER_FAIL: {
return {...state, error: action.payload.error}
}
case actionTypes.Logout: {
routerHelpers.forgotLastLocation();
logout(action.payload.id)
return iniState;
}
default:
return state;
}
}
);
Actions from auth.duck.js:
export const actions = {
login: (email, password) => ({
type: actionTypes.Login,
//payload: { email, password }
payload: { email: email, password: password }
}),
doLoginSuccess: (data) => ({
type: actionTypes.LOGIN_SUCCESS,
payload: {data: data}
}),
doLoginFail: (error) => ({
type: actionTypes.LOGIN_FAIL,
payload: {error: error}
//payload: error
}),
register: (username, fullname, email, password) => ({
type: actionTypes.Register,
//payload: { email, password }
payload: { username: username, fullname: fullname, email: email, password: password }
}),
doRegisterSuccess: (data) => ({
type: actionTypes.REGISTER_SUCCESS,
// payload: {data: data}
payload: {data: data}
//payload: data
}),
doRegisterFail: (error) => ({
type: actionTypes.REGISTER_FAIL,
payload: {error: error}
//payload: error
}),
logout: (id) => ({
type: actionTypes.Logout,
payload: { id: id}
})
};
Saga from auth.duck.js:
export function* saga() {
yield takeLatest( actionTypes.Login, function* loginSaga(action) {
// const {email, password} = action
const {email, password} = action.payload
// const payload = {email, password}
let data;
try {
// data = login(email, password)
/* data = yield call (login, payload)*/ data = yield call (login, email, password)
// data = {...data, isAuthorized: true, error: undefined}
console.log("Data in takelatest login in auth.duck: ", data)
yield put(actions.doLoginSuccess(data))
}
catch (error) {
console.log("Login axios post error is!: ", error)
yield put(actions.doLoginFail(error))
}
});
yield takeLatest(actionTypes.Register, function* registerSaga(action) {
// const {username, fullname, email, password} = action
const {username, fullname, email, password} = action.payload
// const payload = {username, fullname, email, password}
let data
try {
// data = register(username, fullname, email, password)
data = yield call (register, username, fullname, email, password)
// data = {...data, isAuthorized: false, error: undefined}
console.log("Data in takelatest register in auth.duck: ", data)
yield put(actions.doRegisterSuccess(data));
}
catch (error) {
console.log("Login axios post error is!: ", error)
yield put(actions.doRegisterFail(error));
}
});
rootReducer and rootSaga from rootDuck.js:
import { all } from "redux-saga/effects";
import { combineReducers } from "redux";
import * as auth from "./ducks/auth.duck";
import { metronic } from "../../_metronic";
import * as candi from "./ducks/candidateForm.duck";
export const rootReducer = combineReducers({
auth: auth.reducer,
i18n: metronic.i18n.reducer,
builder: metronic.builder.reducer,
candidateForm: candi.reducer
});
export function* rootSaga() {
yield all([auth.saga()]);
}
store.js:
import { applyMiddleware, compose, createStore } from "redux";
import createSagaMiddleware from "redux-saga";
import { persistStore } from "redux-persist";
import { rootReducer, rootSaga } from "./rootDuck";
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
rootReducer,
composeEnhancers(applyMiddleware(sagaMiddleware))
);
export const persistor = persistStore(store);
sagaMiddleware.run(rootSaga);
export default store;
Related
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)
In my React app I am working in user login. My goal is to show current user's username when the user is logged in. I'm fetching the user data in redux actions and, as I followed some tutorials, I need to get jwt token coming from backend in fetch function. In login Fetch function I'm trying to get and save the token(see fetching function), but it shows undefined in devtools/localStorage. This is how InitialState updates in LoginSuccess in Reducers.
state
{user: {…}, loading: true, error: "", isAuthenticated: false, users: {…}}
error: ""
isAuthenticated: false
loading: true
user: {user: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRVc…xMTB9.hNsYTKGYIFRsPXw66AhB1o0EXyyfgfRTzOFzqBfjaTg"}
users: {user: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRVc…xMTB9.hNsYTKGYIFRsPXw66AhB1o0EXyyfgfRTzOFzqBfjaTg"}
__proto__: Object
I don't know how to get access to the current logged in user data: username or firstName for instanse.
Any help will be appreciated.
Actions
import axios from 'axios'
import { Dispatch } from 'redux'
import {
FETCH_USER_REQUEST,
UserActions,
User,
LOGIN_USER_SUCCESS,
FETCH_LOGIN_FAILURE,
LOGOUT,
} from '../../types/UserType'
export const fetchUserRequest = () => {
return {
type: FETCH_USER_REQUEST,
}
}
export const fetchLoginFailure = (error: UserActions) => {
return {
type: FETCH_LOGIN_FAILURE,
payload: error,
}
}
export function logout(): UserActions {
return {
type: LOGOUT,
}
}
export function loginSuccess(user: User): UserActions {
return {
type: LOGIN_USER_SUCCESS,
payload: {
user,
},
}
}
export const login = ({ email, password }: any) => {
return (dispatch: Dispatch) => {
dispatch(fetchUserRequest())
axios
.post('http://localhost:8000/logIn', {
email: email,
password: password,
})
.then((response) => {
const users = response.data
dispatch(loginSuccess(users))
localStorage.setItem('jwt', users.auth_token)
console.log('users', users) // undefined
})
.catch((error) => {
dispatch(fetchLoginFailure(error.message))
})
}
}
Reducer
import {
LOGIN_USER_SUCCESS,
UserActions,
UserState,
LOGOUT,
} from '../../types/UserType'
const initialState: UserState = {
user: {},
loading: false,
error: '',
isAuthenticated: false,
}
const UserReducer = (state = initialState, action: UserActions) => {
switch (action.type) {
case LOGIN_USER_SUCCESS:
console.log('state', state) // initialState update see above
return {
...state,
loading: false,
user: action.payload,
users: action.payload,
isAuthenticated: true,
error: '',
}
case LOGOUT:
return {
...state,
isAuthenticated: false,
user: null,
users: [],
}
default:
return state
}
}
export default UserReducer
And I assume I am going to show user userName or firstName in logout component
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { Link } from 'react-router-dom'
import { Icon, Button } from 'semantic-ui-react'
import { logout } from '../../../redux/User/UserActions'
import { AppState } from '../../../types'
function Logout() {
const dispatch = useDispatch()
const user = useSelector((state: AppState) => state.user.user)
console.log('user', user)
const logoutOnClick = () => {
dispatch(logout())
localStorage.clear()
}
return (
<Button
color="black"
as={Link}
to="Login"
name="logout"
onClick={logoutOnClick}
>
<Icon name="sign out"> </Icon>Logout
</Button>
)
}
export default Logout
You save your logged-in data to localStorage like auth_token you did and clear in logout function.
axios
.post('http://localhost:8000/logIn', {
email: email,
password: password,
})
.then((response) => {
const users = response.data
dispatch(loginSuccess(users))
localStorage.setItem('jwt', users.auth_token)
localStorage.setItem('user', JSON.stringify(users))
console.log('users', users) // undefined
})
.catch((error) => {
dispatch(fetchLoginFailure(error.message))
})
and access inside your logout component or wherever you need that
let userDetails = JSON.parse(localStorage.getItem('user'));
and clear it inside logout function
const logoutOnClick = () => {
dispatch(logout())
localStorage.clear() // already clearing
}
I have created an authentification system in react with redux and axios but I can`t figure out how to render the data in my components.
This is my actions/auth.js:
import axios from 'axios';
import {
SIGNUP_SUCCESS,
SIGNUP_FAIL,
LOGIN_SUCCESS,
LOGIN_FAIL,
ACTIVATION_SUCCESS,
ACTIVATION_FAIL,
RESET_PASSWORD_SUCCESS,
RESET_PASSWORD_FAIL,
RESET_PASSWORD_CONFIRM_SUCCESS,
RESET_PASSWORD_CONFIRM_FAIL,
LOGOUT,
USER_LOADED_SUCCESS,
USER_LOADED_FAIL,
AUTHENTICATED_FAIL,
AUTHENTICATED_SUCCESS
} from './types';
export const checkAuthenticated = () => async dispatch => {
if (typeof window == 'undefined') {
dispatch({
type: AUTHENTICATED_FAIL
});
}
if (localStorage.getItem('access')) {
const config = {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ token: localStorage.getItem('access') });
try {
const res = await axios.post(`${process.env.REACT_APP_API_URL}/auth/jwt/verify/`, body, config);
if (res.data.code !== 'token_not_valid') {
dispatch({
type: AUTHENTICATED_SUCCESS
});
} else {
dispatch({
type: AUTHENTICATED_FAIL
});
}
} catch (err) {
dispatch({
type: AUTHENTICATED_FAIL
});
}
} else {
dispatch({
type: AUTHENTICATED_FAIL
});
}
};
export const load_user = () => async dispatch => {
if (localStorage.getItem('access')) {
const config = {
headers: {
'Content-Type': 'application/json',
'Authorization': `JWT ${localStorage.getItem('access')}`,
'Accept': 'application/json'
}
};
try {
const res = await axios.get(`${process.env.REACT_APP_API_URL}/auth/users/me/`, config);
dispatch({
type: USER_LOADED_SUCCESS,
payload: res.data
});
} catch (err) {
dispatch({
type: USER_LOADED_FAIL
});
}
} else {
dispatch({
type: USER_LOADED_FAIL
});
}
};
export const login = (email, password) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ email, password });
try {
const res = await axios.post(`${process.env.REACT_APP_API_URL}/auth/jwt/create/`, body, config);
dispatch({
type: LOGIN_SUCCESS,
payload: res.data
});
dispatch(load_user());
} catch (err) {
dispatch({
type: LOGIN_FAIL
});
}
};
export const logout = () => dispatch => {
dispatch({ type: LOGOUT });
};
This is my reducers/auth.js:
import {
SIGNUP_SUCCESS,
SIGNUP_FAIL,
LOGIN_SUCCESS,
LOGIN_FAIL,
LOGOUT,
AUTHENTICATED_FAIL,
AUTHENTICATED_SUCCESS,
USER_LOADED_SUCCESS,
USER_LOADED_FAIL
} from '../actions/types';
const initialState = {
access: localStorage.getItem('access'),
refresh: localStorage.getItem('refresh'),
isAuthenticated: null,
user: null
};
export default function(state = initialState, action) {
const { type, payload } = action;
switch(type) {
case AUTHENTICATED_SUCCESS:
return {
...state,
isAuthenticated: true
}
case LOGIN_SUCCESS:
localStorage.setItem('access', payload.access);
return {
...state,
isAuthenticated: true,
access: payload.access,
refresh: payload.refresh
}
case USER_LOADED_SUCCESS:
return {
...state,
user: payload
}
case AUTHENTICATED_FAIL:
return {
...state,
isAuthenticated: false
}
case USER_LOADED_FAIL:
return {
...state,
user: null
}
case LOGIN_FAIL:
case LOGOUT:
localStorage.removeItem('access');
localStorage.removeItem('refresh');
return{
...state,
access: null,
refresh: null,
isAuthenticated: false,
user: null
}
default:
return state
}
}
If I log in and use the redux Devtool I can see this state:
{
auth: {
access: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjAzNDgzODY1LCJqdGkiOiJhYTAzYzIzNTUwN2M0YTkxYjA2NjNmNDc0ZTU2MjIxMSIsInVzZXJfaWQiOjF9.Jyld4U7i6EqmsNoi0_qT9O9Kcu1TiEuyLLYCWWaoBrU',
refresh: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTYwMzU2OTk2NSwianRpIjoiOWIzMWIyN2M1ODkyNDRiZDk3Y2EwMDI1NTY2Mzk3ZWMiLCJ1c2VyX2lkIjoxfQ.UgH_753OoWD3NXiwPwa1645_vIHUl-FwyvQMJWMgHtk',
isAuthenticated: true,
user: {
name: 'Jonas Levin',
id: 1,
email: 'jonaslevin1903#gmail.com'
}
}
}
But I can`t figure out how to display the data, for example user.name.
I already tried to use mapStateToProps in one of my components but I get the error: "TypeError: Cannot read property 'name' of undefined"
const mapStateToProps = state => ({
userName: state.user.name,
userEmail: state.user.email
});
Edit
This is the response data that I get. But as you can see there is another API call which is still from the login page where I was on before I got redirected to '/' and that light red /me call has an error message in it because when your on the login page you don`t have an access token.
How can I access this response data in my Components to render the Name?
Store.js:
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const initialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
I managed to access the username in my layout.js file by adding the state to the props:
const mapStateToProps = (state, ownProps) => {
return {
isAuthenticated: state.auth.isAuthenticated,
user: state.auth.user,
props: ownProps
}
};
I used ownProps to be able to also use props.children in the layout container. Than I gave tham as parameters to the layout container and was able to access the username with user.name.
I´m not entirely sure why it worked now and not before when I already tried to use mapStateToProps.
this is how you should do it access auth reducer then th user
const mapStateToProps = state => ({
userName: state.auth.user.name,
userEmail: state.auth.user.email
});
this is how redux works , let's say this is your store
import {cartReducer} from './reducers/CartReducer'
import { authReducer } from './reducers/AuthReducer'
import { ordersReducer } from './reducers/OrdersReducer'
import { errorsReducer } from './reducers/ErrorsReducer'
const initialState={
products:{
items:[],
filterdProducts:[]
},
cart:{
items:[],
},
orders:{
items:[],
canOrder:true,
},
auth: {
access: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjAzNDgzODY1LCJqdGkiOiJhYTAzYzIzNTUwN2M0YTkxYjA2NjNmNDc0ZTU2MjIxMSIsInVzZXJfaWQiOjF9.Jyld4U7i6EqmsNoi0_qT9O9Kcu1TiEuyLLYCWWaoBrU',
refresh: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTYwMzU2OTk2NSwianRpIjoiOWIzMWIyN2M1ODkyNDRiZDk3Y2EwMDI1NTY2Mzk3ZWMiLCJ1c2VyX2lkIjoxfQ.UgH_753OoWD3NXiwPwa1645_vIHUl-FwyvQMJWMgHtk',
isAuthenticated: true,
user: {
name: 'Jonas Levin',
id: 1,
email: 'jonaslevin1903#gmail.com'
}
},
error:{
msg:null,
status:null,
id:null
}
}
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(combineReducers({
products: productsReducer,
cart : cartReducer ,
orders : ordersReducer ,
auth : authReducer,
error : errorsReducer ,
}),
initialState,
composeEnhancer(applyMiddleware(thunk))
)
export default store
if you want to access user from any other component you'mm need to access auth reducer first , same for items you can either access products.items or cart .items and so on
If you use a functional component, you could use useSelector hook.
const user = useSelector(state => state.auth.user)
I have the following redux module:
import { Map, fromJS } from 'immutable'
import api from '../lib/api'
import config from '../config/application'
import storage from '../lib/storage'
import { history } from '../lib/create-store'
// Actions
const USER_LOGIN = 'ts/user/USER_LOGIN'
const USER_LOGIN_RESPONSE = 'ts/user/USER_LOGIN_RESPONSE'
const USER_LOGOUT = 'ts/user/USER_LOGOUT'
const CREATE_ACCOUNT_TEAM = 'ts/user/CREATE_ACCOUNT_TEAM'
const CREATE_ACCOUNT_TEAM_RESPONSE = 'ts/user/CREATE_ACCOUNT_TEAM_RESPONSE'
const RECEIVE_ACCESS_TOKEN = 'ts/user/RECEIVE_ACCESS_TOKEN'
const RECEIVE_USER = 'adsdean/user/RECEIVE_USER'
const initialState = Map({
accessToken: null,
loginPending: false,
loginError: false,
creatingAccountTeam: false,
creatingAccountTeamSuccess: false,
creatingAccountTeamError: Map({}),
profile: Map({
id: null,
email: null,
firstName: null,
lastName: null,
company: null,
mobile: null,
mobileShare: true,
dob: null
})
})
// Reducer
export default function user (state = initialState, action = {}) {
switch (action.type) {
case RECEIVE_ACCESS_TOKEN: {
storage.save('key', action.token)
api.setAuth(action.token)
return state.set('accessToken', fromJS(action.token))
}
case USER_LOGIN:
return state.set('loginPending', true)
case USER_LOGIN_RESPONSE: {
let nextState = state.set('loginPending', false)
if (action.status === 'success') {
nextState = nextState
.set('loginError', initialState.loginError)
} else {
nextState = nextState.set('loginError', action.error)
}
return nextState
}
case USER_LOGOUT: {
storage.delete('key')
api.setAuth(null)
return state.set('accessToken', null)
}
case CREATE_ACCOUNT_TEAM:
return state.set('creatingAccountTeam', true)
case CREATE_ACCOUNT_TEAM_RESPONSE: {
console.log(action)
let nextState = state.set('creatingAccountTeam', false)
if (action.status === 'success')
state.set('creatingAccountTeamSuccess', true)
else
nextState = nextState.set('creatingAccountTeamError', fromJS(action.error))
return nextState
}
case RECEIVE_USER:
return state
.setIn('profile', 'id', action.payload.id)
.setIn('profile', 'email', action.payload.email)
.setIn('profile', 'firstName', action.payload.first_name)
.setIn('profile', 'lastName', action.payload.last_name)
.setIn('profile', 'company', action.payload.company)
.setIn('profile', 'mobile', action.payload.mobile)
.setIn('profile', 'mobileShare', action.payload.mobile_share)
.setIn('profile', 'dob', action.payload.dob)
}
return state
}
// ==============================
// Action Creators
// ==============================
export const userLoginResponse = (status, error) => ({
type: USER_LOGIN_RESPONSE,
status,
error,
})
export const receiveAccessToken = token => ({
type: RECEIVE_ACCESS_TOKEN,
token
})
export const userCreateWithTeamResponse = (status, error) => ({
type: CREATE_ACCOUNT_TEAM_RESPONSE,
status,
error,
})
export const receiveUser = user => ({
type: RECEIVE_USER,
payload: user
})
export const getAccessToken = state =>
state.get('accessToken')
export const isLoggedIn = state =>
state.get('accessToken')
// ==============================
// SIDE EFFECTS
// ==============================
//
//
export const getUser = () => async dispatch => {
api.request.get('/user')
.then(response => {
dispatch(receiveUser(response.data))
})
.catch(error => {
})
}
export const userLogin = (username, password) => async dispatch => {
dispatch({ type: USER_LOGIN })
api.request.post('/oauth/token', {
username,
password,
'grant_type': 'password',
'client_id': config.clientId,
'client_secret': config.clientSecret,
})
.then(response => {
console.log(response)
const { access_token } = response.data
dispatch(userLoginResponse('success', null))
dispatch(receiveAccessToken(access_token))
dispatch(receiveUser(response.data))
window.gtag('event', 'login')
})
.catch(error => {
console.log(error)
dispatch(userLoginResponse('error', error))
})
}
export const userLogout = () => dispatch => {
dispatch({ type: USER_LOGOUT })
history.push('/')
window.gtag('event', 'logout')
//logFormSubmission('Logout')
}
export const userCreateWithTeam = (form) => async dispatch => {
dispatch({ type: CREATE_ACCOUNT_TEAM })
api.request.post('/account/register/team', {
email: form.email,
password: form.password,
'first_name': form.firstName,
'last_name': form.lastName,
company: form.company,
name: form.name,
location: form.location,
dob: form.dob,
mobile: form.mobile,
'mobile_share': (form.mobileShare === true) ? 1 : 0
})
.then(response => {
console.log(response)
dispatch(userCreateWithTeamResponse('success', null))
dispatch(userLogin(form.email, form.password))
window.gtag('event', 'create account and team')
window.gtag('set', {'user_id': response.data.id})
})
.catch(error => {
console.log(error.response)
dispatch(userCreateWithTeamResponse('error', error.response.data))
})
}
everything was working fine until I added the side effect function named getUser. As soon as I do import { getUser } from '../../modules/user' from one of my components/containers I get hit with
Error: Reducer "user" returned undefined during initialization. If the state passed to the reducer is undefined, you must explicitly return the initial state. The initial state may not be undefined.
As soon as I comment out the import, everything else that uses the module continues working fine. What is it about that new side effect, that looks the same as the others that is causing this?
My create store code:
import { createStore, applyMiddleware, compose } from 'redux'
import { ConnectedRouter, routerReducer, routerMiddleware, push } from 'react-router-redux'
import createHistory from 'history/createBrowserHistory'
import { combineReducers } from 'redux-immutablejs'
import { fromJS, Map } from 'immutable'
import thunk from 'redux-thunk'
import user from '../modules/user'
const initialState = fromJS({})
const enhancers = []
const middleware = [
thunk,
routerMiddleware(history)
]
export const history = createHistory()
const reducer = combineReducers({
user,
router: routerReducer,
})
if (process.env.NODE_ENV === 'development') {
const devToolsExtension = window.devToolsExtension
if (typeof devToolsExtension === 'function') {
enhancers.push(devToolsExtension())
}
}
const composedEnhancers = compose(
applyMiddleware(...middleware),
...enhancers
)
const store = createStore(
reducer,
initialState,
composedEnhancers
)
export default store
I can fix the error by moving initialState Map directly into the default param part of the reducer... It seems the error is produced when having it in a separate const. However it's been working that way for weeks before the introduction of this function...
e.g
// Reducer
export default function user (state = Map({
accessToken: null,
loginPending: false,
loginError: false,
creatingAccountTeam: false,
creatingAccountTeamSuccess: false,
creatingAccountTeamError: Map({}),
profile: Map({
id: null,
email: null,
firstName: null,
lastName: null,
company: null,
mobile: null,
mobileShare: true,
dob: null
})
}), action = {}) {
setIn syntax that you have used is incorrect, the nested path needs to be inside [].
.setIn(['profile', 'id'], action.payload.id)
.setIn(['profile', 'email'], action.payload.email)
.setIn(['profile', 'firstName'], action.payload.first_name)
.setIn(['profile', 'lastName'], action.payload.last_name)
.setIn(['profile', 'company'], action.payload.company)
.setIn(['profile', 'mobile'], action.payload.mobile)
.setIn(['profile', 'mobileShare'], action.payload.mobile_share)
.setIn(['profile', 'dob'], action.payload.dob)
Check this documentation
also in
if (action.status === 'success')
state.set('creatingAccountTeamSuccess', true) . // <--
you missed to assign this to nextState which may lead to inconsistencies
Here is the code I'm playing with
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import axios from 'axios'
const initialState = {
user: {},
requesting: false,
err: null
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'REQ_USER_INIT': return { ...state, requesting: true }
case 'REQ_USER_DATA': return { ...state, requesting: false, user: action.user }
case 'REQ_USER_ERR': return { ...state, requesting: false, err: action.err }
}
return state;
}
const logger = (store) => (next) => (action) => {
let previous = JSON.stringify(store.getState())
next(action)
console.log(
'action: ' + JSON.stringify(action) +
'\n\tprevious: ' + previous +
'\n\tcurrent: ' + JSON.stringify(store.getState())
)
}
const store = createStore(reducer, applyMiddleware(logger, thunk))
store.dispatch((dispatch) => {
dispatch({ type: 'REQ_USER_INIT' })
// Fake Online REST API for Testing and Prototyping
// break url to get an error response
let usersEndpoint = 'https://jsonplaceholder.typicode.com/users/1'
axios.get(usersEndpoint)
.then((response) => {
dispatch({
type: 'REQ_USER_DATA',
user: {
id: response.data.id,
username: response.data.username,
email: response.data.email,
}
})
})
.catch((error) => {
dispatch({
type: 'REQ_USER_ERR',
err: error.message
})
})
})
I believe it is pretty straightforward, right? I dispatch REQ_USER_INIT and then REQ_USER_DATA once the response is received. I should log two actions, however I get 3. Second action is undefined and I am strugling to figure out what causes it. Is it a bug with redux-thunk or am I doing something wrong?
Here is the output from my console:
action: {"type":"REQ_USER_INIT"}
·previous: {"user":{},"requesting":false,"err":null}
·current: {"user":{},"requesting":true,"err":null}
action: undefined
·previous: {"user":{},"requesting":false,"err":null}
·current: {"user":{},"requesting":true,"err":null}
action: {"type":"REQ_USER_DATA","user":{"id":1,"username":"Bret","email":"Sincere#april.biz"}}
·previous: {"user":{},"requesting":true,"err":null}
·current: {"user":{"id":1,"username":"Bret","email":"Sincere#april.biz"},"requesting":false,"err":null}
The order of middlewares matters. Try making logger last
const store = createStore(reducer, applyMiddleware(thunk, logger))