I have the following React component connected to the Redux store, and even though the state of the store changes (I checked), the component prop userIsLogged won't change its value. Any help is appreciated!
const mapDispatchToProps = (dispatch) => bindActionCreators({deauthenticateUser}, dispatch);
const mapStateToProps = (state) => ({ userIsLogged: state.auth.loggedUser !== null });
const Logout = (props) => {
const { userIsLogged } = props;
return (
userIsLogged?
<Button
variant="outlined"
color="primary"
onClick={(e) => {
props.deauthenticateUser();
history.push('/login');
}}>
Exit
</Button>
:<div />
);
}
Logout.propTypes = {
userIsLogged: PropTypes.bool.isRequired
};
export default connect(mapStateToProps, mapDispatchToProps)(Logout);
The reducer is as follow:
const initialState = {
jwt: null,
loggedUser: null,
isLoading: false
}
export default function auth(state = initialState, action) {
switch (action.type) {
case 'GOT_JWT':
return Object.assign(state, { jwt: action.jwt });
case 'USER_LOGGING_IN':
return Object.assign(initialState, { isLoading: action.isLoading });
case 'USER_LOGGED_IN':
return Object.assign(state, { loggedUser: action.loggedUser, isLoading: false });
case 'NO_JWT':
return initialState;
case 'USER_LOGGED_OUT':
return initialState;
default:
return state;
}
}
In your reducer code you're mutating the passed state object.
What happens next is that react treats the state as unchanged (it's the same object), hence it does not re-render it.
To fix it change the
Object.assign(state, { jwt: action.jwt });
to
Object.assign({}, state, { jwt: action.jwt });
It would create a new object and copy properties from the original state + the new ones.
Related
this is my container
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import Login from './Login';
import { loginAction } from './LoginAction';
const mapStateToProps = (state, ownProps) => {
console.log('mapStateToProps', state)
return {
payload: state.Auth
};
};
const mapDispatchToProps = dispatch => {
console.log('mapDispatchToProps')
return bindActionCreators(
{
loginAction
},
dispatch
);
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Login);
this is my function in my component
const handleSubmit = async (event) => {
const form = event.currentTarget
if (form.checkValidity() === false) {
event.preventDefault()
event.stopPropagation()
return false
}
setValidated(true)
props.loginAction(username, password).then(() =>{
console.log('props loginAction', props) // this props not updated immediately after loginAction called
if (props.payload.error){
return false
}
})
// navigate('/dashboard')
}
useEffect(()=>{console.log('payload effect', props.payload)},[props.payload])
this is my reducer
import * as types from './LoginActionTypes';
var initialState = {
loading: false,
result: null,
error: null,
message: null,
};
function AuthReducer(state = initialState, action) {
switch (action.type) {
case types.ACTION_REQUEST:
return Object.assign({}, state, {
loading: true,
error: null
});
break;
case types.AUTHENTICATED:
return Object.assign({}, state, {
loading: false,
status: true,
result: action.payload
});
break;
case types.AUTHENTICATION_ERROR:
return Object.assign({}, state, {
loading: false,
status: false,
error: action.error
});
break;
case types.UNAUTHENTICATED:
return Object.assign({}, state, initialState);
break;
default:
return state;
}
}
export default AuthReducer;
my question is how to get updated props after i call loginAction?
when im using react component is work fine, but im not sure if the problem is the functional component, any suggestion?
props.loginAction(username, password).then(() =>{
console.log('props loginAction', props) // this props not updated immediately after loginAction called
if (props.payload.error){
return false
}
})
this console that proof props mapStateToProps have new props, but the props still not updated, and useEffect is updated
To get actual props:
//add this
const propsRef = React.useRef(props);
propsRef.current = props;
props.loginAction(username, password).then(() =>{
console.log('props loginAction', propsRef.current) // <-- change this
if (propsRef.current.payload.error){ // <--- and this
return false
}
})
I am having a code that looks like this
reducer
const initState = { isLoggedIn: false };
const isLoggedInReducer = (state = initState, action) => {
switch (action.type) {
case "LOG_IN":
return { isLoggedIn: true };
case "LOG_OUT":
return { isLoggedIn: false };
default:
return {isLoggedIn:false};
}
};
export default isLoggedInReducer;
action
export const logIn = () => {
return {
type:'LOG_IN'
}
}
export const logOut = () => {
return {
type:'LOG_OUT'
}
}
screen
import React from 'react'
import {useDispatch,useSelector} from 'react-redux'
import {logIn , logOut} from '../redux/actions/isLoggedInAction'
const AuthScreen = () => {
console.log('auth page re-rendered')
let status = useSelector(state => state.isLoggedIn)
console.log(status)
const dispatch = useDispatch()
return <>
<h1> auth is {status} status</h1>
<button onClick={()=>dispatch(logIn())}>authenticate me</button>
<button onClick={()=>dispatch(logOut())}>un auth me</button>
</>
}
export default AuthScreen
The problem is, something causes the app to render twice, and update the store
The variable should not have changed unless I dispatch an action, which I clearly did not. Also the value of the variable is logged out but doesnt print inside the h1 tag.
If I change the default case of the reducer to something like
const initState = { isLoggedIn: false };
const isLoggedInReducer = (state = initState, action) => {
switch (action.type) {
case "LOG_IN":
return { isLoggedIn: true };
case "LOG_OUT":
return { isLoggedIn: false };
default:
return {isLoggedIn:' hello world'};
}
};
export default isLoggedInReducer;
Then I get this output
The above output suggests that the default case was somehow run. But again, I did not dispatch any action to it. I am only reading the data using the "useSelect" but something is dispatching actions that I dont know about.
I am very new to redux and trying to learn. Thanks for your time.
In your default case, return the state as is:
default:
return state;
If you return a new object, React will treat the state as having changed and rerender the component, as equality is checked by ref by default.
I have two redux state variables, one that hold an array of user information and one that holds a true/false value for a side drawer open/close feature. The true/false value triggers a className change which triggers CSS to open/close the drawer. The array of user information is fetched from a firebase cloud firestore database collection.
For some reason after the user array is fetched and saved to the redux state and a user opens the side drawer the redux action sent is only for the side drawer, but the side drawer and users information is changed.
The side drawer opens like normal, but the user array is set to null.
Redux Events:
Initial State: https://imgur.com/a/IgvXMLe
After side drawer is opened: https://imgur.com/a/wVRg6Az
Side Drawer Event Difference: https://imgur.com/a/u1hrcvT
Side Drawer Component
class SideDrawer extends Component {
render() {
let drawerClasses = ['side-drawer'];
if (this.props.toggled) {
drawerClasses = ['side-drawer', 'open'];
}
return (
<div className={drawerClasses.join(' ')} >
<div className="side-drawer-container" >
<div className="router-login-button" onClick={this.props.toggleSideDrawer} >
<OktaAuthButton />
</div>
<div className="side-drawer-link" onClick={this.props.toggleSideDrawer} >
<Link to="/" >Map</Link>
</div>
<div className="side-drawer-link" onClick={this.props.toggleSideDrawer} >
<Users />
</div>
</div>
</div>
)
}
}
const mapStateToProps = ({ sideDrawer }) => ({
toggled: sideDrawer.toggled,
});
const mapDispatchToProps = (dispatch) => {
return {
toggleSideDrawer: () => dispatch({ type: TOGGLE_SIDEDRAWER, payload: true })
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SideDrawer);
Side Drawer Reducer
import { TOGGLE_SIDEDRAWER } from './actions';
const initialState = {
toggled: false
};
export default function sideDrawerReducer(state = initialState, action) {
switch (action.type) {
case TOGGLE_SIDEDRAWER:
return Object.assign({}, state, {
toggled: action.payload
});
default:
return state;
}
}
Users Component
class Users extends Component {
/* commented code not needed to be shown */
componentDidMount() {
initializeFirebaseApp();
// Get user list from firestore 'users' collection
this.loadUsers();
}
async loadUsers() {
getAllUsers().then((users) => {
this.props.setUsers(users);
});
}
render() {
if(this.props.users != null) {
var users = this.props.users.map((el, i) => (
<li key={el.id} className='user' onClick={this.props.toggleSideDrawer}><Link to={"/user/" + el.id}>{el.firstname}</Link></li>
));
console.log(users);
}
console.log(this.props.users);
return (
<div className="user-container">
{users}
</div>
)
}
}
const mapStateToProps = ({ users }) => ({
users: users.friends
});
const mapDispatchToProps = (dispatch) => {
return {
setUsers: (users) => dispatch({type: SET_FRIENDS, payload: users}),
toggleSideDrawer: () => dispatch({ type: TOGGLE_SIDEDRAWER, payload: false })
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Users);
Users Reducer
import { SET_FRIENDS } from './actions';
const initialState = {
friends: null,
groups: null
};
export default function userReducer(state = initialState, action) {
switch(action.type) {
case SET_FRIENDS:
return Object.assign({}, state, {
friends: action.payload
});
default:
return initialState;
}
}
I expect the side drawer to open and render the list of users in the drawer under the "Login" and "Map" Links
The default case for userReducer is returning initialState instead of state so every action through the redux store that is not SET_FRIENDS (e.g. TOGGLE_SIDEDRAWER) will reset the userReducer to initialState where users is null. Return state instead and you should be good to go.
export default function userReducer(state = initialState, action) {
switch(action.type) {
case SET_FRIENDS:
return Object.assign({}, state, {
friends: action.payload
});
// Change to `return state;`
default:
return initialState;
}
}
I am new in redux.
My code :
Home Screen
<Text> {{this.props.mycity}} </Text>
const mapStateToProps = function(state) {
return {
mycity: state.layersFlag.baseDistrictADhabi //consist true/false
}
}
export default connect(mapStateToProps)(HomeScreen);
Sidemenu Screen :
UI
<Switch onValueChange={(flag) => {
this.props.toggleCity();
} value={this.state.city} />
const mapDispatchToProps = dispatch => {
return {
toggleCity: () => {
dispatch({ type: "changeCity" })
}
};
};
export default connect(null, mapDispatchToProps)(SideMenuScreen);
Store and reducer setup :
const initialState = {
city : {
mycity: true
}
};
const reducer = (state = initialState, action)=>{
switch (action.type) {
case "changeCity":
return Object.assign({}, state, {
mycity: action.payload.mycity
})
default:
return state;
}
}
const Store = createStore(reducer);
I am stuck in sidemenu. How to dispach in mapDispatchToProps method:
How to pass action in mapDispatchToProps in sidemenu?
If my assumptions on what your Switch component does is correct, it would trigger the onValueChange event-listener when you pass in this.state.city to the value prop. You end up calling this.props.toggleCity() to dispatch your changeCity action. I think the set-up is correct for here...
However, it looks like your reducer is expecting an action.payload which you never passed in as part of the action.
const reducer = (state = initialState, action)=>{
switch (action.type) {
case "changeCity":
return Object.assign({}, state, {
mycity: action.payload.mycity
})
default:
return state;
}
}
So yes the dispatch is working correctly, but you are not passing all the necessary data for your reducer to return a new piece of state.
You need to update your mapDispatchToProps, your event-handler and your reducer to something like
<Switch onValueChange={(flag) => {
this.props.toggleCity(this.state.city);
} value={this.state.city} />
const mapDispatchToProps = dispatch => {
return {
toggleCity: (myCity) => {
dispatch({ type: "changeCity", payload: myCity })
}
};
};
export default connect(null, mapDispatchToProps)(SideMenuScreen);
Your reducer also seems to have an extra key, you don't need to access the mycity prop in payload if its already the payload. Update to:
const reducer = (state = initialState, action)=>{
switch (action.type) {
case "changeCity":
return Object.assign({}, state, {
mycity: action.payload
})
default:
return state;
}
}
Adding on, if you want your Hone component to re-render with the new data in your redux-state, you can do something like this.
In your HomeScreen component, make use of a state-variable to save your abudhabi or whatever city-value and call componentDidUpdate() to setState and re-render your component.
class HomeScreen extends React.Component{
state = {
abudhabi: false
}
//when the component gets the new redux state this will trigger
componentDidUpdate(prevProps){
if(this.props.abudhabi !== prevProps.abudhabi){
this.setState({
abudhabi: this.props.abudhabi
})
}
}
}
I use mapStateToProps to get an nested Object from an object by Id. The problem is, the props don't get updated and componentDidUpdate won't fire when the redux store state changes.
Here are my reducers:
export const programmReducers = (state = initialState, action) => {
let programms = state.programms;
switch (action.type) {
case actionTypes.FETCH_CATEGORIES:
return Object.assign({}, state, {
categories: action.payload
})
case actionTypes.FETCH_PROGRAMM:
programms[action.payload.id] = action.payload;
console.log(programms);
return {
...state,
programms: Object.assign({}, programms)
}
case actionTypes.FETCH_PROGRAMM_COMPONENTS:
programms[action.programmId].components = action.payload;
console.log('Added Components')
return {
...state,
programms: Object.assign({}, programms)
}
case actionTypes.FETCH_PROGRAMM_SECTIONS:
programms[action.programmId].sections = action.payload;
console.log('Added Sections')
return {
...state,
programms: Object.assign({}, programms)
}
default:
return state
}
}
Here is my components:
class ProgrammPage extends Component {
static async getInitialProps({ store, query: {id} }) {
if (!store.getState().programm.programms[id]) {
console.log('Programm not! found');
await store.dispatch(loadProgramm(id));
await store.dispatch(loadProgrammComponents(id));
} else {
console.log('Programm found')
}
return {
programmId: id
}
}
constructor(props) {
super(props);
if (this.props.user) {
console.log('Loading init!');
this.props.loadProgrammComponents(this.props.programmId)
this.props.loadProgrammSections(this.props.programmId);
}
}
componentDidUpdate(prevProps) {
console.log('Update')
if (!prevProps.user && this.props.user) {
console.log('Loading update');
this.props.loadProgrammComponents(this.props.programmId);
this.props.loadProgrammSections(this.props.programmId);
}
}
render() {
return (
<div>
<h1>Programm</h1>
<h2>{this.props.programm.name}</h2>
<h2>{this.props.programm.id}</h2>
<h3>Components: {this.props.programm.components ? this.props.programm.components.length : 'None'}</h3>
<h3>Sections: {this.props.programm.sections ? this.props.programm.sections.length : 'None'}</h3>
<br></br>
<h1>User: { this.props.user ? this.props.user.uid : 'None'}</h1>
<button onClick={() => this.props.loadProgramm('ProgrammLevel2')}>Load Programm</button>
<button onClick={() => this.props.loadProgrammComponents(this.props.programmId)}>Load Components</button>
</div>
)
}
}
function mapStateToProps(state, ownProps) {
return {
programm: state.programm.programms[ownProps.programmId],
// programms: state.programm.programms <--- Fixed the problem
user: state.auth.user
}
}
const mapDispatchToProps = dispatch => bindActionCreators({
loadProgramm,
loadProgrammComponents,
loadProgrammSections
}, dispatch)
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProgrammPage)
When the Reducer for FETCH_PROGRAMM_COMPONENTS or FETCH_PROGRAMM_SECTIONS changes the redux state, componentDidUpdate isn't called and the component doesn't dispay the changes.
The problem seems to be related to the mapStateToPropsmethod, because, when I add programms: state.programm.programms everything works fine. However I don't need the whole programms object.
Why are doesn't the component recognize that the programm has updated when I map only a nested object to my props?
Your problem is within the programmReducers, your component doesn't rerender because you don't change the state.
After changing mapStateToProps you need to make changes in your component.
The next code probably breaks when you change programms: state.programm.programms to programm: state.programm.programms[ownProps.programmId]
export const programmReducers = (state = initialState, action) => {
let programms = state.programms;
...
}
So I'm guessing your reducers aren't doing what you intended.