dispatching actions won't trigger render when using combinedReducers - reactjs

When I don't use combineReducers:
const store = createStore<StoreState,any,any,any>(pointReducer, {
points: 1,
languageName: 'Points',
});
function tick() {
store.dispatch(gameTick());
requestAnimationFrame(tick)
}
tick();
everything works and my component updates. However when I do:
const reducers = combineReducers({pointReducer}) as any;
const store = createStore<StoreState,any,any,any>(reducers, {
points: 1,
languageName: 'Points',
});
The store does update (checked by console logging) however the component doesn't render the change and I have no idea why!
The reducer:
export function pointReducer(state: StoreState, action: EnthusiasmAction): StoreState {
switch (action.type) {
case INCREMENT_ENTHUSIASM:
return { ...state, points: state.points + 1 };
case DECREMENT_ENTHUSIASM:
return { ...state, points: Math.max(1, state.points - 1) };
case GAME_TICK:
return { ...state, points: state.points + 1 };
default:
return state;
}
}
and component:
export interface Props {
name: string;
points: number;
onIncrement: () => void;
onDecrement: () => void;
}
class Points extends React.Component<Props, object> {
constructor(props: Props) {
super(props);
}
render() {
const { name, points, onIncrement, onDecrement } = this.props;
return (
<div className="hello">
<div className="greeting">
Hello {name + points}
</div>
<button onClick={onDecrement}>-</button>
<button onClick={onIncrement}>+</button>
</div>
);
}
}
export default Points;
The container:
export function mapStateToProps({ points, languageName }: StoreState) {
return {
points: points,
name: languageName,
}
}
export function mapDispatchToProps(dispatch: Dispatch<actions.EnthusiasmAction>) {
return {
onIncrement: () => dispatch(actions.incrementEnthusiasm()),
onDecrement: () => dispatch(actions.decrementEnthusiasm())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Points);
Store state:
export interface StoreState {
languageName: string;
points: number;
food: number;
wood: number;
}
When making the suggested changes (changing the reducer and combinereducers I get a new error:
my reducer now looks like:
export function pointReducer(state: 1, action: EnthusiasmAction) {
switch (action.type) {
case INCREMENT_ENTHUSIASM:
return state + 1;
case DECREMENT_ENTHUSIASM:
return Math.max(1, state - 1);
case GAME_TICK:
return state + 1;
default:
return state;
}
}

The problem is likely in how you're using combineReducers, vs how you're writing your mapState function.
I'm going to guess that your mapState function looks like:
const mapState = (state) => {
return {
points : state.points
}
}
This works okay when you use your pointsReducer by itself, because your state has state.points.
However, when you use combineReducers the way you are, you're creating two problems for yourself:
You're naming the state field state.pointsReducer, not state.points
Your pointsReducer is further nesting the data as points
So, the actual data you want is at state.pointsReducer.points, when the component is expecting it at state.points.
To fix this, you should change how you're calling combineReducers, and change the pointsReducer definition to just track the data without nesting:
export function pointReducer(state: 1, action: EnthusiasmAction): StoreState {
switch (action.type) {
case INCREMENT_ENTHUSIASM:
return state + 1
case DECREMENT_ENTHUSIASM:
return Math.max(1, state - 1);
case GAME_TICK:
return state.points + 1;
default:
return state;
}
}
// later
const rootReducer = combineReducers({
points : pointsReducer,
languageName : languageNameReducer,
});
See the Redux docs page on "Using combineReducers" for more details

Related

Array inside redux state is undefined

here I'm facing a little problem with react - redux .....
I'm getting state attribute (array) undefined.... What might be the reason?
Here is my Reducers.js code ..
const initialState = {
cList: [], //I'm getting this array undefined...
selectedCatagory: "",
}
export const reducerForCatagoryList = (state = initialState , action) => {
switch(action.type){
case "CATAGORY_LIST":
let lis = [...state.cList,action.payload];
return Object.assign({},state,{ cList: lis });
default:
return state;
}
}
Actions.js code is:
export const catagoryList = (list) => {
return {
type: "CATAGORY_LIST",
payload: list
}
}
And my Component's code is:
//This array is just for testing purpose....
const list = [{
cName: 1,
heading: "Heading",
link: ""
},
{
cName: 2,
heading: "Heading2",
link: ""
}]
const mapStateToProps = (state) => {
console.log("state is: ",state);
return {
cList: state.cList
}
}
const mapDispatchToProps = (dispatch) => {
return {
catagoryList: (list) => {
dispatch(catagoryList(list));
}
}
}
class MainPage extends Component{
render(){
console.log("Props are: ",this.props)
return(
<div id = "Cards-div">
{this.props.catagoryList(list)}
{
createCards(this.props.cList) //I want to send Array inside redux store (cList) to this......
}
</div>
)
}
}
export default connect(mapStateToProps,mapDispatchToProps)(MainPage);
So, Plz can someone explain me what's going wrong here?

React-Redux's mapStateToProps working in unexpected way

I have a component which shows a list of names fetched from the Redux store. The component looks like this:
class Details extends React.Component {
constructor (props) {
super(props);
}
render () {
const listName = [...this.props.listName];
return (
<div className="container detailsBox">
<p>Details Grid:</p>
{listName ? (
listName.map((el, index) => {
return (
<li key={index}>
{el}
</li>
)
})
): null}
</div>
)
}
}
const mapStateToProps = (state) => {
return {
listName: state.listName
}
}
export default connect(mapStateToProps)(Details);
Notice here I only map the listName from the store. But the code does not display the <li> with the elements of listName even when logging in console shows listName is populated with data
But data shows as expected when the name is also fetched from the store, with this modification in mapStateToProps:
const mapStateToProps = (state) => {
return {
listName: state.listName,
name: state.name
}
}
I am befuddled as well as curious to know why the code behaves in this unexpected way? What am I missing here?
The reducer code looks like this:
import { UPDATE_NAME, ADD_NAME } from '../actions/addName.action';
const initialState = {
name: '',
listName: new Set()
}
function rootReducer (state = initialState, action) {
switch (action.type) {
case ADD_NAME:
return {
name: '',
listName: state.listName.add(action.payload)
}
case UPDATE_NAME:
return {
...state,
name: action.payload
}
default:
return state;
}
}
export default rootReducer;
and the actions like this:
export const ADD_NAME = 'ADD_NAME';
export const UPDATE_NAME = 'UPDATE_NAME';
export function addName (data) {
return {
type: ADD_NAME,
payload: data
}
}
export function updateName (name) {
return {
type: UPDATE_NAME,
payload: name
}
}
This is happens because you mutate the state. When you mutate listNames it still points to the same object in memory, and react thinks that nothing is changed, while the contents of listNames is changed. You should return a new object.
case ADD_NAME:
return {
name: '',
listName: new Set([...state.listName, action.payload]),
}
But it is not recommended to use Sets in reducers. Instead you can store your listNames as an Array and use lodash union function to have only unique values.
import { union } from 'lodash';
const initialState = {
name: '',
listName: []
}
// some code
case ADD_NAME:
return {
name: '',
listName: union(state.listNames, action.payload)
}
You can instead use an object to hold the listName and then recreate the structure of your state into a new object and then return.
const initialState = {
name: '',
listName: {}
}
case ADD_NAME:
return {
name: '',
listName: {...state.listName, action.payload}
}

React-redux - can't extract part of state

There is this action creator:
export function selectBook(book) {
// selectBook is an ActionCreator, it needs to return an action,
// an object with a type property.
return {
type: 'BOOK_SELECTED',
payload: book,
};
}
There's this simple reducer:
export default function() {
return [
{ title: 'Javascript: The Good Parts', pages: 101 },
{ title: 'Harry Potter', pages: 39 },
{ title: 'The Dark Tower', pages: 85 },
{ title: 'Eloquent Ruby', pages: 1 }
];
}
The rest of the code is here:
https://github.com/StephenGrider/ReduxCasts/tree/master/book_list/src
Now, I am playing with it and wanted to return eg. number of characters in the title but first I just want to extract the title in the action creator and display it. I've modified the action creator as follows:
export function selectBook(book) {
// selectBook is an ActionCreator, it needs to return an action,
// an object with a type property.
let title = book.title;
console.log(title);
return {
type: 'BOOK_SELECTED',
payload: book,
count: title
};
}
Afterwards, I'll add methods to 'title' - I just want to access it for now from a component. The console.log(title) above outputs the title correctly. As you can see from the link, it then goes through mapStateToProps and then
<div>Title: {this.props.book.title}</div>
<div>Pages: {this.props.book.pages}</div>
<div>Count: {this.props.book.count}</div>
The first two output fine, as per original code. The third one, Count, does not. The {this.props.book.count} outputs blank.
Sorry for the confusion with multiple titles. Under 'Count' for now, I want to output the title. Once I've done it, I'll change the action creator to count the title's characters.
EDIT:
// State argument is not application state, only the state
// this reducer is responsible for
export default function (state = null, action) {
switch (action.type) {
case 'BOOK_SELECTED':
return { action.payload, action.count };
}
return state;
}
I tried the above but the syntax is wrong.
Some more code (reducers index):
import { combineReducers } from 'redux';
import BooksReducer from './reducer_books';
import ActiveBook from './reducer_active_book';
const rootReducer = combineReducers({
books: BooksReducer,
activeBook: ActiveBook
});
export default rootReducer;
Container with mapStateToProps:
import React, { Component } from 'react';
import { connect } from 'react-redux';
class BookDetail extends Component {
render() {
if (!this.props.book) {
return <div>Select a book to get started.</div>;
}
return (
<div>
<h3>Details for:</h3>
<div>Title: {this.props.book.title}</div>
<div>Pages: {this.props.book.pages}</div>
<div>Count: {this.props.book.count}</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
book: state.activeBook
};
}
export default connect(mapStateToProps)(BookDetail);
In a reducer you need to augment the state object to reflect the new payload you have sent. Not sure what your requirement is, but I guess you just want to return the an object with title, pages and count in the reducer.
You are getting the error in your reducer as indeed there is a syntax error.
The correct implementation if I understand the requirement right would be:-
export default function (state = null, action) {
switch (action.type) {
case 'BOOK_SELECTED':
return { title: action.payload.title,
pages: action.payload.pages,
count: action.count };
}
return state;
}
Another thing I would like to say is that you are anyways passing book in the payload then why do you need to pass the count separately, as it can easily be inferred from the book object.
Your action could be:-
export function selectBook(book) {
return {
type: 'BOOK_SELECTED',
payload: book
};
Reducer will be:-
export default function (state = null, action) {
switch (action.type) {
case 'BOOK_SELECTED':
return book;
}
return state;
}
As per advice given in the comments, what it needed is object augmentation:
export default function (state = null, action) {
switch (action.type) {
case 'BOOK_SELECTED':
return { payload: action.payload, count: action.count };
}
return state;
}

Update redux state in two places with one action

I am building a webshop and I´ve run in to some problems. When I click on the buy button I want three things to happen:
Add the product to the redux state (shoppingcartReducer)
Update the sum and quantity in the redux state (shoppingcartReducer)
Render out the sum and quantity in my shoppingCart.js
component
The problem is in the shoppingcartReducer. I dont know how to update the state in two different paths on one action.
return state
.setIn(['products', action.articleNr], addArticle(action, state))
Here I am in the 'products/articleNr' path and manipulating data, but I also wants to update the sum and quantity in './'.
So my alternatives that I thought of are maybe to have some sort of middleware thing in the shoppingcartActions but I don´t really know if this is right, nor how to do it!
I appreciate all the help, thanks!
shoppingcartReducer:
import Immutable from 'seamless-immutable'
import { addToShoppingcart, createShoppingCart } from '../actions/shoppingcartActions'
const ADD_TO_SHOPPINGCART = 'ADD_TO_SHOPPINGCART'
const CREATE_SHOPPING_CART = 'CREATE_SHOPPING_CART'
const initialState = Immutable({
sum: 0,
quantity: 0
})
export default function shoppingcartReducer(state = initialState, action) {
switch (action.type) {
case ADD_TO_SHOPPINGCART:
return state
.setIn(['products', action.articleNr], addArticle(action, state))
case CREATE_SHOPPING_CART:
return state
.set(action.id, createCart(action))
}
return state
}
function addArticle(action, state) {
return {
product: action.product
}
}
function createCart(action) {
return {
id: action.id,
}
}
shoppingcartActions:
let nextTodoId = 0
const ADD_TO_SHOPPINGCART = 'ADD_TO_SHOPPINGCART'
const CREATE_SHOPPING_CART = 'CREATE_SHOPPING_CART'
export function addToShoppingcart(product) {
return {
type: ADD_TO_SHOPPINGCART,
articleNr: product.articleNr,
product: product,
}
}
export function createShoppingCart() {
return {
type: CREATE_SHOPPING_CART,
id: 'productNr:'+nextTodoId++,
}
}
ShoppingCart.js:
import React, { Component } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import * as shoppingcartActions from '../../actions/shoppingcartActions'
class ShoppingCart extends Component {
componentWillMount() {
this.state = {
shoppingcartReducer: []
}
}
componentWillReceiveProps(nextProps) {
this.setState({
shoppingcartReducer: nextProps.shoppingcartReducer ? nextProps.shoppingcartReducer : ''
})
}
render() {
const { shoppingcartReducer } = this.props
const { sum, quantity } = shoppingcartReducer
return (
<div className="shoppingCart">
<ul>
<li>Summa: {sum} :-</li>
<li>Antal varor: {quantity}</li>
</ul>
</div>
)
}
}
function mapStateToProps(state) {
return {
shoppingcartReducer: state.shoppingcartReducer
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(shoppingcartActions, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(ShoppingCart)
This worked!
export function addProduct(product) {
return {
type: ADD_PRODUCT,
articleNr: product.articleNr,
product: product,
}
}
export function addSummary(product) {
return {
type: ADD_SUMMARY,
product: product
}
}
export function addToShoppingcart(product) {
return (dispatch) => {
dispatch( addProduct(product))
dispatch( addSummary(product))
}
}

What is the purpose of using Object.assign in this redux reducer?

I know that state should be immutable and this is a no-no, mutating state with push,
//action = Object {type: "CREATE_COURSE", course: {title: algebra}}
export default function courseReducer(state = [], action) {
switch(action.type) {
case 'CREATE_COURSE':
state.push(action.course)
return state;
default:
return state;
}
}
Pluralsight recommends this:
export default function courseReducer(state = [], action) {
switch(action.type) {
case 'CREATE_COURSE':
return [...state,
Object.assign({}, action.course)
];
default:
return state;
}
}
but what is wrong with not using object.assign? What is wrong with this, seems like the app still works. state is still not being mutated, a new array is being returned.
export default function courseReducer(state = [], action) {
switch(action.type) {
case 'CREATE_COURSE':
return [...state,
action.course
];
default:
return state;
}
}
CourseActions:
export function createCourse(course) {
return { type: 'CREATE_COURSE', course};
}
CoursesPage Component:
import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import * as courseActions from '../../actions/courseActions';
class CoursesPage extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
course: { title: '' }
};
this.onTitleChange = this.onTitleChange.bind(this);
this.onClickSave = this.onClickSave.bind(this);
}
onTitleChange(event) {
const course = this.state.course; // assign this.state.course to course
course.title = event.target.value; // reassign course.title to whatever was in input
this.setState({course: course}); // reassign state course to new course object with updated title
}
onClickSave() {
this.props.actions.createCourse(this.state.course);
}
courseRow(course, index) {
return <div key={index}>{course.title}</div>;
}
render() {
return (
<div>
<h1>Courses</h1>
{this.props.courses.map(this.courseRow)}
<h2>Add Course</h2>
<input
type="text"
onChange={this.onTitleChange}
value={this.state.course.title} />
<input
type="submit"
value="Save"
onClick={this.onClickSave} />
</div>
);
}
}
CoursesPage.propTypes = {
courses: PropTypes.array.isRequired,
actions: PropTypes.object.isRequired
};
function mapStateToProps(state, ownProps) {
return {
courses: state.courses
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(courseActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(CoursesPage);
If you don't use Object.assign, last element of new array will be a reference to action.course and whatever that is a reference to. Passing a reference may work fine, but if something mutates it somewhere down the line and it causes problems - they will be hard to debug. Better safe than sorry.
if you just put the reference to action.course in the array, all of your components that use this.props.courses will see all the course objects in the array share the same reference, which shouldn't be an issue up to the point where any of the component intentionally/unintentionally mutate any of them even temporarily.
FYI: Object.assign is also more performant than the spread operator.

Resources