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.
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.
- 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,
);
- Import this file in the
index.tsx
to make them available globally by addingimport './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.
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:
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
.
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. :)