React-Redux: Cannot read property 'map' of undefined when deleting an item - reactjs

I have an error after clicking the delete button saying:
Cannot read property 'map' of undefined.
I'm new in React Redux JS.
Please see my code below of my component reducers and actions:
Post.js
class Post extends Component {
constructor(){
super();
this.deletePost = this.deletePost.bind(this);
}
deletePost(postId){
this.props.deletePost(postId);
}
render(){
const postItems = this.props.posts.map(post => (
<div key={post.id} className="row">
<div className="container">
<h3>{post.title}</h3>
<p>{post.body}</p>
<button
onClick={() =>this.deletePost(post.id)}
className="btn btn-danger">
Delete
</button>
</div>
</div>
))
const divStyle = {
padding: '15px',
}
return (
<div style={divStyle}>
<PostForm />
<hr/>
{postItems}
</div>
)
}
}
const mapStateToProps = state => ({
posts: state.posts.items,
newPost: state.posts.item
})
export default connect(mapStateToProps, { fetchPosts, deletePost })(Post);
PostAction.js (Here is my delete action. I am using jsonplaceholder API post.)
export const deletePost = (postId) => dispatch => {
fetch('https://jsonplaceholder.typicode.com/posts/'+postId, {
method: 'DELETE',
})
.then(dispatch({
type: DELETE_POST,
payload: postId
}));
}
PostReducer.js (This is my reducer.)
case DELETE_POST:{
const newState = Object.assign([], state);`enter code here`
const filteredItems = newState.items.filter(items => {
return items.id != action.payload;
});
return filteredItems;
}

case DELETE_POST:{
const { items } = state;
const filteredItems = items.filter(items => {
return items.id != action.payload;
});
return {
...state,
items: [ ...filteredItems ]
};
}

Yes just replace
return filteredItems; to return { items: filteredItems }
But please can you check my code if it's correct. Thanks

Related

How to edit data received from a rest call in react?

I am trying to make a rest call, edit the data and then render it. The problem is that, while editing, I am getting a error - undefined even after checking if the data is there.
component I am making the rest call from:
function Header ({timesheetData, loadTimesheet}) {
useEffect(() => {
loadTimesheet(date)
}, [])
return(
<>
<header className="header">
<div className="wrap">
<span className="btn-icon">
<IconPlus onClick={() => setIsOpen(true)} className="icon icon-plus js-modal-init"/>
</span>
<div className="header-blockquote">
<h1 className="header-quote">{currentQuote.quote}</h1>
<div className="header-cite">{currentQuote.author}</div>
</div>
</div>
<div className="header-inner">
<div className="wrap">
<VegaIcon className="logo" alt="VegaIT"/>
<div className="date-wrap">
<IconCalendar className="icon icon-plus js-modal-init"/>
//
<time>{timesheetData.timesheet.loading ? "asd" : edit(timesheetData) }</time>
//
</div>
</div>
</div>
</header>
</>
)
}
function edit(timesheetData){
let newDate = timesheetData.timesheet.date
newDate = newDate.split("-")
newDate = newDate.reverse()
return newDate.join("/")
}
the redux action:
export const loadTimesheet = (date) => {
let url = "http://localhost:8080/api/timesheet/" + date
return (dispatch) => {
dispatch(getTimesheet)
axios.get(url)
.then((response) => {
const timesheet = response.data
dispatch(getTimesheetSuccess(timesheet))
})
.catch(error => {
const errorMsg = error.message
dispatch(getTimesheetFailure)
})
}
}
Edit: added my mapStateToProps and mapDispatchToProps
const mapStateToProps = state => {
return {
timesheetData: state.timesheet
}
}
const mapDispatchToProps = dispatch => {
return {
loadTimesheet: (date) => dispatch(loadTimesheet(date))
}
}
Edit2: The code: https://codesandbox.io/s/adoring-tharp-o9ibe
use mapStateToProps, mapDispatchToProps:
import getTimesheet from '../actions/...'
Header ({timesheetData, loadTimesheet}) => {
}
const mapDispatchToProps = dispatch => {
return {
loadTimesheet: () => dispatch(getTimesheet()),
}
}
const mapStateToProps = (state) => {
return { timesheetData: state.timesheetData };
};
export default connect(mapStateToProps, mapDispatchToProps)(Header);
also in component:
before render:
check that you have:
timesheetData - not undefined
timesheetData.timesheet - not undefined
timesheetData.timesheet.loading - not undefined
const hasData = timesheetData && !timesheetData.timesheet.loading;
const time = hasData ? edit(timesheetData): "asd";
in render:
<time>{time}</time>

optimistic ui updates - react

I imagine this is a basic in react but I'm not sure how to get it to work, basically when I delete, create or edit anything in my components I want the change to happen in realtime without refreshing the page, I've achieved it at some level with the search function but not entirely sure how to do with for the delete function for example:
Here is what I'm working with, how would I get this to work with my axios delete function?
Thanks
import { connect } from 'react-redux';
import { fetchTournaments } from '../actions/tournaments';
import Item from './Item';
import EditTournament from './EditTournament';
import axios from 'axios';
import '../styles/Item.css';
class SearchAndDisplay extends React.PureComponent {
componentDidMount() {
this.props.fetchTournaments();
}
state = {
searchCriteria: '',
isLoading: false
};
handleChange = event => {
this.setState({
searchCriteria: event.target.value
});
};
async handleDelete(id) {
const url = `http://localhost:4000/tournaments/`;
await axios
.delete(url + id)
.then(res => {
console.log(res.data);
})
.catch(err => {
console.log(err);
});
}
formatDate(date) {
let options = {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
hour12: false
};
let newDate = new Date(Date.parse(date));
let format = new Intl.DateTimeFormat('default', options).format(newDate);
return format;
}
handleChange = event => {
this.setState({ searchCriteria: event.target.value });
};
renderList() {
let tournmentsArray = this.props.tournaments;
const filterTournaments = tournmentsArray.filter(item =>
item.name.includes(this.state.searchCriteria)
);
if (filterTournaments === undefined || filterTournaments.length === 0) {
return (
<React.Fragment>
<div className="notFound">
Something went wrong.
<br />
<button
className="notFoundButton"
onClick={() => {
this.setState({ searchCriteria: '' });
}}
>
Retry
</button>
</div>
</React.Fragment>
);
} else {
return filterTournaments.map(item => (
<Item
key={item.name}
name={item.name}
organizer={item.organizer}
participants={Object.values(item.participants)}
game={item.game}
start={this.formatDate(item.startDate)}
>
<div className="buttonBar">
<EditTournament id={item.id} />
<button
className="button"
onClick={() => {
if (
window.confirm('Are you sure you want to delete this item?')
) {
this.handleDelete(item.id);
}
}}
>
Delete
</button>
</div>
</Item>
));
}
}
render() {
return (
<div className="container">
<input
onChange={this.handleChange}
className="input"
placeholder="Search..."
id="searchField"
value={this.state.searchCriteria}
/>
<div className="row">{this.renderList()}</div>
</div>
);
}
}
function mapStateToProps({ tournaments }) {
return {
tournaments: Object.values(tournaments).flat()
};
}
export default connect(mapStateToProps, {
fetchTournaments
})(SearchAndDisplay);
unlike delete the create and edit data is handled by redux like so:
Create tournament:
import { reduxForm, Field } from 'redux-form';
import '../styles/promptForms.css';
import '../styles/Header.css';
import { connect } from 'react-redux';
import { createTournaments } from '../actions/tournaments';
class CreateTournamentPromptFrom extends React.Component {
constructor(props) {
super(props);
this.state = {
showHide: false
};
}
createTournamentButton() {
return (
<div>
<button
className="genericButton"
onClick={() => this.setState({ showHide: true })}
>
CREATE TOURNAMENT
</button>
</div>
);
}
renderInput = ({ input, label }) => {
return (
<div>
<label>{label}</label>
<br />
<input className="promptInput" {...input} autoComplete="off" />
</div>
);
};
onSubmit = formValues => {
this.props.createTournaments(formValues);
};
render() {
const { showHide } = this.state;
return (
<React.Fragment>
<div className={`overlay ${showHide ? 'toggle' : ''} `} />
<div className={`promptBox ${showHide ? 'toggle' : ''} `}>
<h3>localhost:3000 says</h3>
<form onSubmit={this.props.handleSubmit(this.onSubmit)}>
<Field
name="name"
component={this.renderInput}
label="Enter Tournament:"
/>
<button className="okayButton">OK</button>
</form>
<button
className="cancelButton"
onClick={() => this.setState({ showHide: false })}
>
Cancel
</button>
</div>
{this.createTournamentButton()}
</React.Fragment>
);
}
}
const formWrapped = reduxForm({
form: 'promptForm'
})(CreateTournamentPromptFrom);
export default connect(null, { createTournaments })(formWrapped);
actions:
import {
FETCH_TOURNAMENTS,
FETCH_TOURNAMENT,
CREATE_TOURNAMENT,
EDIT_TOURNAMENT
} from './types';
import { API_TOURNAMENTS_URL } from '../constants/api';
import axios from 'axios';
export const fetchTournaments = () => async dispatch => {
const response = await axios.get(API_TOURNAMENTS_URL);
dispatch({
type: FETCH_TOURNAMENTS,
payload: response.data.flat()
});
};
export const fetchTournament = id => async dispatch => {
const response = await axios.get(`http://localhost:4000/tournaments/${id}`);
dispatch({ type: FETCH_TOURNAMENT, payload: response.data });
};
export const createTournaments = formValues => async dispatch => {
const response = await axios.post(API_TOURNAMENTS_URL, {
...formValues
});
dispatch({ type: CREATE_TOURNAMENT, payload: response.data });
};
export const editTournaments = (id, formValues) => async dispatch => {
const response = await axios.patch(
`http://localhost:4000/tournaments/${id}`,
formValues
);
dispatch({ type: EDIT_TOURNAMENT, payload: response.data });
};
reducers:
import _ from 'lodash';
import {
FETCH_TOURNAMENT,
CREATE_TOURNAMENT,
FETCH_TOURNAMENTS,
EDIT_TOURNAMENT,
DELETE_TOURNAMENT
} from '../actions/types';
export default (state = {}, action) => {
switch (action.type) {
case FETCH_TOURNAMENT:
return { ...state, [action.payload.id]: action.payload };
case FETCH_TOURNAMENTS:
return { ...state, [action.payload.id]: action.payload };
case CREATE_TOURNAMENT:
return { ...state, [action.payload.id]: action.payload };
case EDIT_TOURNAMENT:
return { ...state, [action.payload.id]: action.payload };
case DELETE_TOURNAMENT:
return _.omit(state, action.payload);
default:
return state;
}
};
To "optimistically" delete an item from state you'll need to immediately delete it from state to reflect the change right away in the UI. BUT you will need to add extra redux state to "hold" a pending delete with your backend. When the delete is successful you clear the held delete, if it fails you clear the held delete and add it back in to your regular data (and perhaps display some error message or toast, etc..).
I see you don't do the delete via redux, so use local component state and you'll have to filter your tournament data when rendering.
class SearchAndDisplay extends PureComponent {
componentDidMount() {
this.props.fetchTournaments();
}
state = {
searchCriteria: "",
isLoading: false,
optimisticTournaments: null // <-- state to hold temp "deleted" data
};
handleChange = event => {
this.setState({
searchCriteria: event.target.value
});
};
async handleDelete(id) {
console.log("delete id", id);
// optimistically remove element
this.setState({
optimisticTournaments: this.props.tournaments.filter(
item => item.id !== id
)
});
await axios
.delete(url + id)
.then(res => {
console.log(res.data);
// Need to create a call back to let parent know element was deleted
this.props.deleteSuccess(id);
})
.catch(err => {
console.log(err);
alert("Failed to delete");
})
.finally(() => {
this.setState({ optimisticTournaments: null });
});
}
formatDate(date) {
let options = {
year: "numeric",
month: "numeric",
day: "numeric",
hour: "numeric",
minute: "numeric",
second: "numeric",
hour12: false
};
let newDate = new Date(Date.parse(date));
let format = new Intl.DateTimeFormat("default", options).format(newDate);
return format;
}
handleChange = event => {
this.setState({ searchCriteria: event.target.value });
};
renderList() {
let tournmentsArray =
this.state.optimisticTournaments || this.props.tournaments;
const filterTournaments = tournmentsArray.filter(item =>
item.name.includes(this.state.searchCriteria)
);
if (filterTournaments === undefined || filterTournaments.length === 0) {
return (
<React.Fragment>
<div className="notFound">
Something went wrong.
<br />
<button
className="notFoundButton"
onClick={() => {
this.setState({ searchCriteria: "" });
}}
>
Retry
</button>
</div>
</React.Fragment>
);
} else {
return filterTournaments.map(item => (
<Item
key={item.name}
name={item.name}
organizer={item.organizer}
participants={Object.values(item.participants)}
game={item.game}
start={this.formatDate(item.startDate)}
>
<div className="buttonBar">
<EditTournament id={item.id} />
<button
className="button"
onClick={() => {
if (
window.confirm("Are you sure you want to delete this item?")
) {
this.handleDelete(item.id);
}
}}
>
Delete
</button>
</div>
</Item>
));
}
}
render() {
return (
<div className="container">
<input
onChange={this.handleChange}
className="input"
placeholder="Search..."
id="searchField"
value={this.state.searchCriteria}
/>
<div className="row">{this.renderList()}</div>
</div>
);
}
}
Here is how you can do using forceUpdate() (Since you don't want to use state):
import React, { Component } from 'react';
import { render } from 'react-dom';
class App extends Component {
constructor() {
super();
this.items = [
{id: 1, name: "item 1"},
{id: 2, name: "item 2"},
];
this.handleDelete = this.handleDelete.bind(this);
}
handleDelete(index) {
const newState = [...this.items];
delete newState[index];
// or
// newState.splice(index, 1);
this.items = newState;
this.forceUpdate();
}
render() {
return (
<div>
{
this.items.map((item, index) => {
return (
<div>
{item.name}
<button onClick={() => this.handleDelete(index)}>Delete item</button>
</div>
)
})
}
</div>
);
}
}
render(<App />, document.getElementById('root'));
Just pass the index in the map method :
...map((item, index) => ...);
Do it in the then() after your axios call.
Note that the documentation highly advice you to avoid using forceUpdate() when you can, so you really should use a state for this, I don't see any good reason for you not to use it here.
Here is a quick repro on Stackblitz.

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.

Deleting an item from my redux state gives multiple errors

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.

React, Redux Deleting an item on click deletes all items

I need fresh eyes on this. As I am slowly learning React and Redux i have run into a roadblock again.
/actions/items.js
export const DELETE_ITEM = "DELETE_ITEM"
export function deleteItem(id) {
return {
type: DELETE_ITEM,
id
}
}
/components/Item.jsx
export default class Item extends React.Component {
renderDelete = () => {
return <button onClick={this.props.onDelete}>x</button>
};
renderItem = () => {
const onDelete = this.props.onDelete
return (
<div onClick={this.edit}>
<span>{this.props.text}</span> {onDelete ? this.renderDelete() : null}
</div>
)
}
/components/Items.jsx
export default class Items extends React.Component {
handleOnDelete = (id) => {
this.props.dispatch(actions.deleteItem(id))
}
render() {
const {items, onEdit, onDelete } = this.props
return (
<ul>{items.map(item =>
<li key={item.id}>
<Item
id={item.id}
text={item.text}
onEdit={this.handleOnEdit}
onDelete={this.handleOnDelete.bind(null, item.id)}
/>
</li>
)}</ul>
);
}
}
export default connect(
state => ({
items: state.items
})
)(Items)
/reducers/items.js
case types.DELETE_ITEM:
const filteredItems = state.filter((item) => {
item.id !== action.id
});
return filteredItems
I'm not sure why clicking on x button to delete an item deletes all of them. Thanks in advance for the help
You do not return value in filter in your reducers.
Your should add return:
const filteredItems = state.filter((item) => {
return item.id !== action.id;
});
Or use short version, without brackets:
const filteredItems = state.filter((item) => item.id !== action.id);

Resources