peter flanagan
peter flanagan

Reputation: 9790

Toggle item in an array react

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

Answers (6)

frogatto
frogatto

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

Vaibhav Singh
Vaibhav Singh

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

Rajesh
Rajesh

Reputation: 24915

If you are comfortable to use a class based approach, you can try something like this:

  • Create a class that holds property value for team.
  • Create a boolean property in this class, say isHomeWinner. This property will decide the winner.
  • Then create a getter property winner which will lookup this.isHomeWinner and will give necessary value.
  • This will enable you to have a clean 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

Shevchenko Viktor
Shevchenko Viktor

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

nubinub
nubinub

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

Praveen
Praveen

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

Related Questions