Render not being called after state gets updated - reactjs

I have created a basic reducer the state do get updated but render method does not get called here is the reducer file.
reducer.js
const accountSelector = (
store = {
items: [],
selectAll: false,
searchList: [],
filterList: [],
labelCount: 0
},
action
) => {
let newStore = {};
switch (action.type) {
case 'INITIALIZE_LIST':
console.log('in INITIALIZE_LIST');
newStore = { ...store, items: action.payload };
break;
case 'SEARCH_LIST':
newStore = { ...store, searchList: action.payload };
break;
case 'FILTER_LIST':
newStore = { ...store, filterList: action.payload };
break;
case 'SELECT_ALL_ACCOUNTS':
newStore = { ...store, selectAll: !store.list.selectAll };
break;
case 'UPDATE_LABEL_COUNT':
newStore = { ...store, labelCount: action.payload };
break;
default:
newStore = { ...store };
break;
}
console.log(' newStore: ', newStore);
return newStore;
};
export default accountSelector;
The state does get updated as I already logged it.
As you can see there are no mutation in reducer and still the render method does not get called.
Here are mapStateToProps and mapDispatchToProps
const mapStateToProps = state => ({
accountSelector: state.accountSelector
});
const mapDispatchToProps = dispatch => ({
initializeAccountsList: list => {
console.log('~~~~ ac selector ~~~~~~ in initializeAccountsList method');
dispatch({
type: 'INITIALIZE_LIST',
payload: list
});
}
Updated The question with store.js file where I combine reducer:
import { createStore, applyMiddleware, combineReducers } from 'redux';
import thunk from 'redux-thunk';
import promise from 'redux-promise-middleware';
import group from './reducer/groupReducer';
import clients from './reducer/clientReducer';
import accounts from './reducer/accountReducer';
import accountSelector from './reducer/accountSelectorReducer';
import createfeed from './reducer/createfeed';
const middleware = applyMiddleware(thunk, promise());
const reducers = combineReducers({
createfeed,
group,
clients,
accounts,
accountSelector
});
export default createStore(reducers, {}, middleware);
UPDATE: code for render method that has component that uses one of the state properties of accountSelector
render() {
let list = this.props.accountSelector.items;
if (this.props.accountSelector.searchList.length > 0) {
list = this.props.accountSelector.searchList;
}
return (
<div className="accountv2-select">
<UserList
updateLabelCount={this.updateLabelCount}
list={list}
selectedAccounts={this.props.selectedAccounts}
selectAll={this.props.selectAll}
selectedLoginIds={this.props.selectedLoginIds}
/>
);
}
Ofcourse the list will get the default value of items i.e is an empty array already defined in the reducer since the render method is not called even though state gets updated within reducer.

Removed old answer, as it's irrelevant now
It looks like your store.js file is combining reducers correctly, your mapStateToProps and mapDispatchToProps functions look fine, and your accountSelector reducer file looks fine, so at this point the only thing left is actually using your redux store data (from mapStateToProps) in your component.
You should be accessing state date (like items, searchList, filterList, ect.) like this:
// you mapped your entire accountSelector state to this.props.accountSelector
this.props.accountSelector.items
this.props.accountSelector.searchList
this.props.accountSelector.filterList
// ... etc

Related

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.

how to prevent redux generate sub object of same property after dispatch an action

the code below works fine, except something weird,
initial props is an empty object { photos: {} }
after invoking action creator the state in component looks like
{ photos: photos : {...} }
whereas I expect only photos: {...}
What cause this and how can I prevent generate another sub property in state?
component:
import {connect} from 'react-redux'
import {getPhotos} from '../actions'
class Photos extends Component {
constructor(props){
super(props)
}
componentDidMount(){
this.props.getPhotos();
}
invokeFunc= () =>{
this.props.getPhotos();
}
render() {
return (
<div>
<PhotoItem />
<button onClick={()=> this.invokeFunc()} >call action creator</button>
</div>
)
}
}
const mapStateToProps = (state) => {
const {photos} = state;
return {
photos,
}
}
export default connect(mapStateToProps,{getPhotos})(Photos)
action creator:
export const getPhotos = () =>{
return async (dispatch) => {
const response = await PhotosApi.get('/photos')
dispatch({
type:"GET_PHOTOS",
payload:response
})
}
}
reducer.js
const photosReducer = (state = {}, action) =>{
if(action.type=="GET_PHOTOS"){
return {...state, photos:action.payload}
}
return state;
}
export default combineReducers({
photos:photosReducer,
})
Your reducer is returning an object with photos as a property in it. Instead, try returning an object that is the photos property.
const photosReducer = (state = {}, action) =>{
switch(action.type){
case "GET_PHOTOS":
return { ...state, action.payload }; // or just `{ action.payload }` if you want it to wipe out the last state
default:
return state;
}
}
export default combineReducers({
photos:photosReducer,
});
The key here is combineReducers. From docs
The combineReducers helper function turns an object whose values are different reducing functions into a single reducing function you can pass to createStore.
The resulting reducer calls every child reducer, and gathers their results into a single state object. The state produced by combineReducers() namespaces the states of each reducer under their keys as passed to combineReducers()
So, since you are giving the namespace photos to your reducer, photosReducer state is under key photos in your global state.

Redux Store populates in getInitialProps but is empty on client

I'm using the Redux Thunk example template. When I dispatch an action in getInitialProps, that populates my store, the data is loaded but after the page is rendered, the store is still empty.
static async getInitialProps({ reduxStore }) {
await reduxStore.dispatch(fetchCategories())
const categories = reduxStore.getState().programm.categories;
console.log('STATE!!!', categories)
return { categories }
}
The categories will load correctly but when I inspect my store, the categories state is empty.
Here is my store:
import db from '../../api/db'
// TYPES
export const actionTypes = {
FETCH_PROGRAMMS: 'FETCH_PROGRAMMS',
FETCH_CATEGORIES: 'FETCH_CATEGORIES'
}
// ACTIONS
export const fetchCategories = () => async dispatch => {
const categories = await db.fetchCategories();
console.log('loaded Cate', categories)
return dispatch({
type: actionTypes.FETCH_CATEGORIES,
payload: categories
})
}
// REDUCERS
const initialState = {
programms: [],
categories: []
}
export const programmReducers = (state = initialState, action) => {
switch (action.type) {
case actionTypes.FETCH_PROGRAMMS:
return Object.assign({}, state, {
programms: action.payload
})
case actionTypes.FETCH_CATEGORIES:
console.log('Payload!', action);
return Object.assign({}, state, {
categories: action.payload
})
default:
return state
}
}
How can I make the redux state loaded on the server (in getInitialProps) be carried over to the client?
After hours of searching for solution it seems like I found my problem. It seems like I need to pass an initialState when creating the store. So instead of this:
export function initializeStore() {
return createStore(
rootReducers,
composeWithDevTools(applyMiddleware(...middleware))
)
}
I'm doing this and it works now
const exampleInitialState = {}
export function initializeStore(initialState = exampleInitialState) {
return createStore(
rootReducers,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
)
}
If you do this:
return { categories }
in getInitialProps, categories should be available in component's props in client side.
It should be available in Redux as well, this could cause the problem:
return Object.assign({}, state, {
categories: action.payload
})
Take a look at this Object.assign, the function only takes 2 parameters.
My normal way of doing this:
return {
...state,
categories: action.payload,
};

mapStateToProps doesn't provide a data from the state

I have this error:
TypeError: Cannot read property 'map' of undefined
And don't know how can this.props.movies be undefined while I have initialState declared as [] and then it must straightly go to render?
So there are files:
Reducer.js
import * as types from "./ActionTypes.js";
const initialState = { movies: [] };
export const reducer = (state = initialState, action) => {
console.log(state.movies);
switch (action.type) {
case types.GET_MOVIES:
return {
...state,
movies: action.value
};
default:
return state;
}
};
Movies.js
import React, { Component } from "react";
import { connect } from "react-redux";
import MovieItem from "./MovieItem";
const mapStateToProps = state => ({
movies: state.movies
});
class Movies extends Component {
render() {
let movieItems = this.props.movies.map(movie => {
return <MovieItem movie={movie} />;
});
return <div className="Movies">{movieItems}</div>;
}
}
export default connect(mapStateToProps, null)(Movies);
And even if I put if-statement like
if (this.props.movies){
...
}else return 1;
it never rerenders
It should be:
const mapStateToProps = state => ({
movies: state.reducer.movies
});
Because your initial state is a object:
initialState = { movies: [] };
And you are using combineReducers like this:
const rootReducer = combineReducers({ reducer: reducer });
Now all the initialState of that reducer will be accessible by state.reducer.propertyName.
Note: Instead of using reducer, better to use some intuitive name like moviesReducer. Later in future if you will add more reducers then it will help to identify the the data.

Understanding combineReducers

New to react and redux so playing around with some very simple code to see how it all works.
When I try passing in a combineReducers method to a redux store then I get an error. If I remove the combinedReducers and pass the reducer in directly to the store all works fine.
let store = createStore(rootReducer);
Error
Uncaught Error: Objects are not valid as a React child (found: object
with keys {reducer}). If you meant to render a collection of children,
use an array instead or wrap the object using createFragment(object)
from the React add-ons. Check the render method of App.
Why do I get an error when I use combineReducers ? What if I wanted to add more reducers I presume thats what combineReducers is there for ?
main.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, combineReducers } from 'redux';
import App from './components/app';
let reducer = (state=0, action) => {
switch (action.type) {
case 'INCREASE':
return state+1
case 'DECREASE':
return state-1
default: return state
}
}
const rootReducer = combineReducers({
reducer:reducer
});
let store = createStore(rootReducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.querySelector('.container'));
//app.js
import React, { Component } from 'react';
import {connect} from 'react-redux';
class App extends Component {
render() {
let {number, increase, decrease} = this.props
return(
<div>
<div>{number}</div>
<button onClick={e=>increase()}>+</button>
<button onClick={e=>decrease()}> - </button>
</div>
);
}
}
let mapStateToProps = state => ({
number: state
})
let mapDispatchToProps = dispatch => ({
increase: () => dispatch({type: 'INCREASE'}),
decrease: () => dispatch({type: 'DECREASE'})
});
export default connect(mapStateToProps, mapDispatchToProps)(App);
Combine reducers takes a hash of reducers and returns a reducer. The resulting reducer represents an object of the same shape as the hash.
So, a call like this:
combineReducers({ name: nameReducer})
Would produce a state object that might look something like this:
{ name: 'Joe Shmo' }
In your example, you are producing a global state tree that looks like this:
{ reducer: 0 }
But you are trying to pull a property called number out of this in your mapStateToProps.
If you change your reducer declaration to look like this:
const number = (state=0, action) => {
switch (action.type) {
case 'INCREASE':
return state+1
case 'DECREASE':
return state-1
default: return state
}
}
const rootReducer = combineReducers({
number
});
Then change your mapStateToProps to look like this:
const mapStateToProps = ({number}) => ({number});
Your code should start working.
https://redux.js.org/docs/basics/Reducers.html
import { combineReducers } from 'redux'
const todoApp = combineReducers({
visibilityFilter,
todos
})
export default todoApp
Note that this is equivalent to:
export default function todoApp(state = {}, action) {
return {
visibilityFilter: visibilityFilter(state.visibilityFilter, action),
todos: todos(state.todos, action)
}
}
You could also give them different keys, or call functions differently. These two ways to write a combined reducer are equivalent:
const reducer = combineReducers({
a: doSomethingWithA,
b: processB,
c: c
})
function reducer(state = {}, action) {
return {
a: doSomethingWithA(state.a, action),
b: processB(state.b, action),
c: c(state.c, action)
}
}
And don't forget connect each parts
#connect(state => ({
reducerName: state[partStoreName]
}))
I believe you could have also done:
const INITIAL_STATE = { number: 0 };
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'INCREASE':
return state+1
case 'DECREASE':
return state-1
default: return state
}
};

Resources