npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@form-atoms/list-atom

v2.3.13

Published

<div align="center"> <img height="300" style="margin: 32px" src="./public/form-atoms-banner-transparent.png#gh-dark-mode-only"> <img width="180" style="margin: 32px" src="./public/form-atoms-field.svg#gh-light-mode-only"> <h1>@form-atoms/list-atom</

Readme

npm install @form-atoms/list-atom jotai-effect

Features

  • 🏎️ Performant rendering. Each list item is rendered with a stable key derived from Jotai atom.
  • 🎮 Easy controls. The <List.Item> provides add, remove, moveUp and moveDown actions for each item.
  • 🧩 Compound JSX Components. Use the <List.Add />, or <List.Empty /> instead of manual hooks.
  • 🔑 Scoped field names. The fields are dynamically indexed enabling serialization to FormData.
  • 🐢 Lists all the way down. Render lists within lists within lists... with the <List.Of>.

Quick start 🎨 Demo

import { formAtom, useForm, fieldAtom, InputField } from "form-atoms";
import { listAtom, createList } from "@form-atoms/list-atom";

const envVars = listAtom({
  name: "envVars",
  fields: () => ({
    variable: fieldAtom({ name: "variable", value: "" }),
    value: fieldAtom({ name: "value", value: "" }),
  }),
});

const form = formAtom({ envVars });

const { List } = createList(envVars);

export const Form = () => {
  const { submit } = useForm(form);

  return (
    <form onSubmit={submit(console.log)}>
      <List
        initialValue={[
          { variable: "PACKAGE_NAME", value: "form-atoms" },
          { variable: "APP_URL", value: "https://jotai.org" },
        ]}
      >
        <List.Item>
          {({ fields, remove }) => (
            <>
              <InputField atom={fields.variable} component="input" />
              <InputField atom={fields.value} component="input" />
              <button type="button" onClick={remove}>
                Remove
              </button>
            </>
          )}
        </List.Item>
        <List.Add>
          {({ add }) => (
            <button type="button" onClick={() => add()}>
              Add variable
            </button>
          )}
        </List.Add>
      </List>
    </form>
  );
};

Table of contents

| Atoms | Description | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | listAtom() | An atom that represents a list of form fields in a form. It manages state for the list, including the name, value, errors, dirty, validation and empty state. |

| Hooks | Description | | ------------------------------------- | -------------------------------------------------------------------------------------------------------------- | | useListActions() | A hook that returns a add, remove & move actions, that can be used to interact with the list atom state. | | useList() | A hook that returns the list items ready to be rendred together with the list actions. |

| Components | Description | Demo | | --------------------------------------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | | createList(listAtom) | A function to create components bound to the listAtom. | | | <List> | A component to initialize the listAtom via initialValue prop. | 🎨 | | <List.Add> | Adds new or initialized items to the list. | 🎨 | | <List.Empty> | Render children only when the list has no items. | 🎨 | | <List.Item> | Iterate and render each of the list items. | 🎨 |

| <List.Of> | Render a nested list within a <List.Item>. | 🎨 |

List atoms

listAtom()

An atom that represents a list of form fields in a form. It manages state for the list, including the name, value, errors, dirty, validation and empty state.

Arguments

| Name | Type | Required? | Description | | ------ | -------------------------------------------------- | --------- | ------------------------------------------------- | | config | ListAtomConfig<Fields, Value> | Yes | The initial state and configuration of the field. |

ListAtomConfig

export type ListAtomConfig<Fields extends FormFields, Value> = {
  /**
   * Optionally provide a name for the field that will be added
   * prefixed to inner fields
   * E.g. list name "contacts" and field name "email"
   * will have scoped name for the 4th item "contacts[3].email"
   */
  name?: string;
  /**
   * The initial array of values of the list
   */
  value: Value[];
  /**
   * A function to initialize the fields for each of the initial values.
   */
  fields: (value: Value) => Fields;
  /**
   * Error message the listAtom will have, when its items have nested errors.
   * It will be one of errors returned by the `useFieldErrors()` hook.
   */
  invalidItemError?: string;
  /**
   * A function that validates the value of the field any time
   * one of its atoms changes. It must either return an array of
   * string error messages or undefined. If it returns undefined,
   * the validation is "skipped" and the current errors in state
   * are retained.
   */
  validate?: (state: {
    /**
     * A Jotai getter that can read other atoms
     */
    get: Getter;
    /**
     * The current value of the field
     */
    value: Value;
    /**
     * The dirty state of the field
     */
    dirty: boolean;
    /**
     * The touched state of the field
     */
    touched: boolean;
    /**
     * The event that caused the validation. Either:
     *
     * - `"change"` - The value of the field has changed
     * - `"touch"` - The field has been touched
     * - `"blur"` - The field has been blurred
     * - `"submit"` - The form has been submitted
     * - `"user"` - A user/developer has triggered the validation
     */
    event: ValidateOn;
  }) => void | string[] | Promise<void | string[]>;
};

Returns

An extended FieldAtom:

export type ListAtom<Fields extends FormFields, Value> = ExtendFieldAtom<
  Value[],
  {
    /**
     * An atom indicating whether the list is empty.
     */
    empty: Atom<boolean>;
    /**
     * A splitAtom() instance from jotai/utils.
     * It handles adding, removing and moving of items in the list.
     * @internal
     */
    _splitList: WritableAtom<
      SplitListItem<Fields>[],
      [SplitAtomAction<ListItemForm<Fields>>],
      void
    >;
    /**
     * An atom holding the list of forms of each item.
     * @internal
     */
    _formList: WritableAtom<
      ListItemForm<Fields>[],
      [typeof RESET | SetStateAction<ListItemForm<Fields>[]>],
      void
    >;
    /**
     * An atom holding the fields of the internal formAtom of each item.
     * @internal
     */
    _formFields: Atom<Fields[]>;
    buildItem(): ListItemForm<Fields>;
  }
>;

⇗ Back to top

Hooks

useListActions()

A hook that returns a add, remove & move actions, that can be used to interact with the list atom state.

Arguments

| Name | Type | Required? | Description | | -------- | -------------------------------------------- | --------- | ----------------------------------------------------------------------------------- | | listAtom | ListAtom<Fields extends FormFields, Value> | Yes | The atom that stores the list's state | | options | UseAtomOptions | No | Options that are forwarded to the useAtom, useAtomValue, and useSetAtom hooks |

Returns

export type UseListActions<Fields extends FormFields, Value> = {
  /**
   * Removes the item from the list.
   *
   * @param item - An item from the listAtom's splitList array.
   */
  remove: (item: SplitListItem<Fields>) => void;
  /**
   * Appends a new item to the list by default, when no 'before' position is used.
   * Optionally the item can be initialized, with the 'fields' argument.
   *
   * @param before - An item from the listAtom's splitList array.
   * @param value - A custom list item value.
   * @returns The created ListItemForm<Fields>
   */
  add: (
    before?: SplitListItem<Fields> | undefined,
    value?: Value | undefined,
  ) => ListItemForm<Fields>;
  /**
   * Moves the item to the end of the list, or where specified when the 'before' is defined.
   *
   * @param item - A splitList item to be moved.
   * @param before - A splitList item before which to place the moved item.
   */
  move: (
    item: SplitListItem<Fields>,
    before?: SplitListItem<Fields> | undefined,
  ) => void;
};

⇗ Back to top

useList()

A hook that returns the list items ready to be rendred together with the list actions.

Arguments

| Name | Type | Required? | Description | | -------- | -------------------------------------------- | --------- | ----------------------------------------------------------------------------------- | | listAtom | ListAtom<Fields extends FormFields, Value> | Yes | The atom that stores the list's state | | options | UseAtomOptions | No | Options that are forwarded to the useAtom, useAtomValue, and useSetAtom hooks |

Returns

export type UseList<Fields extends FormFields, Value> = UseListActions<
  Fields,
  Value
> & {
  /**
   * Resolved value from the list.empty atom.
   */
  isEmpty: boolean;
  items: {
    /**
     * The item from the internal splitList.
     */
    item: SplitListItem<Fields>;
    /**
     * Stable React key prop derived from atom id.
     */
    key: string;
    /**
     * The form fields of the current item.
     */
    fields: Fields;
    /**
     * A function to remove the current item from the list.
     */
    remove: () => void;
    /**
     * A helper function to move the item to the previous index in the list.
     */
    moveUp: () => void;
    /**
     * A helper function to move the item to the next index in the list.
     */
    moveDown: () => void;
  };
};

⇗ Back to top

createList(listAtom)

Create a compound List components:

import { createList } from "@form-atoms/list-atom";

const { List } = createList(myListAtom);

// Usage:
// List.Add
// List.Empty
// List.Item
// List.Of

Returns

export type ListComponents<Fields extends FormFields> = {
  /**
   * A component to initialize the listAtom value.
   */
  List: FunctionComponent<ListProps<FormFieldValues<Fields>>> & {
    /**
     * A component to control adding new or initialized items to the list.
     */
    Add: FunctionComponent<AddButtonProps<Fields>>;
    /**
     * A component which renders children only when the list is empty.
     */
    Empty: FunctionComponent<EmptyProps>;
    /**
     * A component to iterate and render each of the list items.
     */
    Item: FunctionComponent<ItemProps<Fields>>;
    /**
     * A component to create these ListComponents for a nested listAtom within a <List.Item>
     */
    Of: <Fields extends FormFields>(props: ListOfProps<Fields>) => ReactNode;
  };
};

<List>

🎨 Storybook

Props

| Name | Type | Required? | Description | | ------------ | ----------- | --------- | -------------------------------------------------------------- | | children | ReactNode | Yes | A react nodes | | initialValue | Value[] | No | A value to initialize the listAtom | | store | AtomStore | No | A Jotai store |

<List.Add>

🎨 Storybook

Props

| Name | Type | Required? | Description | | -------- | ------------------------------------------ | --------- | ------------- | | children | (props: AddChildrenProps) => JSX.Element | No | A render prop |

Children Props

type AddChildrenProps<Fields extends FormFields> = {
  /**
   * An action to append a new item to the end of the list.
   * @param fields optionaly set the items initial value.
   * @returns The created ListItemForm<Fields>
   */
  add: (value?: FormFieldValues<Fields>) => ListItemForm<Fields>;
};

<List.Empty>

🎨 Storybook

Props

| Name | Type | Required? | Description | | -------- | ----------- | --------- | ---------------------------------------- | | children | ReactNode | No | Content to render when the list is empty |

<List.Item>

🎨 Storybook

Props

| Name | Type | Required? | Description | | -------- | --------------------------------------- | --------- | ------------- | | children | (props: ListItemProps) => JSX.Element | Yes | A render prop |

Children Props

type ListItemProps<Fields extends FormFields> = {
  /**
   * The fields of the current item, as created with the listAtom's `fields` config function.
   */
  fields: Fields;
  /**
   * The item from the internal splitList.
   */
  item: SplitListItem<Fields>;
  /**
   * The index of the current item.
   */
  index: number;
  /**
   * Total count of items in the list.
   */
  count: number;
  /**
   * Append a new item to the list.
   * When called with the current item, it will prepend it.
   * @returns The created ListItemForm<Fields>
   */
  add: (
    before?: SplitListItem<Fields>,
    value?: FormFieldValues<Fields>,
  ) => ListItemForm<Fields>;
  /**
   * Removes the current item from the list.
   */
  remove: () => void;
  /**
   * Moves the current item one slot up in the list.
   * Supports carousel - the first item will become the last.
   */
  moveUp: () => void;
  /**
   * Moves the current item one slot down in the list.
   * Supports carousel - the last item will become the first.
   */
  moveDown: () => void;
};

<List.Of>

🎨 Storybook

Props

| Name | Type | Required? | Description | | -------- | ------------------------------------ | --------- | ------------------------------------------------------------------ | | atom | ListAtom<Fields> | Yes | A listAtom for which to create nested compound components | | children | (props: Components) => JSX.Element | Yes | A render prop with a List compound component bound to the listAtom |

⇗ Back to top

Let's see what's in this listAtom

atom in atoms

⇗ Back to top

LICENSE

MIT