Deleting an item from my redux state gives multiple errors - reactjs

I am having 2 issues:
Initially I can add clients to the empty array through action creators and my reducer. However, whenever I delete the items from the list and try to add new clients to it, it gives me an error: TypeError: Invalid attempt to spread non-iterable instance.
When I said I am deleting the items, what really happens is I create the clients, and then when I click on the delete button next to one of them, all of the clients delete. There is not error in the console, but I just want to delete the specific client with the corresponding id.
Here is my code!
Clients.js
import React, { Component } from 'react'
import AddClient from './AddClient'
import {connect} from 'react-redux'
import {deleteClient} from '../../store/actions/clientActions'
class Clients extends Component {
handleClick = (id) => {
console.log(id)
this.props.deleteClient(id)
}
render() {
const {clientList} = this.props
return (
<div className="container mt-5">
<h2>Here Are Your List of Clients...</h2>
{clientList && clientList.map(client => {
return(
<div key={client.id}>
<div>
Client Name: {client.name} | Client Price: {client.price}
<button onClick={() => {this.handleClick(client.id)}}>Delete</button>
</div>
</div>
)
})}
<AddClient/>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
clientList : state.clients.clientList,
}
}
const mapDispatchToProps = (dispatch) => {
return{
deleteClient : (id) => dispatch(deleteClient(id))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Clients)
Actions:
export const addClient = (client) => {
return(dispatch, getState) => {
dispatch({type: 'ADD CLIENT', client})
}
}
export const deleteClient = (id) => {
return(dispatch, getState) => {
dispatch({type: 'DELETE CLIENT', id})
}
}
Reducer:
const initState = {
clientList: []
}
const clientReducer = (state = initState, action) => {
switch (action.type) {
case 'ADD CLIENT' :
action.client.id = Math.random();
let clientList = [...state.clientList, action.client];
clientList.sort((a, b) => a.name.localeCompare(b.name));
return {
clientList
};
case 'DELETE CLIENT' :
const id = action.id;
clientList = state.clientList.filter(client =>
{return client.id !== id});
return clientList;
default : return state;
}
}
export default clientReducer
Lastly, this is AddClient.js
import React, { Component } from 'react'
import {connect} from 'react-redux'
import {addClient} from '../../store/actions/clientActions'
class AddClient extends Component {
state = {
id: null,
name: null,
price: null,
}
handleChange = (e) => {
this.setState({
[e.target.id] : e.target.value
})
}
handleSubmit = (e) => {
e.preventDefault();
this.props.addClient(this.state);
e.target.reset();
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit} className="mt-5">
<h3>Add a new client:</h3>
<label htmlFor="name">Client Name: </label>
<input type="text" id="name" onChange={this.handleChange}/><br/>
<label htmlFor="price">Client Price: </label>
<input type="text" id="price" onChange={this.handleChange}/> <br/>
<button className="btn btn-primary">Add Client</button>
</form>
</div>
)
}
}
const mapDispatchToProps = (dispatch) => {
return {
addClient: (client) => dispatch(addClient(client))
}
}
export default connect(null, mapDispatchToProps)(AddClient)
Thank you for all the help, I am fairly new to React and Redux. Let me know if there is any other code you would like to see.

Here's how you can accomplish the delete:
export const deleteClient = (id) => {
const index = find the index of the client you want to delete from the array
return(dispatch, getState) => {
dispatch({type: 'DELETE CLIENT', index})
}
}
case 'DELETE CLIENT' :
return {
...state,
clientList: [
...state.clientList.slice(0, action.index),
...state.clientList.slice(action.index + 1)
]
}

I figured it out, the problem is within my clientReducer.js
This needs to change:
case 'DELETE CLIENT' :
const id = action.id;
clientList = state.clientList.filter(client =>
{return client.id !== id});
return clientList;
to...
case 'DELETE CLIENT' :
const id = action.id;
let newClientList = state.clientList.filter(client => {
return id !== client.id;
})
return {clientList : newClientList};

case 'DELETE CLIENT' :
const id = action.id;
const clientList = state.clientList.filter(client =>
{return client.id !== id});
return {
...state,
clientList
}
You're currently returning just an array, instead of an object. Since that's probably the only thing you have in your redux store right now, it's not breaking (in the ADD action), but you probably want to apply the previous state first, then add your newly filtered clientlist to the state you're returning.

Related

React component not updating using redux

I'm trying to create a very simple Project in React to learn Redux, and I'm expecting that, whenever I click a button, the text inside a paragraph will be rendered, however, I can't get the app component (the only one I have) to get updated. Where am i failing?.
import { createStore } from 'redux'
const deleteUsers = 'delete users';
const createUser = 'créate user';
const createUserStranger = 'create stranger';
function deleteUsersActionCreator() {
return {
type: deleteUsers,
data: []
}
}
function createUsersActionCreator() {
return {
type: createUser,
data: ['Irina', 'Denis', 'Julio']
}
}
function createUserStrangerActionCreator() {
return {
type: createUserStranger,
data: 'stranger'
}
}
const initialState = {
users: []
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case createUser:
return {
...state,
users: [...state.users, ...action.data]
}
case deleteUsers:
return {
...state,
users: []
}
case createUserStranger:
return {
...state,
users: [...state.users, action.data]
}
default:
return state
}
};
function dispatchAction(action) {
store.dispatch(action)
}
const store = createStore(reducer);
let users = [];
store.subscribe(() => {
users = store.getState().users
console.log('users', users)
});
const App = () => {
console.log(store.getState().users.map(e => e));
console.log('rendering again')
return (
<div className="App">
<h1>People App </h1>
<hr/>
<p>
{
store.getState().users.map( user => {
return
(<p>{user}</p>)
})
}
</p>
<button onClick={()=> dispatchAction(createUsersActionCreator())}>Add Family</button>
<button onClick={()=> dispatchAction(createUserStrangerActionCreator())}>Add Stranger</button>
<button onClick={()=> dispatchAction(deleteUsersActionCreator())}>Delete users</button>
</div>
)
}
export default App
I know I should be using useState, usteEffect and this approaches, but since I'm learning react from zero, I thought it was a Good idea to learn redux as well. I've also Heard about Zustand (which I'm gonna learn too) but I'd like to have a Good grasp of raw redux. Any advise is very Good welcome :)
Since you are using React - better to use Hooks useSelector and useDispatch inside components
const App = () => {
const users = useSelector(state => state.users)
const dispatch = useDispatch()
console.log('rendering again', users)
return (
<div className="App">
<h1>People App </h1>
<hr/>
<p>
{
users.map( user => {
return
(<p>{user}</p>)
})
}
</p>
<button onClick={()=> dispatch(createUsersActionCreator())}>Add Family</button>
<button onClick={()=> dispatch(createUserStrangerActionCreator())}>Add Stranger</button>
<button onClick={()=> dispatch(deleteUsersActionCreator())}>Delete users</button>
</div>
)
}
export default App

How to get the list of checked items in React Redux?

I have a store that includes booksSlice. booksSlice has two states: data and checked. The checked is an empty array. How can I get the list of checked items. The point is that the checked list in state base should be changed in order that I can use it in other components.
BooksSlice.jsx
import booksData from '../data/books';
const booksSlice = createSlice({
name: 'books',
initialState: {
data: booksData,
checked: [],
},
reducers: {
}
})
export const booksActions = booksSlice.actions;
export default booksSlice;
BooksList.jsx
import { useSelector } from "react-redux";
const BookList = () => {
const data = useSelector((state) => state.books.data);
const checked = useSelector((state) => state.books.checked);
const handleChoice = (event) => {
var updatedList = [...checked]
if(event.target.checked) {
updatedList = [...checked, event.target.value]
}else{
updatedList.splice(checked.indexOf(event.target.vaue, 1));
}
console.log(updatedList);
return updatedList;
}
return (
<div>
<h3>Books in our Store</h3>
<p>Please select the books you want</p>
<form>
{data.map((book) => {
return (
<div key={book.isbn}>
<label>
<input type="checkbox" onChange={handleChoice} value={book.title}/>
{book.title} - {book.author}
</label>
</div>
);
})}
</form>
</div>
);
};
export default BookList;

React context: send input data to another component

I have 3 components:
Search.js, Customers.js and Customer.js
In Search.js I have an input field. I want to send whatever value entered in the field over to the Customer.js component. I thought this would be straightforward, but I was wrong ...
I have also a context.js component that stores state for the application (I don't want to use redux because I don't know it yet).
Sorry but this is gonna be a long post as I want to give the background for this specific situation:
context.js
const Context = React.createContext();
const reducer = (state, action) => {
switch (action.type) {
case "SEARCH_CUSTOMERS":
return {
...state,
customer_list: action.payload,
firstName: ''
};
default:
return state;
}
};
export class Provider extends Component {
state = {
customer_list: [],
firstName: "",
dispatch: action => this.setState(state => reducer(state, action))
};
componentDidMount() {
axios
.get("/api")
.then(res => {
console.log(res.data);
this.setState({ customer_list: res.data });
})
.catch(error => console.log(error));
}
render() {
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
);
}
}
export const Consumer = Context.Consumer;
Search.js: the input value I want to send to Customer is 'firstName'
class Search extends Component {
state = {
firstName: ""
};
onChange = e => {
this.setState({ [e.target.name]: e.target.value });
};
findCustomer = (dispatch, e) => {
e.preventDefault();
axios
.get("/api/customers", {
params: {
firstName: this.state.firstName,
}
})
.then(res => {
dispatch({
type: "SEARCH_CUSTOMERS",
payload: res.data
});
this.setState({ firstName: "" });
});
};
return (
<Consumer>
{value => {
const { dispatch } = value;
return (
<form onSubmit={this.findCustomer.bind(this, dispatch)}>
<div className="form-group">
<input
ref={input => {
this.nameInput = input;
}}
type="text"
name="firstName"
value={this.state.firstName}
onChange={this.onChange}
/>
the Customers.js:
class Customers extends Component {
render() {
const key = Date.now();
return (
<Consumer>
{value => {
const { customer_list} = value;
if (customer_list === undefined || customer_list.length === 0) {
return <Spinner />;
} else {
return (
<React.Fragment>
<h3 className="text-center mb-4">{heading}</h3>
<div className="row">
{customer_list.map(item => (
<Customer key={item.key} customer={item} />
))}
</div>
</React.Fragment>
);
}
}}
</Consumer>
);
}
}
export default Customers;
and Finally theCustomer.js: this is where I want the input value to be displayed:
const Customer = props => {
const { customer } = props;
return (
<div className="col-md-12">
<div className="card-body">
<strong>{customer.firstName}</strong> // not working
...
}
the {customer.firstName} does not show the value.
Is is necessary to go through the intermediate Customers.js component to pass the input value?
I would like to keep the architecture as is (with the context.js) and display the value in the Customer.js component.

Redux with Immer not updating component

I'm trying to add an element to an array in an object in my Redux store. I see the object get added to the store but it is not updating the component. If I leave the page and return it is showing up.
I'm pretty sure this is a state mutation issue but I can't figure out where I'm going wrong unless I fundamentally misunderstand what Immer is doing. In the component I'm using produce to add the string to the array, passing the new object to my reducer and using produce to add that object to an array of those objects.
I've looked through a ton of similar questions that all relate to state mutation, but the way I understand it the return from the component's call to produce should be a fully new object. Then in the reducer the call to produce should be returning a new object array.
This is the first time using Immer in a large project so it's entirely possible I don't fully get how it's working it's magic.
Component
import produce from 'immer';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import uuid from 'uuid/v4';
import { generate } from 'generate-password';
import { updateLeague } from '../../actions/leagues';
import { addTeam } from '../../actions/teams';
import { addUser } from '../../actions/users';
import Team from '../../classes/Team';
import User from '../../classes/User';
import UserWidget from '../utils/user/UserWidget';
class ViewLeague extends Component {
state = {
league : null,
isOwner : false,
owner : '',
teams : [],
inviteEmail: ''
};
componentWillMount() {
console.log('mount props', this.props.leagues);
const { leagues, uuid, leagueId, users, teams } = this.props;
if (leagues.length > 0) {
const league = leagues.find(league => league.uuid === leagueId);
const owner = users.find(user => league.leagueManager === user.uuid);
const leagueTeams = teams.filter(team => league.teams.includes(team.uuid));
this.setState({
league,
isOwner: league.leagueManager === uuid,
owner,
teams : leagueTeams
});
}
}
handleUpdate(event, fieldName) {
this.setState({ [ fieldName ]: event.target.value });
}
findUserByEmail(email) {
//Todo if not found here hit server
return this.props.users.find(user => user.email === email);
}
sendInvite = () => {
const { addTeam, addUser, updateLeague } = this.props;
const { league } = this.state;
const newManager = this.findUserByEmail(this.state.inviteEmail);
const newTeamUuid = uuid();
let newLeague = {};
if (newManager) {
const newTeam = new Team('New Team', newManager.uuid, newTeamUuid);
addTeam(newTeam);
} else {
const newPass = generate({
length : 10,
number : true,
uppercase: true,
strict : true
});
const newUserUuid = uuid();
const newUser = new User('', this.state.inviteEmail, newPass, '', '', newUserUuid);
addUser(newUser);
const newTeam = new Team('New Team', newUserUuid, newTeamUuid);
addTeam(newTeam);
newLeague = produce(league, draft => {draft.teams.push(newTeamUuid);});
updateLeague(newLeague);
console.log('invite props', this.props);
console.log('league same', league === newLeague);
}
//Todo handle sending email invite send password and link to new team
console.log('Invite a friend', this.state.inviteEmail);
};
renderInvite() {
const { isOwner, league, teams } = this.state;
if (isOwner) {
if ((league.leagueType === 'draft' && teams.length < 8) || league.leagueType !== 'draft') {
return (
<div>
<p>You have an empty team slot. Invite a fried to join!</p>
<input type="text"
placeholder={'email'}
onChange={() => this.handleUpdate(event, 'inviteEmail')}/>
<button onClick={this.sendInvite}>Invite</button>
</div>
);
}
}
}
renderViewLeague() {
console.log('render props', this.props.leagues);
const { league, owner, teams } = this.state;
const editLink = this.state.isOwner ?
<Link to={`/leagues/edit/${this.props.leagueId}`}>Edit</Link> :
'';
return (
<div>
<h2>{league.leagueName} </h2>
<h3>League Manager: <UserWidget user={owner}/> - {editLink}</h3>
<p>League Type: {league.leagueType}</p>
{this.renderInvite()}
<br/>
<hr/>
<h2>Teams</h2>
<span>{teams.map((team) => (<p key={team.uuid}>{team.teamName}</p>))}</span>
<span>
<h2>Scoring: </h2>
{league.scoring.map((score, index) => (
<p key={index}>{`Round ${index + 1}: ${score} points`}</p>
)
)}
</span>
</div>
);
}
render() {
if (!this.state.league) {
return (
<div>
<h2>No league Found</h2>
</div>
);
} else {
return (
<div>
{this.renderViewLeague()}
</div>
);
}
}
}
export default connect(
({ leagues: { leagues }, teams: { teams }, users: { users }, auth: { uuid } },
{ match: { params: { leagueId } } }) => ({
leagues,
teams,
users,
uuid,
leagueId
}), ({
addTeam : (team) => addTeam(team),
addUser : (user) => addUser(user),
updateLeague: (league) => updateLeague(league)
})
)(ViewLeague);
Reducer
import produce from 'immer';
import {
ADD_LEAGUE,
UPDATE_LEAGUE
} from '../actions/types';
const DEFAULT_LEAGUES = {
leagues: [ {
leagueName : 'Test League',
leagueManager: 'testUser12345',
uuid : 'testLeague12345',
teams : [ 'testTeam12345', 'testTeam23456' ],
scoring : [ 25, 20, 15, 10, 5, -5 ],
leagueType : 'draft'
} ]
};
const leaguesReducer = (state = DEFAULT_LEAGUES, action) =>
produce(state, draft => {
// noinspection FallThroughInSwitchStatementJS
switch (action.type) {
case ADD_LEAGUE:
draft.leagues.push(action.league);
case UPDATE_LEAGUE:
console.log('updating league', action.league);
const { league } = action;
const leagueIndex = draft.leagues.findIndex(fLeague => league.uuid === fLeague.uuid);
draft.leagues.splice(leagueIndex, 1, league);
}
});
export default leaguesReducer;
Any help is greatly appreciated!! More info available if needed
Try adding return; at the end of your case blocks.
You can read more about returning data from producers and see examples of what to do and what not to do here.

Condition on checkbox in React with Redux

This is surely very simple but I dont understand how it works. I try to bind checkbox with state and with state display different string. It is in React with Redux. The code below (bold font)
container:
class DropingList extends Component {
**conditionHandler() {
if(this.props.pet === 'cat'){
return "YEAH!!!"
}else {return null;}**
}
render() {
return (
<div>
<AddHimHer
click={this.props.onAddMan}
/>
{ this.props.pers.map(per =>(
<NewPerson
key={per.id}
click={() => this.props.onManDown(per.id)}
name={per.name}
age={per.age}
**animal={this.conditionHandler(this.props.pet)}**
/>
))
}
</div>
)
}
}
const mapStateToProps = state => {
return {
pers: state.persons
}
}
const mapDispatchToProps = dispatch => {
return {
onAddMan: (name,age,**pet**) => dispatch({type:actionTypes.ADD_MAN, data: {nam: name, ag: age, **superp: pet**}}),
onManDown: (id) => dispatch({type:actionTypes.MAN_DOWN, Id: id})
}
}
export default connect(mapStateToProps,mapDispatchToProps)(DropingList);
component:
const NewPerson = (props) => (
<div onClick={props.click}>
<h1>Is {props.name} a SUPERHERO? ? ???</h1>
<h2>He is {props.age} years old</h2>
**<h1>{props.animal}</h1>**
</div>
);
export default NewPerson;
reducer:
const initState = {
persons: []
}
const personReducer = (state = initState,action) => {
switch (action.type) {
case actionTypes.ADD_MAN:
const newMan = {
id: Math.random(),
name: action.data.nam,
age: action.data.ag,
**pet: action.data.superp**
};
return {
...state,
persons: state.persons.concat(newMan)
};
case actionTypes.MAN_DOWN:
return {
...state,
persons: state.persons.filter(person => person.id !== action.Id)
};
}
return state;
};
export default personReducer;
I am still newbe in React and Redux. I think I have ommited something.
Could you tell me whats wrong with my code?
Issue is pet is the part of the object (each object of the array), not a separate prop so you need to use per.pet in map callback function, like this:
{this.props.pers.map(per =>(
<NewPerson
key={per.id}
click={() => this.props.onManDown(per.id)}
name={per.name}
age={per.age}
animal={this.conditionHandler(per.pet)} // here
/>
))}
Now you are passing the pet value to function conditionHandler, so no need to use this.props.pet inside that directly use pet, like this:
conditionHandler(pet) {
if (pet === 'cat') {
return "YEAH!!!"
} else {
return null;
}
}

Resources