0

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.

5
  • Where's changeWinner function? Commented Jan 23, 2019 at 9:55
  • Where and the home and away variables declared ? Commented Jan 23, 2019 at 9:57
  • the code works but it feels like it is a bit too much. Code review requests are off-topic here. Look at CR Commented Jan 23, 2019 at 9:59
  • 1
    Quick question. How is this: function Parent = () => { working?? Commented Jan 23, 2019 at 10:08
  • it is a functional component being rendered in App. It is using Hooks if that is what you are asking? Commented Jan 23, 2019 at 10:36

6 Answers 6

4
const updatedFixtures = [...fixtures];
const fixture = updatedFixtures[i];
updatedFixtures[i] = {
  ...fixture,
  winner: fixture.winner === fixture.teams.home ? fixture.teams.away : fixture.teams.home,
};
Sign up to request clarification or add additional context in comments.

3 Comments

this will mutate the array though, no?
It will only mutate the updatedFixtures array. As this one is not part of the state (yet) you can mutate it however you like.
If your concern were about the const declaration, mdn states that The const declaration creates a read-only reference to a value. It does not mean the value it holds is immutable, just that the variable identifier cannot be reassigned.
2

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 */
    });
    

Comments

1

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.

Comments

0

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>

Comments

0

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))

1 Comment

thanks for taking the time to answer, but it doesn't really fit with what I want to do.
0
      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);
      }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.