I want to access Redux Store State in presentational component, but the state is undefined. I made Container Component, Presentational Component, and Redux Store.
So I tried to access user
in presentational component but user
is undefined.
I use Redux-Thunk when create Redux Store for asynchronous API request.
The doubful part is that Rdux Store State has a value but i can't access the state user
in presentaional component
// headerState.js
import { handleActions } from 'redux-actions';
import * as api from '../lib/api';
const USER_STATE = 'headerState/USER_STATE';
const USER_STATE_SUCCESS = 'headerState/USER_STATE_SUCCESS';
const USER_STATE_FAILURE = 'headerState/USER_STATE_FAILURE';
const USER_LOGOUT = 'headerState/USER_LOGOUT';
const USER_LOGOUT_SUCCESS = 'headerState/USER_LOGOUT_SUCCESS';
const USER_LOGOUT_FAILURE = 'headerState/USER_LOGOUT_FAILURE';
export const getUserInfo = () => async (dispatch) => {
dispatch({ type: USER_STATE });
try {
const response = await api.getUser();
dispatch({
type: USER_STATE_SUCCESS,
payload: response.data,
});
} catch (error) {
dispatch({
type: USER_STATE_FAILURE,
payload: error,
error: true,
});
throw error;
}
};
export const userLogout = () => async (dispatch) => {
dispatch({ type: USER_LOGOUT });
try {
const response = await api.logout();
dispatch({
type: USER_LOGOUT_SUCCESS,
payload: response.data,
});
} catch (error) {
dispatch({
type: USER_LOGOUT_FAILURE,
payload: error,
error: true,
});
throw error;
}
};
const initialState = {
user: null,
loading: {
isProcessing: false,
},
};
const headerState = handleActions(
{
[USER_STATE]: (state) => ({
...state,
loading: {
...state.loading,
isProcessing: true,
},
}),
[USER_STATE_SUCCESS]: (state, action) => ({
...state,
user: action.payload,
loading: {
...state.loading,
isProcessing: false,
},
}),
[USER_STATE_FAILURE]: (state) => ({
...state,
loading: {
...state.loading,
isProcessing: false,
},
}),
[USER_LOGOUT]: (state) => ({
...state,
loading: {
...state.loading,
isProcessing: true,
},
}),
[USER_LOGOUT_SUCCESS]: (state, action) => ({
...state,
user: action.payload,
loading: {
...state.loading,
isProcessing: false,
},
}),
[USER_LOGOUT_FAILURE]: (state) => ({
...state,
loading: {
...state.loading,
isProcessing: false,
},
}),
},
initialState,
);
export default headerState;
// HeaderContainer.js
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import Header from './common/Header';
import { getUserInfo, userLogout } from '../modules/headerState';
const HeaderContainer = ({ user, getUserInfo, userLogout }) => {
useEffect(() => {
getUserInfo();
}, [getUserInfo]);
return <Header user={user} logout={userLogout} />;
};
const mapStateToProps = (state) => {
return {
user: state.user,
loading: state.loading,
};
};
export default connect(mapStateToProps, {
getUserInfo,
userLogout,
})(HeaderContainer);
// Header.js
(...)
const Header = ({ user, logout }) => { ** // user is undefined **
return (
<>
<TopNav>
<Link to="/" style={TitleLinkStyle}>
<NavTitle>
<img
src={logo}
width="50"
height="50"
color="white"
style={{ paddingRight: '25px', alignSelf: 'center' }}
alt="logo"
/>
Service Title
</NavTitle>
</Link>
<div style={{ display: 'flex', flex: 4 }} />
<SubNav>
<Link to="/home" style={{ textDecoration: 'none', color: 'white' }}>
<HomeDiv>Home</HomeDiv>
</Link>
{!user.snsId && (
<Link
to="/login"
style={{ textDecoration: 'none', color: 'white' }}
>
<LoginDiv>Login</LoginDiv>
</Link>
)}
{user.snsId && (
<LoginDiv onClick={logout} style={{ cursor: 'pointer' }}>
Logout
</LoginDiv>
)}
</SubNav>
</TopNav>
</>
);
};
export default Header;
There are two potential problems.
First, the initial value of user
is null until USER_STATE_SUCCESS
is dispatched. So trying to access user.snsId
in your presentation component will cause a type error:
Cannot read property 'snsId' of undefined
You can use optional chaining to attempt to access snsId
and simplify your logic.
// Header.js
const Header = ({ user }) => {
return user?.snsId ? <div>Logout</div> : <div>Login</div>;
};
The second issue potentially lies in how you are creating and accessing your store. I have assumed that you are doing something like this:
const reducers = combineReducers({
header: headerReducer
});
const store = createStore(reducers);
When using mapStateToProps
, your container component needs to access that state slice by the same key with which it was created - header
.
// HeaderContainer.js
const mapStateToProps = (state) => {
return {
user: state.header.user, // state.user is undefined
loading: state.header.loading // state.loading is undefined
};
};