Reputation: 9790
I want to toggle a property of an object in an array. The array looks as follows. This is being used in a react component and When a user clicks on a button I want to toggle the winner.
const initialFixtures = [{
teams: {
home: 'Liverpool',
away: 'Manchester Utd'
},
winner: 'Liverpool'
},
{
teams: {
home: 'Chelsea',
away: 'Fulham'
},
winner: 'Fulham'
}, ,
{
teams: {
home: 'Arsenal',
away: 'Tottenham'
},
winner: 'Arsenal'
}
];
My react code looks something like this
function Parent = () => {
const [fixtures, setUpdateFixtures] = useState(initialFixtures)
const toggleWinner = (index) => {
const updatedFixtures = fixtures.map((fixture, i) => {
if (i === index) {
return {
...fixture,
winner: fixture.winner === home ? away : home,
};
} else {
return fixture;
}
})
setUpdateFixtures(updatedFixtures);
}
return <Fixtures fixtures={fixtures} toggleWinner={toggleWinner} />;
}
function Fixtures = ({ fixtures, toggleWinner }) => {
fixtures.map((fixture, index) => (
<div>
<p>{fixture.winner} </p>
<button onClick = {() => toggleWinner(index)}> Change Winner</button>
</div>
))
}
the code works but it feels like it is a bit too much. I am sure there is a better more succinct way of doing this. Can anyone advise? I do need to pass the fixtures in from the parent of the Fixture
component for architectural reasons.
Upvotes: 0
Views: 5215
Reputation: 29285
You can slice the fixtures array into three parts:
from 0 to index
: fixtures.slice(0, index)
. This part is moved to the new array intact.
The single item at index
. This part/item is thrown away because of being changed and a new item is substituted.
The rest of the array: fixtures.slice(index + 1)
.
Next, put them into a new array:
const newFixtures = [
...fixtures.slice(0, index), // part 1
{/* new item at 'index' */}, // part 2
...fixtures.slice(index + 1) // part 3
];
To construct the new item:
Using spread operator:
const newFixture = {
...oldFixture,
winner: /* new value */
};
Using Object.assign
:
const newFixture = Object.assign({}, oldFixture, {
winner: /* new value */
});
Upvotes: 2
Reputation: 942
const toggleWinner = (index) => {
let updatedFixtures = [...fixtures].splice(index, 1, {...fixtures[index],
winner: fixtures[index].winner === fixtures[index].teams.home
? fixtures[index].teams.away : fixtures[index].teams.home})
setUpdateFixtures(updatedFixtures);
}
Upvotes: 0
Reputation: 24915
If you are comfortable to use a class based approach, you can try something like this:
isHomeWinner
. This property will decide the winner.winner
which will lookup this.isHomeWinner
and will give necessary value.toggle
function: this.isHomeWinner = !this.isHomeWinner
.You can also write your toggleWinner
as:
const toggleWinner = (index) => {
const newArr = initialFixtures.slice();
newArr[index].toggle();
return newArr;
};
This looks clean and declarative. Note, if immutability is necessary then only this is required. If you are comfortable with mutating values, just pass fixture.toggle
to your react component. You may need to bind context, but that should work as well.
So it would look something like:
function Fixtures = ({ fixtures, toggleWinner }) => {
fixtures.map((fixture, index) => (
<div>
<p>{fixture.winner} </p>
<button onClick = {() => fixture.toggle() }> Change Winner</button>
// or
// <button onClick = { fixture.toggle.bind(fixture) }> Change Winner</button>
</div>
))
}
Following is a sample of class and its use:
class Fixtures {
constructor(home, away, isHomeWinner) {
this.team = {
home,
away
};
this.isHomeWinner = isHomeWinner === undefined ? true : isHomeWinner;
}
get winner() {
return this.isHomeWinner ? this.team.home : this.team.away;
}
toggle() {
this.isHomeWinner = !this.isHomeWinner
}
}
let initialFixtures = [
new Fixtures('Liverpool', 'Manchester Utd'),
new Fixtures('Chelsea', 'Fulham', false),
new Fixtures('Arsenal', 'Tottenham'),
];
const toggleWinner = (index) => {
const newArr = initialFixtures.slice();
newArr[index].toggle();
return newArr;
};
initialFixtures.forEach((fixture) => console.log(fixture.winner))
console.log('----------------')
initialFixtures = toggleWinner(1);
initialFixtures.forEach((fixture) => console.log(fixture.winner))
initialFixtures = toggleWinner(2);
console.log('----------------')
initialFixtures.forEach((fixture) => console.log(fixture.winner))
Upvotes: 0
Reputation: 5396
if you write your code in such a way - this will do the job.
const toggleWinner = index => {
const { winner, teams: { home, away } } = fixtures[index];
fixtures[index].winner = winner === home ? away : home;
setUpdateFixtures([...fixtures]);
};
Setting a new array of fixtures to state is completely enough to trigger render
on Fixtures
component.
I have made a working example for you.
Upvotes: 1
Reputation: 1938
const updatedFixtures = [...fixtures];
const fixture = updatedFixtures[i];
updatedFixtures[i] = {
...fixture,
winner: fixture.winner === fixture.teams.home ? fixture.teams.away : fixture.teams.home,
};
Upvotes: 4
Reputation: 2419
You can use libraries like immer to update nested states easily.
const initialFixtures = [{
teams: {
home: 'Liverpool',
away: 'Manchester Utd'
},
winner: 'Liverpool'
},
{
teams: {
home: 'Chelsea',
away: 'Fulham'
},
winner: 'Fulham'
}, ,
{
teams: {
home: 'Arsenal',
away: 'Tottenham'
},
winner: 'Arsenal'
}
];
const newState = immer.default(initialFixtures, draft => {
draft[1].winner = "something";
});
console.log(newState);
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/immer.umd.js"></script>
Upvotes: 0