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);
}
Related
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.
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 trying to set up Redux in React for the first time and I can't seem to pass my initial state from the store to the component. My store file is setting state to the return value of the reducer. Here is what happens when I log this.props to the console
Component
import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { exampleAction } from '../../actions';
class Header extends React.Component {
constructor(props) {
super(props);
this.state = {}
}
render() {
console.log(this.props)
return (
<div>
<p>this is {this.props.examplePropOne}</p>
</div>
);
}
}
const mapStateToProps = state => ({
examplePropOne: state.examplePropOne,
examplePropTwo: state.examplePropTwo
});
const mapDispatchToProps = dispatch => {
return bindActionCreators({ exampleAction }, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(Header);
Reducer
import { EXAMPLE_ACTION } from './../actions/types'
const initialState = {
examplePropOne : 'Example Property One',
examplePropTwo : 'Example Property Two'
}
export default function (state = initialState, action) {
switch(action.type) {
case EXAMPLE_ACTION:
return {
...state,
examplePropOne: action.payload
}
default:
return state
}
}
Action
import { EXAMPLE_ACTION } from './types'
export const exampleAction = text => ({
type: EXAMPLE_ACTION,
payload: text,
})
[Edit]
Here is what happens when I log the state within mapStateToProps
import React from 'react';
import { createStore, combineReducers } from 'redux';
import reducers from '../reducers';
export const store = createStore(
combineReducers({
state: reducers
}),
);
With how combineReducers() was used with state passed in as a key, your mapStateToProps() would need to look like this instead to access examplePropOne and examplePropTwo:
const mapStateToProps = state => ({
examplePropOne: state.state.examplePropOne,
examplePropTwo: state.state.examplePropTwo
});
Given that combineReducers():
The state produced by combineReducers() namespaces the states of each
reducer under their keys as passed to combineReducers()
The issue is that:
export const store = createStore(
combineReducers({
state: reducers
}),
);
The key state passed to combineReducers() created a namespace/property of state. With the argument named state for the mapStateToProps(), requires that properties are accessed as state.state. This can probably be resolved by instead giving the key passed to combineReducers() a more descriptive name representing what is being used to manage in the store. For example, if it's related to authentication, it could be called some like auth. It would look like:
export const store = createStore(
combineReducers({
auth: reducers
}),
);
// ...
const mapStateToProps = state => ({
examplePropOne: state.auth.examplePropOne,
examplePropTwo: state.auth.examplePropTwo
});
Hopefully that helps!
I have created action and reducer for saving messages (array) in redux store. I have created actions and reducer for it but how can I display data once it is stored in redux store ?
reducer.js:
import { SAVE_ITEMS, SAVE_MESSAGES} from '../actions/types';
const initialState = {
messages: [],
items: []
}
export default function (state = initialState, action) {
switch (action.type) {
case SAVE_MESSAGES:
return {
...state,
messages: action.payload
};
default:
return state;
}
}
action.js:
import { SAVE_MESSAGES } from './types';
export const saveMessages = (messages) => ({
type: SAVE_MESSAGES,
payload: { messages }
})
In component I am saving data like this:
this.props.saveMessages(data)
and also the connect:
const mapStateToProps = state => ({
author: state.chat.author,
messages: state.chat.messages,
message: state.chat.message
})
export default connect (mapStateToProps, { saveAuthor, saveMessages, deleteAuthor, deleteMessage })(Chat);
In combineReducer i.e index.js:
import {combineReducers} from 'redux';
import users from './loginReducer'
import allusers from './userReducer'
import chatReducer from './chatReducer'
export default combineReducers({
users: users,
allusers: allusers,
chat: chatReducer
})
Now if I do console.log(this.props) see screenshot below:
Now if I do console.log(this.props.messages) see screenshot below:
Now I want to map over messages data and display it but I am getting error if I do this.props.messages.messages[0] -> error this.props.messages[0] gives undefined.
Screenshot: (redux tools)
I think first you can check if this.props.messages.messages is not undefined and then you can use map() to print messages like this:
{this.props.messages && this.props.messages.messages && this.props.messages.messages.map(function(msg,i) {
return (
<p>{msg.message}</p>
)
})}
Can anyone help me figure out why my Component is not updating after dispatch?
function mapStateToProps(state) {
const { isAuthenticated } = state
return { isAuthenticated }
}
class LoginForm extends Component {
handleSubmit(event) {
event.preventDefault();
const { dispatch } = this.props
const credentials = this.state
dispatch(attemptLogIn(credentials));
}
// Dispatch works properly when API validates Login:
// Redux Log:
// nextState:{ authReducer: {isAuthenticated: true}}
render() {
const { isAuthenticated } = this.props;
return (
<div>
{ isAuthenticated && <div> Authenticated: True </div> }
// Does not render even after dispatch
<form onSubmit={this.handleSubmit}>
{... Form Stuff ...}
</form>
</div>
)
}
export default withRouter(connect(mapStateToProps)(LoginForm))
Just simple conditional render from Redux store, I am expecting the extra div to show up to inform the user that he has authenticated, but It does not render.
This type of example of conditional rendering was used in the AsyncApp example during the Redux Async Tutorial, so I'm not sure why it doesn't work. My actions are dispatched, and reducers successfully update the state, passing it down to the connected component. Here are my reducers:
const initialState = { isAuthenticated: false}
const authReducer = (state = initialState, action) => {
switch(action.type){
case ('USER_AUTHENTICATED'): {
return Object.assign({}, state, {
isAuthenticated: true,
userPermissions: action.userInfo.userPermissions,
instanceType: action.userInfo.instanceType
}
)
}
case ('INVALID_CREDENTIALS'): {
return Object.assign({}, state, {
isAuthenticated:false
}
)
}
case ('LOG_OUT'): {
return initialState
}
default:
return state
}
}
const rootReducer = combineReducers({
authReducer,
routerReducer
})
export default rootReducer
Does anyone know why my Component does not re-render?
Change your mapStateToProps function to this.
function mapStateToProps(state) {
const { isAuthenticated } = state.authReducer;
return { isAuthenticated };
}