I get an array and want objects with different TPAs (Item.Type) add to different components. But in the end I only get the last. How to do what would both ITEM be saved in the component and did not flip each other?
{
list.map((item) => (
<div>{item.type == 1 ? <DetailedOne item={item} /> : ""}</div>
));
}
{
list.map((item) => (
<div>{item.type == 2 ? <DetailedTwo item={item} /> : ""}</div>
));
}
I think it as simple as using find to find both items from the list and passing those to their components:
const [item1, item2] = useMemo(() => {
const item1 = list.find((item) => item.type === 1)
const item2 = list.find((item) => item.type === 2)
return [item1, item2]
}, [list])
// JSX
<DetailedOne item={item1} />
<DetailedTwo item={item2} />
You can use concat() to combine the results from both map() calls.
Furthermore, I would recommend using .filter() because you are now creating empty <div>s that look like nothing but pollution to me.
Code:
{
list.filter(item.type == 1).map((item) => (
<div><DetailedOne item={item}</div>
)).concat(
list.filter(item.type == 2).map((item) => (
<div><DetailedTwo item={item}</div>
))
);
}
Related
I'm trying to learn to react online and I understood everything except this line code
const removeItem = (id) => {
let newPeople = people.filter((person) => person.id !== id);
setPeople(newPeople);
};
especially how person.id !== idremoves the item from list and add to new list
here is the full code
import React from 'react';
import { data } from '../../../data';
const UseStateArray = () => {
const [people, setPeople] = React.useState(data);
const removeItem = (id) => {
let newPeople = people.filter((person) => person.id !== id);
setPeople(newPeople);
};
return (
<>
{people.map((person) => {
const { id, name } = person;
return (
<div key={id} className='item'>
<h4>{name}</h4>
<button onClick={() => removeItem(id)}>remove</button>
</div>
);
})}
<button className='btn' onClick={() => setPeople([])}>
clear items
</button>
</>
);
};
export default UseStateArray;
first you shold khow how filter works,
The filter() method creates a new array filled with elements that pass a test provided by a function.
in your case test is person.id !== id,
if test passed for an element that element will be in new array.
otherwise element will not be in new array. is it clear?
The filter method creates a shallow copy of an array but not the whole array but only those elements that fulfills the predicate.
So newPeople will contain a copy of all the elements within people that it's people[element].id is different than id.
Visit https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter for additional details of filter method.
This component checks if the fields have been changed from the initial value and display the number of changed fields. There's a useEffect for each field. It would be better with something more DRY, but haven't figured out how.
Is there a way to do this faster and better?
States
const [unsavedChanges, setUnsavedChanges] = useState<Array<string>>([]);
const [customField1, setCustomField1] = useState<string | null | undefined>(
Data.field1
);
const [customField2, setCustomField2] = useState<string | null | undefined>(
Data.field2
);
Set unsavedChanges state
//Field 1
useEffect(() => {
if (
(customField1 === Data.field1 ||
customField1 === null) &&
unsavedChanges?.includes('customfield1')
) {
let filtered = unsavedChanges.filter(
(item) => item !== 'customfield1'
);
setUnsavedChanges(filtered);
} else if (
customField1 === Data.field1 ||
unsavedChanges?.includes('customfield1')
) {
return;
} else {
setUnsavedChanges([...unsavedChanges, 'customfield1']);
}
}, [customField1]);
//Field 2
useEffect(() => {
if (
(customField2 === Data.field2 ||
customField2 === null) &&
unsavedChanges?.includes('customfield2')
) {
let filtered = unsavedChanges.filter(
(item) => item !== 'customfield2'
);
setUnsavedChanges(filtered);
} else if (
customField2 === Data.field2 ||
unsavedChanges?.includes('customfield2')
) {
return;
} else {
setUnsavedChanges([...unsavedChanges, 'customfield2']);
}
}, [customField2]);
Return
<>
<Input value={customField1} onChange={setCustomField1(e.target.value}/>
<Input value={customField2} onChange={setCustomField2(e.target.value}/>
<h1>{unsavedChanges.length} # of unsaved changes.</h1>
<Button disabled={unsavedChanges.length > 0 ? false : true} label='Submit'/>
</>
i created an example of how to make it simple (in my opinion).
I merged the three states into one. this way i can get each one in more dynamic way. then i created on change handler that handles the changes and doing the all if statements (with less code).
each input is firing the change handler on change. and it sets the state according to the if statements.
this way we can create as many inputs as we want, we just need to pass the right arguments to the change handler and it will take care of the rest for us (and to make sure to include one more key value pair to the state)
this is the dummy data for Data:
const Data = { field1: "abc", field2: "efg" };
the state:
const [generalState, setGeneralState] = useState({
unsavedChanges: [],
customField1: "",
customField2: ""
});
the handler:
const changeTheState = (type, value, field, customField) => {
//setting the values to the state. so we can fetch the values for the inputs and for later use
setGeneralState((prev) => ({ ...prev, [type]: value }));
// i narrowed down the if statements. now its only two.
if (
value === Data[field] &&
generalState.unsavedChanges?.includes(customField)
) {
return setGeneralState((prev) => {
let filtered = prev.unsavedChanges.filter(
(item) => item !== customField
);
return { ...prev, unsavedChanges: filtered };
});
}
if (!generalState.unsavedChanges?.includes(customField)) {
setGeneralState((prev) => ({
...prev,
unsavedChanges: [...prev.unsavedChanges, customField]
}));
}
};
and the jsx:
<div className="App">
<input
value={generalState.customField1}
onChange={(e) => {
changeTheState(
"customField1",
e.target.value,
"field1",
"customField1"
);
}}
/>
<input
value={generalState.customField2}
onChange={(e) => {
changeTheState(
"customField2",
e.target.value,
"field2",
"customField2"
);
}}
/>
<h1>{generalState.unsavedChanges.length} # of unsaved changes.</h1>
<button disabled={generalState.unsavedChanges.length > 0 ? false : true}>
Submit
</button>
</div>
here is the example : codesandbox example
One more thing you can do , is to create a reusable component for the input. create array of objects to represent each input. loop through the array and generate as many inputs you want.
if you need extra explaination let me know.
I started my learning path few months ago (html, css, js) and I have a question for an issue that I have with react (just started learning it).
I have an error that says : data.map is not a function
I want to loop trough my array of objects with map, and dispatch the props (title, answer) to the child for each loop, to make a list of different FaqComponent having each the {title and answer}
const data = useSelector(state => ({
...state.homePage.list
}))
console.log(data);
return (
<div>
{data.map((title, answer) => (
<FaqComponent
title={title}
answer={answer}
/>
))}
</div>
);
}
export default ...;
Thanks for your replies
You're using an object {} instead of an array [] syntax.
Try with:
const data = useSelector(state => ([
...state.homePage.list
]));
You should declare "data"s type, if it is an array so your function should be like :
const data = useSelector(state => ([
...state.homePage.list
]))
console.log(data);
return (
<div>
{(data && data.length > 0) ? data.map((item, i) => (
<FaqComponent
title={item.title}
answer={item.answer}
key={i}
/>))
: <div>No Result!...</div>}
</div>
);
}
export default ...;
I want to remove object from my list by clicking on delete icon, but with my logic either everything is deleted from list or nothing, I am not sure how to do it without provided ID to object, I don't have anything unique and I am kinda lost.
Component that renders as many Food as there is in useState:
{cartFood.map((food) => {
return (
<CartFood
key={Math.random()}
foodName={food.foodName}
foodPrice={food.foodPrice}
numberOfPortions={food.numberOfPortions}
cartFood={cartFood}
setCartFood={setCartFood}
/>
);
})}
Logic for removing that particular item that is selected (which is not working and also bad solution since there can be case where you get same name and price twice)
const CartFood = ({
foodName,
foodPrice,
numberOfPortions,
cartFood,
setCartFood,
}) => {
const handleRemoveFood = () => {
setCartFood(
cartFood.filter(
(el) =>
el.foodName &&
el.foodPrice !== cartFood.foodName &&
cartFood.foodPrice
)
);
};
return (
<div className='cartFood-container'>
<p>{foodName}</p>
<p>x{numberOfPortions}</p>
<p>{foodPrice}kn</p>
<p>
<MdDeleteForever
className='cartFood__icon'
onClick={handleRemoveFood}
/>
</p>
</div>
);
};
export default CartFood;
List of objects looks like this:
[{
foodName: "Njoki with sos"
foodPrice: 35
numberOfPortions: 1
},
{
foodName: "Chicken Wingos"
foodPrice: 45
numberOfPortions: 2
}]
Put the index of the item in the array as the id. Pass it as your id.
{cartFood.map((food, index) => {
return (
<CartFood
key={index}
id={index}
foodName={food.foodName}
foodPrice={food.foodPrice}
numberOfPortions={food.numberOfPortions}
cartFood={cartFood}
setCartFood={setCartFood}
/>
);
})}
Use the id to remove the food.
const CartFood = ({
foodName,
foodPrice,
numberOfPortions,
cartFood,
setCartFood,
id,
}) => {
const handleRemoveFood = () => {
setCartFood(cartFood.filter((el) => el.id !== id));
};
return (
<div className='cartFood-container'>
<p>{foodName}</p>
<p>x{numberOfPortions}</p>
<p>{foodPrice}kn</p>
<p>
<MdDeleteForever
className='cartFood__icon'
onClick={handleRemoveFood}
/>
</p>
</div>
);
};
Something like this should work :
const handleRemoveFood = (obj) => {
setCartFood((oldList) => oldList.filter((item) => item.foodName !== obj.foodName));
};
Your button (icon) should call this function with current object data (obj)
A working example : https://codesandbox.io/s/cart-isz6c?file=/index.js
From what I see in your repo:
Just pass the food._id to FoodCard so you access it when you want to add or remove an item from cart:
FoodList.js
const foodList = (typeOfList) =>
typeOfList.map(food => {
return (
<FoodCard
key={food._id}
foodId={food._id}
foodName={food.title}
foodPrice={food.price}
foodPic={food.image}
setCartFood={setCartFood}
cartFood={cartFood}
/>
);
});
FoodCard.js
const handleAddToCard = () => {
setCartFood([
...cartFood,
{
foodId,
foodName,
foodPrice,
numberOfPortions,
},
]);
};
CartFood.js
const handleRemoveFood = () => {
setCartFood(cartFood => cartFood.filter((el) => el.foodId !== foodId));
};
Working example:
You could use useReducer with useContext so you don't have to pass props down manually at every level, check this article for more info
You don't need to pass the cartFood as a property just for updating the state since you can use setState callback:
setCartFood(cartFood => [
...cartFood,
{
foodId,
foodName,
foodPrice,
numberOfPortions,
},
]);
I have an array of objects with categories. I need to grab all of the categories except for one that I can define manually. I've set that category up in my state. I want the notCat excluded.
So to get all of the objects, I have this:
state = {
collapse: false,
active: false,
category: 'General Questions',
notCat: 'Deal FAQ',
};
const cats = [...new Set(Faqs.map(q => q.category))];
const catsR = cats.map((name, i) => {
return (
<li
key={i}
onClick={() => this.faqNavigation(name)}
className={
this.state.category === name ? 'active' : 'not-active'
}
>
{name}
</li>
);
});
Then I'm simply rendering it like this:
<ul className="toc">{catsR}</ul>
How can I exclude a certain category. Say Category A in the {catsR} render?
Assuming that Faqs is an array there is really no need to convert it into a set and then into an array afterwards. Array.map already returns a new array so you could simply do:
const cats = Faqs.map(q => q.category);
As a response to your question you can use the Array.filter method to filter out certain categories before mapping it:
const catsR = cats.filter(c => c !== 'notCat').map((name, i) => {
return (
<li
key={i}
onClick={() => this.faqNavigation(name)}
className={
this.state.category === name ? 'active' : 'not-active'
}
>
{name}
</li>
);
});