shadcn-ahooks

useDynamicList

A hook that helps you manage dynamic list and generate unique key for each item.

Overview

A hook that helps you manage dynamic list and generate unique key for each item.

Documentation and Examples

Installation

Open in
pnpm dlx shadcn@latest add https://shadcn-ahooks.vercel.app/r/useDynamicList.json
npx shadcn@latest add https://shadcn-ahooks.vercel.app/r/useDynamicList.json
yarn shadcn@latest add https://shadcn-ahooks.vercel.app/r/useDynamicList.json
bun shadcn@latest add https://shadcn-ahooks.vercel.app/r/useDynamicList.json

A hook that helps you manage dynamic list and generate unique key for each item.

Examples

Basic usage

import useDynamicList from '@/src/hooks/ahooks/useDynamicList';
import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons';
import { Button, Input, Space } from 'antd';
import React from 'react';


const Example = () => {
 const { list, remove, batchRemove, getKey, insert, replace } = useDynamicList(['David', 'Jack']);
  const listIndexes = list.map((item, index) => index);

  const Row = (index: number, item: any) => (
    <div key={getKey(index)} style={{ marginBottom: 16 }}>
      <Input
        style={{ width: 300 }}
        placeholder="Please enter name"
        onChange={(e) => replace(index, e.target.value)}
        value={item}
      />

      {list.length > 1 && (
        <MinusCircleOutlined
          style={{ marginLeft: 8 }}
          onClick={() => {
            remove(index);
          }}
        />
      )}
      <PlusCircleOutlined
        style={{ marginLeft: 8 }}
        onClick={() => {
          insert(index + 1, '');
        }}
      />
    </div>
  );

  return (
    <>
      {list.map((ele, index) => Row(index, ele))}

      <Space style={{ marginBottom: 16 }}>
        <Button
          danger
          disabled={list.length <= 1}
          onClick={() => batchRemove(listIndexes.filter((index) => index % 2 === 0))}
        >
          Remove odd items
        </Button>
        <Button
          danger
          disabled={list.length <= 1}
          onClick={() => batchRemove(listIndexes.filter((index) => index % 2 !== 0))}
        >
          Remove even items
        </Button>
      </Space>

      <div>{JSON.stringify([list])}</div>
    </>
  );
};

export default Example;

Using with antd Form

import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons';
import useDynamicList from '@/src/hooks/ahooks/useDynamicList';
import { Button, Form, Input } from 'antd';
import React, { useEffect, useState } from 'react';

const DynamicInputs = ({
  value = [],
  onChange,
}: {
  value?: string[];
  onChange?: (value: string[]) => void;
}) => {
  const { list, remove, getKey, insert, replace, resetList } = useDynamicList(value);

  useEffect(() => {
    // If value change manual, reset list
    if (value !== list) {
      resetList(value);
    }
  }, [value]);

  useEffect(() => {
    onChange?.(list);
  }, [list]);

  const Row = (index: number, item: any) => (
    <div key={getKey(index)} style={{ marginBottom: 16 }}>
      <Input
        style={{ width: 300 }}
        placeholder="Please enter name"
        onChange={(e) => replace(index, e.target.value)}
        value={item}
      />

      {list.length > 1 && (
        <MinusCircleOutlined
          style={{ marginLeft: 8 }}
          onClick={() => {
            remove(index);
          }}
        />
      )}
      <PlusCircleOutlined
        style={{ marginLeft: 8 }}
        onClick={() => {
          insert(index + 1, '');
        }}
      />
    </div>
  );

  return <>{list.map((ele, index) => Row(index, ele))}</>;
};

export default () => {
  const [form] = Form.useForm();

  const [result, setResult] = useState('');

  return (
    <>
      <Form form={form}>
        <Form.Item name="names" initialValue={['David', 'Jack']}>
          <DynamicInputs />
        </Form.Item>
      </Form>
      <Button
        type="primary"
        onClick={() =>
          form
            .validateFields()
            .then((val) => {
              setResult(JSON.stringify(val.names));
            })
            .catch(() => {})
        }
      >
        Submit
      </Button>
      <Button style={{ marginLeft: 16 }} onClick={() => form.resetFields()}>
        Reset
      </Button>

      <p>{result}</p>
    </>
  );
};

Another way of writing used in antd Form

import React, { useState } from 'react';
import { Form, Button, Input } from 'antd';
import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons';
import useDynamicList from '@/src/hooks/ahooks/useDynamicList';

export default () => {
  const { list, remove, getKey, insert, resetList, sortList } = useDynamicList(['David', 'Jack']);
  const [form] = Form.useForm();

  const [result, setResult] = useState('');

  const Row = (index: number, item: any) => (
    <div style={{ display: 'flex' }} key={getKey(index)}>
      <div>
        <Form.Item
          rules={[{ required: true, message: 'required' }]}
          name={['names', getKey(index)]}
          initialValue={item}
        >
          <Input style={{ width: 300 }} placeholder="Please enter your name" />
        </Form.Item>
      </div>
      <div style={{ marginTop: 4 }}>
        {list.length > 1 && (
          <MinusCircleOutlined
            style={{ marginLeft: 8 }}
            onClick={() => {
              remove(index);
            }}
          />
        )}
        <PlusCircleOutlined
          style={{ marginLeft: 8 }}
          onClick={() => {
            insert(index + 1, '');
          }}
        />
      </div>
    </div>
  );

  return (
    <>
      <Form form={form}>{list.map((ele, index) => Row(index, ele))}</Form>
      <Button
        type="primary"
        onClick={() =>
          form
            .validateFields()
            .then((val) => {
              const sortedResult = sortList(val.names);
              setResult(JSON.stringify(sortedResult, null, 2));
            })
            .catch(() => {})
        }
      >
        Submit
      </Button>
      <Button style={{ marginLeft: 16 }} onClick={() => resetList(['David', 'Jack'])}>
        Reset
      </Button>

      <div>{result}</div>
    </>
  );
};

Draggable dynamic table

import { DragOutlined } from '@ant-design/icons';
import { Button, Form, Input, Table } from 'antd';
import React, { useState } from 'react';
import ReactDragListView from 'react-drag-listview';
import useDynamicList from '@/src/hooks/ahooks/useDynamicList';

interface Item {
  name?: string;
  age?: string;
  memo?: string;
}

export default () => {
  const { list, remove, getKey, move, push, sortList } = useDynamicList<Item>([
    { name: 'my bro', age: '23', memo: "he's my bro" },
    { name: 'my sis', age: '21', memo: "she's my sis" },
    {},
  ]);

  const [form] = Form.useForm();

  const [result, setResult] = useState('');

  const columns = [
    {
      title: 'Name',
      dataIndex: 'name',
      key: 'name',
      render: (text: string, row: Item, index: number) => (
        <>
          <DragOutlined style={{ cursor: 'move', marginRight: 8 }} />
          <Form.Item name={['params', getKey(index), 'name']} initialValue={text} noStyle>
            <Input style={{ width: 120, marginRight: 16 }} placeholder="name" />
          </Form.Item>
        </>
      ),
    },
    {
      title: 'Age',
      dataIndex: 'age',
      key: 'age',
      render: (text: string, row: Item, index: number) => (
        <Form.Item name={['params', getKey(index), 'age']} initialValue={text} noStyle>
          <Input style={{ width: 120, marginRight: 16 }} placeholder="age" />
        </Form.Item>
      ),
    },
    {
      key: 'memo',
      title: 'Memo',
      dataIndex: 'memo',
      render: (text: string, row: Item, index: number) => (
        <>
          <Form.Item name={['params', getKey(index), 'memo']} initialValue={text} noStyle>
            <Input style={{ width: 300, marginRight: 16 }} placeholder="please input the memo" />
          </Form.Item>
          <Button.Group>
            <Button danger onClick={() => remove(index)}>
              Delete
            </Button>
          </Button.Group>
        </>
      ),
    },
  ];

  return (
    <div>
      <Form form={form}>
        <ReactDragListView
          onDragEnd={(oldIndex: number, newIndex: number) => move(oldIndex, newIndex)}
          handleSelector={'span[aria-label="drag"]'}
        >
          <Table
            columns={columns}
            dataSource={list}
            rowKey={(r: Item, index: number) => getKey(index).toString()}
            pagination={false}
            style={{ overflow: 'auto' }}
          />
        </ReactDragListView>
      </Form>
      <Button
        style={{ marginTop: 8 }}
        block
        type="dashed"
        onClick={() => push({ name: 'new row', age: '25' })}
      >
        + Add row
      </Button>
      <Button
        type="primary"
        style={{ marginTop: 16 }}
        onClick={() => {
          form
            .validateFields()
            .then((val) => {
              console.log(val, val.params);
              const sortedResult = sortList(val.params);
              setResult(JSON.stringify(sortedResult, null, 2));
            })
            .catch(() => {});
        }}
      >
        Submit
      </Button>
      <div style={{ whiteSpace: 'pre' }}>{result && `content: ${result}`}</div>
    </div>
  );
};

API

const result: Result = useDynamicList(initialValue: T[]);

Result

PropertyDescriptionTypeRemarks
listCurrent listT[]-
resetListReset list current data(list: T[]) => void-
insertAdd item at specific position(index: number, item: T) => void-
mergeMerge items into specific position(index: number, items: T[]) => void-
replaceReplace item at specific position(index: number, item: T) => void-
removeDelete specific item(index: number) => void-
moveMove item from old index to new index(oldIndex: number, newIndex: number) => void-
getKeyGet the uuid of specific item(index: number) => number-
getIndexRetrieve index from uuid(key: number) => number-
sortListSort the form data(using with antd form)(list: T[]) => T[]seeAnother way of writing used in antd Form
pushPush new item at the end of list(item: T) => void-
popRemove the last item from the list() => void-
unshiftAdd new item at the front of the list(item: T) => void-
shiftRemove the first item from the list() => void-

Params

PropertyDescriptionTypeDefault
initialValueInitial value of the listT[][]

On this page