How to create an enhanced Transfer List in Material UI?


In this article, we are going to see a step-by-step guide to create an enhanced transfer list in React MUI.

A transfer list is a type of list that allows the user to move one or more list items to another list. Here, if there are multiple items in the first list and the user wants to transfer some items to the second list, then we use the transfer list component. In React MUI, there is no specific component for Transfer List; rather, we create it on our own.

There is also a concept of an enhanced list in Material UI, which is just an advanced or an enhanced version of the basic transfer list. We can include various things in an enhanced list, like checkboxes, applying custom colors, etc.

Steps to Create an Enhanced Transfer List

Below are the steps for creating an enhanced transfer list in Material UI −

Step 1: Create a React Application

The very first step to create a transfer list in MUI is to create a react application. To create a new React app, run the below commands in your terminal −

npx create react app formcontrolproject

Once the project is created, navigate to its directory by running −

cd formcontrolproject

Step 2: Add MUI to React

Once you have created the react app, it's time to install the Material UI into the react application. To install MUI, run the following command −

npm install @mui/material @emotion/react @emotion/styled

Step 3: Define Items

Before we create the transfer list, we must define the list items with some values to the right and left. Below is the syntax for defining the items −

const [lftItems, setLftItems] = useState([1, 2, 3 ]);
const [rytItems, setRytItems] = useState([4, 5, 6]);
const [chosenLftItems, setChosenLftItems] = useState([]);
const [chosenRytItems, setChosenRytItems] = useState([]);

Step 4: Create Transfer Buttons

To transfer the list itesm, we must define the transfer buttons with handling functions. Below are how the four different buttons are created in React −

const handlePush = () => {
   …
};

const handlePull = () => {
   …
};

const selectLftItem = (i) => {
   …
};

const selectRightItem = (i) => {
   …
};

Step 5: Render the List items in Main

Finally, once we have defined all the handling functions to transfer the list, we now have to render the list items using Stack in App.js component. Below is the syntax for how to render the list items −

function App() {
   return (
      <Stack>
         <Stack item sx={{ gap: 2 }}>
            {lftItems.map((item, index) => (
               <ListItem key={index}>
                  …
               </ListItem>
            ))}
         </Stack>
         <Stack item>
            <Stack>
               <Button>
                  >
               </Button>
               <Button>
                  <
               </Button>
            </Stack>
         </Stack>
         <Stack sx={{ gap: 2 }}>
            {rytItems.map((item, index) => (
               <ListItem key={index}>
                  …
               </ListItem>
            ))}
         </Stack>
      </Stack>
   )
}

export default App;

That's all! Now we have successfully learned the steps to create the enhanced transfer list in MUI. So, let's see some examples of different approaches.

Example

In this example, we have created a custom enhanced transfer list in which, when the user selects any item to transfer, the checkbox gets enabled. Here, the user is also allowed to select all list items at once to transfer to other lists.

import React from "react";
import { useState } from "react";
import { Stack } from "@mui/material";
import {List, ListItem, ListItemText, ListItemIcon} from '@mui/material';
import Checkbox from '@mui/material/Checkbox';
import Button from '@mui/material/Button';
import {Card, CardHeader} from '@mui/material/';

function not(a, b) {
   return a.filter((value) => b.indexOf(value) === -1);
}

function intersection(a, b) {
   return a.filter((value) => b.indexOf(value) !== -1);
}

function union(a, b) {
   return [...a, ...not(b, a)];
}

const App = () => {
   const [chk, setChk] = useState([]);
   const [lftItems, setLftItems] = useState(["Item 11",
      "Item 12",
      "Item 13",
      "Item 14",
      "Item 15"]);
   const [rightItems, setRightItems] = useState(["Item 21",
      "Item 22",
      "Item 23",
      "Item 24",
      "Item 25"]);

   const chkLeftChecked = intersection(chk, lftItems);
   const chkRightChecked = intersection(chk, rightItems);

   const handleSingleToggle = (val) => () => {
      const currentIdx = chk.indexOf(val);
      const latestChecked = [...chk];

      if (currentIdx === -1) {
         latestChecked.push(val);
      } else {
         latestChecked.splice(currentIdx, 1);
      }

      setChk(latestChecked);
   };

   const chkNo = (items) => intersection(chk, items).length;

   const handleMultipleToggle = (i) => () => {
      if (chkNo(i) === i.length) {
         setChk(not(chk, i));
      } else {
         setChk(union(chk, i));
      }
   };

   const pushRight = () => {
      setRightItems(rightItems.concat(chkLeftChecked));
      setLftItems(not(lftItems, chkLeftChecked));
      setChk(not(chk, chkLeftChecked));
   };

   const pushLeft = () => {
      setLftItems(lftItems.concat(chkRightChecked));
      setRightItems(not(rightItems, chkRightChecked));
      setChk(not(chk, chkRightChecked));
   };

   const ListComponent = (listItemName, ListItems) => (
      <Card sx={{ p: 3 }}>
         <CardHeader
            sx={{ p: 2 }}
            avatar={
               <Checkbox
                  onClick={handleMultipleToggle(ListItems)}
                  checked={chkNo(ListItems) === ListItems.length && ListItems.length !== 0}
                  indeterminate={
                     chkNo(ListItems) !== ListItems.length && chkNo(ListItems) !== 0
                  }
                  disabled={ListItems.length === 0}
               />
            }
            title={listItemName}
            subheader={`${chkNo(ListItems)}/${ListItems.length} selected`}
         />
         <hr />
         <List sx={{overflow: 'auto',}}>
            {ListItems.map((value) => {
               const labelId = `transfer-list-all-item-${value}-label`;
               return (
                  <ListItem
                     key={value}
                     onClick={handleSingleToggle(value)}
                     >
                     <ListItemIcon>
                        <Checkbox
                           checked={chk.indexOf(value) !== -1}
                           tabIndex={-1}
                           disableRipple
                        />
                     </ListItemIcon>
                     <ListItemText id={labelId} primary={value} />
                  </ListItem>
               );
            })}
         </List>
      </Card>
   );

   return (
      <div style={{
            padding: 40,
            display: 'flex',
            flexDirection: 'column',
            gap: 20,
            backgroundColor: 'lightcyan'
         }}>
         <Stack direction="row" container spacing={5}>
            <Stack item>{ListComponent('Select from below', lftItems)}</Stack>
            <Stack item>
               <Stack container direction="column" sx={{ gap: 5 }} alignItems="center">
                  <Button
                     variant="contained"
                     color="info"
                     onClick={pushRight}
                     disabled={chkLeftChecked.length === 0}
                     >
                     >
                  </Button>
                  <Button
                     variant="contained"
                     color="info"
                     onClick={pushLeft}
                     disabled={chkRightChecked.length === 0}
                     >
                     <
                  </Button>
               </Stack>
            </Stack>
            <Stack item>{ListComponent('Selected', rightItems)}</Stack>
         </Stack>
      </div>
   );
};

export default App;

Output

Example

In this example, we have created a custom enhanced transfer list in which, when the user selects any item to transfer, the checkbox gets enabled. Here, the list items are customized using different colors.

import React, { useState } from "react";
import { Stack, Checkbox, ListItem, ListItemText, Button } from "@mui/material";

const App = () => {
   const [lftItems, setLftItems] = useState([1, 2, 3]);
   const [rytItems, setRytItems] = useState([4, 5, 6]);
   const [chosenLftItems, setChosenLftItems] = useState([]);
   const [chosenRytItems, setChosenRytItems] = useState([]);

   const handlePush = () => {
      setRytItems((before) => [...before, ...chosenLftItems]);
      setLftItems((prevItems) =>
         prevItems.filter((_, index) => !chosenLftItems.includes(index))
      );
      setChosenLftItems([]);
   };

   const handlePull = () => {
      setLftItems((before) => [...before, ...chosenRytItems]);
      setRytItems((prevItems) =>
         prevItems.filter((_, index) => !chosenRytItems.includes(index))
      );
      setChosenRytItems([]);
   };

   const selectLftItem = (i) => {
      if (chosenLftItems.includes(i)) {
         setChosenLftItems((beforeSelected) =>
            beforeSelected.filter((item) => item !== i)
         );
      } else {
         setChosenLftItems((beforeSelected) => [...beforeSelected, i]);
      }
   };

   const selectRightItem = (i) => {
      if (chosenRytItems.includes(i)) {
         setChosenRytItems((beforeSelected) =>
            beforeSelected.filter((item) => item !== i)
         );
      } else {
         setChosenRytItems((beforeSelected) => [...beforeSelected, i]);
      }
   };

   return (
      <div
         style={{
            padding: 40,
            display: "flex",
            flexDirection: "column",
            gap: 20,
            backgroundColor: "lightcyan"
         }}>
         <Stack direction="row" container spacing={5}>
            <Stack sx={{ gap: 2 }}>
               {lftItems.map((item, index) => (
                  <ListItem
                     key={index}
                     onClick={() => selectLftItem(index)}
                     sx={{
                        backgroundColor: "lightblue",
                        borderRadius: 1,
                        cursor: "pointer"
                     }}>
                     <Checkbox
                        checked={chosenLftItems.includes(index)}
                        color="primary"
                     />
                     <ListItemText primary={item} />
                  </ListItem>
               ))}
            </Stack>
            <Stack item>
               <Stack
                  container
                  justifyContent="center"
                  direction="column"
                  sx={{ gap: 3 }}
                  alignItems="center"
                  >
                  <Button
                     variant="contained"
                     color="info"
                     onClick={handlePush}
                     disabled={chosenLftItems.length === 0}
                     >
                     >
                  </Button>
                  <Button
                     variant="contained"
                     color="info"
                     onClick={handlePull}
                     disabled={chosenRytItems.length === 0}
                     >
                     <
                  </Button>
               </Stack>
            </Stack>
            <Stack sx={{ gap: 2 }}>
               {rytItems.map((item, index) => (
                  <ListItem
                     key={index}
                     onClick={() => selectRightItem(index)}
                     sx={{
                        backgroundColor: "lightblue",
                        borderRadius: 1,
                        cursor: "pointer"
                     }}
                     >
                     <Checkbox
                        checked={chosenRytItems.includes(index)}
                        color="primary"
                     />
                     <ListItemText primary={item} />
                  </ListItem>
               ))}
            </Stack>
         </Stack>
      </div>
   );
};

export default App;

Output

Conclusion

This article discusses the complete details of creating the enhanced transfer list in React MUI. In this article, we have learned the complete steps to create the enhanced transfer list, along with different examples with different approaches.

Updated on: 01-Nov-2023

225 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements