Error says that it reads isAuthenticated as undefined, even though in my global state I have the variable under state.authReducer.isAuthenticated.
I'm using redux and it appears that I can't access the global state (I think the issue lies in store.js but I really don't know what exactly is the issue). Some fellow learner has posted a similiar (maybe identical) issue, but the answers did not help me as it still reads isAuthenticated as undefined.
store.js:
const initialState = {};
const middleware = [thunk];
const store = legacy_createStore(
rootReducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
authReducer:
const initialState = {
token: localStorage.getItem('token'),
isAuthenticated: null,
loading: true,
user: null,
};
const authReducer = (state = initialState, action) => {
const { type, payload } = action;
switch (type) {
case LOGIN_SUCCESS:
localStorage.setItem('token', payload.token);
return {
...state,
...payload,
isAuthenticated: true,
loading: false,
};
case LOGIN_FAIL:
localStorage.removeItem('token');
return {
...state,
token: null,
isAuthenticated: false,
loading: false,
};
default:
return state;
}
};
Login.js component:
const Login = ({ loginUser, isAuthenticated }) => {
const [formData, setFormData] = useState({
email: '',
password: '',
});
const { email, password } = formData;
const onChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const onSubmit = async (e) => {
e.preventDefault();
loginUser(email, password);
};
// Redirect if logged in
if (isAuthenticated) {
return <Navigate to='/dashboard' />;
}
return(some JSX form)
Login.propTypes = {
login: PropTypes.func.isRequired,
isAuthenticated: PropTypes.bool,
};
const mapStateToProps = (state) => ({
isAuthenticated: state.auth.isAuthenticated,
});
export default connect(null, { loginUser })(Login);
Edit: I found out that if I set connect() function first parameter to null, the component renders, but if I set the parameter to mapStateToProps it doesn't render (inside the component). Still, my issue is the same: isAuthenticated is undefined.
How are you defining rootReducer?
My guess, without looking, is that you're either treating all of the auth reducer as rootReducer, or calling combineReducers({authReducer}). In either case, there won't be a state.auth field, because your store configuration did not define one.
The short fix here is:
const rootReducer = combineReducers({
auth: authReducer
})
The better answer is to use our official Redux Toolkit package and its configureStore API, instead of the legacy createStore API:
const store = configureStore({
reducer: {
auth: authReducer
}
})
// this added `state.auth`, _and_ the thunk middleware,
// _and_ the Redux DevTools, in one function call!
You should also be using RTK's createSlice instead of writing reducers by hand.
Related
Trying to use redux toolkit for state management. I have the store and reducers all set up, and at first everything seemed to work fine. When I try accessing some of the state from a component all I get is 'undefined' BUT some state variables come through just fine so it is very confusing to me.
here is my store:
import { configureStore, combineReducers } from '#reduxjs/toolkit';
import productReducer from './productRedux';
import userReducer from './userRedux';
import {
persistStore,
persistReducer,
FLUSH,
REHYDRATE,
PAUSE,
PERSIST,
PURGE,
REGISTER,
} from 'redux-persist';
import storage from 'redux-persist/lib/storage';
const persistConfig = {
key: 'root',
version: 1,
storage,
};
const rootReducer = combineReducers({
product: productReducer,
user: userReducer,
});
const persistedReducer = persistReducer(persistConfig, rootReducer);
export const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoreActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}),
});
export let persistor = persistStore(store);
here is the userReducer:
import { createSlice } from '#reduxjs/toolkit';
const userSlice = createSlice({
name: 'user',
initialState: {
currentUser: 'TestUser',
isLoading: false, //this can get accessed and console.logged just fine
error: false,
msg: false,
random: 'hello',
},
reducers: {
loginStart: (state) => {
state.isLoading = true;
state.error = false;
},
loginSuccess: (state, action) => {
state.isLoading = false;
state.currentUser = action.payload;
state.msg = false;
},
loginFailure: (state) => {
state.isLoading = false;
state.error = true;
},
logout: (state) => {
state.currentUser = null;
},
},
});
export const {
loginStart,
loginSuccess,
loginFailure,
logout,
} = userSlice.actions;
export default userSlice.reducer;
Here is where i try accessing and console.log the state var:
import React from 'react';
import { useSelector } from 'react-redux';
import { AnalyticsCon } from './styles';
const Analytics = () => {
const error = useSelector((state) => state.user.error);
const isLoading = useSelector((state) => state.user.isLoading);
const currentUser = useSelector((state) => state.user.currentUser);
console.log(error); //console logs just fine
console.log(isLoading); //console logs just fine
console.log(currentUser); //for some reason undefined but it is in the state
return (
<AnalyticsCon>
<div>Analytics - view your store Analytics here</div>
</AnalyticsCon>
);
};
export default Analytics;
No matter how many variables I add to the state, I cannot access them in either reducer BUT I can access the ones that are there now. I do not understand this at all.
I am trying to add a reducer to my react native app.
Here is the store constant:
export const USER_PROFILE = 'USER_PROFILE';
Here is the action.js
import {USER_PROFILE} from '../constants/index';
export function userProfile(userReducer) {
return {
type: USER_PROFILE,
payload: {
email: '',
},
};
}
This the userReducer that is causing the error. I keep getting an error of customerReducer is not a function.
import {USER_PROFILE} from '../constants/index';
const initialState = {
userProfile: '',
};
const customerReducer = (state = initialState, action) => {
switch (action.type) {
case USER_PROFILE:
return {
...state,
userProfile: action.payload,
};
default:
return state;
}
};
export default customerReducer;
And I have declared the email as a state... const [email, setEmail] useState('')
calling the reducer here. const customerReducer = useSelector(state => state.userProfile);
Now dispatching it with the useDispatch method.
const dispatch = useDispatch();
...
dispatch(customerReducer(email));
Your action.js should look somthing like this:
import {USER_PROFILE} from '../constants/index';
export function userProfile(userReducer) {
return {
type: USER_PROFILE,
payload: {
email: userReducer.email,
},
};
and when you dispatch you should use the action "userProfile" not the reducer "customerReducer". Your dispatch should look somthing like this:
dispatch(userProfile({email}));
make sure to use curly brackets. beacause you should pass an object not a string.
first questioner here!
I'm new to React and find it confusing to manage state with redux. From the redux-logger output, it seems that I am successfully changing the redux state regarding a user sign-in but I don't really know how to set it to props, and as such, I'm getting an undefined value for currentUser (which is the prop I want to manage across all my pages). I'm using both withRouter and Redux in an effort to pass user properties to app.js.
It starts with an API call to the backend to see if the user can login, if success then returns an object {isAdmin: "", uId: ""}.
import React from "react";
import { withRouter } from "react-router-dom";
import { setCurrentUser } from "../../redux/user/user-actions";
import { connect } from "react-redux";
// sign-in.jsx
class Login extends React.Component {
constructor(props) {
super(props);
}
onSubmitClick = async (e) => {
e.preventDefault();
fetch("/api/login", {
method: "post",
body: JSON.stringify({
email: "",
password: "",
}),
})
.then((res) => res.json())
.then((user) => {
if (user.error) {
this.setState({ error: user.error });
} else {
// Set the user in redux too:
this.props.dispatch(setCurrentUser(user));
// Redirect to main page after login
this.props.history.push({
pathname: "/",
search: "?uid=" + user.key + "?admin=" + user.admin,
state: { userId: user.key, isAdmin: user.admin },
});
}
});
};
render() {
return (...)
}
const mapStateToProps = ({ user }) => ({
currentUser: user.currentUser,
});
export default connect(mapStateToProps)(withRouter(Login));
The line with code: this.props.dispatch(setCurrentUser(user)); successfully changed the state but not the props value.
Here is the redux stuff:
// user-actions.js --------------------------------------------------------------------------------------
export const setCurrentUser = (user) => ({
type: "SET_CURRENT_USER",
payload: user,
});
// user-reducer.js --------------------------------------------------------------------------------------
// The initial state is basically a null user (ID)
const initialState = {
user: null,
};
/*
This is essentially a function that takes the current state
and action as an argument and returns a new state result.
i.e. (state, action) => newState
*/
const userReducer = (state = initialState, action) => {
// Conditional for the current action type
if (action.type.localeCompare("SET_CURRENT_USER") === 0) {
// Return a new state object
return {
// Which has the existing data but also..
...state,
// The new user object (just an ID at this point)
user: action.payload,
};
} else {
// Otherwise we return the state unchanged
// (usually when the reducer doesnt pick up the certain action)
return state;
}
};
export default userReducer;
// store.js --------------------------------------------------------------------------------------
import { createStore, applyMiddleware } from "redux";
/*
Useful for debugging redux --> logger
Is a logger middleware that console.logs the actions fired and change of state
*/
import logger from "redux-logger";
import rootReducer from "./root-reducer";
const middlewares = [logger];
const store = createStore(rootReducer, applyMiddleware(...middlewares));
export default store;
// root-reducer.js --------------------------------------------------------------------------------------
import { combineReducers } from "redux";
import userReducer from "./user/user-reducer";
export default combineReducers({
user: userReducer,
});
And finally, the App.js relevant code
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
...props,
u_id: null,
};
}
unsubscribeFromAuth = null;
componentDidMount() {
const { setCurrentUser } = this.props;[enter image description here][1]
const userState = this.props.location;
console.log(this.props);
// Make sure that state for a user isnt undefined
if (userState.state) {
this.unsubscribeFromAuth = true;
const user = userState.state.userId;
this.props.dispatch(setCurrentUser(user));
}
console.log(this.props);
}
componentWillUnmount() {
this.unsubscribeFromAuth = false;
}
render() {
return (...)
}
}
const mapStateToProps = (state) => ({
currentUser: state.currentUser,
});
//Access the state and dispatch function from our store
const mapDispatchToProps = (dispatch) => ({
setCurrentUser: (user) => dispatch(setCurrentUser(user)),
dispatch,
});
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(App));
Console output with redux-logger:
https://i.stack.imgur.com/r9JyV.png
As you can see, currentUser is undefined but all props in the location are there, I'm probably making some really dumb mistake when setting currentUser with the setCurrentUser action, both in the login and then again in the componentDidMount in the app.jsx
I'll add more detail upon request
Any help would be appreciated GREATLY! :)
You are saving the user in redux under user but you are trying to access it in the mapStateToPRops via currentUser:
const mapStateToProps = (state) => ({ currentUser: state.currentUser, });
Change it to const mapStateToProps = (state) => ({ currentUser: state.user, });
and it should work.
Also this:
const mapDispatchToProps = (dispatch) => ({
setCurrentUser: (user) => dispatch(setCurrentUser(user)),
dispatch,
});
is equivalente to:
const mapDispatchToProps = ({
setCurrentUser
});
https://react-redux.js.org/using-react-redux/connect-mapdispatch#defining-mapdispatchtoprops-as-an-object
I am just started using redux in my react app and I have successfully added some values on my redux store.On the same component where dispatching happens I can access the store via
store.getState();
but on other components I can't access it by mapStateToProps or the above method. I really need to why this happens.
index.js
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={store} > <App /> </Provider>, rootElement);
store.js
import { createStore } from "redux";
import rootReducer from "../reducers/index";
const store = createStore(rootReducer);
export default store;
reducer.js
const initialState = {
token:"",email:"",uid:""
};
function userReducer(state = initialState, action) {
console.log("check ", state, action);
switch(action.type) {
case "ADD_USER":
return Object.assign({}, state, {
token : action.token,
email : action.email,
uid : action.uid
});
default : return state;
}
}
export default userReducer;
action.js
const addUser = (token,email,uid) => ({
type:"ADD_USER",token:token,email : email,uid:uid
})
export default addUser;
login.js
function mapDispatchToProps (dispatch) {
console.log(dispatch);
return { addUser : (token,email,uid)=> dispatch(addUser(token,email,uid))
};}
class Sample extends React.Component {
constructor(props){
super(props);
this.state = {...........}
}
componentDidMount() {
let token = localStorage.getItem("myToken");
let user = decode(token);
let uid = user.id;
let email = user.email;
this.props.addUser(token,email,uid);
console.log(this.props.state);
console.log(store.getState());
}
}
const mapStateToProps = state => {
return {state:state}
}
export default connect(mapStateToProps,mapDispatchToProps)(Sample);
anotherPage.js
export default function AnPage() {
const Data = useSelector(state=>state.userReducer);
useEffect(()=> {
somFunct(); },[]);
}
someFunct=() => {
console.log(Data) =>output is ({token: "", email: "", uid: ""})
return(
)
}
console output at reducer.js
check {token: "", email: "", uid: ""}token: ""email: ""uid: ""__proto__: Object {type: "ADD_USER",
token: "*******", email: "dfgsdhf#gmail.com", uid: 6264}
console.log(this.props.state)->
userReducer: {token: "", email: "", uid: ""}
__proto__: Object
console.log(store.getState()) ->
userReducer: {token: "*******", email: "dfgsdhf#gmail.com", uid: 6234}
__proto__: Object
I have edited the question.
I have found out that the reason for the initial values of state as output on other components was due to the fact that I was refreshing the page each time a new component was loaded.Since redux states have a special behaviour of wiping the state on refresh as I found in this stack I have to add 'Link' from react-router-dom to avoid refreshing and used redux-persist library to load the state if refreshed for other reasons.
I hope this will be helpful for someone who comes across on these kind of issues.
You shouldnt declare the state again in the constructor . You will get the state from the props using the mapStateToProps method.
export const mapStateToProps = function(state) {
return {
token: state.token,
email: state.email,
uid: state.uid
};
};
class Sample extends React.Component {
constructor(props){
super(props);
}
I have come across Redux Toolkit (RTK) and wanting to implement further functionality it provides. My application dispatches to reducers slices created via the createSlice({}) (see createSlice api docs)
This so far works brilliantly. I can easily use the built in dispatch(action) and useSelector(selector) to dispatch the actions and receive/react to the state changes well in my components.
I would like to use an async call from axios to fetch data from the API and update the store as the request is A) started B) completed.
I have seen redux-thunk and it seems as though it is designed entirely for this purpose, but the new RTK does not seem to support it within a createSlice() following general googling.
Is the above the current state of implementing thunk with slices?
I have seen in the docs that you can add extraReducers to the slice but unsure if this means I could create more traditional reducers that use thunk and have the slice implement them?
Overall, it is misleading as the RTK docs show you can use thunk, but doesn't seem to mention it not being accessible via the new slices api.
Example from Redux Tool Kit Middleware
const store = configureStore({
reducer: rootReducer,
middleware: [thunk, logger]
})
My code for a slice showing where an async call would fail and some other example reducers that do work.
import { getAxiosInstance } from '../../conf/index';
export const slice = createSlice({
name: 'bundles',
initialState: {
bundles: [],
selectedBundle: null,
page: {
page: 0,
totalElements: 0,
size: 20,
totalPages: 0
},
myAsyncResponse: null
},
reducers: {
//Update the state with the new bundles and the Spring Page object.
recievedBundlesFromAPI: (state, bundles) => {
console.log('Getting bundles...');
const springPage = bundles.payload.pageable;
state.bundles = bundles.payload.content;
state.page = {
page: springPage.pageNumber,
size: springPage.pageSize,
totalElements: bundles.payload.totalElements,
totalPages: bundles.payload.totalPages
};
},
//The Bundle selected by the user.
setSelectedBundle: (state, bundle) => {
console.log(`Selected ${bundle} `);
state.selectedBundle = bundle;
},
//I WANT TO USE / DO AN ASYNC FUNCTION HERE...THIS FAILS.
myAsyncInSlice: (state) => {
getAxiosInstance()
.get('/')
.then((ok) => {
state.myAsyncResponse = ok.data;
})
.catch((err) => {
state.myAsyncResponse = 'ERROR';
});
}
}
});
export const selectBundles = (state) => state.bundles.bundles;
export const selectedBundle = (state) => state.bundles.selectBundle;
export const selectPage = (state) => state.bundles.page;
export const { recievedBundlesFromAPI, setSelectedBundle, myAsyncInSlice } = slice.actions;
export default slice.reducer;
My store setup (store config).
import { configureStore } from '#reduxjs/toolkit';
import thunk from 'redux-thunk';
import bundlesReducer from '../slices/bundles-slice';
import servicesReducer from '../slices/services-slice';
import menuReducer from '../slices/menu-slice';
import mySliceReducer from '../slices/my-slice';
const store = configureStore({
reducer: {
bundles: bundlesReducer,
services: servicesReducer,
menu: menuReducer,
redirect: mySliceReducer
}
});
export default store;
I'm a Redux maintainer and creator of Redux Toolkit.
FWIW, nothing about making async calls with Redux changes with Redux Toolkit.
You'd still use an async middleware (typically redux-thunk), fetch data, and dispatch actions with the results.
As of Redux Toolkit 1.3, we do have a helper method called createAsyncThunk that generates the action creators and does request lifecycle action dispatching for you, but it's still the same standard process.
This sample code from the docs sums up the usage;
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit'
import { userAPI } from './userAPI'
// First, create the thunk
const fetchUserById = createAsyncThunk(
'users/fetchByIdStatus',
async (userId, thunkAPI) => {
const response = await userAPI.fetchById(userId)
return response.data
}
)
// Then, handle actions in your reducers:
const usersSlice = createSlice({
name: 'users',
initialState: { entities: [], loading: 'idle' },
reducers: {
// standard reducer logic, with auto-generated action types per reducer
},
extraReducers: (builder) => {
// Add reducers for additional action types here, and handle loading state as needed
builder.addCase(fetchUserById.fulfilled, (state, action) => {
// Add user to the state array
state.entities.push(action.payload)
})
},
})
// Later, dispatch the thunk as needed in the app
dispatch(fetchUserById(123))
See the Redux Toolkit "Usage Guide: Async Logic and Data Fetching" docs page for some additional info on this topic.
Hopefully that points you in the right direction!
You can use createAsyncThunk to create thunk action, which can be trigger using dispatch
teamSlice.ts
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
const axios = require("axios");
export const fetchPlayerList = createAsyncThunk(
"team/playerListLoading",
(teamId: string) =>
axios
.get(`https://api.opendota.com/api/teams/${teamId}/players`)
.then((response) => response.data)
.catch((error) => error)
);
const teamInitialState = {
playerList: {
status: "idle",
data: {},
error: {},
},
};
const teamSlice = createSlice({
name: "user",
initialState: teamInitialState,
reducers: {},
extraReducers: {
[fetchPlayerList.pending.type]: (state, action) => {
state.playerList = {
status: "loading",
data: {},
error: {},
};
},
[fetchPlayerList.fulfilled.type]: (state, action) => {
state.playerList = {
status: "idle",
data: action.payload,
error: {},
};
},
[fetchPlayerList.rejected.type]: (state, action) => {
state.playerList = {
status: "idle",
data: {},
error: action.payload,
};
},
},
});
export default teamSlice;
Team.tsx component
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { fetchPlayerList } from "./teamSlice";
const Team = (props) => {
const dispatch = useDispatch();
const playerList = useSelector((state: any) => state.team.playerList);
return (
<div>
<button
onClick={() => {
dispatch(fetchPlayerList("1838315"));
}}
>
Fetch Team players
</button>
<p>API status {playerList.status}</p>
<div>
{playerList.status !== "loading" &&
playerList.data.length &&
playerList.data.map((player) => (
<div style={{ display: "flex" }}>
<p>Name: {player.name}</p>
<p>Games Played: {player.games_played}</p>
</div>
))}
</div>
</div>
);
};
export default Team;
Use redux-toolkit v1.3.0-alpha.8
Try this
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit';
export const myAsyncInSlice = createAsyncThunk('bundles/myAsyncInSlice', () =>
getAxiosInstance()
.get('/')
.then(ok => ok.data)
.catch(err => err),
);
const usersSlice = createSlice({
name: 'bundles',
initialState: {
bundles: [],
selectedBundle: null,
page: {
page: 0,
totalElements: 0,
size: 20,
totalPages: 0,
},
myAsyncResponse: null,
myAsyncResponseError: null,
},
reducers: {
// add your non-async reducers here
},
extraReducers: {
// you can mutate state directly, since it is using immer behind the scenes
[myAsyncInSlice.fulfilled]: (state, action) => {
state.myAsyncResponse = action.payload;
},
[myAsyncInSlice.rejected]: (state, action) => {
state.myAsyncResponseError = action.payload;
},
},
});