Blueshift
Blueshift

Reputation: 160

React setState with dynamic keys

I've got a simple problem here with creating dynamic keys for a state object in React using computed property names, but after trying for hours I can't figure out how to fix it.

See the comments in the code for details, and a short overview below.

Scenario: a user clicks on an option inside a given page and the updateSelection method below is called. This method updates the state with new information.

Problem: Using computed property names I think(?) makes the keys be unique inside of an object, and any future keys of the same name are replaced. In some cases, I want to allow multiple selections of the same page, this is the part that I can't figure out. I'm open to turning my state into an array of objects if that works better.

state = {
  userSelection: {}
}

const updateSelection = page => {
  const { pageName, category, multiple } = page;

  const selection = {
    pageName,
    category
  }

  // If `multiple` is false, pages must be unique in the state object.
  // This currently works fine.
  if (!multiple) {
    this.setState(prevState => ({
      userSelection: {
        ...prevState.userSelection,
        [pageName]: selection
      }
    }));

    // New state for userSelection is now this:
    // { 
    //   Page 1: {pageName: "Page 1", category: X}, 
    //   Page 2: {pageName: "Page 2", category: Y}
    // }

    return;
  }

  // If `multiple` is true for a page, the same page key can be added to
  // the state object as long as the category is different. If the category
  // is the same, the page is replaced just like when `multiple` is false.
  // The desired result for userSelection would look something like this:
  // { 
  //   Page 1: {pageName: "Page 1", category: X}, 
  //   Page 2: {pageName: "Page 2", category: Y},
  //   Page 2: {pageName: "Page 2", category: Z},
  //   Page 2: {pageName: "Page 2", category: W, other unique props}
  // }
}

Update: for deleting a page, the input would be the pageName which could be passed as an argument to a deletePage method similar to updateSelection.

Upvotes: 2

Views: 603

Answers (1)

Aprillion
Aprillion

Reputation: 22324

If you wish to use an array for multiple values of each page, like this:

{ 
  "Page 1": [ {pageName: "Page 1", category: X} ], 
  "Page 2": [
    {pageName: "Page 2", category: Y},
    {pageName: "Page 2", category: Z},
    {pageName: "Page 2", category: W, other unique props}
  ]
}

then you can adapt following code for adding/replacing items in the arrays:

function updateSelection ({pageName, category, multiple}) {
  const selection = {pageName, category} // assuming more props here
  if (!multiple) {} // TODO keep old code or change to arrays too
  
  this.setState(prevState => {
    const {userSelection} = prevState
    if (!userSelection.hasOwnProperty(pageName)) {
      userSelection[pageName] = []
    }
    let categoryWasReplaced = false
    const nextUserSelection = {
      ...userSelection,
      [pageName]: userSelection[pageName].map(item => {
        // TODO use .filter() if needed to remove from selection
        if (item.category === category) {
          categoryWasReplaced = true
          return selection
        }
        return item
      })
    }
    if (!categoryWasReplaced) {
      nextUserSelection[pageName].push(selection)
    }
    return {userSelection: nextUserSelection}
  })
}

// ### test ###
let state = {userSelection: {}}
const mockSetState = f => {
  state = {...state, ...f(state)}
}
const testUpdateSelection = updateSelection.bind({state, setState: mockSetState})
testUpdateSelection({pageName: 'Page 1', category: 'X', multiple: true})
testUpdateSelection({pageName: 'Page 2', category: 'Y', multiple: true})
testUpdateSelection({pageName: 'Page 2', category: 'Y', multiple: true})
testUpdateSelection({pageName: 'Page 2', category: 'Y', multiple: true})
testUpdateSelection({pageName: 'Page 2', category: 'Z', multiple: true})
testUpdateSelection({pageName: 'Page 2', category: 'W', multiple: true})
console.log(state)

Upvotes: 1

Related Questions