Props not updating when redux state change in React Hooks - reactjs

I am trying to implement Redux on a React Hooks project, but it doesnt seems to work good. Am I doing something wrong here?
reducer.js
const initialState = {
educations: []
};
export default function home(state = initialState, action){
switch(action.type){
case GET_EDUCATIONS: {
state.educations = action.payload;
return state;
}
default:
return state;
}
}
action.js
import * as types from '../constans/home';
export const getEducations = () => {
return dispatch => {
const edus = [
{value: 1, name: 'Bachelor'},
{value: 2, name: "Master"}
]
dispatch({
type: types.GET_EDUCATIONS,
payload: edus
})
}
}
component
import React, {useEffect} from 'react';
import {connect} from 'react-redux';
import {getEducations} from '../../redux/actions/home';
function Header({educations, getEducations}) {
useEffect(() => {
getEducations(); //calling getEducations()
}, [])
useEffect(() => {
console.log(educations) //console educations after every change
})
return (
<div className="main-header">
</div>
)
}
const mapStateToProps = (state) => {
return {
educations: state.home.educations
}
}
const mapDispatchToProps = (dispatch) => {
return {
getEducations: () => { dispatch(getEducations())}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Header);
And the education property in Header function is always an empty array, as in initialState.
While when I check on browser with Redux Devtools, it shows that the state contains those two object in array.
So no matter if I change the redux state or not, the properties of the component are going to stay as initialState.

In redux, you should avoid directly mutating the state of your reducer. Refrain from doing something like state.reducers = blah. In order for redux to know that you are trying to make an update to state, you need to return a completely new state object. Following these principles, your reducers will update correctly and your components will get the new data.
Reducer.js
const initialState = {
educations: []
};
export default function home(state = initialState, action){
switch(action.type){
case GET_EDUCATIONS: {
return {
...state,
educations: action.payload
};
}
default:
return state;
}
}
In the code above, we return a new state object. It will include everything from the existing state, hence ...state, and we just update the educations property with the action.payload.

Can try with the reducer written this way :
const initialState = {
educations: []
};
export default function home(state = initialState, action){
switch(action.type){
case GET_EDUCATIONS:
return {
...state, educations:action.payload
}
default:
return state;
}
}

It looks like you’re mutating the state in the reducer. The reducer should always return a new state object if something updated.
You could do what the answers above suggest, but i would recommend using a package like immer (https://www.npmjs.com/package/immer) or immutable.js to prevent any bugs down the line. Using the spread syntax can be dangerous if your state object has some deeply nested properties, and it’s hard to be 100% sure that you haven’t accidentally mutated something, especially as your app grows in size.

It looks like you have solved this while I was getting this typed up - I decided to post it regardless, as it may be helpful.
On top of what Christopher Ngo already mentioned, the following example outlines how you can interact with your store to create new educations and then view them, in separate components..
Cheers!

I encounter this all the time and resolved it with CLEAR then GET/SET state. This ensures a reset of the state call.
Reducers.js
const initialState = {
educations: []
};
export default function home(state = initialState, action){
switch(action.type){
case GET_EDUCATIONS: {
return {
...state,
educations: action.payload
};
}
case CLEAR_EDUCATIONS: {
return initialState;
}
default:
return state;
}
}
Hooks.js
...
const clearEducation = () => {
dispatch({ type: CLEAR_EDUCATION });
}
const getEducations = (payload) => {
clearEducation(); // this clearing of the state is key fire re-render
dispatch({ type: GET_EDUCATIONS, payload });
};
}

Related

Can't send data to the state

I have been trying to add the functionality of adding favourites in the movie app I created by watching a tutorial about Redux, but for some reason the state is always empty.
This is what my action and reducers look like
Reducers
const initState = {
favourites: [],
};
const favouriteReducer = (state = initState, action) => {
switch (action.type) {
case "ADD_FAVOURITE":
return {
...state,
favourites: action.payload.favourites,
};
default:
return { ...state };
}
};
export default favouriteReducer;
actions
export const favouriteActions = () => {
return {
type: "ADD_FAVOURITE",
payload: "zz",
};
};
The action is dispatched and showed in the redux dev tools too but nothing is added to the favorite state which I have created.
I have this onclick event set to an image of the star on which I actually want to pass in the Id of the movie which I have access to from another state.
const addFav = () => {
dispatch(favouriteActions(id));
};
reutrn{
<img src={favNot} onClick={addFav} />
}
I am just posting the main part of my component file here. I have also attached an Image showing my current state after I click in the image.
return {
...state,
favourites: action.payload.favourites,
};
Just only need "action.payload" or if you want favourites like array should change reducer : favourites : [...state.favourites,action.payload]
You are using
case "ADD_FAVOURITE":
in reducer but in action the type is
type: "ADD_FAVOURITES",
Could this be the cause?
export const favouriteActions = () => {
return {
type: "ADD_FAVOURITE",
favourites: "zz",
};
};
Please check whether above works. Instead of "playload" change to "favourites"

Redux not triggering re-render even-though I am not mutating my state in my reducer

I'm having some difficulty with React Redux. It's related to components not re-rendering after a state change. Every question that is asked online refers to it probably being that you are mutating the state, however, I am almost 100% sure that I am not making that mistake. After having tried multiple approaches I just don't know what is going wrong.
Here is my original reducer code:
import * as actionTypes from '../actions/actionTypes';
import { updateObject } from '../utility';
const initialState = {
jwsToken: null,
accessToken: null,
};
const updateTokens = (state, action) => {
return updateObject(state, {jwsToken: action.jwsToken, accessToken: action.accessToken})
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.UPDATE_TOKENS: return updateTokens(state, action);
default:
return state;
};
};
export default reducer
I'm using a utility function (updateObject) to make a copy of my object that I want to return in the reducer. It looks like this:
export const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties
};
};
I also tried it without the updateObject utility function and using Object.assign():
const updateTokens = (state, action) => {
return Object.assign({}, state, {
jwsToken: action.jwsToken,
accessToken: action.accessToken,
})
};
I fear that I'm doing something super stupid, but I've spent too much time on this now not to ask. Can someone please tell me what I'm doing wrong?
Edit
Here is my component:
import React, { Component } from "react";
import * as actions from "../../store/actions/index";
import { connect } from "react-redux";
class Calendar extends Component {
componentDidMount () {
if (this.props.accessToken) {
this.onGetEvents()
}
}
onGetEvents = () => {
this.props.getEventsSelectedMonth(this.props.selectedMonth,
this.props.accessToken)
}
render() {
return (
//JSX here
)
}
}
const mapStateToProps = state => {
return {
accessToken: state.accessToken,
selectedMonth: state.selectedMonth
};
};
const mapDispatchToProps = dispatch => {
return {
getEventsSelectedMonth: (selectedMonth, accessToken) =>
dispatch(actions.getEventsSelectedMonth(selectedMonth, accessToken))
};
};
export default connect(mapStateToProps, mapDispatchToProps) (Calendar);
To avoid the infinite loop you can make sure to call the required function only when the value is changed:
componentDidUpdate(prevProps, prevState) {
if (prevProps.accesstoken !== this.props.accessToken) {
this.onGetEvents()
}
}
The infinite loop happens as without the check we would continuously change the state on every update(which occurs because of the state change).
From your comments, I'm assuming you're getting different access tokens every time so maybe you just want to call onGetEvents when you get an accessToken for the first time.
componentDidUpdate(prevProps, prevState) {
// only call when the previous token is falsy and there's a new truthy token
if (!prevProps.accesstoken && this.props.accessToken) {
this.onGetEvents()
}
}

Props not updating when I change the store in react

I have a problem, when I get my data from API and update the store the data doesn't changed. It is binded as a prop and I think it should changed, one more thing I noticed is that it doesn't call mapStateToProps after the store was updated. When i give some initial value to the store it displays it so I think it can see the store, something else is wrong obiviously but I can't figure out what.
Reducer code:
import { ADD_POST } from "../actions/addAction";
import { GET_POSTS } from "../actions/getAction";
import { DELETE_POST } from "../actions/deleteAction";
import { UPDATE_POST } from "../actions/updateAction";
import axios from "axios";
const initialState = {
posts: []
};
export default function postsReducer(state = initialState, { type, payload }) {
switch (type) {
case ADD_POST:
state = state.slice();
state.push(payload);
break;
case GET_POSTS:
axios
.get("http://localhost:59511/api/post?date=31-12-2019")
.then(response => {
response.data.forEach(thePost => {
state.posts = [...state.posts, thePost];
});
console.log(state.posts);
return state;
});
break;
default:
return state;
}
return state;
}
index (here I am creating my store and wrapping the app component with provider):
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import { combineReducers, createStore } from "redux";
import { Provider } from "react-redux";
import postReducer from "./reducers/postsReducers";
const allReducers = combineReducers(
{
post: postReducer
},
window.devToolsExtension && window.devToolsExtension()
);
const store = createStore(allReducers);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
serviceWorker.unregister();
Mapping it in my component like this:
const mapStateToProps = state => ({
posts: state.post.posts
});
if you guys need anything else let me know, I have a file that is a bit large so I wouldn't like to add it if it's not neccessary, I am banging my head against the wall for a couple of hours now. Thanks in advance
===EDIT===
I also mapped my action to props
const mapActionToProps = {
onDeletePost: deletePost,
onUpdatePost: updatePost,
onGetPost: getPosts
};
I have my action defined as
export const ADD_POST = "posts:addPost";
export function addPost(newTitle, newHours, newDate) {
return {
type: ADD_POST,
payload: {
id: new Date().toString(),
title: newTitle,
hours: newHours,
date: new Date().toLocaleDateString()
}
};
}
So I already have the action defined there so I am not sure I need a dispatchToAction? I am looking it up as we speak and will try to make something, just a bit confused.
==END OF EDIT==
I think that technically your problem is that your reducer returns (after all of axios) before the fetching is done. But that's not the problem you want to solve.
First of all, you have too much going on in your reducer. You shouldn't be implementing the action (fetching the data) in your reducer. I imagine in your component you're constructing an action that looks like {type: 'GET_POSTS'}, and then...dispatching it? Except you don't appear to be providing your component with a dispatch. So the action is never making it to the store. I can only assume because you haven't shown us where you're calling your action from.
You should be moving your fetching to its own async (thunk) action method:
function getPosts() {
return dispatch => {
axios
.get("http://localhost:59511/api/post?date=31-12-2019")
.then(response => {
const posts = response.data
dispatch({type: 'GET_POSTS', payload: posts})
});
}
}
And then simply add the posts to your state in your reducer:
export default function postsReducer(state = initialState, { type, payload }) {
switch (type) {
case GET_POSTS:
return { ...state, posts: payload }
default:
return state;
}
And then you'll have to connect the getPosts() function to your store using mapDispatchToProps. And you'll also have to use redux-thunk or this won't work at all.
You've got a good start with react-redux, but there's some gaps in your learning. You're going to need to look into async actions and redux thunk (or some other async action middleware). I'd suggest reviewing all the Redux documentation, mainly the advanced tutorials.
Your reducer is mutating state, and that's breaking the app.
In addition, you are making an AJAX call in a reducer, which is never allowed. All async logic happens outside reducers, and reducers only look at their state and action parameters to calculate the new state.
This why the first two "Essential" rules of the Redux Style Guide are Do Not Mutate State and Reducers Must Not Have Side Effects.
I'd strongly encourage you to use our new official Redux Toolkit package. Its configureStore() function sets up mutation detection by default, and it has functions like createSlice() which let you write simpler immutable update logic.
Beyond that, I'd suggest taking some more time to read through the Redux docs to understand how you are supposed to use Redux correctly.
I changed my action to be
import axios from "axios";
export const GET_POSTS = "posts:getPosts";
export function getPosts(theDate) {
return dispatch => {
axios
.get("http://localhost:59511/api/post?date=31-12-2019")
.then(response => {
const posts = response.data;
dispatch({ type: GET_POSTS, payload: posts });
});
};
}
reducer:
import { ADD_POST } from "../actions/addAction";
import { GET_POSTS } from "../actions/getAction";
import { DELETE_POST } from "../actions/deleteAction";
import { UPDATE_POST } from "../actions/updateAction";
const initialState = {
posts: []
};
export default function postsReducer(state = initialState, { type, payload }) {
switch (type) {
case ADD_POST:
state = state.slice();
state.push(payload);
break;
case GET_POSTS:
payload.forEach(element => {
state.posts = [...state.posts, element];
});
break;
default:
return state;
}
return state;
}
in the component that I want to show posts I have:
const mapStateToProps = state => ({
posts: state.post.posts
});
Then showing it with:
render() {
return (
<div className="contentWindow">
{this.props.posts.map((post, i) => {
return (
#some displaying logic
store creation changed with middleware:
const store = createStore(
allReducers,
applyMiddleware(thunk)
);
Still doesn't update my props in my component where I am mapping state to props.
When I inspected the store with react dev tools changes are being made but some how my props weren't updated.
=====EDIT=====
I have changed my reducer code to:
import { ADD_POST } from "../actions/addAction";
import { GET_POSTS } from "../actions/getAction";
import { DELETE_POST } from "../actions/deleteAction";
import { UPDATE_POST } from "../actions/updateAction";
const initialState = {
posts: [123]
};
export default function postsReducer(state = initialState, { type, payload }) {
switch (type) {
case ADD_POST:
state = state.slice();
state.push(payload);
break;
case GET_POSTS:
return { ...state, posts: payload };
case DELETE_POST:
state = state.filter(post => post.id !== payload.id);
break;
case UPDATE_POST:
for (let index = 0; index < state.length; index++) {
if (state[index].id === payload.theId) {
state[index].id = payload.theId;
state[index].date = payload.newDate;
state[index].hours = payload.newHours;
state[index].title = payload.newTitle;
}
}
break;
default:
return state;
}
return state;
}
Now I have a problem that it's changing its initial state every time xD, I am glad I managed to get through the previous one btw feeling so strong now :D.

Redux store updating, but React views is not

Hi when i console log my components props (passed down from redux) i get the initial state which is null. however using the react inspector i have the result of the axios request. I tried reading dozens of similar problems but cannot seen to resolve my issue.
Actions
import { searchService } from '../api/searchService';
export const actions = {
FETCH_USERS: 'FETCH_USERS',
}
export const searchUsers = () => dispatch => {
searchService.get('/search')
.then((result) => {
dispatch({
type: actions.FETCH_USERS,
payload: result
})
})
}
Reducers
import { actions } from '../actions';
export default (state = null, action) => {
switch(action.type) {
case actions.FETCH_USERS:
return action.payload;
default:
return state;
}
}
Search Component
function mapStateToProps ({search}) {
return {search};
}
const mapDispatchToProps = dispatch => ({
searchUsers: () => dispatch(searchUsers())
});
export default connect(mapStateToProps, mapDispatchToProps)(withAuth()(Search));
Your problem is in the Reducer
First you should make an initial state, and then you need to edit this state in order for redux to feel the changes and update
Check the code below and let me know if it worked for you.
import { actions } from '../actions';
const INITIAL_STATE= {search: ""};
export default (state = INITIAL_STATE, action) => {
switch(action.type) {
case actions.FETCH_USERS:
return {...state, search: action.payload};
default:
return state;
}
}

React-Native + Redux: fetching data from API as an array of objects and rendering in list component

I've been learning React-Native + Redux and have recently hit a wall while trying to build a relatively small app. My problem is that when I fetch json data from an api (from another one of my apps) using axios, the data comes in the form of an array of objects (e.g. all the door objects). Whenever i try to transfer that array to my component through my reducer, the data gets lost and the array turns up empty every time
My action creator, which uses redux-thunk:
export const doorsFetch = () => {
return (dispatch) => {
axios.get('http://localhost:3000/api/v1/doors')
.then(response => {
dispatch({ type: FETCH_DOORS_SUCCESS, payload: response.data });
})
.catch(error => console.log(error.response.data));
};
};
My DoorsReducer:
const INITIAL_STATE = [];
export default (state = INITIAL_STATE, action) => {
console.log(action.payload);
switch (action.type) {
case FETCH_DOORS_SUCCESS:
return [...state, action.payload];
default:
return state;
}
};
The action.payload console.log turns up what i would expect, an array like this [{object}, {object}]. However in my component the array becomes empty, so i think the problem is in the reducers or in how I map the state to props.
I've also tried the doorReducer this way:
const INITIAL_STATE = {
doors: []
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case FETCH_DOORS_SUCCESS:
return { ...state, doors: action.payload };
default:
return state;
}
};
with the same result of an empty array in the component props.
Here is my reducers index:
import { combineReducers } from 'redux';
import OrientationReducer from './OrientationReducer';
import TraitsReducer from './TraitsReducer';
import DoorsReducer from './DoorsReducer';
export default combineReducers({
orientation: OrientationReducer,
traits: TraitsReducer,
doorState: DoorsReducer
});
And finally my component, DoorList:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { ListView } from 'react-native';
import { doorsFetch } from '../actions';
import ListItem from './ListItem';
class DoorList extends Component {
componentWillMount() {
this.props.doorsFetch();
this.createDataSource(this.props);
}
componentWillReceiveProps(nextProps) {
this.createDataSource(nextProps);
}
createDataSource({ doors }) {
const ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2
});
this.dataSource = ds.cloneWithRows(doors);
}
renderRow(door) {
return <ListItem door={door} />;
}
render() {
console.log(this.props); // for testing
return (
<ListView
enableEmptySections
dataSource={this.dataSource}
renderRow={this.renderRow}
/>
);
}
}
const mapStateToProps = state => {
return {
doors: state.doorState
};
};
export default connect(mapStateToProps, { doorsFetch })(DoorList);
When I console out this.props, I get an object with a doors array that is empty. I can't seem to figure out what happened to the data and how to render it properly in the view. Any insight would be much appreciated. Would happily provide more code/info if requested.
EDIT:
For clarification, this is where i put my console.log statements for debugging purposes:
const INITIAL_STATE = [];
export default (state = INITIAL_STATE, action) => {
console.log(action.payload);
console.log(action.type);
switch (action.type) {
case FETCH_DOORS_SUCCESS:
console.log('fetching doors case triggered');
return [...state, ...action.payload];
default:
console.log('not working!');
return state;
}
};
SOLUTION:
On top of the suggested corrections below, the other glaring issue was that my FETCH_DOORS_SUCCESS constant was improperly imported from another file, and so was undefined in the reducer file. Therefore my switch statement didn't pick it up and it went to the default case. I was missing curly brackets around FETCH_DOORS_SUCCESS.
If action.payload is an array, you also need to put the spread operator on it to properly combine it with the array in state:
Change:
case FETCH_DOORS_SUCCESS:
return [...state, action.payload];
to:
case FETCH_DOORS_SUCCESS:
return [...state, ...action.payload];
It also looks like you're not returning the promise from axios.get():
Change this:
return (dispatch) => {
axios.get('http://localhost:3000/api/v1/doors')
to
return (dispatch) => {
return axios.get('http://localhost:3000/api/v1/doors')

Resources