Build a CRUD App in React with Tanstack Query and Material UI [Final]
Tags: react, reactquery, tutorial, beginners
Published: 2024-01-21 22:32:27

*If simply reading this tutorial isn't enough for you, you [can go here and write code alongside](https://www.frontendly.dev/ihor-filippov/build-a-crud-app-in-react-with-tanstack-query-and-material-ui/5) it.*

Now, we are faced with a new challenge. 

We need to implement the feature of editing existing users. Of course, we could use the `AddUserForm` component, but let's not complicate things and create a separate form for editing, which we will show under a certain condition.

First, let's start by adding a new field to our table, which will contain an edit button.

`components/users-table.js`
```jsx
import { 
  ...other imports
  Button
} from "@mui/material";

export const UsersTable = () => {
  const { users, createEditUserTemplate } = useUsersContext();

  const handleEditUserAction = (user) => {
    createEditUserTemplate(user);
  }
  
  const renderUsers = (users) => {
    return users.map(user => {
      return (
        <TableRow key={user.id}>
          <TableCell>{user.name}</TableCell>
          <TableCell>{user.email}</TableCell>
          <TableCell>
            <div style={{ display: 'flex', justifyContent: 'space-between' }}>
              <Button
                variant="contained"
                color="info"
                onClick={() => handleEditUserAction(user)}>
                Edit
              </Button>
            </div>
          </TableCell>
        </TableRow>
      );
    })
  }

  return (
    <TableContainer component={Paper}>
      <Table>
        <TableHead>
          <TableRow>
            <TableCell>Name</TableCell>
            <TableCell>Email</TableCell>
            <TableCell>Actions</TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {renderUsers(users)}
        </TableBody>
      </Table>
    </TableContainer>
  );
}
```
Here, we have added a new `Actions` column to our user table. Now, each user has an `Edit` button, which, when clicked, passes the user's data to the editing template. 

Let's now implement this in the `UsersProvider`.

`components/users-provider.js`
```jsx
...code
export const UsersProvider = ({ data, children }) => {
  ...code
  const [editUserTemplate, setEditUserTemplate] = useState(null);

  ...code

  const createEditUserTemplate = (user) => {
    setEditUserTemplate(user);
  }

  const editUser = (userToEdit) => {
    setUsers(users => {
      return users.map(user => {
        if (user.id === userToEdit.id) {
          user.name = userToEdit.name;
          user.email = userToEdit.email;
        }
        return user;
      });
    });
    setEditUserTemplate(null);
  }
  
  return (
    <UsersContext.Provider value={{ 
      users,
      addUser,
      editUser,
      editUserTemplate,
      createEditUserTemplate
    }}>
      {children}
    </UsersContext.Provider>
  );
}
```
Here, we define two new functions: 
* `editUser`: finds the user that needs to be updated and returns a new list
* `createEditUserTemplate`: simply adds data to the editing template that we are currently using further.

Now, let's implement form for user editing.

`components/edit-user-form.js`
```jsx
import { useEffect } from "react";
import { useInput } from "../hooks/use-input";
import { useUsersContext } from "./users-provider";
import { Button, TextField } from "@mui/material";

export const EditUserForm = () => {
  const [nameProps, nameActions] = useInput('');
  const [emailProps, emailActions] = useInput('');

  const { editUser, editUserTemplate } = useUsersContext();

  useEffect(() => {
    const { name, email, company } = editUserTemplate;

    nameActions.update(name);
    emailActions.update(email);

  }, [editUserTemplate]);

  const onSubmit = (e) => {
    e.preventDefault();
    editUser({
      id: editUserTemplate.id,
      name: nameProps.value,
      email: emailProps.value,
    })
  }

  return (
    <form onSubmit={onSubmit} style={{ margin: '24px 0' }}>
      <TextField
        {...nameProps}
        fullWidth
        type="text"
        label="Name"
        variant="outlined"
      />
      <br />
      <br />
      <TextField
        {...emailProps}
        fullWidth
        type="email"
        label="Email"
        variant="outlined"
      />
      <div style={{ textAlign: 'right', marginTop: 12 }}>
        <Button
          variant="contained"
          color="primary"
          type="submit">
          Save
        </Button>
      </div>
    </form>
  );
}
```
This form has almost the same functionality as the `AddUserForm`, except that we update the input fields' state based on the data taken from the `editUserTemplate`.

The final touch is to tell the `Users` component which form we currently want to show the user. 

`components/users.js`
```jsx
import { Container } from '@mui/material';
import { UsersTable } from './users-table';
import { AddUserForm } from './add-user-form';
import { EditUserForm } from './edit-user-form';
import { useUsersContext } from './users-provider';

export const Users = () => {
  const { editUserTemplate } = useUsersContext();
  
  return (
    <Container 
      maxWidth="md" 
      sx={{ margin: '20px auto' }}>
      {editUserTemplate ? <EditUserForm /> : <AddUserForm />}
      <UsersTable />
    </Container>
  );
}
```

Finally, we are on the home stretch. We've become a bit familiar with how to build a user-friendly layout using `Material UI`. We've learned how to effectively fetch data from an API using `React Query`, and we've also set up the overall architecture for our application, small as it may be.

Now it's time to implement user deletion to wrap things up.

First off all, let's add a `Delete` button for each row in `UsersTable`.

`components/users-table.js`
```jsx
...imports

export const UsersTable = () => {
  const { users, createEditUserTemplate, deleteUser } = useUsersContext();

  ...code

  const handleDeleteUserAction = (user) => {
    deleteUser(user);
  }

  const renderUsers = (users) => {
    return users.map(user => {
      return (
        <TableRow key={user.id}>
          <TableCell>{user.name}</TableCell>
          <TableCell>{user.email}</TableCell>
          <TableCell>
            <div style={{ display: 'flex', justifyContent: 'space-between' }}>
              <Button
                variant="contained"
                color="info"
                onClick={() => handleEditUserAction(user)}>
                Edit
              </Button>
              <Button
                variant="contained"
                color="error"
                onClick={() => handleDeleteUserAction(user)}>
                Delete
              </Button>
            </div>
          </TableCell>
        </TableRow>
      );
    })
  }
  
  ...code
}
```
The logic is very similar to what we did earlier. We just take data about user and execute function we received from `useUsersContext`.

`components/users-provider.js`
```jsx
...code

export const UsersProvider = ({ data, children }) => {
  ...code

  const deleteUser = (userToDelete) => {
    setUsers(users => {
      return users.filter(user => user.id !== userToDelete.id)
    });
    setEditUserTemplate(null);
  }
  
  return (
    <UsersContext.Provider value={{ 
      users,
      addUser,
      editUser,
      editUserTemplate,
      createEditUserTemplate,
      deleteUser
    }}>
      {children}
    </UsersContext.Provider>
  );
}

export const useUsersContext = () => useContext(UsersContext);
```

That's it! Our application is working as intended, I hope you enjoyed it.