more composable code with react hooks

notes from Fun with React Hooks - Michael Jackson and Ryan Florence

the Michael Jackson part of the talk

##but first, go and watch it! i'll wait.

composability and what that means

the number one feature of react hooks is composability

here are some key takeaways

an "effect" in functional programming terms refers to "side" effects. if your function adds two numbers, its "main" effect is to add two numbers and return the result. it's side effect is anything else, like printing it to console, timestamping it, keeping track of the previously added numbers...

in react, useEffect() is the same (it refers to side effects, or things your component does that is out of sync with the initial render flow).

an example (similar to the video)

  1. import stuff:
import React, { useState, useEffect } from "react";
import axios from "axios";
  1. write a data fetching thing or anything that requires state:
const fetchSomething = async (query) => {
  return await axios
    .get(`http://hn.algolia.com/api/v1/search?query=${query}`)
    .then((res) => {
      return { result: res, error: false };
    })
    .catch((e) => {
      return { result: null, error: true };
    });
};
  1. if you wanted to use it in the past in a component you wrote some variation of this:
// all the lifecycle methods needed to make sure your
// function fetches and doesn't leak memory
class User extends React.Component {
  state = { user: null };
  fetch() {
    fetchSomething(this.props.query).then((res) => {
      this.setState({ user: res.result });
    });
  }
  componentDidMount() {
    this.fetch();
  }
  componentDidUpdate(prevProps) {
    if (this.props.query !== prevProps.query) {
      if (!this.__isUnMounted) this.fetch();
    }
  }
  componentWillUnmount() {
    this.__isUnMounted = true;
  }
  render() {
    // render props pattern, so send data to children
    return this.props.children(this.state.user);
  }
}
  1. and your app had to do this crazy acrobatics: sidenote: this is the first time i wrote this in a render props pattern. i'm usually a Higher Order Components guy
const AppComponent = ({ query }) => {
  return (
<User query={query}>
{user => {
return <p>{JSON.stringify(user)}</p>
}}
</User> ) }

this is where hooks come in. in particular, useEffect().

useEffect():

A: reduces boilerplate code

this is, in effect, item number 3.:

const AppHooks = ({ query }) => {
const [user, setUser] = useState(null)
useEffect(
() => {
let current = true
fetchSomething(query).then(res => {
if (current) setUser(res)
})
return () => {
current = false
}
},
[query]
) return <p>{JSON.stringify(user)}</p> }

or more similarly:

// maybe put it in a file you import, or npm publish it, or whatever
const useUser = query => {
const [user, setUser] = useState(null)
useEffect(
() => {
let current = true
fetchSomething(query).then(res => {
if (current) setUser(res)
})
return () => {
current = false
}
},
[query]
) return user } const AppHooks = ({ query }) => {
const user = useUser(query) // ... and then use it like this
return <p>{JSON.stringify(user)}</p> }

B: makes react state management composable

no more lifecycle methods.

useEffect() feels to me like the keystone to the hooks pattern, as it reduces boilerplate code while making your state management code composable.

what is composability you say?

it is the fact that you can take a chunk of related logic and put it in a function somewhere else, so that you can abstract away the complexity, while making it possible for your colleagues and other programmers to use your stateful component without having to resort to different (sometimes unintuitive) ways of passing around stateful data.

it allows you to take this

const AppHooks = ({ query }) => {
const [user, setUser] = useState(null)
useEffect(
() => {
let current = true
fetchSomething(query).then(res => {
if (current) setUser(res)
})
return () => {
current = false
}
},
[query]
) return <p>{JSON.stringify(user)}</p> }

and put it somewhere else, like so:

// maybe put it in a file you import, or npm publish it, or whatever
const useUser = query => {
const [user, setUser] = useState(null)
useEffect(
() => {
let current = true
fetchSomething(query).then(res => {
if (current) setUser(res)
})
return () => {
current = false
}
},
[query]
) return user } const AppHooks = ({ query }) => {
const user = useUser(query) // ... and then use it like this
return <p>{JSON.stringify(user)}</p> }

####it allows you to rearrange multiple stateful components that depended on data from the each other:

const AppComponent = ({ query }) => {
  return (
    <User query={query}>
      {(user) => {
        return (
          <>
            <p>{JSON.stringify(user)}</p>
            <UserDetails query={user}>
              {(details) => {
                return <p>{JSON.stringify(details)}</p>;
              }}
            </UserDetails>
          </>
        );
      }}
    </User>
  );
};

it starts to look a bit confusing (all the brackets!! where's the fetching part? and nobody likes pyramids (only one, he's the sun god so he can.)

####into this:

const AppHooks = ({ query }) => {
  const user = useUser(query);
  const details = useUserDetails(user);

  return (
    <>
      <p>{JSON.stringify(user)}</p>
      <p>{JSON.stringify(details)}</p>
    </>
  );
};

making things composable frees up the programmer from having to follow some sort of arbitrary structure defined by a library that might not map to his/her mental model of the app he/she is building.

##it's like the react team made a better lego block.

components as lego

bad

hooks as lego

good