Categories
Material UI

Material UI — Customize Tables

Material UI is a Material Design library made for React.

It’s a set of React components that have Material Design styles.

In this article, we’ll look at how to add tables with Material UI.

Customized Tables

We can customize the styling of table components with the withStyles function.

For example, we can write:

import React from "react";
import { withStyles } from "@material-ui/core/styles";
import Table from "@material-ui/core/Table";
import TableBody from "@material-ui/core/TableBody";
import TableCell from "@material-ui/core/TableCell";
import TableContainer from "@material-ui/core/TableContainer";
import TableHead from "@material-ui/core/TableHead";
import TableRow from "@material-ui/core/TableRow";
import Paper from "@material-ui/core/Paper";

const StyledTableCell = withStyles(theme => ({
  head: {
    backgroundColor: theme.palette.common.black,
    color: theme.palette.common.white
  },
  body: {
    fontSize: 12
  }
}))(TableCell);

const StyledTableRow = withStyles(theme => ({
  root: {
    "&:nth-of-type(odd)": {
      backgroundColor: theme.palette.action.hover
    }
  }
}))(TableRow);

function createData(name, age) {
  return { name, age };
}

const rows = [createData("james", 15), createData("mary", 23)];

export default function App() {
  return (
    <TableContainer component={Paper}>
      <Table>
        <TableHead>
          <TableRow>
            <StyledTableCell>name</StyledTableCell>
            <StyledTableCell align="right">age</StyledTableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {rows.map(row => (
            <StyledTableRow key={row.name}>
              <StyledTableCell component="th" scope="row">
                {row.name}
              </StyledTableCell>
              <StyledTableCell align="right">{row.age}</StyledTableCell>
            </StyledTableRow>
          ))}
        </TableBody>
      </Table>
    </TableContainer>
  );
}

We created our own table cell and table row components.

All we did is pass all the styles into the withStyles function.

Then we put them all in the App component.

Fixed Header

We can make the header fixed with the stickyHeader prop.

For example, we can write:

import React from "react";
import Table from "@material-ui/core/Table";
import TableBody from "@material-ui/core/TableBody";
import TableCell from "@material-ui/core/TableCell";
import TableContainer from "@material-ui/core/TableContainer";
import TableHead from "@material-ui/core/TableHead";
import TableRow from "@material-ui/core/TableRow";
import Paper from "@material-ui/core/Paper";
import { makeStyles } from "@material-ui/core/styles";

const useStyles = makeStyles({
  container: {
    maxHeight: 200
  }
});

function createData(name, age) {
  return { name, age };
}

const rows = [
  createData("james", 15),
  createData("mary", 15),
  createData("alex", 15),
  createData("may", 15),
  createData("sam", 15),
  createData("john", 15),
  createData("mary", 23)
];

export default function App() {
  const classes = useStyles();

  return (
    <TableContainer component={Paper} className={classes.container}>
      <Table stickyHeader>
        <TableHead>
          <TableRow>
            <TableCell>name</TableCell>
            <TableCell align="right">age</TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {rows.map(row => (
            <TableRow key={row.name}>
              <TableCell component="th" scope="row">
                {row.name}
              </TableCell>
              <TableCell align="right">{row.age}</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </TableContainer>
  );
}

We have the makeStyles function to style the table with the height.

This way, we get a sticky header.

Then we create the rows for the table.

And then to make the table sticky we have the stickyHeader prop for the Table .

Now when we scroll the content, we’ll see the header stay on top.

Collapsible Table

We can make a table with a collapsible column table with the TableRow component.

Inside it, we add our TableCell with the Collapse component inside it.

To do that, we can write:

import React from "react";
import PropTypes from "prop-types";
import { makeStyles } from "@material-ui/core/styles";
import Box from "@material-ui/core/Box";
import Collapse from "@material-ui/core/Collapse";
import IconButton from "@material-ui/core/IconButton";
import Table from "@material-ui/core/Table";
import TableBody from "@material-ui/core/TableBody";
import TableCell from "@material-ui/core/TableCell";
import TableContainer from "@material-ui/core/TableContainer";
import TableHead from "@material-ui/core/TableHead";
import TableRow from "@material-ui/core/TableRow";
import Typography from "@material-ui/core/Typography";
import Paper from "@material-ui/core/Paper";
import KeyboardArrowDownIcon from "@material-ui/icons/KeyboardArrowDown";
import KeyboardArrowUpIcon from "@material-ui/icons/KeyboardArrowUp";

function createData(name, age, history) {
  return { name, age, history };
}

const rows = [createData("james", 15, [{ date: "2020-01-01" }])];

function Row(props) {
  const { row } = props;
  const [open, setOpen] = React.useState(false);

  return (
    <React.Fragment>
      <TableRow>
        <TableCell>
          <IconButton
            aria-label="expand row"
            size="small"
            onClick={() => setOpen(!open)}
          >
            {open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
          </IconButton>
        </TableCell>
        <TableCell component="th" scope="row">
          {row.name}
        </TableCell>
        <TableCell>{row.age}</TableCell>
      </TableRow>
      <TableRow>
        <TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}>
          <Collapse in={open} timeout="auto" unmountOnExit>
            <Box margin={1}>
              <Typography variant="h6" gutterBottom component="div">
                History
              </Typography>
              <Table size="small">
                <TableHead>
                  <TableRow>
                    <TableCell>Date</TableCell>
                  </TableRow>
                </TableHead>
                <TableBody>
                  {row.history.map(historyRow => (
                    <TableRow key={historyRow.date}>
                      <TableCell component="th" scope="row">
                        {historyRow.date}
                      </TableCell>
                    </TableRow>
                  ))}
                </TableBody>
              </Table>
            </Box>
          </Collapse>
        </TableCell>
      </TableRow>
    </React.Fragment>
  );
}

export default function App() {
  return (
    <TableContainer component={Paper}>
      <Table>
        <TableHead>
          <TableRow>
            <TableCell />
            <TableCell>name</TableCell>
            <TableCell>age</TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {rows.map(row => (
            <Row key={row.name} row={row} />
          ))}
        </TableBody>
      </Table>
    </TableContainer>
  );
}

We created a Row component and then TableCell .

Inside it, we have the Collapse component that we can click to toggle a table on or off.

And inside that, we have a Table with more stuff inside it.

Conclusion

We can add table components with customized styles.

Also, we have can make a table with another table nested inside that we can expand or collapse.

Categories
Material UI

Material UI — Chips and Dividers

Material UI is a Material Design library made for React.

It’s a set of React components that have Material Design styles.

In this article, we’ll look at how to customize chips and add dividers with Material UI.

Small Chip

We can add the size prop to change the size of the chip.

For example, we can write:

import React from "react";
import Chip from "@material-ui/core/Chip";

export default function App() {
  return (
    <div>
      <Chip size="small" label="Basic" />
    </div>
  );
}

to make a small basic chip.

Outlined Variant

We can make the chip outlined by setting the variant prop to outlined .

For example, we can write:

import React from "react";
import Chip from "@material-ui/core/Chip";

export default function App() {
  return (
    <div>
      <Chip variant="outlined" label="Basic" />
    </div>
  );
}

Avatars

We can add avatars to chips.

For example, we can write:

import React from "react";
import Chip from "@material-ui/core/Chip";
import Avatar from "@material-ui/core/Avatar";

export default function App() {
  return (
    <div>
      <Chip
        label="cat"
        avatar={<Avatar alt="cat" src="[http://placekitten.com/200/200](http://placekitten.com/200/200)" />}
      />
    </div>
  );
}

We set the label to display with the label prop.

avatar has the avatar we want to show on the left.

Icons

We can display an icon besides the chip label.

To do that, we set an icon component as the value of the icon prop.

For example, we can write:

import React from "react";
import Chip from "@material-ui/core/Chip";
import FaceIcon from "@material-ui/icons/Face";

export default function App() {
  return (
    <div>
      <Chip label="person" icon={<FaceIcon />} />
    </div>
  );
}

We set the icon to the FaceIcon to display it to the left of the label.

The delete icon can be changed with the deleteIcon label.

For example, we can write:

import React from "react";
import Chip from "@material-ui/core/Chip";
import DoneIcon from "@material-ui/icons/Done";

export default function App() {
  const handleDelete = () => {
    console.info("delete icon clicked");
  };

  return (
    <div>
      <Chip label="task" onDelete={handleDelete} deleteIcon={<DoneIcon />} />
    </div>
  );
}

to add a delete icon of our choice.

We have the onDelete prop to run a function to do something when it’s clicked.

Also, we have the deleteIcon to show the delete icon that we set.

We can also make a chip clickable by passing in an onClick prop.

For example, we can write:

import React from "react";
import Chip from "@material-ui/core/Chip";

export default function App() {
  const handleClick = () => {
    console.info("d clicked");
  };

  return (
    <div>
      <Chip label="task" onClick={handleClick} />
    </div>
  );
}

We add the onClick prop and pass in a function to it to handle clicks.

Divider

Dividers lets us add a thin line to group items.

We can add dividers to lists.

For example, we can write:

import React from "react";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
import ListItemText from "@material-ui/core/ListItemText";
import Divider from "@material-ui/core/Divider";

const fruits = [
  { key: 1, name: "apple" },
  { key: 2, name: "orange" },
  { key: 3, name: "grape" }
];

export default function App() {
  return (
    <div>
      <List component="nav">
        {fruits.map(f => [
          <ListItem button>
            <ListItemText primary={f.name} key={f.key} />
          </ListItem>,
          <Divider />
        ])}
      </List>
    </div>
  );
}

to add a divider below each ListItem .

Inset Dividers

We can add dividers that don’t take up the full width of the container.

For example, we can write:

import React from "react";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
import ListItemText from "@material-ui/core/ListItemText";
import Divider from "@material-ui/core/Divider";
import ListItemAvatar from "@material-ui/core/ListItemAvatar";
import Avatar from "@material-ui/core/Avatar";
import ImageIcon from "@material-ui/icons/Image";

const fruits = [
  { key: 1, name: "apple" },
  { key: 2, name: "orange" },
  { key: 3, name: "grape" }
];

export default function App() {
  return (
    <div>
      <List component="nav">
        {fruits.map(f => [
          <ListItem>
            <ListItemAvatar>
              <Avatar>
                <ImageIcon />
              </Avatar>
            </ListItemAvatar>
            <ListItemText primary={f.name} key={f.key} secondary={f.name} />
          </ListItem>,
          <Divider variant="inset" component="li" />
        ])}
      </List>
    </div>
  );
}

to add the list items with an avatar.

To the right of it, we have the list item text.

Then after that, we have the Divider component.

The variant is set to inset to make it display below the text only.

component is set to li to display it as an li element.

Conclusion

We can add chips to display small pieces of text, images, and icons.

Also, we can show dividers to separate items.

Categories
Material UI

Material UI — Tabs

Material UI is a Material Design library made for React.

It’s a set of React components that have Material Design styles.

In this article, we’ll look at how to add tabs with Material UI.

Tabs

We can add tabs with the Tabs component.

For example, we can write:

import React from "react";
import AppBar from "@material-ui/core/AppBar";
import Tabs from "@material-ui/core/Tabs";
import Tab from "@material-ui/core/Tab";
import Box from "@material-ui/core/Box";

function TabPanel(props) {
  const { children, value, index, ...other } = props;

  return (
    <div {...other}>
      {value === index && <Box p={3}>{children}</Box>}
    </div>
  );
}

export default function App() {
  const [value, setValue] = React.useState(0);

  const handleChange = (event, newValue) => {
    setValue(newValue);
  };

  return (
    <>
      <AppBar position="static">
        <Tabs value={value} onChange={handleChange}>
          <Tab label="Item One" />
          <Tab label="Item Two" />
          <Tab label="Item Three" />
        </Tabs>
      </AppBar>
      <TabPanel value={value} index={0}>
        Item One
      </TabPanel>
      <TabPanel value={value} index={1}>
        Item Two
      </TabPanel>
      <TabPanel value={value} index={2}>
        Item Three
      </TabPanel>
    </>
  );
}

We create our own TabPanel to show the content that belongs to the given tab.

We pass in the value to the value prop and compare that with the index prop to check what to display.

In the TabPanel component, we compare value and index to see if they’re the same.

If they’re, then we display the content.

Otherwise, we hide it.

label has the text for the tab.

We’ll update the value with the newValue , which is the index of the tab.

Wrapped Labels

If we have long labels, then it may be cut off if it’s too long.

To make it wrap, we can add the wrapped prop.

For example, we can write:

import React from "react";
import AppBar from "@material-ui/core/AppBar";
import Tabs from "@material-ui/core/Tabs";
import Tab from "@material-ui/core/Tab";
import Box from "@material-ui/core/Box";

function TabPanel(props) {
  const { children, value, index, ...other } = props;

  return <div {...other}>{value === index && <Box p={3}>{children}</Box>}</div>;
}

export default function App() {
  const [value, setValue] = React.useState(0);

  const handleChange = (event, newValue) => {
    setValue(newValue);
  };

  return (
    <>
      <AppBar position="static">
        <Tabs value={value} onChange={handleChange}>
          <Tab
            label="Lorem ipsum dolor sit amet, consectetur adipiscing elit."
            wrapped
          />
          <Tab label="Item Two" />
          <Tab label="Item Three" />
        </Tabs>
      </AppBar>
      <TabPanel value={value} index={0}>
        Item One
      </TabPanel>
      <TabPanel value={value} index={1}>
        Item Two
      </TabPanel>
      <TabPanel value={value} index={2}>
        Item Three
      </TabPanel>
    </>
  );
}

The first Tab has a long label value.

Since we added the wrapped prop, it’ll be wrapped.

Disabled Tab

To disable a tab, we can add the disabled prop.

For instance, we can write:

import React from "react";
import AppBar from "@material-ui/core/AppBar";
import Tabs from "@material-ui/core/Tabs";
import Tab from "@material-ui/core/Tab";
import Box from "@material-ui/core/Box";

function TabPanel(props) {
  const { children, value, index, ...other } = props;

  return <div {...other}>{value === index && <Box p={3}>{children}</Box>}</div>;
}

export default function App() {
  const [value, setValue] = React.useState(0);

  const handleChange = (event, newValue) => {
    setValue(newValue);
  };

  return (
    <>
      <AppBar position="static">
        <Tabs value={value} onChange={handleChange}>
          <Tab label="Item One" />
          <Tab label="Item Two" />
          <Tab label="Item Three" disabled />
        </Tabs>
      </AppBar>
      <TabPanel value={value} index={0}>
        Item One
      </TabPanel>
      <TabPanel value={value} index={1}>
        Item Two
      </TabPanel>
      <TabPanel value={value} index={2}>
        Item Three
      </TabPanel>
    </>
  );
}

Then we can disable the 3rd tab.

Full-Width Tabs

We can make tabs full width with the variant prop set to fullWidth .

To do that, we write:

import React from "react";
import AppBar from "@material-ui/core/AppBar";
import Tabs from "@material-ui/core/Tabs";
import Tab from "@material-ui/core/Tab";
import Box from "@material-ui/core/Box";

function TabPanel(props) {
  const { children, value, index, ...other } = props;

  return <div {...other}>{value === index && <Box p={3}>{children}</Box>}</div>;
}

export default function App() {
  const [value, setValue] = React.useState(0);

  const handleChange = (event, newValue) => {
    setValue(newValue);
  };

  return (
    <>
      <AppBar position="static">
        <Tabs value={value} onChange={handleChange} variant="fullWidth">
          <Tab label="Item One" />
          <Tab label="Item Two" />
        </Tabs>
      </AppBar>
      <TabPanel value={value} index={0}>
        Item One
      </TabPanel>
      <TabPanel value={value} index={1}>
        Item Two
      </TabPanel>
    </>
  );
}

to make all tabs fill the space evenly.

Together, they take up the full width of the screen.

Conclusion

We can add tabs with the Tabs component.

The content can be added to any element.

We can determine what to display by check the index of our content against the value that we set when clicking the tabs.

Categories
Material UI

Material UI — Steppers

Material UI is a Material Design library made for React.

It’s a set of React components that have Material Design styles.

In this article, we’ll look at how to add steppers with Material UI.

Stepper

A stepper lets us show the steps that a user has to go through to complete a process.

To use it, we can use the Stepper component add the stepper:

import React from "react";
import Stepper from "@material-ui/core/Stepper";
import Step from "@material-ui/core/Step";
import StepLabel from "@material-ui/core/StepLabel";
import Button from "@material-ui/core/Button";

function getSteps() {
  return ["step 1", "step 2", "step 3"];
}

function getStepContent(step) {
  switch (step) {
    case 0:
      return "do step 1";
    case 1:
      return "do step 2";
    case 2:
      return "do steo 3";
    default:
      return "unknown step";
  }
}

export default function App() {
  const [activeStep, setActiveStep] = React.useState(0);
  const [skipped, setSkipped] = React.useState(new Set());
  const steps = getSteps();

  const isStepOptional = step => {
    return step === 1;
  };

  const isStepSkipped = step => {
    return skipped.has(step);
  };

  const handleNext = () => {
    let newSkipped = skipped;
    if (isStepSkipped(activeStep)) {
      newSkipped = new Set(newSkipped.values());
      newSkipped.delete(activeStep);
    }

  setActiveStep(prevActiveStep => prevActiveStep + 1);
    setSkipped(newSkipped);
  };

  const handleBack = () => {
    setActiveStep(prevActiveStep => prevActiveStep - 1);
  };

  const handleSkip = () => {
    if (!isStepOptional(activeStep)) {
      throw new Error("You can't skip a step that isn't optional.");
    }

  setActiveStep(prevActiveStep => prevActiveStep + 1);
    setSkipped(prevSkipped => {
      const newSkipped = new Set(prevSkipped.values());
      newSkipped.add(activeStep);
      return newSkipped;
    });
  };

  const handleReset = () => {
    setActiveStep(0);
  };

  return (
    <div>
      <Stepper activeStep={activeStep}>
        {steps.map((label, index) => {
          const stepProps = {};
          const labelProps = {};
          if (isStepOptional(index)) {
            labelProps.optional = "optional";
          }
          if (isStepSkipped(index)) {
            stepProps.completed = false;
          }
          return (
            <Step key={label} {...stepProps}>
              <StepLabel {...labelProps}>{label}</StepLabel>
            </Step>
          );
        })}
      </Stepper>
      <div>
        {activeStep === steps.length ? (
          <div>
            All steps completed
            <Button onClick={handleReset}>Reset</Button>
          </div>
        ) : (
          <div>
            {getStepContent(activeStep)}
            <div>
              <Button disabled={activeStep === 0} onClick={handleBack}>
                Back
              </Button>
              {isStepOptional(activeStep) && (
                <Button
                  variant="contained"
                  color="primary"
                  onClick={handleSkip}
                >
                  Skip
                </Button>
              )}

              <Button variant="contained" color="primary" onClick={handleNext}>
                {activeStep === steps.length - 1 ? "Finish" : "Next"}
              </Button>
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

We create the step headings with the getSteps function.

The content of each step is in the getStepContent function.

When we click the Next button, handleNext is called.

Then we add the steps that are added to the skipped state.

handleBack is run when the back button is clicked.

We just set the activeStep to the previous step.

The handleSkip function is called when the skip button is clicked.

We check if an active step is optional.

If it’s optional, then we let them skip it and add it to the skipped set.

In the JSX code, we use the Stepper component with the Step prop to add the steps.

Then we add the content for the step and the buttons below it.

The content is shown according to the activeStep value.

Alternative Label

We can change the step labels to what we want with the StepLabel component.

For instance, we can write:

import React from "react";
import Stepper from "@material-ui/core/Stepper";
import Step from "@material-ui/core/Step";
import StepLabel from "@material-ui/core/StepLabel";
import Button from "@material-ui/core/Button";

function getSteps() {
  return ["step 1", "step 2", "step 3"];
}

function getStepContent(step) {
  switch (step) {
    case 0:
      return "do step 1";
    case 1:
      return "do step 2";
    case 2:
      return "do steo 3";
    default:
      return "unknown step";
  }
}

export default function App() {
  const [activeStep, setActiveStep] = React.useState(0);
  const steps = getSteps();

  const handleNext = () => {
    setActiveStep(prevActiveStep => prevActiveStep + 1);
  };

  const handleBack = () => {
    setActiveStep(prevActiveStep => prevActiveStep - 1);
  };

  const handleReset = () => {
    setActiveStep(0);
  };

  return (
    <div>
      <Stepper activeStep={activeStep} alternativeLabel>
        {steps.map(label => (
          <Step key={label}>
            <StepLabel>{label}</StepLabel>
          </Step>
        ))}
      </Stepper>
      <div>
        {activeStep === steps.length ? (
          <div>
            All steps completed
            <Button onClick={handleReset}>Reset</Button>
          </div>
        ) : (
          <div>
            {getStepContent(activeStep)}

<div>
              <Button disabled={activeStep === 0} onClick={handleBack}>
                Back
              </Button>
              <Button variant="contained" color="primary" onClick={handleNext}>
                {activeStep === steps.length - 1 ? "Finish" : "Next"}
              </Button>
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

We added the alternativeLabel prop to let us override the label.

Then in the render children prop, we return our own Step component with the StepLabel to display the step number.

Conclusion

We can add a stepper with the Stepper component.

We can make steps optional and customize the labels.

The content can be displayed below it.

Categories
Material UI

Material UI — Stepper Customization

Material UI is a Material Design library made for React.

It’s a set of React components that have Material Design styles.

In this article, we’ll look at how to customize steppers with Material UI.

Non-Linear Steppers

We can add non-linear steppers to add a stepper that allows users to navigate to any step they want.

For example, we can write:

import React from "react";
import Stepper from "@material-ui/core/Stepper";
import Step from "@material-ui/core/Step";
import StepButton from "@material-ui/core/StepButton";
import Button from "@material-ui/core/Button";

function getSteps() {
  return ["step 1", "step 2", "step 3"];
}

function getStepContent(step) {
  switch (step) {
    case 0:
      return "do step 1";
    case 1:
      return "do step 2";
    case 2:
      return "do step 3";
    default:
      return "unknown step";
  }
}

export default function App() {
  const [activeStep, setActiveStep] = React.useState(0);
  const [completed, setCompleted] = React.useState({});
  const steps = getSteps();

  const totalSteps = () => {
    return steps.length;
  };

  const completedSteps = () => {
    return Object.keys(completed).length;
  };

  const isLastStep = () => {
    return activeStep === totalSteps() - 1;
  };

  const allStepsCompleted = () => {
    return completedSteps() === totalSteps();
  };

  const handleNext = () => {
    const newActiveStep =
      isLastStep() && !allStepsCompleted()
        ? steps.findIndex((step, i) => !(i in completed))
        : activeStep + 1;
    setActiveStep(newActiveStep);
  };

  const handleBack = () => {
    setActiveStep(prevActiveStep => prevActiveStep - 1);
  };

  const handleStep = step => () => {
    setActiveStep(step);
  };

  const handleComplete = () => {
    const newCompleted = completed;
    newCompleted[activeStep] = true;
    setCompleted(newCompleted);
    handleNext();
  };

  const handleReset = () => {
    setActiveStep(0);
    setCompleted({});
  };

return (
    <div>
      <Stepper nonLinear activeStep={activeStep}>
        {steps.map((label, index) => (
          <Step key={label}>
            <StepButton
              onClick={handleStep(index)}
              completed={completed[index]}
            >
              {label}
            </StepButton>
          </Step>
        ))}
      </Stepper>
      <div>
        {allStepsCompleted() ? (
          <div>
            All steps completed - you&apos;re finished
            <Button onClick={handleReset}>Reset</Button>
          </div>
        ) : (
          <div>
            {getStepContent(activeStep)}

            <div>
              <Button disabled={activeStep === 0} onClick={handleBack}>
                Back
              </Button>
              <Button variant="contained" color="primary" onClick={handleNext}>
                Next
              </Button>
              {activeStep !== steps.length &&
                (completed[activeStep] ? (
                  `Step {activeStep + 1} already completed`
                ) : (
                  <Button
                    variant="contained"
                    color="primary"
                    onClick={handleComplete}
                  >
                    {completedSteps() === totalSteps() - 1
                      ? "Finish"
                      : "Complete Step"}
                  </Button>
                ))}
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

We add the nonLinear prop to let users click on any step they want.

The Next button calls handleNext to go to the next step or go to the step that isn’t completed.

handleBack is run when the back button is clicked.

It sets the step to a previous step.

handleStep is run when we click on a step.

We also set the completed prop to indicate which step is completed.

handleReset is run when the reset button is clicked.

It sets the activeStep state to 0 and set the completed state to an empty object to clear the completed steps.

For example, we can write:

import React from "react";
import Stepper from "@material-ui/core/Stepper";
import Step from "@material-ui/core/Step";
import StepButton from "@material-ui/core/StepButton";
import Button from "@material-ui/core/Button";

function getSteps() {
  return ["step 1", "step 2", "step 3"];
}

function getStepContent(step) {
  switch (step) {
    case 0:
      return "do step 1";
    case 1:
      return "do step 2";
    case 2:
      return "do step 3";
    default:
      return "unknown step";
  }
}

export default function App() {
  const [activeStep, setActiveStep] = React.useState(0);
  const [completed, setCompleted] = React.useState(new Set());
  const [skipped, setSkipped] = React.useState(new Set());
  const steps = getSteps();

const totalSteps = () => {
    return getSteps().length;
  };

const isStepOptional = step => {
    return step === 1;
  };

const handleSkip = () => {
    if (!isStepOptional(activeStep)) {
      throw new Error("You can't skip a step that isn't optional.");
    }

setActiveStep(prevActiveStep => prevActiveStep + 1);
    setSkipped(prevSkipped => {
      const newSkipped = new Set(prevSkipped.values());
      newSkipped.add(activeStep);
      return newSkipped;
    });
  };

const skippedSteps = () => {
    return skipped.size;
  };

const completedSteps = () => {
    return completed.size;
  };

const allStepsCompleted = () => {
    return completedSteps() === totalSteps() - skippedSteps();
  };

const isLastStep = () => {
    return activeStep === totalSteps() - 1;
  };

const handleNext = () => {
    const newActiveStep =
      isLastStep() && !allStepsCompleted()
        ? steps.findIndex((step, i) => !completed.has(i))
        : activeStep + 1;

setActiveStep(newActiveStep);
  };

const handleBack = () => {
    setActiveStep(prevActiveStep => prevActiveStep - 1);
  };

const handleStep = step => () => {
    setActiveStep(step);
  };

const handleComplete = () => {
    const newCompleted = new Set(completed);
    newCompleted.add(activeStep);
    setCompleted(newCompleted);

if (completed.size !== totalSteps() - skippedSteps()) {
      handleNext();
    }
  };

const handleReset = () => {
    setActiveStep(0);
    setCompleted(new Set());
    setSkipped(new Set());
  };

const isStepSkipped = step => {
    return skipped.has(step);
  };

function isStepComplete(step) {
    return completed.has(step);
  }

return (
    <div>
      <Stepper alternativeLabel nonLinear activeStep={activeStep}>
        {steps.map((label, index) => {
          const stepProps = {};
          const buttonProps = {};
          if (isStepOptional(index)) {
            buttonProps.optional = `Optional`;
          }
          if (isStepSkipped(index)) {
            stepProps.completed = false;
          }
          return (
            <Step key={label} {...stepProps}>
              <StepButton
                onClick={handleStep(index)}
                completed={isStepComplete(index)}
                {...buttonProps}
              >
                {label}
              </StepButton>
            </Step>
          );
        })}
      </Stepper>
      <div>
        {allStepsCompleted() ? (
          <div>
            All steps completed
            <Button onClick={handleReset}>Reset</Button>
          </div>
        ) : (
          <div>
            {getStepContent(activeStep)}

<div>
              <Button disabled={activeStep === 0} onClick={handleBack}>
                Back
              </Button>
              <Button variant="contained" color="primary" onClick={handleNext}>
                Next
              </Button>
              {isStepOptional(activeStep) && !completed.has(activeStep) && (
                <Button
                  variant="contained"
                  color="primary"
                  onClick={handleSkip}
                >
                  Skip
                </Button>
              )}

                {activeStep !== steps.length &&
                (completed.has(activeStep) ? (
                  `Step ${activeStep + 1} already completed`
                ) : (
                  <Button
                    variant="contained"
                    color="primary"
                    onClick={handleComplete}
                  >
                    {completedSteps() === totalSteps() - 1
                      ? "Finish"
                      : "Complete Step"}
                  </Button>
                ))}
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

We have our Step and StepButton to add the step button.

We apply all the props and allow users to click on each link regardless of status.

buttonProps.optioal lets us render a string or component to indicate that the step is optional.

We can also set the completed property of stepProps to false .

Conclusion

We can customize non-linear steppers to our liking.

The labels and content can be changed with various components.