2

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.

4
  • 1
    Javascript object keys must all be unique, so I am afraid, { // 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} // } isn't really possible Commented Jan 3, 2018 at 19:47
  • 2
    What you could have instead is an array of objects Commented Jan 3, 2018 at 19:47
  • Would agree with @ShubhamKhatri. You can make an array of objects instead of having just one value for a page in your state. Managing those pages should be piece of the cake after that. Commented Jan 3, 2018 at 20:39
  • "piece of cake" - perhaps similar to baking, not eating :) Blueshift, if you need more features than in my answer, e.g. deleting pages, please add a new "update" section with examples of input/output and notify me (currently you ask to keep at most 1 page per category, but did not mention deletion, so no idea how to detect if some other action is needed sometimes) Commented Jan 3, 2018 at 21:33

1 Answer 1

1

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)

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for the completeness of your answer, Aprillion! I have gone ahead and adopted the same format for the state when !multiple, so: [pageName]: [selection]. I've also updated my post with input information for deleting a page (thank you for this too BTW, I was thinking of doing that as well).

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.