All Articles

Using the React useState hook to switch between a tiled or tabled display

Hooks
While they look different, React hooks can be just as useful as traditional ones

In the previous post, we started out with the frontend part of the application. In the end, we had a small list of accounts that were displayed in a table, but everything was hardcoded, i.e. there was no state or interaction with third party services.

In this post, we will go over what React hooks are, which ones are most common and how and when to use them.

What are React hooks?

React hooks are a new addition to React, since version 16.8. They allow you to use state, perform side effects, and other features without writing specific new classes.

It may be important to note that using hooks is not mandatory. All the same functionality can be achieved using other methods. However, they are extremely effective and can greatly improve your application with little difficulty.

While they do not replace existing features, they do provide a much more direct API to those features.

Here are some general advantages of using hooks instead of their counterparts:

  • Make it easier to introduce state in your components
  • Add re-usability of stateful logic
  • Remove complexity from components by replacing hard to understand methods (e.g. componentDidMount)
  • Do not require classes, so you don’t need to understand how this works
  • Better for hot reloading
  • Easier to test and work with
  • Can replace some third parties (e.g. Redux)

Aside from using a version of React that is up-to-date, there are no requirements to use the default hooks. You can of course write your own hooks also, but this article will only cover the usage of the useState hook, and how it can be used to change the displaying on information between a tiled and a listed view.

UseState

This is the most commonly used hook, and it is also very simple.

Functionality

In short, this hook enables the functional component to add state to it. That’s it. I guess I didn’t have to say in short in the beginning…

The state is ephemeral though. Upon refreshing of the browser, the state will be gone. It can trigger re-renders of the component, but those will not remove its state.

It’s also important to note that you can use as many useState in a component as you wish. Every useState handles the state of one variable, and that variable only. In this way, it is different to the state management of “regular” components.

Usage

So how are they set up?

Essentially, they are defined in one line:

import * as React from "react";

const [variable, setVariable] = React.useState<type>(initialValue);
// example
const [count, setCount] = React.useState<number>(0);

Starting point

Let’s see this in action now, shall we.

If you look back to our previous application, we had a table containing a list of our accounts. This is all very nice, but some people might prefer to have the same information displayed in tiles. There is no point in creating a different URL for this, as that approach may confuse people more than anything. So we will add a small ticker, where the user can add their preference. (In the future, this may be saved as a setting stored in the user profile, but for now, let’s not overcomplicate things.)

The image of that table can be seen below. Some additional accounts have been added, just to make it look a bit more filled.

Table on MUI paper
The starting point is this table with rows of entries

This is currently in the AccountList component with the following code.

function AccountTableList() {
  const accounts: Array<Account> = [{
    id: 1,
    amount: 320.8,
    currency: "CHF",
    name: "myNiceAccount",
    description: "Wow, this is advancing!"
  }, {
    id: 2,
    amount: 94.6,
    currency: "EUR",
    name: "eurozz",
  }, {
    id: 3,
    amount: 420,
    currency: "USD",
    name: "Dollar account",
    description: "The account of dollars I use for stock investments"
  }, {
    id: 4,
    amount: 2600,
    currency: "CHF",
    name: "Savings",
    description: "Savings account. Do NOT touch!",
  }, {
    id: 5,
    amount: 5,
    currency: "NOK",
    name: "holiday money",
  }];

  return (
    <div>
      <Paper elevation={6} className={classes.root}>
        <TableContainer className={classes.container}>
          <Table stickyHeader aria-label="sticky table">
            <TableHead>
              <TableRow>
                <TableCell align={'center'}>
                  Name
                </TableCell>
                <TableCell align={'center'}>
                  Value
                </TableCell>
                <TableCell align={'center'}>
                  Description
                </TableCell>
              </TableRow>
            </TableHead>
            <TableBody>
              {accounts.map((account) => {
                return (
                  <AccountItem key={account.id} account={account}/>
                );
              })}
            </TableBody>
          </Table>
        </TableContainer>
      </Paper>
    </div>
  )
}

So, first things first. Let’s add a small area where the user can select his preference of a listed vs tiled representation.

In order to display the selection, I will be using a ToggleButtonGroup from Material UI, and some icons from FontAwesome. The lab dependency for Material UI can be added by running npm install @material-ui/lab.

Sidestep to fontawesome

Fontawesome is - as the name suggests - pretty awesome, as it provides you with some very simple yet stylish icons. However, it may not be the easiest to install. Here are some installs that are available. They are not all needed, but if you want special brand icons for example, they cannot hurt:

  • npm install @fortawesome/react-fontawesome (free version).
  • npm install @fortawesome/fontawesome-svg-core
  • npm install @fortawesome/free-solid-svg-icons
  • npm install @fortawesome/free-brands-svg-icons
  • npm install @fortawesome/free-regular-svg-icons

Aside from running the commands mentioned above, there are two more steps required.

  1. Add the icons you want to use in your application

Since you do not want to overly increase the size of your application, you can define only those icons you actually do want to use. I have added a file src/shared/fontawesome.tsx where I have specified the icons I wish to use.

// import the library
import {library} from '@fortawesome/fontawesome-svg-core';
// import your icons
import {
  faList,
  faTh,
} from '@fortawesome/free-solid-svg-icons';

library.add(
  faTh,
  faList,
);
  1. Import this file in the index.tsx to make them available globally by adding import './shared/fontawesome';

Now you can use the icons anywhere in your application, like so: <FontAwesomeIcon color="white" icon={['fas', 'list']}/>.

Creating the tiled view

Great, now that the imports are all done, we can get back to the application.

So let’s create a layer above the AccountList, in which the toggle will be.

type Props = {
  accounts: Array<Account>
}

function AccountDisplay({accounts}: Props) {

  return (
          <Grid container>
            <Grid container justify="flex-end">
              <ToggleButtonGroup exclusive
                                 color="primary"
                                 aria-label="contained button group"
                                 >
                 <ToggleButton value="list" aria-label="left aligned">
                   <FontAwesomeIcon color="black" icon={['fas', 'list']}/>
                 </ToggleButton>
                 <ToggleButton value="tiled" aria-label="left aligned">
                   <FontAwesomeIcon color="black" icon={['fas', 'th']}/>
                 </ToggleButton>
              </ToggleButtonGroup>
             </Grid>
             <Grid xs={12}>
               <AccountList accounts={accounts}/>
             </Grid>
             <Grid container justify="flex-end">
               <Button size="small">Add account</Button>
             </Grid>
          </Grid>
   )
};

You may notice I snuck in a button to add new accounts. This is just common sense, as we probably want to add that feature in the future. Currently, it does not yet have any functionality though.

Table header with toggle group
Toggle group in the top right corner

This may look nice, but it doesn’t yet do anything. Before we add state though, let’s also define the layout for the tiled view.

In order to do this, we will create two new components - the AccountTiles and the AccountTileItem.

type Props = {
  accounts: Array<Account>
}

function AccountTiledList({accounts}: Props) {

  return (
    <Grid container direction="row" spacing={2}>
      {accounts.length > 0 ?
        accounts.map((account: Account) =>
          <AccountTileItem key={account.id}
                           account={account}
          />
        ) :
        <div>You currently do not have any accounts. Feel free to add them now!</div>
      }
    </Grid>
  )
}

export default AccountTiledList;
type Props = {
  account: Account
}

const useStyles = makeStyles({
  root: {
    height: "100%"
  },
  title: {
    fontSize: 14,
  },
});

export const AccountTileItem = ({account}: Props) => {
  const classes = useStyles();

  return (
    <Grid item xs={12} sm={6} md={4} xl={3}>
      <Card className={classes.root} elevation={12}>
        <CardContent>
          <Typography color="textPrimary" gutterBottom variant="h5">
            {account.name}
          </Typography>
          <Typography color="textSecondary">
            {account.currency} {account.amount}
          </Typography>
          <Typography>
            {account.description}
          </Typography>
        </CardContent>
        {/*<CardActions>*/}
        {/*  <Button size="small">Edit</Button>*/}
        {/*</CardActions>*/}
      </Card>
    </Grid>
  )
};

For completeness, this is what the App.tsx currently looks like to make the layout nicer and responsible:

function App() {
  const accounts: Array<Account> = [{
    id: 1,
    amount: 320.8,
    currency: "CHF",
    name: "myNiceAccount",
    description: "Wow, this is advancing!"
  }, {
    id: 2,
    amount: 94.6,
    currency: "EUR",
    name: "eurozz",
  }, {
    id: 3,
    amount: 420,
    currency: "USD",
    name: "Dollar account",
    description: "The account of dollars I use for stock investments"
  }, {
    id: 4,
    amount: 2600,
    currency: "CHF",
    name: "Savings",
    description: "Savings account. Do NOT touch!",
  }, {
    id: 5,
    amount: 5,
    currency: "NOK",
    name: "holiday money",
  }];

  return (
    <div className="App">
      <Grid container xs={12}
            md={10}
            lg={9}
            xl={8}
            direction="column"
            justify="center"
            alignItems="center"
            spacing={1}
            style={{margin: "auto"}}>
        <AccountDisplay accounts={accounts}/>
      </Grid>
    </div>
  );
}

Okay, if we now switch the AccountDisplay to show the AccountTiles, the application looks as follows:

Accounts in tiles
Accounts in shadow-heavy tiles

UseState in action

Yes, this has indeed been quite some work already, and we still haven’t introduced state. We have now laid a lot of the groundwork though.

Introducing the state of the preferred view is now done in the AccountDisplay component. In order to do so, we only need to add the following line before the return (usually state is defined in the first few lines of the component). const [view, setView] = useState("tiled");

Next, we can consume the state of the preferredView, and adapt the Grid to show the correct child based on it:

<Grid xs={12}>
    {view === "tiled" ?
      <AccountTiles accounts={accounts}/> :
      <AccountList accounts={accounts}/>
    }
</Grid>

So now the state is set, and only the tiled view will appear, even though both are defined (conditionally) in the AccountDisplay. Pretty neat!

But wait, what if we want to change the view? Currently, pressing the button group doesn’t actually change anything.

Well, in order to change the state, we can use the API of the ToggleButtonGroup and add a click handler. I may go over click handlers in a future article - for now, if you don’t quite get what is done here, we are essentially defining what happens when a value is clicked in the group.

  const handleView = (event: React.MouseEvent<HTMLElement>, view: string) => {
    setView(view);
  };

Now the ToggleButtonGroup can be adapted, and we can add its current value and connect the click handler:

<ToggleButtonGroup
          exclusive
          color="primary"
          aria-label="contained button group"
          value={view}
          onChange={handleView}
        >
          ...
</ToggleButtonGroup>

There you have it! If you click on the tiles, you will get the tiled view. Similarly, if you click on the list icon, you will get your accounts in the table view. Your users now have a way of seeing their accounts in the way they are most comfortable with, simply because some state (and the ability to manipulate it) has been added to the component. You may also note that the currently chosen view is highlighted in the ToggleButtonGroup.

State management for different displays
Toggled tiles
Do you prefer tiles?
Toggled list
Or would you rather see a table?

Well, I hope you enjoyed learning how you can use the useState hook to change the displaying of information in your application. There are obviously many other use cases where the state management can come in handy, but they work pretty much always the same way. Many more examples will appear in future articles.

I also hope you enjoyed this guide a bit more than one where simply a counter is counting up on every click. :)