How to render the whole value of my array - reactjs

Iam still a bit new to react and how it works. I'm trying to create a shopping list from a list of recipe that are objects who contain an array of ingredient but react only displays the 1st element and displays the other elements after each click
here is my code :
import { useState } from 'react';
import { MealContext } from '../../../Context/MealContext';
import GroceryListComponent from './GroceryListComponent';
const GrocerysListContainer = () => {
const [selectedMeals,setMeals] = useContext(MealContext);
const [groceryList, setGroceryList]= useState([]);
const [showList,setShowList] = useState(false);
const handleClick = ()=>{
setShowList(true);
selectedMeals.forEach(meal => {
meal.ingredient.forEach((dish)=>{
if(!groceryList.find(({name})=>name === dish.toLowerCase())){
setGroceryList([...groceryList, {name : dish.toLowerCase(), qty : 1}]);
console.log('add')
}else{
console.log('remove')
return;
}
})
});
}
return (
<div className="groceryList">
<button className="btn btn-submit" onClick={()=>handleClick()}>Creer liste de course</button>
{showList && <ul>
{ groceryList.map((grocery,index)=>{
return(
<GroceryListComponent
key={index}
grocery={grocery}
/>
)
})
}
</ul>}
</div>
);
};
export default GrocerysListContainer;

Most likey groceryList is not getting updated in function, so it will help to add the new elements to a array and then finally add them
Quick fix
setGroceryList(list => [...list, {name : dish.toLowerCase(), qty : 1}]);
To move towards a improved solution, the quantity of existing item also needs to be udpated
const handleClick = () => {
// can include try catch
setShowList(true);
let newList = []
for (const meal of selectedMeals) {
// include check with ?. and ??
const ingredient = meal.ingredient;
// can add another loop here
const dish = ingredient.find(dish => !groceryList.find(({ name }) => name === dish.toLowerCase()))
if (!dish) {
newList.push({ name : dish.toLowerCase(), qty : 1})
} else {
// depends on what you want to achieve
// increase qty or something
}
}
setGroceryList([ ...groceryList, ...newList ])
}
Hope it helps you in some way
Cheers

Related

set in useState does not reload the data on the page [duplicate]

This question already has answers here:
Correct way to push into state array
(15 answers)
Closed 2 months ago.
I just started learning how to work with React and I ran into a problem that I still can't seem to solve.
I'm trying to program a simple app for adding tasks like a simple to-do list.
But when I want to add a task and save the data via useState, the new data is not written to the page.
i have code in file AllTasks.js:
const [myTasks, setMyTasks] = useState(data);
const tasksHandler = (id) => {
const filteredTasks = myTasks.filter((oneTask) =>{
return oneTask.id !== id;
})
setMyTasks(filteredTasks);
}
const checkID = () => {
for(let i = 0; i < myTasks.length; i++){
if(i === myTasks.length -1){
return myTasks[i].id + 1;
}
}
}
const addNewTask = (newTask) => {
let task = {
id: checkID(),
name: newTask
};
const newTasks = myTasks;
newTasks.push(task);
setMyTasks(newTasks);
}
const deleteAllTasks = () => {
setMyTasks([]);
}
return(
<div className='tasks'>
<Title />
<AddTasks addTask={addNewTask}/>
{
myTasks.map((oneTask) => {
const {id, name} = oneTask;
return <div className='one-task' key={id}>
<p>{name}</p>
<button onClick={() => tasksHandler(id)}><img src={deleteImg} alt='todoApp'/></button>
</div>
})
}
<button className='main-button' onClick={deleteAllTasks}>Delete all tasks</button>
</div>
)
}
export default AllTasks;
The code in the AddTask.js file that I use to send the new task to the AllTasks.js file
import './AddTask.css';
import addImg from "../img/plus.png";
const AddTask = (props) => {
const add = () => {
const input = document.getElementById('new-task');
const newTask = input.value;
if(input.value.length > 0){
props.addTask(newTask);
}
}
return(
<div className='add-task'>
<input type='text' id='new-task'></input>
<button id='send-task' onClick={add}><img src={addImg} alt='todoApp'/></button>
</div>
)
}
export default AddTask;
I don't understand why when I click on add task and run the addNewTask() function, why doesn't the added task appear on the page? When I upload new data to myTasks via setMyTasks(newTaskt)?
Thank you all for your reply.
This is because you are adding new data inside existing array, react needs a brand new array to update it's state and keep track of state.
In addNewTask function instead of adding element to the existing array you should do
setState((previousStateOfArray) => {
return [...previousStateOfArray, newElement];
})
Or shorthand would be setState(prev => [...prev, newElement])
Try change final line of addNewTask function from setMyTasks(newTasks); to setMyTasks([...newTasks]);
Should use immutable state setting:
setMyTasks([...newTasks]);

Error message "Cannot read properties of null (reading 'filter')"

I'm new to learning react and have been having problems getting the array to filter using the .filter() method. I'm trying to create a grocery list and I keep getting the error message "Cannot read properties of null (reading 'filter')" Can someone please assist me on getting this work? Here is the code that I have.
import Header from './Header';
import SearchItem from './SearchItem';
import AddItem from './AddItem';
import Content from './Content';
import Footer from './Footer';
import { useState, useEffect } from 'react';
function App() {
const [items, setItems] = useState(JSON.parse(localStorage.getItem('shoppinglist')));
const [newItem, setNewItem] = useState('')
const [search, setSearch] = useState('')
console.log('before useEffect')
//useEffect looks to it's dependency and if the dependency changes then it will run the anonymous function
useEffect(() => {
console.log('inside useEffect')
},[items])
const setAndSaveItems = (newItems) => {
setItems(newItems);
localStorage.setItem('shoppinglist', JSON.stringify(newItems));
}
console.log('after useEffect')
const addItem = (item) => {
const id = items.length ? items[items.length - 1].id + 1 : 1;
const myNewItem = { id, checked: false, item };
const listItems = [...items, myNewItem];
setAndSaveItems(listItems);
}
const handleCheck = (id) => {
const listItems = items.map((item) => item.id === id ? { ...item, checked: !item.checked } : item);
setAndSaveItems(listItems);
}
const handleDelete = (id) => {
const listItems = items.filter((item) => item.id !== id);
setAndSaveItems(listItems);
}
const handleSubmit = (e) => {
e.preventDefault();
if (!newItem) return;
addItem(newItem);
setNewItem('');
}
return (
<div className="App">
<Header title="Grocery List" />
<AddItem
newItem={newItem}
setNewItem={setNewItem}
handleSubmit={handleSubmit}
/>
<SearchItem
search={search}
setSearch={setSearch}
/>
<Content
items={items.filter(item => ((item.item).toLowerCase()).includes(search.toLowerCase()))}
handleCheck={handleCheck}
handleDelete={handleDelete}
/>
<Footer length={items.length} />
</div>
);
}
export default App;
I feel that you're mentioning about this code excerpt:
items.filter((item) => item.id !== id);
can you please check if the items array is null or not. Only if items is null, filtering wouldn't be applicable and you will receive such error messages
can you log items before deletion?
Few pointers that could help
initilize the items in an useEffect as it could be null, it will make it easy to fetch data a api later
const [items, setItems] = useState([]);
useEffect(() => {
try {
const items = JSON.parse(localStorage.getItem('shoppinglist'))
setItems(items)
} catch(error) {
}
}, [])
// put ?. checks on items when calling filter, map
const handleDelete = (id) => {
const listItems = items?.filter((item) => item.id !== id);
if (listItems) {
setAndSaveItems(listItems);
}
}
Id generated will clash and cause bugs
const id = items.length ? items[items.length - 1].id + 1 : 1;
if the person deletes on item and adds another the new item will have the same id as the last one
item { id: 1}
item { id: 2}
item { id: 3}
after deleting id 2, when you add new items it will have id 3
and will cause bugs with select
either use a id that is a timestamp or check for unique ids
Save the items in local storage on submit, as calls get/set items to localstorage can lead to performace issues in the UI
Checkout the new docs on working with arrays
Hope it helps

After copying array, why can't I edit nested array?

I'm trying to edit an array by removing a specific date. I'm using React18 and Redux Toolkit holds the original array, but for some reason after copying it, I cannot edit the array. Here is the current error message;
"Uncaught TypeError: Cannot assign to read only property 'dates' of object '#'"
What is wrong with my approach?
import { useDispatch, useSelector } from "react-redux";
import { setCurrentMonthBookings } from "./location";
const Component = () => {
const { booking, currentMonthBookings } = useSelector(state => state.calendar);
const handleDelete = () => {
let reservations = currentMonthBookings.slice();
const bookingIndex = reservations.findIndex(
(curBooking) =>
curBooking.date === booking.date && curBooking.id === booking.id,
);
const newDates = reservations[bookingIndex].dates.filter(
(date) => date !== booking.date,
);
reservations.splice(bookingIndex, 1);
reservations.forEach((reservation) => {
if (reservation.id === booking.id) {
reservation.dates = newDates; //error happens here...
}
});
dispatch(setCurrentMonthBookings(reservations));
}
return (
<div>
<button onClick={handleDelete}>Delete It</button>
</div>
);
}
export default Component;
What the booking object looks like...
{
date: "2022-05-03",
dates: (2) ['2022-05-03', '2022-05-04'],
guestId: "1938479385798579",
id: "9879287498765"
}
The currentMonthBookings array is a series of booking objects.
Thank you for your replies.

Issue in react component

I have a problem in the following component, it seems that the component doesn't render and I get the following error in console: "Cannot read property 'operationalHours' of null". I don't get why operationalHours it's null.. maybe someone can help me with a posible solution for this issue.
Here is the component:
import React, { useState, useEffect } from 'react';
import Search from 'client/components/ui/Search';
import { performSearchById } from 'client/actions/api/search';
import { get } from 'lodash';
import {
SEARCH_STORE_NOT_CLOSED,
SEARCH_STORE_OPEN_TEXT,
SEARCH_STORE_CLOSED_TEXT
} from 'app/client/constants/values';
import DownArrow from 'components/UI/icons/DownArrow';
import styles from './styles.module.scss';
const StoreDetails = ({ storeInfo }) => {
const [expanded, setIsExpanded] = useState(false);
const [storeData, setStoreData] = useState(null);
useEffect(() => {
async function fetchData() {
const storeId = storeInfo.store_id;
const {
data: {
Location: {
contactDetails: { phone },
operationalHours
}
}
} = await performSearchById(storeId);
setStoreData({ phone, operationalHours });
}
fetchData();
}, [storeInfo.store_id]);
const infoText = expanded ? 'Hide details' : 'View details';
function parseHours(hours) {
const formattedHours = {};
hours.forEach(dayObj => {
const closed = get(dayObj, 'closed', '');
const day = get(dayObj, 'day', '');
if (closed === SEARCH_STORE_NOT_CLOSED) {
const openTime = get(dayObj, 'openTime', '');
const closeTime = get(dayObj, 'closeTime', '');
if (openTime === null || closeTime === null) {
formattedHours[day] = SEARCH_STORE_OPEN_TEXT;
} else {
formattedHours[day] = `${openTime}-${closeTime}`;
}
} else {
formattedHours[day] = SEARCH_STORE_CLOSED_TEXT;
}
});
return formattedHours;
}
const storeHours = storeData.operationalHours
? parseStoreHours(storeData.operationalHours)
: '';
return (
<div className={styles.viewStoreDetails}>
<span
className={expanded ? styles.expanded : undefined}
onClick={() => setIsExpanded(!expanded)}
>
<DownArrow />
</span>
<div>
<span className={styles.viewStoreDetailsLabel}>{infoText}</span>
<div>
{expanded && (
<Search
phoneNumber={storeData.phone}
storeHours={storeHours}
/>
)}
</div>
</div>
</div>
);
};
export default StoreDetails;
Its because you're setting the values of storeData after the component has already rendered the first time. Your default value for storeData is null.
It breaks here: storeData.operationalHours because null isn't an object and therefore cannot have properties to access on it.
You should probably just set your initial state to something more representative of your actual state:
const [storeData, setStoreData] = useState({}); // Or even add keys to the object.
Also read here about the useEffect hook and when it runs. It seems that the underlying issue is misunderstanding when your data will be populated.
You are getting error at this line :
const storeHours = storeData.operationalHours ?
parseStoreHours(storeData.operationalHours): '';
Reason : You initialised storeData as Null and you are trying to access operationalHours key from Null value.
Correct Way is :
Option 1: Initialise storeData as blank object
const [storeData, setStoreData] = useState({});
Option 2:
const storeHours =storeData && storeData.operationalHours ?
parseStoreHours(storeData.operationalHours): '';
It's happen because in 1st moment of your application, storeData is null, and null don't have properties, try add a empty object as first value ({}) or access a value like that:
Correct method:
const object = null;
console.log(object?.myProperty);
// output: undefined
Wrong method:
const object = null;
console.log(object.myProperty);
// Generate a error
The Question Mark(?) is a method to hidden or ignore if the variable are a non-object, to decrease verbosity in the code with logic blocks try and catch, in Correct method code there will be no mistake, but in Wrong method code, there will have a mistake.
Edit 1:
See more here

React es6, Remove item from array, by changing props

I’m new to React and right now I’m working on a project where a user should be able to choose a base ingredient, and it gets added to an array. By clicking on another base ingredient the first one should be removed from the array. Right now the chosen ingredient only removes when clicking on the same one.
I want it to be removed when clicking on another one. Please help :)
import React from 'react';
import Actions from '../../../actions/actions';
import BaseIngredientButton from './BaseIngredientButton';
class BaseIngredientItem extends React.Component {
_OnClick (props) {
if (this.props.isChosen) {
Actions.removeItem(this.props.baseIngredient);
} else {
Actions.addItem(this.props.baseIngredient)
Actions.setBaseIngredient( this.props.baseIngredient );
}
console.log(this.props.isChosen)
}
render () {
return (
<BaseIngredientButton isChosen={this.props.isChosen} onClick={ this._OnClick.bind(this)} txt={ this.props.baseIngredient.name } />
)
}
}
BaseIngredientItem.propTypes = {
baseIngredient: React.PropTypes.object.isRequired,
isChosen: React.PropTypes.bool
}
export default BaseIngredientItem;
here is my store.js
let _cart = [];
const _removeItem = ( item ) => {
_cart.splice( _cart.findIndex( i => i === item ), 1 );
console.log("Ingredients in cart after removal", _cart);
};
const _getItemInCart = ( item ) => {
return _cart.find( ingredient => ingredient.name === item.name )
};
const _addItem = ( item ) => {
if (!_getItemInCart( item )) {
_cart.push(item);
}
console.log("Ingredients in cart after addition", _cart);
};
let _currentBaseIngredient = {};
const _setCurrentBaseIngredient = ( baseIngredient ) => {
_currentBaseIngredient = baseIngredient
};
here is my action.js
addItem( item ){
dispatch({
actionType: AppConstants.ADD_ITEM,
item
})
},
removeItem( item ){
dispatch({
actionType: AppConstants.REMOVE_ITEM,
item
})
},
setBaseIngredient( baseIngredient ){
dispatch({
actionType: AppConstants.SET_BASE_INGREDIENT,
baseIngredient
})
},
Your BaseIngredientItem component has no knowledge of whether there is another base ingredient in the array, so as I mentioned in the comment, this would definitely be something to inspect at the Store level.
Is there any way to determine whether an item is of type base? If there is, you can check for its presence in your addItem function:
(please don't mind some of the psuedo-code)
const _addItem = ( item ) => {
if (item is a base ingredient)
removeCurrentBaseIngredient()
if (!_getItemInCart( item )) {
_cart.push(item);
}
console.log("Ingredients in cart after addition", _cart);
};
const removeCurrentBaseIngredient = () => {
_removeItem( _currentBaseIngredient );
};
Since the store already knows about the _currentBaseIngredient, you should be able to look it up pretty easily and call _removeItem to remove it from the _cart.
I hope that helps!

Resources