Skip to content

Latest commit

 

History

History
577 lines (473 loc) · 10.2 KB

File metadata and controls

577 lines (473 loc) · 10.2 KB
title Arrays and Keys
description Rendering arrays and handling keys in ReScript and React
canonical /docs/react/arrays-and-keys
section Main Concepts
order 4

Arrays and Keys

Whenever we are transforming data into an array of elements and put it in our React tree, we need to make sure to give every element an unique identifier to help React distinguish elements for each render. This page will explain the key attribute and how to apply it whenever we need to map data to React.elements.

Rendering Arrays

Arrays require a special function React.array to convert an array<Jsx.element> to render as Jsx.element.

<CodeTab labels={["ReScript", "JS Output", "JSX Preserved Output"]}>

type todo = {id: string, text: string}

@react.component
let make = () => {
  let todos = [{id: "todo1", text: "Todo 1"}, {id: "todo2", text: "Todo 2"}]

  let items = Array.map(todos, todo => {
    <li key={todo.id}> {React.string(todo.text)} </li>
  })

  <ul> {React.array(items)} </ul>
}
import * as JsxRuntime from "react/jsx-runtime";

function Example(props) {
  let todos = [
    {
      id: "todo1",
      text: "Todo 1",
    },
    {
      id: "todo2",
      text: "Todo 2",
    },
  ];
  let items = todos.map((todo) =>
    JsxRuntime.jsx(
      "li",
      {
        children: todo.text,
      },
      todo.id,
    ),
  );
  return JsxRuntime.jsx("ul", {
    children: items,
  });
}

let make = Example;

export { make };
import * as JsxRuntime from "react/jsx-runtime";

function Example(props) {
  let todos = [
    {
      id: "todo1",
      text: "Todo 1",
    },
    {
      id: "todo2",
      text: "Todo 2",
    },
  ];
  let items = todos.map((todo) => <li key={todo.id}>{todo.text}</li>);
  return <ul>{items}</ul>;
}

let make = Example;

export { make };

Keys

Keys help React identify which elements have been changed, added, or removed throughout each render. Keys should be given to elements inside the array to give the elements a stable identity:

<CodeTab labels={["ReScript", "JS Output", "JSX Preserved Output"]}>

let numbers = [1, 2, 3, 4, 5]

let items = Array.map(numbers, (number) => {
  <li key={Int.toString(number)}> {React.int(number)} </li>
})
import * as JsxRuntime from "react/jsx-runtime";

let numbers = [1, 2, 3, 4, 5];

let items = numbers.map((number) =>
  JsxRuntime.jsx(
    "li",
    {
      children: number,
    },
    number.toString(),
  ),
);

export { numbers, items };
import * as JsxRuntime from "react/jsx-runtime";

let numbers = [1, 2, 3, 4, 5];

let items = numbers.map((number) => <li key={number.toString()}>{number}</li>);

export { numbers, items };

The best way to pick a key is to use a string that uniquely identifies a list item among its siblings. Most often you would use IDs from your data as keys:

<CodeTab labels={["ReScript", "JS Output"]}>

type todo = {id: string, text: string}

let todos = [
  {id: "todo1", text: "Todo 1"},
  {id: "todo2", text: "Todo 2"}
]

let items = Array.map(todos, todo => {
  <li key={todo.id}> {React.string(todo.text)} </li>
})
var todos = [
  {
    id: "todo1",
    text: "Todo 1",
  },
  {
    id: "todo2",
    text: "Todo 2",
  },
];

var items = todos.map(function (todo) {
  return JsxRuntime.jsx(
    "li",
    {
      children: todo.text,
    },
    todo.id,
  );
});

If you don’t have stable IDs for rendered items, you may use the item index as a key as a last resort:

<CodeTab labels={["ReScript", "JS Output", "JSX Preserved Output"]}>

let items = Array.mapWithIndex(todos, (todo, i) => {
  // Only do this if items have no stable id
  <li key={Int.toString(i)}>
    {React.string(todo.text)}
  </li>
})
import * as JsxRuntime from "react/jsx-runtime";

let todos = [
  {
    id: "todo1",
    text: "Todo 1",
  },
  {
    id: "todo2",
    text: "Todo 2",
  },
];

todos.map((todo) =>
  JsxRuntime.jsx(
    "li",
    {
      children: todo.text,
    },
    todo.id,
  ),
);

let items = todos.map((todo, i) =>
  JsxRuntime.jsx(
    "li",
    {
      children: todo.text,
    },
    i.toString(),
  ),
);

export { todos, items };
import * as JsxRuntime from "react/jsx-runtime";

let todos = [
  {
    id: "todo1",
    text: "Todo 1",
  },
  {
    id: "todo2",
    text: "Todo 2",
  },
];

todos.map((todo) => <li key={todo.id}>{todo.text}</li>);

let items = todos.map((todo, i) => <li key={i.toString()}>{todo.text}</li>);

export { todos, items };

Keys Must Only Be Unique Among Siblings

Keys used within arrays should be unique among their siblings. However they don’t need to be globally unique. We can use the same keys when we produce two different arrays:

<CodeTab labels={["ReScript", "JS Output", "JSX Preserved Output"]}>

type post = {id: string, title: string, content: string}

module Blog = {
  @react.component
  let make = (~posts: array<post>) => {
    let sidebar =
      <ul>
        {Array.map(posts, post => {
          <li key={post.id}> {React.string(post.title)} </li>
        })->React.array}
      </ul>

    let content = Array.map(posts, post => {
      <div key={post.id}>
        <h3> {React.string(post.title)} </h3>
        <p> {React.string(post.content)} </p>
      </div>
    })

    <div>
      {sidebar}
      <hr />
      {React.array(content)}
    </div>
  }
}

let posts = [
  {
    id: "1",
    title: "Hello World",
    content: "Welcome to learning ReScript & React!",
  },
  {
    id: "2",
    title: "Installation",
    content: "You can install @rescript/react from npm.",
  },
]

let blog = <Blog posts />
import * as JsxRuntime from "react/jsx-runtime";

let todos = [
  {
    id: "todo1",
    text: "Todo 1",
  },
  {
    id: "todo2",
    text: "Todo 2",
  },
];

let items = todos.map((todo) =>
  JsxRuntime.jsx(
    "li",
    {
      children: todo.text,
    },
    todo.id,
  ),
);

function Example$Blog(props) {
  let posts = props.posts;
  let sidebar = JsxRuntime.jsx("ul", {
    children: posts.map((post) =>
      JsxRuntime.jsx(
        "li",
        {
          children: post.title,
        },
        post.id,
      ),
    ),
  });
  let content = posts.map((post) =>
    JsxRuntime.jsxs(
      "div",
      {
        children: [
          JsxRuntime.jsx("h3", {
            children: post.title,
          }),
          JsxRuntime.jsx("p", {
            children: post.content,
          }),
        ],
      },
      post.id,
    ),
  );
  return JsxRuntime.jsxs("div", {
    children: [sidebar, JsxRuntime.jsx("hr", {}), content],
  });
}

let Blog = {
  make: Example$Blog,
};

let posts = [
  {
    id: "1",
    title: "Hello World",
    content: "Welcome to learning ReScript & React!",
  },
  {
    id: "2",
    title: "Installation",
    content: "You can install @rescript/react from npm.",
  },
];

let blog = JsxRuntime.jsx(Example$Blog, {
  posts: posts,
});

export { todos, items, Blog, posts, blog };
import * as JsxRuntime from "react/jsx-runtime";

let todos = [
  {
    id: "todo1",
    text: "Todo 1",
  },
  {
    id: "todo2",
    text: "Todo 2",
  },
];

let items = todos.map((todo) => <li key={todo.id}>{todo.text}</li>);

function Example$Blog(props) {
  let posts = props.posts;
  let sidebar = (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
  let content = posts.map((post) => (
    <div key={post.id}>
      <h3>{post.title}</h3>
      <p>{post.content}</p>
    </div>
  ));
  return (
    <div>
      {sidebar}
      <hr />
      {content}
    </div>
  );
}

let Blog = {
  make: Example$Blog,
};

let posts = [
  {
    id: "1",
    title: "Hello World",
    content: "Welcome to learning ReScript & React!",
  },
  {
    id: "2",
    title: "Installation",
    content: "You can install @rescript/react from npm.",
  },
];

let blog = <Blog.make posts={posts} />;

export { todos, items, Blog, posts, blog };

Rendering list Values

In case you ever want to render a list of items, you can do something like this:

<CodeTab labels={["ReScript", "JS Output", "JSX Preserved Output"]}>

@react.component
let make = () => {
  let todoList = list{
    {id: "todo1", text: "Todo 1"},
    {id: "todo2", text: "Todo 2"},
  }

  let items =
    todoList
    ->List.toArray
    ->Array.map(todo => {
      <li key={todo.id}> {React.string(todo.text)} </li>
    })

  <div> {React.array(items)} </div>
}
import * as Stdlib_List from "@rescript/runtime/lib/es6/Stdlib_List.js";
import * as JsxRuntime from "react/jsx-runtime";

let todos = [
  {
    id: "todo1",
    text: "Todo 1",
  },
  {
    id: "todo2",
    text: "Todo 2",
  },
];

let items = todos.map((todo) =>
  JsxRuntime.jsx(
    "li",
    {
      children: todo.text,
    },
    todo.id,
  ),
);

function Example(props) {
  let items = Stdlib_List.toArray({
    hd: {
      id: "todo1",
      text: "Todo 1",
    },
    tl: {
      hd: {
        id: "todo2",
        text: "Todo 2",
      },
      tl: /* [] */ 0,
    },
  }).map((todo) =>
    JsxRuntime.jsx(
      "li",
      {
        children: todo.text,
      },
      todo.id,
    ),
  );
  return JsxRuntime.jsx("div", {
    children: items,
  });
}

let make = Example;

export { todos, items, make };
import * as Stdlib_List from "@rescript/runtime/lib/es6/Stdlib_List.js";
import * as JsxRuntime from "react/jsx-runtime";

let todos = [
  {
    id: "todo1",
    text: "Todo 1",
  },
  {
    id: "todo2",
    text: "Todo 2",
  },
];

let items = todos.map((todo) => <li key={todo.id}>{todo.text}</li>);

function Example(props) {
  let items = Stdlib_List.toArray({
    hd: {
      id: "todo1",
      text: "Todo 1",
    },
    tl: {
      hd: {
        id: "todo2",
        text: "Todo 2",
      },
      tl: /* [] */ 0,
    },
  }).map((todo) => <li key={todo.id}>{todo.text}</li>);
  return <div>{items}</div>;
}

let make = Example;

export { todos, items, make };

We use List.toArray to convert our list to an array before creating our array<React.element>. Please note that using list has performance impact due to extra conversion costs.

99% of the time you'll want to use arrays (seamless interop, faster JS code), but in some cases it might make sense to use a list to leverage advanced pattern matching features etc.