Using React Hooks in Ionic React
Tags: react, ionic, tutorial
Published: 2019-07-08 16:01:24

If you've been following the news in the React ecosystem, you've likely heard about the new [React Hooks API](https://reactjs.org/docs/hooks-intro.html) available in React v16.8.

Hooks expose React features like state and context to functional, or non-class components. They also make it easier to share "stateful logic" between components, such as accessing data in a store, without complex wrapping components.

And now that Ionic supports React (in beta at the time of this writing, [try it out!](https://blog.ionicframework.com/announcing-the-ionic-react-beta/)), we were excited to see how hooks can make common app building tasks and accessing native APIs really easy and clean, and wanted to walk through the new Hooks APIs in the context of an Ionic React app, including a [real demo app](https://github.com/mlynch/ionic-react-hooks-demo) that we will dig into at the end of this post.

We'll soon see there's more to Hooks than it seems!

<!--more-->

## Stateless Functional Components

Historically, functional components in React did not manage their own state, as there was no way to access these features outside of classes that extended `React.Component`. This was partly why they were referred to as "Stateless Functional Components," and anything resembling state used in them was seen as a serious code smell (and likely broken).

Let's take a simple Stateless Functional Component for example:

```jsx
export const MyComponent = ({ name }) => <h1>My name is {name}</h1>;

// Using the component
<MyComponent name="Max" />
```

In this example, `MyComponent` is a functional component (i.e. it is not a class-based component), but it is also *stateless*, given that it manages none of its own internal state, and *pure* in the sense that it has zero side-effects (i.e. modifications it makes outside of itself, such as writing a file or updating a global variable). Rather, data is provided to the component through `props`, such as `name`, and they are merely rendered out by the component in predictable fashion.

Such limitations made Stateless Functional Components great for creating lots of small, presentational components, which are desirable in many situations. However, that still meant that doing anything more complex required class-based components.

## Adding State to Functional Components

Hooks completely change what functional components can do in React, bringing state, async operations such as fetch, and APIs like Context to functional components in a safe, possibly even *superior* way (to their class-based counterparts, that is).

To illustrate this, let's modify this example to use Hooks to manage a small bit of internal state:

```jsx
export const MyComponent = () => {
  const [ name, setName ] = useState('Max');

  return (
  <>
    <h1>My name is {name}</h1>
    <IonInput value={name} onChange={(e) => setName(e.target.value)} />
  </>
  )
}
```

In this example, an `IonInput` is used to type in a name, which is tracked in the internal state for the component and rendered in the `<h1>` tag.

In the first line, we see our first use of Hooks with `useState`. In this case, `useState` *hooks* into the state management features in React, and creates a state variable. `useState` takes an argument for the default value of the state variable, and then returns an array with two values which are *destructured* into two local variables: `name`, and `setName`.

The first value, `name` in this case, is our state variable, and this is the one we render to the `<h1>` element and set as the `value` of the `IonInput`. The second value, `setName` is a function that we call to *set* the state variable's value. Both `name` and `setName` in this case can be called whatever we like.

Of course, most components will have many state variables, and thankfully we can call `useState` as many times as we like, one for each variable.

If you're thinking that seems like magic because the `MyComponent` function will be called each time the component re-renders and you're not sure how React keeps track of all the different `useState` calls, then you're on to something. To make this work, React keeps track of the order in which the `useState` calls were made, and thus has strict rules as to where `useState` can be called (for example, calling it in a conditional statement is *not* allowed). To avoid issues, linting tools can help keep your use of `useState` correct, but a good rule of thumb is to keep `useState` calls at the top-level of the function and not nested inside any conditional or nested scopes. Basically, keep it simple!

## Ionic React and React Hooks Example

Now that we have a basic understanding of hooks and managing a state variable, let's take a look at a more involved example of building a login form using Ionic React and React Hooks:

```jsx
import React, { useState } from 'react';

import {
  IonApp, 
  IonHeader,
  IonTitle,
  IonToolbar,
  IonContent,
  IonInput,
  IonList,
  IonItem,
  IonLabel,
  IonButton
} from '@ionic/react';


const LoginForm = () => {
  const [ email, setEmail ] = useState('');
  const [ password, setPassword ] = useState('');

  const [ formErrors, setFormErrors ] = useState({});

  const submit = async () => {
    try {
      await login({
        email,
        password
      });
    } catch (e) {
      setFormErrors(e);
    }
  }

  return (
    <>
      <IonHeader>
        <IonToolbar>
          <IonTitle>
            Login
          </IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent>
        <form onSubmit={(e) => { e.preventDefault(); submit();}}>
          <div>
            {formErrors ? (
              formErrors.message
            ): null}
          </div>
          <IonList>
            <IonItem>
              <IonLabel>Email</IonLabel>
              <IonInput name="email" type="email" value={email} onChange={(e) => setEmail(e.target.value)}/>
            </IonItem>
            <IonItem>
              <IonLabel>Password</IonLabel>
              <IonInput name="password" type="password" value={email} onChange={(e) => setPassword(e.target.value)}/>
            </IonItem>
          </IonList>

          <IonButton expand={true} type="submit">Log in</IonButton>
        </form>
      </IonContent>
    </>
  )
}

```

## What about TypeScript?

Before we move on, you may have wondered in the above examples (which are plain JS), how `useState` and other hooks work with TypeScript. Thankfully, `useState` is a generic function which can take type arguments if they can't be inferred.

For example, if we had a type of `Car` that we wanted to set in state, we could call it this way:

```jsx
const [ car, setCar ] = useState<Car>({ color: 'red' })
```

Hooks work great with TypeScript-based React apps!

## Ionic React with a Class-Based React Component

The above examples are fun, and Hooks are certainly a quirky, curious new API that is oddly pleasing to use. However, one of the reasons they've practically blown up in the React community are because of the code simplicity benefits they bring.

To illustrate that, let's build the same example above but using the traditional React Class-based component approach:

```jsx
import React, { useState, FormEvent } from 'react';

import {
  IonHeader,
  IonToolbar,
  IonTitle,
  IonContent,
  IonList,
  IonItem,
  IonLabel,
  IonInput,
  IonButton
} from "@ionic/react";

export class LoginPage extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      email: '',
      password: ''
    }
  }

  async handleSubmit(e: FormEvent) {
    e.preventDefault();

    try {
      const user = await login(email, password);

      // ...
    } catch (e) {
      console.error(e);
    }
  }

  handleInputChange(e) {
    this.setState({
      [e.target.name]: e.target.value
    });
  }

  render() {
    return (
    <>
      <IonHeader>
        <IonToolbar color="primary">
          <IonTitle>Login</IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent>
        <form onSubmit={e => this.handleSubmit(e)} action="post">
          <IonList>
            <IonItem>
              <IonLabel>Email</IonLabel>
              <IonInput  type="email" value={email} onInput={(e: any) => this.handleInputChange(e)} />
            </IonItem>
            <IonItem>
              <IonLabel>Password</IonLabel>
              <IonInput type="password" value={password} onInput={(e: any) => this.handleInputChange(e)} />
            </IonItem>
            <IonButton type="submit">Log in</IonButton>
          </IonList>
        </form>
      </IonContent>
    </>
    );
  }
}
```

In the above example, you'll notice a few hallmarks of class-based components: a constructor, calling `this.state`, having to capture `this` in callback handlers (in the above example we've used the arrow function approach for event handlers, but many use `this.eventHandler = this.eventHandler.bind(this)` which has some serious gotchas).

While this example isn't very complicated, it's enough to show that this component is simpler as a functional component using Hooks than its class-based counterpart (though some may prefer the boilerplate of the class-based method, perhaps Java developers in another life).

## Components with Side Effects: useEffect

State variables are just one use case for Hooks. Many components will need to do things that are considered "side effects" after a component is rendered (such as on mount or update). A side effect is any operation that causes something outside of the component to be modified as a side effect of using this Component. For example, making an API request is a side-effect that many Components need to perform.

This is where `useEffect` comes in. For example, let's say we need to fetch some data from our component when it mounts by making a request to our API:

```jsx
const MyComponent: = () => {
  const [data, setData] = useState({});

  useEffect(() => {
    async function loadData() {
      const loadedData = await getDataFromAPI();
      setData(loadedData);
    }

    loadData();
  }, []);

  const items = (data.items || []);

  return (
    <div>
      There are {items.length} entries
    </div>
  );
}
```

For class-based components, data fetching was often done in a lifecycle method such as `componentDidMount`, and at first it's not obvious how calling `useEffect` in the above works in comparison.

You can think of `useEffect` as a combination of the lifecycle methods `componentDidMount`, `componentDidUpdate`, and `componentWillUnmount`, given that it first runs as soon as the component is mounted and has rendered, will run every time the component is updated, and can run cleanup when the component will be unmounted.

However, in the above, we wouldn't want to fetch our data after every update! That could mean thousands of redundant API requests if a component is updated many times in short succession. Instead, `useEffect` takes an extra argument of *dependencies*: `useEffect(effectCallack, dependencyList)`. In `dependencyList`, you can tell the effect to run only after certain state variables have changed, or pass an empty array to only allow the effect to run the first time the component is mounted.

In the above, we pass `[]` as the dependency list so that our effect only runs the first time the component is mounted.

One note: `useEffect` is only necessary if you wish to perform the side-effects relative to renders of the component. If, instead, you wish to make an API request after an action (such as a button click in your component), just make the fetch normally and call the corresponding setter function for your state variable when data is returned and you wish to update the component. In this sense, `useEffect` is a confusing name as you can incorporate side-effects in the component without needing to use it.

## Easy state management with useContext

Most React developers know the struggle of trying to share global state across their application. For better or worse, this struggle has caused many developers to look at powerful solutions like Redux that were overkill for their needs, when something much simpler would have sufficed.

Well, with Hooks, [Context](https://reactjs.org/docs/context.html), and the `useContext` API, this struggle is effectively over. Accessing a global Context instance with `useContext` makes it possible to do rudimentary state management or easily [create your own mini Redux](https://medium.com/simply/state-management-with-react-hooks-and-context-api-at-10-lines-of-code-baf6be8302c) with no external dependencies and a simpler architecture.

We will walk through the `useContext` hook in the example app at the end.

## Custom hooks

React comes with a number of hooks out of the box, but they are useful for far more than just state management or accessing context! 

In the following sections we will take a look at some custom hooks and how they can bring big benefits to React and Ionic app development.

## Native APIs with Ionic and React Hooks

Because hooks are perfect for reusable, stateful logic, maybe they would be perfect for plugins that access Native APIs on iOS, Android, Electron, and the browser? Turns out they are, and we can build or use custom hooks to do just that!

Imagine accessing Geolocation APIs on the device. A custom hook called `useGeolocation` might automatically listen for geolocation position changes and update a state variable:

```jsx
const MyApp = () => {
  const pos = useGeolocation();

  return (
    <span>Lat: {pos.lat}, Lng: {pos.lng}</span>
  );
}
```

This example shows the hidden power of Hooks. With just one line, we have set up a geolocation query on component mount, which starts a watch that will update when our position changes, which will then update a state variable, which will cause the component to re-render and the updated position be displayed.

Now, imagine doing the same for other Native fetures like Camera, Storage, Photos, or Barcode Scanning, and you can get a sense for how easy hooks make interacting with these kinds of APIs.

So, how does this pertain to Ionic? Well, as Ionic React gets off the ground, we are exploring doing a set of hooks for the APIs available in [Capacitor](http://capacitor.ionicframework.com/) which we think will be pretty awesome!

## An example app

With the introduction to hooks out of the way, let's take a look at a simple Ionic React app that uses a number of the hooks above, the Puppers app (source code [here](https://github.com/mlynch/ionic-react-hooks-demo)):

<a href="https://blog.ionicframework.com/wp-content/uploads/2019/04/ss1.png"><img src="https://blog.ionicframework.com/wp-content/uploads/2019/04/ss1-172x300.png" alt="" class="aligncenter size-medium wp-image-2763" /></a>

This app fetches a list of random images of adorable and Very Good puppers from the [Dog API](https://dog.ceo/), with a few features that are completely overkill but also just right, including persisting the last images to localStorage, and a mini-implementation of redux for managing state through the Context API using just the `useReducer` hook (and no external dependencies!). There is also a custom hook called `useLocalStorage` that automatically loads and persists a key and value to localStorage (ported from [usehooks.com](http://usehooks.com)).

This demo app also shows how to use Ionic React in a plain JS app without TypeScript.

#### App.js

In [App.js](https://github.com/mlynch/ionic-react-hooks-demo/blob/master/src/App.js), we have our main App component at the bottom of the file:

```jsx
const App = () => {
  return (
    <IonApp>
      <AppContextProvider>
        <Puppers />
      </AppContextProvider>
    </IonApp>
  );
}

export default App;
```

This component creates an `IonApp` element, and then wraps the contents of the app with an `AppContextProvider` which will be our main `Context` for managing global state. Inside of that component, the `Puppers` page is rendered. Pretty basic, and if you aren't familiar with the Context API, make sure to [read more about it](https://reactjs.org/docs/context.html) before continuing.

Next, we have the `AppContextProvider`:

```jsx
const AppContext = createContext();

const AppContextProvider = (props) => {
  const [data, setData] = useLocalStorage('data', initialState);

  let [state, dispatch] = useReducer(reducer, data);

  let value = { state, dispatch };

  useEffect(() => {
    setData(state);
  }, [state, setData]);

  return (
    <AppContext.Provider value={value}>{props.children}</AppContext.Provider>
  );
}
```

This one is way more complex and uses a number of hooks and even a custom hook! Let's walk through some of it:

The first line calls our custom `useLocalStorage` hook, which will load and automatically persist values to the `data` item in localStorage:

```js
const [data, setData ] = useLocalStorage('data', initialState);
```

Next, we create a reducer using `useReducer` that takes a reducer function and an initial value, which we will pass in the `data` state value from `useLocalStorage`. This will cause the reducer to use any data loaded from localStorage as its initial value! If you haven't used redux before, `useReducer` will likely be weird at first. However, it's a useful utility for complex state logic and lets us manage a single global state object that can be modified by actions in the application. Our application only has one action but you can imagine the average application having hundreds of actions. Read more about [useReducer](https://reactjs.org/docs/hooks-reference.html#usereducer).

```js
let [state, dispatch] = useReducer(reducer, data);
```

And our reducer function is very basic:

```js
const reducer = (state, action) => {
  if (action.type === 'setPuppers') {
    return { ...state, puppers: action.puppers }
  }
  return state;
}
```

If this is confusing, hopefully seeing a component "use" the above context and reducer should make it more clear:

#### Puppers.js

Let's take a look at the Puppers Component, which loops through the list of puppers from the API and renders them one by adorable one:

```jsx
export const Puppers = () => {
  const { state, dispatch } = useContext(AppContext);

  const fetchPuppers = useCallback(async () => {
    const ret = await fetch('https://dog.ceo/api/breeds/image/random/10');
    const json = await ret.json();
    dispatch({
      type: 'setPuppers',
      puppers: json.message
    })
  }, [dispatch]);

  useEffect(() => {
    fetchPuppers();
  }, [fetchPuppers]);

  return (
  <>
    <IonHeader>
      <IonToolbar>
        <IonTitle>Puppers</IonTitle>
        <IonButtons slot="end">
          <IonButton onClick={() => fetchPuppers()}>
            <IonIcon icon="refresh" />
          </IonButton>
        </IonButtons>
      </IonToolbar>
    </IonHeader>
    <IonContent>
      {state.puppers.map(pupper => {
        return (
          <IonCard key={pupper}>
            <IonCardContent>
              <img src={pupper} />
            </IonCardContent>
          </IonCard>
        )
      })}
    </IonContent>
  </>
  );
}
```

Let's take this line by line. The first line accesses the `AppContext` that we instantiated using the `<AppContextProvider>` component in our `App` component, specifically the `value` of the provider:

```js
const { state, dispatch } = useContext(AppContext);
```

The `state` variable will contain our global state in the context, and the `dispatch` variable is a function we can call to send an action to our reducer (to update our state, for example).

Next, we define a function that we can use to call our API:


```js
const fetchPuppers = useCallback(async() => {
  const ret = await fetch('https://dog.ceo/api/breeds/image/random/10');
  const json = await ret.json();
  dispatch({
    type: 'setPuppers',
    puppers: json.message
  })
}, [dispatch]);
```

Since we're going to call `fetchPuppers` from a few different places in our component, we use the `useCallback` hook to make sure the Hooks API properly understands the dependencies this function has. This was a solution to sharing a function in several hooks provided by Dan Abramov on his [Complete Guide to useEffect](https://overreacted.io/a-complete-guide-to-useeffect/), though there are alternative ways to achieve this. We provide the `dispatch` function as a dependency to our `fetchPuppers` call, as it will be called with fresh puppers once the response returns.

Next, we use `useEffect` with an empty dependency list (i.e. `[]` as the last argument) to make a fetch as soon as this component is mounted:

```js
useEffect(() => {
  fetchPuppers();
}, [fetchPuppers]);
```

Finally, we render our component, and loop through each pupper, rendering them to the screen:

```jsx
return (
<>
  <IonHeader>
    <IonToolbar>
      <IonTitle>Puppers</IonTitle>
      <IonButtons slot="end">
        <IonButton onClick={() => fetchPuppers()}>
          <IonIcon icon="refresh" />
        </IonButton>
      </IonButtons>
    </IonToolbar>
  </IonHeader>
  <IonContent>
    {state.puppers.map(pupper => {
      return (
        <IonCard key={pupper}>
          <IonCardContent>
            <img src={pupper} />
          </IonCardContent>
        </IonCard>
      )
    })}
  </IonContent>
</>
);
```

A few things to see here: first, notice the `onClick` event in the button in the toolbar. This will make a new fetch to the API, get 10 more random puppers, which will then cause global state to update, and our component to re-render.

Finally, given that we are using global state instead of local state, when we render out each pupper, we are accessing the `state.puppers` field which came from the initial `useContext` call.

And that's it!

## Where to go from here

Despite React Hooks being very new, the community has created a plethora of interesting Hooks. One such library, <a href="https://streamich.github.io/react-use/">react-use</a>, has some simple yet powerful hooks such as [useVideo](https://streamich.github.io/react-use/?path=/story/ui-usevideo--docs) (for easily interacting with an HTML5 video element). I personally love how clean and simple Hooks make interacting with stateful controls such as HTML5 media elements and APIs like localStorage.

Also, make sure to watch the [React Conf Hooks Keynote Announcement](https://youtu.be/dpw9EHDh2bM?t=698) by Dan Abramov, along with his great blog posts that dig into hooks in more detail, such as [A Complete Guide to useEffect](https://overreacted.io/a-complete-guide-to-useeffect/).

Finally, keep an eye out for some awesome hooks stuff from the Ionic team specifically for Ionic React apps (using Capacitor for native functionality). We love hooks and think they will make building apps considerably easier. And, if you haven't tried the [Ionic React beta](https://blog.ionicframework.com/announcing-the-ionic-react-beta/) give it a shot and let us know what you think!

Any questions on using Hooks and how they might be useful in Ionic React apps specifically? Leave a comment below and we'll try to help!