Dispatching function does not "always" update the props - reactjs

In my sample todo list app, I am trying to update all todos after creating or removing one so I can re-render the list component after changes. It sometimes updates the todos, but sometimes does not. I could not figure out the reason.
I am using Redux and Thunk. When a button is clicked, I dispatch "Create requested", then make async request, then dispatch "Create received", then dispatch fetchAllTodos(), and then catch "Create failed".
In the HomePage container, I pass this.props.todos to TodoList component as props. The problem I have is that it sometimes re-renders the list component with the changes, but sometimes does not. I also figured out in componentWillReceiveProps method that I do not get the updated props. Could this be related to a server issue or is it something I am doing wrong?
Here is how I set up the store:
const middlewares = [thunk];
const store = createStore(
combineReducers({
...reducers,
}),
compose(applyMiddleware(...middlewares))
);
Here is the fetchAllTodos() method:
export function fetchAllTodos() {
return dispatch => {
dispatch({
type: ALL_TODOS_REQUESTED
});
console.log('todos requested');
return SuperFetch.get('/todos')
.then(todos => {
console.log('todos received ' + todos.length);
dispatch({
type: ALL_TODOS_RECEIVED,
payload: todos
})
})
.catch(error => dispatch({
type: ALL_TODOS_FAILED,
payload: error
}));
}
Here is createTodo() method:
export function createTodo(description) {
const body = {description: description};
return dispatch => {
dispatch({
type: CREATE_TODO_REQUESTED
});
return SuperFetch.post(`/todos`, body)
.then(todo => dispatch({
type: CREATE_TODO_RECEIVED,
payload: todo
}))
.then(dispatch(fetchAllTodos()))
.catch(error => dispatch({
type: CREATE_TODO_FAILED,
payload: error
}));
}
}
Here is the reducer:
export default function todoReducer(state = initialState, action) {
switch (action.type) {
case ALL_TODOS_REQUESTED:
state = Object.assign({}, state, {todosLoading: true, todosError: null});
break;
case ALL_TODOS_RECEIVED:
state = Object.assign({}, state, {todos: action.payload, todosLoading: false, todosError: null});
break;
case ALL_TODOS_FAILED:
state = Object.assign({}, state, {todos: null, todosLoading: false, todosError: action.payload});
break;
// rest of it
return state
In HomePage, I just map state to props, and then pass the todos if they exist:
render() {
const {todos, error, loading} = this.props;
// if error part
if (todos && !loading) {
return (
<React.Fragment>
<Container text>
<TodoInput/>
</Container>
<Container style={{marginTop: '1em'}} text>
<TodoList todos={todos}/>
</Container>
</React.Fragment>
);
}
Might the problem be related to the fact that I am dispatching the action in TodoInput component and trying to update the TodoList? If so, how can I fix the problem because I do not want to dispatch all actions on the same HomePage container.

The problem is the way you manage state in your reducers. What you are doing is directly mutating an existing state, by doing state = blah which is against redux-priniciples. In order for redux to effectively recognize that a change has been made to the reducer, you must return a brand-new state object. Only then will your connected components re-render with the updated reducer data.
export default function todoReducer(state = initialState, action) {
switch (action.type) {
case ALL_TODOS_REQUESTED:
return {
...state,
todosLoading: true,
todosError: null
}
case ALL_TODOS_RECEIVED:
return {
...state,
todos: action.payload,
todosLoading: false,
todosError: null
}
case ALL_TODOS_FAILED:
return {
...state,
todos: null,
todosLoading: false,
todosError: action.payload
}
default:
return state
}
}

Problems is how you are updating the state in your reducer :
if todos is Object
state = {...state, {todos: {...action.payload, ...state.todos }, todosLoading: false, todosError: null}}
if todos is list
state = {...state, {todos: state.todos.concat(action.payload) }, todosLoading: false, todosError: null}}

Related

Can't dispatch action in class component

// action
export const getEvents = () => async (dispatch) => {
try {
dispatch({ type: GET_EVENTS_REQUEST })
const data = await axios.get('http://localhost:5000/api/schedule').then((response) => response.data)
dispatch({ type: GET_EVENTS_SUCCESS, payload: data })
} catch (error) {
dispatch({
type: GET_EVENTS_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
})
}
}
// reducer
export const getEventsReducer = (state = { event: [] }, action) => {
switch (action.type) {
case GET_EVENTS_REQUEST:
return { loading: true }
case GET_EVENTS_SUCCESS:
return { loading: false, event: action.payload }
case GET_EVENTS_FAIL:
return { loading: false, error: action.payload }
default:
return state
}
}
// and this is how I'm trying to call my action:
import { getEvents } from '../../redux/actions/calendarActions'
class Calendar extends React.PureComponent {
componentDidMount() {
const { dispatch } = this.props
console.log(dispatch(getEvents()))
}
}
export default connect()(Calendar)
// component is much bigger, I only added relevant parts
Up until my reducer, if I console.log my data, it is correct, as well as in my redux dev tools tab: an array with a few entries. But when console.logging in my Calendar component, it returns a promise, with undefined result:
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: undefined
What am I doing wrong?
Normally you want to have access to either the dispatch or the store of Redux within a component. you already have the dispatch function within the component, but if you need access to Redux state inside it:
first you need to define such function, which makes the redux store available in the component.
const mapStateToProps = (state) => ({
state: state // a "state" prop is available in the component which points to redux state,
})
or you can customize it if you only need certain properties of Redux state:
const mapStateToProps = (state) => ({
state: state.event //
})
and change the connect function like this:
connect(mapStateToProps)(Calendar)

How to append to Redux state

I'm trying to append to my redux state, but having an issue understanding where I'm wrong. At the moment, I'm getting an error state is not iterable.
reducer
const CURRENT_USER = "CURRENT_USER";
const reducer = (state = [], action) => {
switch(action.type) {
case CURRENT_USER:
const currentUser = [
...state,
action.payload
];
return currentUser;
default:
return state;
}
}
export default reducer;
dispatch
posts.forEach((post: BlogPost) => {
if (post.userId === currentUserPosts) {
dispatch({
type: "CURRENT_USER",
payload: { ...post },
});
}
});
So from the reducer after updating the state you have to return the new state
so as I see
case CURRENT_USER:
return {
currentUser: [...state, action.payload]
}
.....
You are returning an object {currentUser: [..} this will be the new state after the action happens
so again if you call this, state is an object ({currentUser: [..}) if you try to treat that as iterable (array) you will get this error
[...{currentUser: []}]

react props comes blank on first transaction

I am using redux promise middleware. I am trying to pass the value in Propsx to state. Props comes empty in useEffect. How can I transfer the contents of the props to state. Props value comes next.
action:
export function fetchBasket() {
return dispatch => {
dispatch({
type: 'GET_BASKET',
payload: axios.get('url', {
})
.then(response => response.data)
});
};
}
reducer:
const initialState = {
fetching: false,
error: {},
basket: []
};
export default (state = initialState, { type, payload }) => {
switch (type) {
case types.GET_BASKET_PENDING:
return {
fetching: true
};
case types.GET_BASKET_FULFILLED:
return {
...state,
fetching: false,
basket: payload.result,
};
case types.GET_BASKET_REJECTED:
return {
fetching: false,
error: payload.result
};
default:
return state;
}
};
use in Component
useEffect(() => {
props.fetchBasket();
console.log(props.basket); // null :/
}, []);
[enter link description here][1]If you want to have values in your first run(Mount). fetch here ==> useLayoutEffect and this will gives the values in useEffect()[]. [uselayouteffect]: https://reactjs.org/docs/hooks-reference.html#uselayouteffect
useEffect(() => {
props.fetchBasket();
console.log(props.basket); // null :/
}, []);
Your props will update only in the next event loop cycle, to use react hooks data updation inside useEffect you need to useReducer https://reactjs.org/docs/hooks-reference.html#usereducer

Simple React components won't update after Redux state change

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.

React not responding to (redux) state change?

For some reason react is not re-rendering when the (redux) state changed. Might it be because it is nested 1 level? Also in the redux inspector I can see that the state has changed correctly and as expected. On manually refreshing the page, I can also see that it worked. But I am confused on why it is not re-rendering automatically.
Simplified Class Component
class Users extends Component {
render() {
const something = this.props.users.users;
return (
<div>
<div className='MAIN_SECTION'>
<SearchBar handleChange={(value) => this.setState({searchQuery: value})} />
<div className='LIST'>
{something.mapObject((user) => <div onClick={() => this.props.deleteUser(user.id)}>{user.name}</div>)}
</div>
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
users: state.users
};
}
export default connect(mapStateToProps, { deleteUser })(Users);
Action Creator
export function deleteUser(userhash) {
return function (dispatch, getState) {
return new Promise(resolve => setTimeout(resolve, 1000)).then(() => {
const data = {
status: 200,
data: {
status: 200
}
};
if (data.status === 200) {
const newState = getState().users.users;
delete newState[userhash];
dispatch({
type: DELETE_USER,
payload: {
data: newState
}
});
}
});
};
}
Reducer
const INITIAL_STATE = {
isFetching: true,
users: {}
};
case DELETE_USER:
return {
...state,
users: action.payload.data
};
Once you dispatch action DELETE_USER,
const something = this.props.users.users;
doesn't look right.
You are doing this in action which means something must be this.props.user.
const newState = getState().users.users;
delete newState[userhash];
dispatch({
type: DELETE_USER,
payload: {
data: newState
}
});
In your render, this.props.users itself is the array of users.
To have a dirty fix, changing mapStateToProps to:
function mapStateToProps(state) {
return {
users: state.users.users || state.users
};
}
Change in render:
const something = this.props.users;
The code doesn't really jive to me. In one spot you are you are using .map() on users.users and in another spot you are using delete[newHash] on it. So is it an array, or an object?
But hypothetically, let's say it did work. You say that that the state has changed correctly.
Let's assume the initial state is
{
isFetching: true,
users: { users: ['12345', '67890'] }
}
Let say user '12345' gets deleted, in the action, we have these sequence of events
const newState = getState().users.users; // ['12345', '67890']
delete newState[userhash]; // ['67890'] if you use your imagination
dispatch({
type: DELETE_USER,
payload: {
data: newState // ['67890']
})
Finally, in the reducer
case DELETE_USER:
return {
...state,
users: action.payload.data // ['67890']
};
So the final state is
{
isFetching: true,
users: ['67890']
}
It's not the same shape as the original state - users no longer has a users.users.

Resources