The component is not rendering on state update, even the mapStateToProps is executing successfully.
Component:
import React from 'react';
import AddItem from './addItemComponent';
import { connect } from 'react-redux';
import RenderItem from './RenderItem';
class HomeComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<div className="container">
<AddItem />
</div>
<div className="container">
{this.props.Items.length}
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
Items: state.auth.items
}
}
export default connect(mapStateToProps)(HomeComponent);
Reducers:
const ItemsReducers = (state = {}, Action) => {
if (!state.items)
state.items = [];
console.log("inside the reducers", state);
var newState = Object.assign({}, state);
switch (Action.type) {
case 'AddItem':
newState.items.push(Action.payload);
return newState;
break;
default:
return newState;
break;
}
return newState;
}
export default ItemsReducers;
To avoid mutation, I just clone the object and doing push operation. But still facing issue. It would be more helpful if I got any help.
Object.assign() make a shallow copy of your object, not a deep copy. So, you have a mutation !
In your example, mapStateToProps is executing because state has changed, but items keep the same reference. So your old items is equal to the new items, even if the length of array has changed. And mapStateToProps do not rerender the component if the output hasn't changed.
You need to copy your state and items like this:
var newState = Object.assign({}, state);
newStats.items = newState.items.slice();
But it's not a good way to use redux.
I recommend to use immutability-helper package with update(). It create a copy of your object without change the original source
import update from 'immutability-helper';
const reducer = (state = {}, action) => {
switch (action.type) {
case: 'addItem':
return !state.items
? update(state, {items: {$set: [action.payload]}})
: update(state, {items: {$push: [action.payload]}})
default:
return state;
}
}
Related
I have a redux State HOC to manage the connection
I Have a problem when I add a new post to the store
import React, { useEffect } from "react";
import { connect } from "react-redux";
export default function withState(WrappedComponent) {
function mapStateToProps(reduxState) {
let state = {};
for(let t of Object.entries(reduxState)) {
state = {...state, ...t[1]}
}
return {
...state,
};
}
return connect(
mapStateToProps,
null
)(function (props) {
useEffect(() => {}, [props.posts, props.comments]) /*tried this but didn't work*/
return (
<React.Fragment>
<WrappedComponent {...props} />
</React.Fragment>
);
});
}
I am trying to make the program render the response from my back-end without me reloading the page manually
I tried using the useEffect
and I saw through the dev tools that the state change correctly
my reducer
import { GET_ALL_POSTS, CREATE_NEW_POST } from "../actions"
const initialState = {
posts: []
}
export default function postReducer(state = initialState, action) {
let newState = {...state}
switch(action.type){
case GET_ALL_POSTS:
return {
...newState,
posts: [...action.posts],
}
case CREATE_NEW_POST:
const posts = [...newState.posts, action.post]
return {
...newState,
posts
}
default:
return {
...newState,
}
}
}
I also read that react changes doesn't respond to shallow copies so I changed the whole array in the post reduces when I add a new post
Your withState HOC is very strange. I'm not sure why you don't just use connect directly (or use hooks). But try this:
export function withState(WrappedComponent) {
return connect(
(state) => ({
posts: state.postsReducer.posts,
comments: state.commentsReducer.comments
}),
null
)(WrappedComponent);
}
When I update counter 1 it increases but the component is not getting the updated state. After the state is updated the counter didn't show updated number. But if I go to another component and back to the counter component then I get the updated state. Here is the counter page-
import React, { Component } from 'react'
import {connect} from 'react-redux'
import {increaseXaomi} from '../../store/actions/counterAction';
class Counter extends Component {
increaseXaomi = ()=>{
this.props.increaseXaomi();
}
render() {
console.log(this.props)
return (
<div className="container">
<div className="row">
<p>Xaomi <strong>{this.props.xaomi}</strong></p>
<p>Iphone <strong>{this.props.iphone}</strong></p>
<p>Huai <strong>{this.props.huai}</strong></p>
<button className="btn" onClick={this.increaseXaomi}>Increase Xaomi</button>
</div>
</div>
)
}
}
const mapStateToProps = ({counter})=>{
return counter;
}
const mapDispatchToProps = (dispatch)=>{
return {
increaseXaomi: ()=>{
increaseXaomi(1)(dispatch)
}
}
}
export default connect(mapStateToProps,mapDispatchToProps)(Counter);
Counter action is called when the "increaseXaomi" method is called from the component.
export function increaseXaomi(num=1){
return (dispatch)=>{
dispatch({
type:'INCREASE_XAOMI',
payload:num
})
}
}
Counter Reducer get the action type and counter value for increasing the counter. It return updated state.
let initState = {
xaomi:0,
iphone:0,
huai:0
}
function counterReducer(state=initState,action){
switch(action.type){
case 'INCREASE_XAOMI':
state.xaomi+=action.payload
return state;
default:
return state;
}
}
export default counterReducer;
When you are using redux you want to make sure you are not mutating the state, you need to create a new state object (a shallow copy).
Refer to this link for more info on mutations and update patterns in redux.
Your code should be:
let initState = {
xaomi:0,
iphone:0,
huai:0
}
function counterReducer(state=initState,action){
switch(action.type){
case 'INCREASE_XAOMI':
return { ...state, xaomi: state.xaomi + action.payload };
default:
return state;
}
}
export default counterReducer;
You are mutating the state on the reducer.
You should always return a new instance of your state.
function counterReducer(state=initState,action){
switch(action.type){
case 'INCREASE_XAOMI':
return { ...state, xaomi: state.xaomi + action.payload };
default:
return state;
}
}
I don't understand why React not update my object. In another component through the dispatch I update the state. In this (in code below) code in mapStateToProps categories are changing (console log show one more category). But component not rerender, although in component in useEffect I use props.categories. Event console.log in element does not run
const LeftSidebar = (props: any) => {
console.log('not render after props.categories changed')
useEffect(() => {
props.dispatch(getCategories())
}, [props.categories]);
const addCategoryHandler = (categoryId: number) => {
props.history.push('/category/create/' + categoryId)
};
return (
<div className='left-sidebar'>
<Logo/>
<MenuSidebar categories={props.categories} onClickAddCategory={addCategoryHandler}/>
</div>
);
};
function mapStateToProps(state: State) {
const categories = state.category && state.category.list;
console.log('this categories changes, but LeftSidebar not changing')
console.log(categories)
return { categories };
}
export default connect(mapStateToProps)(LeftSidebar);
I thought if i update state, react update components dependent on this state. How should it work? how should it work? It may be useful, the item that adds the category is not a parent or child, it is a neighbor
My reducer
import {CATEGORIES_GET, CATEGORY_CREATE} from "../actions/types";
export default function (state={}, action: any) {
switch (action.type) {
case CATEGORIES_GET:
return {...state, list: action.payload};
case CATEGORY_CREATE:
return {...state, list: action.payload};
default: return state;
}
}
Thanks for solving problem. All problem was in inmutable data. I used fixtures, and not copied properly array
import {CATEGORIES_GET, CATEGORY_CREATE} from "./types";
import {categoryMenuItems as items} from "../../fixtureData";
import {NewCategory} from "../../types";
let categoryMenuItems = items; // My mistake, I used not immutable value. Not use fixtures for state))
let id = 33;
export function getCategories() {
return {
type: CATEGORIES_GET,
payload: categoryMenuItems
}
}
export function createCategory(newCategory: NewCategory) {
id++
const category = {
title: newCategory.name,
id: id
};
// MISTAKE I use same array, not cloned like let clonedCategoryMenuItems = [...categoryMenuItems]
categoryMenuItems.push(category);
return {
type: CATEGORY_CREATE,
payload: categoryMenuItems
}
}
Not use fixtures for state, use real api :)
Maybe your state not is inmutable. In your reducer use spread operator to add new items
{
list: [
...state.list,
addedCategory
]
}
Instead of
state.list.push(addedCategory)
Is is correct to pass a reducer as props when i'm using a rootreducer ?
This is my rootReducer.js :
import { combineReducers } from 'redux';
import simpleReducer from './simpleReducer';
import messageReducer from './messageReducer';
import NewReducer from './NewReducer';
export default combineReducers({
simpleReducer,messageReducer,NewReducer
});
And this is one of my action creators addMessage.js
export const addMessage = (message) => dispatch => {
dispatch({
type: 'ADD',
message: message
})
}
Here is the first reducer messageReducer.js
export default (state = [], action) => {
switch (action.type) {
case 'ADD':
return [
...state,
action.message
];
default:
return state;
}
};
And here is another one simpleReducer.js
export default (state = {}, action) => {
switch (action.type) {
case 'SIMPLE_ACTION':
return {
result: action.payload
}
default:
return state
}
}
And finally here is my last reducer NewReducer.js
export default (state = '', action) => {
switch (action.type) {
case 'AnyThing':
return action.WhatToDisplay;
default:
return state;
}
};
Here is my mapping in the App.js
const mapStateToProps = state => ({
...state
})
const mapDispatchToProps = dispatch => ({
simpleAction: () => dispatch(simpleAction()),
submitNewMessage: (message) => {
dispatch(addMessage(message))
},
NewAction: () => dispatch(NewAction())
})
And here is my ِApp Component.Notice my last 2 h2 tags as well as my ul tag .Without me adding the reducer at the end of the prop , it doesn't work.So
is what i'm doing right ? or is there another way to show the redux state in
my react ?.Note that i currently have no errors and the code functions well.I
just wana know if what i am doing is right or wrong and if there is a better
syntax to show the redux state in my create react app.
class App extends Component {
constructor(props) {
super(props);
this.state = {
input: ''
}
this.handleChange = this.handleChange.bind(this);
this.submitMessage = this.submitMessage.bind(this);
}
handleChange(event) {
this.setState({
input: event.target.value
});
}
submitMessage() {
this.props.submitNewMessage(this.state.input);
this.setState({
input: ''
});
}
simpleAction = (event) => {
this.props.simpleAction();
}
localNormalFunction=(event)=>{
this.props.NewAction()
}
render() {
return (
<div >
<h1>fjasgdasdsg</h1>
<button onClick={this.simpleAction}>Test redux action</button>
<pre>
{
JSON.stringify(this.props)
}
</pre>
<h2>Type in a new Message:</h2>
<input
value={this.state.input}
onChange={this.handleChange}/><br/>
<button onClick={this.submitMessage}>Submit</button>
<ul>
{this.props.messageReducer.map( (message,idx) => {
return (
<li key={idx}>{message}</li>
)
})
}
</ul><br/><br/>
<button onClick={this.localNormalFunction}>dsadsdsa</button>
<h2>{this.props.NewReducer}</h2>
<h2>{this.props.simpleReducer.result}</h2>
</div>
);
}
}
It is better practice to get only the props you need from redux in each component. If you pass the whole redux state in mapStateToProps then whenever anything in redux changes you will have everything rerendering even if nothing you use changed.
One common reason you might be getting errors is that you are trying to use the props in render and they get instantiated afterwards.
Try this give default values to the props if you can't get them from redux:
App.defaultProps = {
result: '',
NewReducer: '',
messageReducer: []
}
const mapStateToProps = state => ({
result: state.simpleReducer.result,
NewReducer: state.NewReducer,
messageReducer: state.messageReducer
})
and then change this.props.simpleReducer.result to this.props.result
Problem: shouldComponentUpdate retrieves previous state with this.state, that doesn't work if you keep reference to array at UserList, and update array entity at UserStore.
PureRenderMixin.js
const deepEqual = require('deep-equal');
module.exports = function pureRenderMixin(Component) {
Component.prototype.shouldComponentUpdate = function(nextProps, nextState) {
return !deepEqual(this.props, nextProps) || !deepEqual(this.state, nextState);
};
return Component;
};
UserList.react.js
class UserList extends React.Component {
constructor(props) {
super(props);
this._onChange = this._onChange.bind(this);
}
componentWillMount() {
UsersStore.addChangeListener(this._onChange);
}
_onChange() {
this.setState({userList: UsersStore.getState()});
}
}
module.exports = PureRenderMixin(UserList);
UsersStore.js
......
getState() { return _userList; }
switch(action.type) {
case ActionTypes.UPDATE_USER_FLAG:
//!!!!!!!!!!!!!!
//PROBLEM: since UserList.react keep userList reference, there is no way to retrieve previous state inside shouldComponentUpdate
_userList[action.index].flag = action.flag;
UsersStore.emitChange();
break;
}
#taggon solution
thanks to taggon, now I know how to make shouldComponentUpdate keep the reference to previous state:
UsersStore.js
......
getState() { return _userList; }
switch(action.type) {
case ActionTypes.UPDATE_USER_FLAG:
//SOLUTION: copy an array, so there will be two versions of _userList[action.index]
_userList = _.map(_userList, _.clone);
_userList[action.index].flag = action.flag;
UsersStore.emitChange();
break;
}
I think the problem is in the store.
The store would be better to create another array whenever its state is changed.
For example, think the store as an array:
var store = [ ];
export default store;
You may want to write add() function like this:
export function add(newItem) {
store = [ ...store, newItem ];
// or write this in es5
store = store.concat([newItem]);
// trigger change event or dispatch an action here
}
Likewise, remove() function can be:
export remove(index) {
store = [ ...store.slice(0, index), ...store.slice(index+1) ];
// trigger change event or dispatch an action here
}
In this way, the store dereference the component's state whenever the store is changed. This makesshouldComponentUpdate() return true.
I hope this helps you.
if your props are immutable, you can compare the data by reference safely and easily. you can have a look at immutablejs
class ProductStore extends ReduceStore {
getInitialState() {
return Immutable.OrderedMap({1: new Product('react', 'flux'), 2: new Product('angular', 'mvc')});
}
reduce (state, action) {
switch (action.type) {
case 'product/item-selected':
return state.map((product)=> {
return product.set('selected', product.id === action.id);
});
case 'product/search':
let alldata = this.getInitialState();
return alldata.filter((product)=> {
return product.name.indexOf(action.value) !== -1;
});
default:
return state;
}
}
}
export default class ProductDetail extends Component {
shouldComponentUpdate(nextProps) {
return this.props.product !== nextProps.product;
}
render() {
const {product} = this.props;
return (
<div className="product-detail">
<div className="product-name">
{product.name}
</div>
<div className="product-type">
{product.type}
</div>
</div>
);
}
}
https://facebook.github.io/immutable-js/