React | Update State | using spread and array.push to update array - javascript

I have jsfiddle Link https://jsfiddle.net/ilikeflex/r2uws1ez/30/
I am trying to set new state by accessing the previous state. I have three different cases where i am trying to update the state but i am not sure why does Case2 and Case 3 does not work. Can you please help me ??
const useState = React.useState;
function Example() {
const [todos, setTodos] = useState([
{id:1,text:'Learn React'},
{id:2,text:'Learn TypeScript'},
{id:3,text:'Learn Java'}
]);
const case1 = () => {
const newItem = {id:3,text:'Learn C++'};
setTodos((prevState) => {
return prevState.concat(newItem);
});
}
const case2 = () => {
const newItem = {id:3,text:'Learn .Net'};
setTodos((prevState) => {
prevState.push(newItem); // I am adding to previous state and returning it
return prevState;
});
}
const case3 = () => {
const newItem = {id:3,text:'Learn C#'};
setTodos((prevState) => {
let result = { ...prevState, newItem }; // using spread operator
return result;
});
}
return (
<div>
<button onClick={case1}>Case 1</button>
<button onClick={case2}>Case 2</button>
<button onClick={case3}>Case 3</button>
<ul>
{todos.map((item) => (
<li key={item.id}>{item.text}</li>
))}
</ul>
</div>
);
}
ReactDOM.render(<Example />, document.getElementById('root'))

To make case 2 work, you need to make a variable out of the prevState first. Then you can push to it and return that new variable.
const case2 = () => {
const newItem = {id:3,text:'Learn .Net'};
setTodos((prevState) => {
const newState = [...prevState]
newState.push(newItem);
return newState;
});
}
For case 3, you made a little mistake by making result an object instead of an array. Change the {} to [] and it should work fine, like this.
const case3 = () => {
const newItem = {id:3,text:'Learn C#'};
setTodos((prevState) => {
let result = [...prevState, newItem ] ;
return result;
});
}

In all the cases, a new array need be to created and returned.
Case 1.
return prevState.concat(newItem); [ concat create a new array ]
Case 2
const newState = [...prevState]; [ Spread Operator creates a new array ]
newState.push(newItem);
return newState;
Case 3
let result = [...prevState, newItem ] ; [ Spread Operator creates a new array and newItem is added simultaneously ]
return result;

Related

React pushing to array duplicates it instead of adding

Can anybody help me with this issue? I have a state called "filteredPokemon" which fetches a list of pokemon based on some things, then I pass it to this function called PokemonDisplayArea where I proceed to display the list of pokemon. However, when I change offset I expect the behavior to ADD onto the previous state of "Cards" which are the element rendering the HTML, however instead, it grabs the newest filteredpokemon and appends it twice. Any help would be appreciated!
Here is a video
https://clipchamp.com/watch/EcMJbOMjOaL
The code:
function PokemonDisplayArea({pokemons}) {
const [filteredPokemon, setFilteredPokemon] = useState([]);
const [offset, setOffset] = useState(20);
const [cards, setCards] = useState([]);
useEffect(() => {
let cardsSkele = [];
if (filteredPokemon.length > 0) {
for (let i = 0; i < filteredPokemon.length; i++) {
if (undefined !== filteredPokemon[i].name) {
cardsSkele.push(<PokemonCard key={filteredPokemon[i].id} name={filteredPokemon[i].name}></PokemonCard>);
cardsSkele.sort((a, b) => a.key - b.key)
}
}
setCards(prevArray => [...prevArray, ...cardsSkele]);
}
}, [filteredPokemon])
// SEARCH POKEMON RESULTS
// FILTER POKEMON RESULTS
return (
<div className="pokemon__display-area">
<GetFilteredPokemon pokemons={pokemons} amount={20} offset={offset} filteredPokemon={filteredPokemon} setFilteredPokemon={setFilteredPokemon}></GetFilteredPokemon>
{cards}
</div>
)
}
Any help would be appreciated, I am a beginner, thanks!
I tried passing different states into useEffect, and tried console logging the data but the data seems to change fine, and the list just duplicates.
For starters, I would just store data in the state rather than components itself as it easy to reason about and transform.
Iterate over the cards and update the data for existing items or add the new item. Something along these lines.
function getUpdatedCardsData(prevCards, filteredPokemon) {
// get list of all the ids for the filtered pokemons
const filteredPokemonIds = filteredPokemon.map(fp => fp.id);
// This will combine the data for existing cards that are part of
// filteredPokemon and maintain a map of ids that are new and need to be added to cards
const updatedCards = prevCards.reduce((memo, card) => {
const cardId = card.id;
const isNewCard = !filteredPokemonIds.includes(cardId);
// add it to the map if new
if(isNewCard) {
memo.newCardIds.push(cardId);
} else {
// find the card that is in filtered pokemon
const found = filteredPokemon.find(fp => fp.id === cardId);
if(found) {
const updatedCard = {
...card,
...found
};
memo.updatedCards.push(updatedCard);
}
}
return memo;
}, {
updatedCards: [],
newCardIds: []
});
}
function getNewCards(newCardIds, filteredPokemon) {
// List of the new cards
const newCards = newCardIds.reduce((memo, newCardId) => {
const found = filteredPokemon.find(fp => fp.id === newCardId);
if(found) {
memo.push(found);
}
return memo;
}, []);
}
function PokemonDisplayArea({pokemons}) {
const [filteredPokemon, setFilteredPokemon] = useState([]);
const [offset, setOffset] = useState(20);
const [cards, setCards] = useState([]);
useEffect(() => {
if(filteredPokemon) {
setCards(prevCards => {
const {
updatedCards,
newCardIds
} = getUpdatedCardsData(prevCards, filteredPokemon);
const newCards = getNewCards(newCardIds, filteredPokemon);
// add updated and new and sort them
const sortedCards = [...updatedCards, ...newCards].sort((a, b) => a.id - b.id);
return sortedCards;
});
}
}, [filteredPokemon])
// SEARCH POKEMON RESULTS
// FILTER POKEMON RESULTS
return (
<div className="pokemon__display-area">
<GetFilteredPokemon
pokemons={pokemons}
amount={20}
offset={offset}
filteredPokemon={filteredPokemon}
setFilteredPokemon={setFilteredPokemon}
/>
{cards.map((card, i) => {
const pokemon = card[i];
return (
<PokemonCard
key={pokemon.id}
name={pokemon.name}
/>
})}
</div>
)
}

How can I use useEffect and store an array in Reactnative?

I have data from redux called targetFarm. targetFarm.children contains these array object data.
targetFarm.children = [
{name: 'pizza'},
{name: 'buger'},
{name: 'robster'},
{name: 'water'},
]
I want to put data in targetFarm.children into gyetong using useEffect and setgyeTong hooks.
As an array object value like below
expecter answer
gyetong = [
{'pizza'},
{'buger'},
{'robster'},
{'water'},
]
But with my code it only takes one array.
gyetong = ["pizza"]
How can i fix my code?
const App = () => {
const { targetFarm } = useAppSelector((state) => state.farm);
const [gyetong, setgyeTong] = useState([]);
return (
<Container>
{targetFarm.children.map((item, index) => {
useEffect(() => {
setgyeTong([item.name])
}, [item]);
})}
</Container>
)
}
For your information, you cannot the expected answer of gyetong you mentioned. I assume from the code, you want to have an array of array containing single item.
You can instead of this, directly do this:
const App = () => {
const { targetFarm } = useAppSelector((state) => state.farm);
const [gyetong, setgyeTong] = useState(
targetFarm.children.reduce((p, n) => {
p.push([n.name]);
return p;
}, [])
);
return <Container>{/* your code here */}</Container>;
};
By default gyetong will have the values
gyetong = [
['pizza'],
['buger'],
['robster'],
['water'],
]
Looks like very wrong expectation and usecase for this code!
gyetong = [
{'pizza'},
{'buger'},
{'robster'},
{'water'},
]
above will not possible, below possible result can be
gyetong = [
'pizza',
'buger',
'robster',
'water',
]
If we consider usecase, then there are two ways you can build your component properly,
Way 1:
const App = () => {
const { targetFarm } = useAppSelector((state) => state.farm);
return (
<Container>
{targetFarm.children.map((item) => {
return item.name;
})}
</Container>
);
};
Above is not to use extra state and render values directly.
Way 2:
const App = () => {
const { targetFarm } = useAppSelector((state) => state.farm);
const [gyetong, setgyeTong] = useState([]);
useEffect(() => {
const newGyetong = targetFarm.children.map((item) => {
return item.name;
});
setgyeTong(newGyetong);
}, [targetFarm]);
return (
<Container>
{gyetong.map((item, index) => {
return item.name;
})}
</Container>
);
};
In case you have to use state in component!
I hope this will help you! Happy coding...
You are doing set only one item at a time here
setgyeTong([item.name])
here set only one item at a time which was the last item,
you need to append that item into the array gyetong
setgyeTong({ ... gyetong, item.name });

How to use react.usememo using react and javascript?

i am doing some filtering on items based on ids which is in selection object. and then evaluating true or not based on its completion true or false from items which is an array of objects.
below is my code,
const Items: React.FC<RouteComponentProps> = ({history}) => {
const [{selection}] = tableState;
const {
data: items,
count: itemsCount,
} = React.useMemo(() => {
(!isNil(data) ? data.items : { data: [], count: 0}),
[data]
);
let noJobs;
if (selection) { //how to put this block in useMemo
const filteredItems = items.filter(item =>
Object.keys(selection).includes(item.id)
);
noJobs = filteredItems.map(item => item.jobs.map(job => job.completed))
.flat().every(Boolean);
}
return (
<button disabled = {noJobs}> Click me </button>
);
}
How can i put the block which includes noJobs calcualtion (from if(selection)) to React.useMemo. could someone help me with this. I am learning react and hooks is new to me.
thanks.
Try followings it:
const Items: React.FC<RouteComponentProps> = ({history}) => {
const [{selection}] = tableState;
const {
data: items,
count: itemsCount,
} = React.useMemo(() => {
(!isNil(data) ? data.items : { data: [], count: 0}),
[data]
);
const noJobs = React.useMemo(() => {
if (selection) {
const filteredItems = items.filter(item => {
return Object.keys(selection).includes(item.id)
});
return filteredItems.map(item => item.jobs.map(job => {
return job.completed
}))
.flat()
.every(Boolean);
};
return undefined; //or everything you want
} , [items , selection]);
return (
<button disabled = {noJobs}> Click me </button>
);
}

How do I call a function within another function when the data I need is in the global scope?

I have a set of card objects that I map over.
When I click on a card it adds the selected class which in turn gives it a border to show the user it is selected, it also adds the id of the card to the selectedCards useState array.
WHAT I WANT TO HAPPEN:
Each card object has a creditAvailable key state which is equal to a figure.
On selection (click) of the card, in addition to selecting the card I would also like to add up the creditAvailable and display it on the screen. and when I unselect the card I would like the figure to go down.
WHAT I HAVE TRIED:
I thought it would be as simple as calling the function to add up the credit inside the first function which selects the card, however when console logging inside the first function I see that the state has not yet updated. (scope).
I then tried to call the function outside of the first function but it gave me an infinite loop. Here is my code.
Any ideas? Thanks
const [cards, setCards] = useState([]);
const [selectedCards, setSelectedCards] = useState([]);
const [total, setTotal] = useState();
const handleSelectCard = (id) => {
if (selectedCards.includes(id)) {
const filteredIds = selectedCards.filter((c) => c !== id);
setSelectedCards([...filteredIds]);
} else {
setSelectedCards([...selectedCards, id]);
}
// addUpCreditAvailable(); // nothing happens
console.log(selectedCards); // []
};
console.log(selectedCards) // [1] for example. This is in the global scope
const addUpCreditAvailable = () => {
console.log("inside add up credit");
const chosenCards = selectedCards.map((id) => {
const foundCard = allCards.find((card) => {
return card.id === id;
});
return foundCard;
});
const result = chosenCards.reduce((acc, card) => {
return acc + card.creditAvailable;
}, 0);
setTotal(result);
return result;
};
return (
<div className="Container">
<UserInputForm submitData={handleSubmitData} />
<h1> Cards available to you displayed are below!</h1>
{cards.map(
({
id,
name,
number,
apr,
balanceTransfer,
purchaseDuration,
creditAvailable,
expiry,
}) => (
<CreditCard
key={id}
name={name}
number={number}
apr={apr}
balanceTransferDuration={balanceTransfer}
purchaseOfferDuration={purchaseDuration}
creditAvailable={creditAvailable}
expiry={expiry}
onClickCard={() => handleSelectCard(id)}
selected={selectedCards.includes(id)}
/>
)
)}
<span> £{total}</span>
)}
I figured it out with the help from above. As Wilms said i had to return the result of the handleSelectCard function and return the result of the addUpCredit function. Then I called the addUpCreditAvailable with the selectedCards state and stored the result in a variable which i then displayed in my render method.
const [cards, setCards] = useState([]);
const [selectedCards, setSelectedCards] = useState([]);
const handleSelectCard = (id) => {
if (selectedCards.includes(id)) {
const filteredIds = selectedCards.filter((c) => c !== id);
setSelectedCards([...filteredIds]);
} else {
setSelectedCards([...selectedCards, id]);
}
return selectedCards;
};
const addUpCreditAvailable = (selectedCards) => {
const chosenCards = selectedCards.map((id) => {
const foundCard = allCards.find((card) => {
return card.id === id;
});
return foundCard;
});
const result = chosenCards.reduce((acc, card) => {
return acc + card.creditAvailable;
}, 0);
return result;
};
const totalCredit = addUpCreditAvailable(selectedCards);
render method:
render (
[...]
{selectedCards.length && (
<div className={bem(baseClass, "total-credit")}>
Total Credit available: £{totalCredit}
</div>
)}
[...]
)

useReducer dispatch executing twice

I don't understand why this happens. Since strict mode is enabled in react, this function gets executed twice. So instead of deleting a single item, it deletes two items, one on first round, second on the next.
const deleteItem = (state, index) => {
// index is a number
let indexCounter = 0;
let tempArray = [...state.todos];
const newTodos = tempArray.filter(item => {
if (item.index === index) {
return false;
}
item.index = indexCounter++;
return true;
});
return {
...state,
todos: newTodos,
nextIndex: indexCounter
};
}
But if I use a Set instead of primitive data type (number), this works fine. i.e. only one item will be removed, even though dispatch is called twice.
const deleteItem = (state, set) => {
const newSet = new Set(set);
let indexCounter = 0;
let tempArray = [...state.todos];
const newTodos = tempArray.filter(item => {
if (newSet.has(item.index)) {
newSet.delete(item.index);
return false;
}
item.index = indexCounter++;
return true;
});
return {
...state,
todos: newTodos,
nextIndex: indexCounter
};
}
Am I missing something here? What exactly is happening?
You are mutating the state which influences the next action.
// Is a shallow copy
let tempArray = [...state.todos];
const newTodos = tempArray.filter((item) => {
if (item.index === index) {
return false;
}
// State mutation
item.index = indexCounter++;
return true;
});
Instead, you need to make a deep copy or use Immutable Update Pattern as mentioned in Redux docs.
I've updated the code. It's working fine now. Hope it's correct.
const deleteItem = (state, index) => {
let indexCounter = 0;
const tempArray = state.todos.filter(item => {
return index !== item.index;
});
const newTodos = [];
tempArray.forEach((item) => {
newTodos.push({...item, index: indexCounter++});
})
return {
...state,
todos: newTodos,
nextIndex: indexCounter
};
}

Categories

Resources