React.js Redux reducer does not insert the item - javascript

I'm very new to React.js and Redux.
I'm trying to build a very simple shopping cart application.
What I want is if you hit on an item (eg :- banana) It should appear in the cart.
(It should change the state of the cartReducer.js)
But instead of pushing the item to the reducer state it pushes something else.
What is the reason for this error?
This is my code.
cartReducer
import {ADD_TO_CART} from '../actions/index'
const initialState =[
]
export default (state = initialState,action)=>{
console.log("ACTION PAYLOAD",action.payload)
switch(action.type){
case ADD_TO_CART:
return[...state,action.payload]
default:
return state
}
}
Item component
import React, { Component } from "react";
import { connect } from "react-redux";
import {addToCart} from '../../actions/index'
export class Etem extends Component {
showItems = () => {
const { items, addToCartAction } = this.props;
console.log("ITEMS", items);
return items.map(items => <div key={items.id} onClick={addToCartAction}>{items.name}</div>);
};
render() {
return (
<div>
<h1>Items</h1>
<div>{this.showItems()}</div>
</div>
);
}
}
// export default items;
const mapStateToProps = reduxState => ({
items: reduxState.items
});
const mapDispatchToProps = dispatch => ({
addToCartAction: item => dispatch(addToCart(item))
});
export default connect(mapStateToProps, mapDispatchToProps)(Etem);
Action
export const ADD_TO_CART = 'ADD_TO_CART';
export const addToCart=(item) =>{
console.log("ITEMMMMMMMMMM",item)
return(
{
type:ADD_TO_CART,
payload:item,
}
)
}

<div key={items.id} onClick={addToCartAction}> this will pass the click event to addToCartAction instead of item.
Try this:
return items.map(item => (
<div key={item.id} onClick={() => addToCartAction(item)}>
{item.name}
</div>
));

Can you change the mapStateToProps
const mapStateToProps = reduxState => ({
items: reduxState.cartReducer.items
})

Related

Redux State doesnt change in comonent

The reducers and actions are being called and working properly but my react components arent being changed after state is being changed.
This is my actions called cart.js:
import { ADD_TO_CART, REMOVE_FROM_CART } from './types';
export const addToCart = item => dispatch => {
dispatch({
type: ADD_TO_CART,
payload: {
item
}
});
}
export const removeFromCart = uid => dispatch => {
dispatch({
type: REMOVE_FROM_CART,
payload: {
uid
}
});
}
This is my reducers file called cart.js:
import { ADD_TO_CART, REMOVE_FROM_CART } from '../actions/types';
const initialState = [];
//Cannot mutate array in reducer
export default function(state = initialState, action){
const { type, payload } = action;
switch(type){
case ADD_TO_CART:
for(var i = 0; i < state.length; i++){
if(state[i].item.uid === payload.item.uid){
state[i].item.qty ++;
return [...state];
}
}
return [...state, payload];
case REMOVE_FROM_CART:
for(var j = 0; j < state.length; j++){
if(state[j].item.uid === payload.uid){
state[j].item.qty = 1;
}
}
return state.filter(cartItem => cartItem.item.uid !== payload.uid);
default:
return state;
}
}
My component files:
Cart.js:
import React, { Fragment } from 'react';
import { connect } from 'react-redux';
import { removeFromCart } from '../actions/cart';
import CartItem from './CartItem';
const Cart = ({ cart }) => {
return (
<div>
<h1>Cart</h1>
{cart.map(cartItem => (
<div style={{backgroundColor: 'blue'}}>
<CartItem cartItem={cartItem.item} key={cartItem.item.uid} />
</div>
))}
</div>
)
}
const mapStateToProps = state => ({
cart: state.cart
})
export default connect(
mapStateToProps,
{ removeFromCart }
)(Cart);
CartItem.js:
import React from 'react'
import { removeFromCart } from '../actions/cart';
import { connect } from 'react-redux';
const CartItem = ({ cartItem, removeFromCart }) => {
const handleRemoveClick = () => {
console.log('clicked', cartItem.uid);
removeFromCart(cartItem.uid);
}
return (
<div onClick={handleRemoveClick}>
{cartItem.name}
{cartItem.qty}
</div>
)
}
export default connect(
null,
{ removeFromCart }
)(CartItem);
Im trying to learn redux using react and any help is appreciated. Are for loops allowed in reducers? Im not sure why it isnt updating, the redux devtools shows everything is working. Specifically the part that isnt working is the item quantity is not being updated in component. When i remove an item it works.
Don't mutate the redux state in reducers. Copy the state and mutate it.
const newState = [...state];
//mutate newState;
return newState;

is there any way to make this react component less verbose?

So, i wrote a test project to explore react, react-router and react-redux.
After i got everything working fine i laid my eyes again on Settings.jsx and i am wondering how could i make it less verbose and error prone:
import React, { Component } from "react";
import { connect } from "react-redux";
class Settings extends Component {
state = { name: this.props.settings.name };
render() {
return (
<div>
<h1>Settings</h1>
<p>This is Settings page</p>
My name is{" "}
<input
value={this.state.name}
onChange={e => this.setState({ name: e.target.value })}/>
<button onClick={e => this.props.changeName(this.state.name)}>
Change
</button>
</div>
);
}
}
const mapState = state => ({ settings: state.settings });
const mapDispatch = dispatch => {
return {
changeName(name) {
dispatch({ type: "setName", name });
}
};
};
export default connect(
mapState,
mapDispatch
)(Settings);
My first idea was to convert it into a functional component, but it's said that they don't have state and i need the state to locally handle the input.
With #babel/plugin-proposal-decorators, connect can be used as a decorator:
import React, { Component } from "react";
import { connect } from "react-redux";
const mapState = state => ({ settings: state.settings });
const mapDispatch = dispatch => {
return {
changeName(name) {
dispatch({ type: "setName", name });
}
};
};
#connect(mapState, mapDispatch)
export default class Settings extends Component {
state = { name: this.props.settings.name };
render() {
return (
<div>
<h1>Settings</h1>
<p>This is Settings page</p>
My name is{" "}
<input
value={this.state.name}
onChange={e => this.setState({ name: e.target.value })}/>
<button onClick={e => this.props.changeName(this.state.name)}>
Change
</button>
</div>
);
}
}
small, but imho nice simplification
also, you could use concise syntax with your mapDispatch:
const mapDispatch = dispatch => ({
changeName(name) {
dispatch({ type: "setName", name });
}
});
you can do this if you want to to add the typing text in store:
Settings.js
import React from "react";
import { changeName, typingName } from '../actions/settingsActions'
import { connect } from "react-redux";
const Settings = () => {
const { changeName, typingName, typedName, submittedName } = this.props
return (
<div>
<h1>Settings</h1>
<p>This is Settings page</p>
My name is{" "}
<input
value={typedName}
onChange={e => typingName(e.target.value)}/>
<button onClick={changeName(submittedName)}>
Change
</button>
</div>
);
}
const mapState = state => ({
typedName: state.typedName,
submittedName: state.submittedName
});
const mapDispatchToProps = dispatch => ({
typingName: x => dispatch(typingName(x)),
changeName: x => dispatch(changeName(x))
})
export default connect(
mapState,
mapDispatch
)(Settings);
settingsActions.js
export const typingName = payload => ({
type: 'TYPING_NAME',
payload
});
export const changeName = payload => ({
type: 'CHANGE_NAME',
payload
});
settingsReducer.js
export const typingName = (state = [], action) => {
switch (action.type) {
case 'TYPING_NAME':
return [...state, action.payload];
default:
return state;
}
};
export const changeName = (state = '', action) => {
switch (action.type) {
case 'CHANGING_NAME':
return action.payload;
default:
return state;
}
};
You could maybe achieve something like this. But validating the typing state inside the component then sending the final result to the store as you did is a better idea I think, to avoid so much verbose.
Also you should of course create a constants file, but I guess you know already.

Action doesn't update the store

|I have the following component based on this:
**WarningModal.js**
import React from 'react';
import ReactDOM from 'react-dom';
import {connect, Provider} from 'react-redux';
import PropTypes from 'prop-types';
import {Alert, No} from './pure/Icons/Icons';
import Button from './pure/Button/Button';
import Modal from './pure/Modal/Modal';
import {setWarning} from '../actions/app/appActions';
import configureStore from '../store/configureStore';
const store = configureStore();
export const WarningModal = (props) => {
const {message, withCleanup} = props;
const [
title,
text,
leave,
cancel
] = message.split('|');
const handleOnClick = () => {
props.setWarning(false);
withCleanup(true);
}
return(
<Modal>
<header>{title}</header>
<p>{text}</p>
<Alert />
<div className="modal__buttons-wrapper modal__buttons-wrapper--center">
<button
onClick={() => withCleanup(false)}
className="button modal__close-button button--icon button--icon-only button--text-link"
>
<No />
</button>
<Button id="leave-warning-button" className="button--transparent-bg" onClick={() => handleOnClick()}>{leave}</Button>
<Button id="cancel-warning-button" onClick={() => withCleanup(false)}>{cancel}</Button>
</div>
</Modal>
);
}
WarningModal.propTypes = {
withCleanup: PropTypes.func.isRequired,
message: PropTypes.string.isRequired,
setWarning: PropTypes.func.isRequired
};
const mapStateToProps = state => {
console.log(state)
return {
isWarning: state.app.isWarning
}
};
const WarningModalContainer = connect(mapStateToProps, {
setWarning
})(WarningModal);
export default (message, callback) => {
const modal = document.createElement('div');
document.body.appendChild(modal);
const withCleanup = (answer) => {
ReactDOM.unmountComponentAtNode(modal);
document.body.removeChild(modal);
callback(answer);
};
ReactDOM.render(
<Provider store={store}>
<WarningModalContainer
message={message}
withCleanup={withCleanup}
/>
</Provider>,
modal
);
};
the issue I have is that 'setWarning' doesn't update the state, it does get called as I have a debugger inside the action and the reducer but the actual property doesn't not change to 'false' when:
props.setWarning(false);
gets called.
I use the following to trigger the custom modal:
const togglePromptCondition =
location.hash === '#access-templates' || location.hash === '#security-groups'
? promptCondition
: isFormDirty || isWarning;
<Prompt message={promptMessage} when={togglePromptCondition} />
To test this even further I have added 2 buttons in the application to toggle 'isWarning' (the state property I am talking about) and it works as expected.
I think that although WarningModal is connected in actual fact it isn't.
REDUCER
...
case SET_WARNING:
console.log('reducer called: ', action)
return {
...state,
isWarning: action.payload
};
...
ACTION
...
export const setWarning = status => {
console.log('action called')
return {
type: SET_WARNING,
payload: status
}
};
...
UPDATE
After having to incorporates the following:
const mapStateToProps = state => {
return {
isWarning: state.app.isWarning
}
};
const mapDispatchToProps = dispatch => {
return {
setWarning: (status) => dispatch({ type: 'SET_WARNING', payload: status })
}
};
I am now getting:
Maybe this could help?
You have to dispatch the actions in the action creator and the type of the action to dispatch should be always string.
Try this
const mapStateToProps = state => {
console.log(state)
return {
isWarning: state.app.isWarning
}
};
const mapDispatchToProps = dispatch => {
console.log(dispatch)
return {
setWarning: (status) => dispatch({ type: 'SET_WARNING', payload: status })
}
};
const WarningModalContainer = connect(mapStateToProps, mapDispatchToProps)(WarningModal);
REDUCER
...
case 'SET_WARNING':
console.log('reducer called: ', action)
return {
...state,
isWarning: action.payload
};
...

Pass props to React container component

I'm new to React. I'm displaying a list of songs and I want to allow the user to add songs to their favourites. I'm using Redux to store the favourited songs. My PlayList component looks like:
import AddSong from '../containers/AddSong'
class Playlist extends Component {
render(){
return (
<div>
<h1>Playlists</h1>
<ul className="container">
{this.state.items.map(item =>
<li key={item.track.id}>
{item.track.name} by {item.track.artists[0].name}
<img src={item.track.album.images[0].url} height="150" width="150" />
<AddSong title={item.track.name} />
</li>
)}
</ul>
</div>
);
}
...
}
So I passing the song name to AddSong with <AddSong title={item.track.name} />
And AddSong looks like:
import React from 'react'
import { connect } from 'react-redux'
import { addSong } from '../actions'
let AddSong = ({ dispatch }) => {
let input
console.log('this is ', this);
return (
<div>
<form
onSubmit={e => {
e.preventDefault()
// GET SONG FROM PROPS AND DISPATCH
//dispatch(addSong(input.value))
}}
>
<input
ref={node => {
input = node
}}
/>
<button type="submit">
Add Song
</button>
</form>
</div>
)
}
AddSong = connect()(AddSong)
export default AddSong
However, this is an object with the property:
{
a: Connect(props, context)
}
How do I get the song title in AddSong?
EDIT
So this is what I have now, Im passing the song title to AddSong here:
<AddSong song={item.track.name} title={item.track.name} />
I'm passing the song title in as song and title to show what happens.
In AddSong, I have:
const mapStateToProps = (state) => {
const {song:song} = state; // or whatever the reducer called
return {song};
};
const mapDispatchToProps = (dispatch) => ({
addSong: (value) => dispatch(addSong(value)),
});
export default connect(mapStateToProps, mapDispatchToProps)(AddSong);
And at the top of AddSong I'm doing:
let AddSong = ({ dispatch, ...props }) => {
let input
console.log('props is ', props);
The console outputs:
props is Object {song: undefined, title: "Young Blood"}
I've changed the button to:
<button onClick={(value)=>props.addSong(value)}>
Add Song
</button>
When I click, this gives the error:
Failed prop type: Invalid prop `songs[0].text` of type `object` supplied to `SongList`, expected `string
Try to use this function
const mapStateToProps = function(store) {
return {
data: store.data
};
}
AddSong = connect(mapStateToProps)(AddSong)
I assume you have the reducer and you want to access the state via props, if this the case you can mapStateToProps e.g.
const mapStateToProps = (state) => {
const {song:song} = state; // or whatever the reducer called
return {song};
};
const mapDispatchToProps = (dispatch) => ({
addSong: (value) => dispatch(addSong(value)),
});
export default connect(mapStateToProps, mapDispatchToProps)(AddSong);
then you can just write this.props.song.

Redux Component will not update on store change

I'm trying to get to grips with Redux + React - I have hooked up the relevant bits of Redux with connect() for a small todo app but I cannot for the life of me get the component to update and show the reflected store changes. The store state does update however the component will not. Here are the relevant bits in my code:
actionTypes.js
export const ADD_TODO = "ADD_TODO";
export const DELETE_TODO = "DELETE_TODO";
export const CLEAR_TODO = "CLEAR_TODO";
export const COMPLETE_TODO = "COMPLETE_TODO";
reducers.js
import {ADD_TODO, COMPLETE_TODO, DELETE_TODO, CLEAR_TODO} from '../actions/actionTypes';
const todoApp = (state, action) => {
let updatedState;
switch (action.type) {
case ADD_TODO:
updatedState = Object.assign({}, state);
updatedState.todo.items.push({
text: action.text,
completed: false
});
return updatedState;
case COMPLETE_TODO:
updatedState = Object.assign({}, state);
updatedState.todo.items[action.index].completed = true;
return updatedState;
case DELETE_TODO:
const items = [].concat(state.todo.items);
items.splice(action.index, 1);
return Object.assign({}, state, {
todo: {
items: items
}
});
case CLEAR_TODO:
return Object.assign({}, state, {
todo: {
items: []
}
});
default:
return state;
}
};
export default todoApp;
actions.js
import {ADD_TODO, COMPLETE_TODO, DELETE_TODO, CLEAR_TODO} from './actionTypes.js';
export const addTodoCreator = (text) => {
return {
type: ADD_TODO,
text: text,
completed: false
}
};
export const completeTodo = (index) => {
return {
type: COMPLETE_TODO,
index: index
}
};
export const deleteTodo = (index) => {
return {
type: DELETE_TODO,
index: index
}
};
export const clearTodo = (index) => {
return {
type: CLEAR_TODO,
index: index
}
};
AddTodoContainer.js
import { connect } from 'react-redux';
import TodoList from '../components/TodoList';
const mapStateToProps = (state, ownProps) => {
return {
todo: state.todo
}
};
export default connect(mapStateToProps)(TodoList);
TodoListContainer.js
import { connect } from 'react-redux';
import {addTodoCreator} from '../actions/actions';
import AddTodo from '../components/AddTodo';
const mapStateToProps = (state) => {
console.log(state);
return {
todo: state.todo
}
};
const mapDispatchToProps = (dispatch) => {
return {
addTodo: (text) => {
const action = addTodoCreator(text);
dispatch(action);
},
}
};
export default connect(mapStateToProps, mapDispatchToProps)(AddTodo);
AddTodo.js
import React from 'react'
const handler = (addTodo) => {
const text = document.getElementById('textInput').value;
addTodo(text);
};
const AddTodo = ({addTodo}) => {
return (
<div>
<input id="textInput" type="text" className="textInput" />
<button onClick={(handler).bind(null, addTodo)}>Add</button>
</div>
)
}
export default AddTodo
TodoList.js
import React from 'react';
import AddTodoContainer from '../containers/AddTodoContainer';
class TodoList extends React.Component {
render () {
console.log(this.props);
return (
<div>
<ul>
{this.props.todo.items.map((item) => {
return <li>
{item.text}
</li>
})}
</ul>
<AddTodoContainer/>
</div>
)
}
}
export default TodoList;
I've tried all of the suggestions under Troubleshooting and as far as I can tell I am not mutating state. The reducer is firing and I can log out the states. The code is stored here under react-fulltodo http://gogs.dev.dylanscott.me/dylanrhysscott/learn-redux
Thanks
Dylan
You're passing todo to your component and while the todo object gets updated the actual todo object in redux state is the same exact object as it was before. So react does not see the object as changed. For example:
const a = { foo: 'bar' };
const b = a;
b.foo = 'I made a change';
console.log(a==b);
// logs true because a and b are the same object
// This is exactly what's happening in React.
// It sees the object as the same, so it does not update.
You need to clone the todo object so that react sees it as a changed/new object.
In your reducer:
switch (action.type) {
case ADD_TODO:
updatedState = Object.assign({}, state);
// Shallow clone updatedState.todo
updatedState.todo = Object.assign({}, updatedState.todo);
updatedState.todo.items.push({
text: action.text,
completed: false
});
return updatedState;
Meanwhile, if you passed state.todo.items to your component you would not have to clone todo but you would have to clone items. So in the future, if you have a component that directly mapStateToProps with state.todo.items, it will have the same problem because you are not cloning the items array in ADD_TODO like you are in the DELETE_TODO reducer.

Categories

Resources