Peter Kellner
Peter Kellner

Reputation: 15488

Need Help Simplifying an Immutable.js Array of Objects update

I'm new to immutablejs and have managed to update a property of an object stored in an array of objects. My goal is to simplify my development but I feel like I'v made it more complicated. Clearly, I'm missing something to make this simpler.

I created to stackblitz projects, one with the code without immutablejs https://stackblitz.com/edit/react-before-immutable , and one with immutablejs https://stackblitz.com/edit/react-after-immutable

(code below also).

I've seen some examples here where people use the second parameter of findIndex, but that function never got called for me. It also is not in the docs so I'm guessing it's not supported any more.

With Immutable.js

import React, { useState } from 'react';
import { List } from 'immutable';

export default () => {

  const init = [{
    id: 101,
    interestLevel: 1
  },
  {
    id: 102,
    interestLevel: 0
  }];

  const [myArray, setMyArray] = useState(init);

  const updateRow = (e) => {
    const id = parseInt(e.target.attributes['data-id'].value);

    const immutableMyArray = List(myArray);
    const index = List(myArray).findIndex((item) => {
      return item.id === id;
    });
    const myRow = immutableMyArray.get(index);
    myRow.interestLevel++;
    const newArray = immutableMyArray.set(index, myRow);

    setMyArray(newArray);


  };


  return (

    <ul>
      {
        myArray.map(function (val) {
          return (
            <li key={val.id}>
              <button onClick={updateRow} data-id={val.id}  >Update Title</button>
              {val.id} :  {val.interestLevel}
            </li>
          )
        })
      }
    </ul>

  )
}

Without Immutable.js

import React, { useState } from 'react';

export default () => {

  const init = [{
    id: 101,
    interestLevel: 1
  },
  {
    id: 102, 
    interestLevel: 0
  }];

  const [myArray, setMyArray] = useState(init);

  const updateRow = (e) => {
    const id = e.target.attributes['data-id'].value;
    const newMyArray = [...myArray];
    var index = newMyArray.findIndex(a=>a.id==id);
    newMyArray[index].interestLevel = myArray[index].interestLevel + 1;
    setMyArray(newMyArray);
  }


  return (

    <ul>
      {
        myArray.map(function (val) {
          return (
            <li key={val.id}>
              <button onClick={updateRow} data-id={val.id}  >Update Title</button>
              {val.id} :  {val.interestLevel}
            </li>
          )
        })
      }
    </ul>

  )
}

Upvotes: 1

Views: 356

Answers (2)

frix
frix

Reputation: 165

As you mentioned in this comment, you're looking for easier ways of updating objects in a deep object hierarchy using Immutable.js.

updateIn should do the trick.

const immutableObject = Immutable.fromJS({ outerProp: { innerCount: 1 } });
immutableObject.updateIn(['outerProp', 'innerCount'], count => count + 1);

It's also worth noting that you probably want to call Immutable.fromJS() instead of using Immutable.List() since the latter won't deeply convert your JavaScript object into an Immutable one, which can lead to bugs if you're assuming the data structure to be deeply Immutable. Switching the code above to use Immutable.fromJS() and updateIn we get:

// In updateRow
const immutableMyArray = fromJS(myArray);
const index = immutableMyArray.findIndex((item) => {
  return item.id === id;
});
const newArray = immutableMyArray.updateIn([index, 'interestLevel'], interestLevel => interestLevel + 1);

setMyArray(newArray);

Upvotes: 0

S&#233;bastien Renauld
S&#233;bastien Renauld

Reputation: 19662

Have you considered the purpose of immutablejs?

In your example, you are only adding needless complexity to your code, without leveraging the gains provided by the library.

The purpose of immutable is to provide immutable collections, inspired from scala. In other words, you create your collection, then you pass it to another component, and you can be certain that no element was appended or removed. The individual elements, however, are not under such guarantee, partly due to the constraints (or lack thereof) brought by JS.

As it stands in your code, there are very few reasons to do something like this. I've taken the liberty of changing your quote quite a bit in order to showcase how to do so:

class Comp extends React.Component {

  constructor(props) {
    super(constructor);
    if (props.interests) {
      this.state = {
        interests: props.interests
      }
    } else {
      this.state = {
        interests: Immutable.Set([])
      }
    }
  }

  updateRow(e) {
    return function() {
      this.setState({
        interests: this.state.interests.update((elements) => {
          for (var element of elements) {
            if (element.id == e) {
              element.interestLevel++;
            }
          }
          return elements;
        })
      });
    }
  }


  render() {
    var interests = this.state.interests;
    var updateRow = this.updateRow;
    var list = this;
    //return (<div>Test</div>);
    return ( <
        ul > {
          interests.map(function(val) {
              return ( <
                  li key = {
                    val.id
                  } >
                  <
                  button onClick = {
                    updateRow(val.id).bind(list)
                  }
                  data-id = {
                    val.id
                  } > Update Title < /button> {
                  val.id
                }: {
                  val.interestLevel
                } <
                /li>
            )
          })
      } <
      /ul>

  )
}
}
var interests = Immutable.Set([{
    id: 1,
    interestLevel: 0
  },
  {
    id: 2,
    interestLevel: 0
  }
])
ReactDOM.render( < Comp interests = {
      interests
    }
    />, document.getElementById("app"));
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/immutable.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.7.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.7.0/umd/react-dom.production.min.js"></script>

<div id='app'></div>

The changes:

  1. Rewrote your component as a class. This is purely to highlight the rest of the changes
  2. Your component now takes an external prop containing interests. This means you can pass a Set and be sure that it won't have elements added out of the blue within the component
  3. The component is in charge of the interest levels. As such, whenever you click on one of the buttons, the update function is called on the Set, which is used to update the items inside the collection
  4. The entire thing is rendered as an array through render()

This is both more readable and easier to manage.

Upvotes: 0

Related Questions