Skip to content

Calling toJS on the result of an Immutable map causes the Immutable map to be converted to a JS object when being passed as a prop to a React component inside of the map function #2029

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
ender2012 opened this issue Oct 25, 2024 · 6 comments

Comments

@ender2012
Copy link

ender2012 commented Oct 25, 2024

What happened

I am looking into upgrading from Immutable 3 to Immutable 4.3.7 and I ran into a strange difference that I am having trouble explaining.

When mapping over a list of Immutable maps and then using toJS to create a an array that can be rendered by React, Immutable.Maps that are passed as a prop to a component inside the map function get converted to a javascript object in Immutable 4.

I found that if I used toArray instead of toJS this does not happen so this seems to be a fine workaround but I am wondering if this is expected with toJS and what may have changed between 3 and 4 that may have caused this behaviour to be different.

How to reproduce

Since this is a somewhat complex situation I think its easier to show with code.

This first example creates an Immutable List of Immutable Map that contain a single key called name. Inside our JSX we map over the Immutable List and pass the itemData to a component called item which renders a div with the text name that it tries to pull out by calling itemData.get('name'). Notice that this codesanbox errors out saying:

itemData.get is not a function

Codesanbox using toJS

This is occurring because itemData has been converted to a javascript object inside of the map function (instead of only converting the whole result of the map afterwards which is how it worked in Immutable 3).

In this second example I use toArray instead of toJS and it all works as expected:

Codesanbox using toArray

@jdeniau
Copy link
Member

jdeniau commented Oct 25, 2024

Hi,

I do not have a computer right now, but there has been changes in 4.0 related to toArray / toJson.

You might want to look at the 4.0 changelog (all RCs), especially this change :
https://github.com/immutable-js/immutable-js/blob/main/CHANGELOG.md#other-breaking-changes

The toJSON() method performs a shallow conversion (previously it was an alias for toJS(), which remains a deep conversion).

Or this one
https://github.com/immutable-js/immutable-js/blob/main/CHANGELOG.md#toarray

I remember migrating a lot to toArray when upgrading my codebase to 4.0 too.

Can you tell me if it did solve your issue?

@ender2012
Copy link
Author

Yeah switching to toArray did fix the problem but I am just trying to figure out what is different about toJS that causes the issue so I might be able to explain this to others.

@bdurrer
Copy link
Contributor

bdurrer commented Oct 25, 2024

In this case, deep conversion certainly isn't necessary and just a potential waste of performance, but it's a suprising outcome.
Seems as if the inner react components is being executed as a function

@ender2012
Copy link
Author

I agree that deep conversion isn't necessary but I'm just trying to understand what the cause of this behavior might be so that I might avoid similar pitfalls in the future. In the end if it turns out the answer is just to use what works (toArray) I can live with that.

@bdurrer
Copy link
Contributor

bdurrer commented Oct 26, 2024

I mean, if it is a bug then we would want to fix it. But I am not sure about that.

import * as Immutable from "immutable";
import "./styles.css";

const itemsData = Immutable.List([
  Immutable.Map([["name", "foo"]]),
  Immutable.Map([["name", "bar"]]),
]);
console.log("data", itemsData);

function Item({ itemData }) {
  console.log("react:", itemData);
  return <div>{itemData.name}</div>;
}

export default function App() {
  const data = itemsData
    .map((itemData) => {
      console.log("loopin", itemData);
      return <Item itemData={itemData.name} />;
    })
    .toJS();
  return <div className="App">{data}</div>;
}

When you put a console.log into Item, you can see that itemData is a plain object, but should be an Immutable.Map instead.
So when changing item.get("name") to itemData.name, we would expect undefined as output, but it actually works.

We can now see that during the mapping, the data is indeed a Map, but when the component function is being executed, the input is a plain old object. Also, turning around .map and .toJS solves the issue too (the Item component then receives an Immutable).

So I wonder who is doing that conversion on itemData? Is the react hooks magic playing tricks on us or does Immutable execute the component as a function during toJS? The later actually working would be kinda suprising, so maybe it's something about how this sample is built.

@ender2012
Copy link
Author

ender2012 commented Oct 27, 2024

I would say its not simply react hook magic trickery since this example would work with Immutable 3.1.1 so I believe something has changed in Immutable 4 that causes this different behaviour.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants