Archive for the 'React' Category

Using Flow types with Reach Router route components

I have a project which uses Flow for static typing, and Reach Router for routing. Reach Router uses Route components to assign components to URLs:

<Router>
  <Component path="/somewhere" />
  <AnotherComponent path="/somewhere-else" />
  <YetAnotherComponent default />
</Router>

So if you browse to /somewhere-else, <AnotherComponent /> will be rendered. So far, so good. However, if one of these components doesn’t accept any props, Flow will complain:

export const AnotherComponent = () => {
  return <>I am another component.</>;
};

Error:(116, 10) Cannot create AnotherComponent element because property path is missing in function type [1] but exists in props [2].

To work around that, I created a Route component and a DefaultRoute component:

// @flow

import type { DefaultRouteProps, RouteProps } from "@reach/router";
import * as React from "react";

type RouteComponentProps = RouteProps & {
  component: React$ComponentType<*>,
};

type DefaultRouteComponentProps = DefaultRouteProps & {
  component: React$ComponentType<*>,
};

export const Route = (props: RouteComponentProps) => (
  <props.component {...props} />
);

export const DefaultRoute = (props: DefaultRouteComponentProps) => (
  <props.component {...props} />
);

(Note the imported types are pulled from flow-typed‘s Reach Router definition).

They’re used like so:

<Router>
  <Route component={Component} path="/somewhere" />
  <Route component={AnotherComponent} path="/somewhere-else" />
  <DefaultRoute component={YetAnotherComponent} default />
</Router>

(And yes, eventually we’ll likely migrate this project to React Router.)

React’s useEffect and arrays in its’ dependency array

Could I get any more arrays into that title? I’m working on a React app at the moment. Adopting hooks has made my life a bit easier. But useEffect‘s dependency array took a while to get my head around – specifically what to do if your only dependency is itself an array.

If useEffect‘s dependency array itself contains an array, like this, you get an infinite loop and effectively perform a DOS attack on your own API.

const Thing = () => {
  const [arrayOfThings, setArrayOfThings] = useState([]);

  useEffect(() => {
    // Imagine some code here to 
    // fetch things from an API
    setArrayOfThings(loadedArrayOfThings);
  }, [arrayOfThings])

  return (
    <ul>
      {arrayOfThings.map(el => (<li>{el.title}</li>))}
    </ul>
  )
}

I imagine it’s because useEffect isn’t doing a deep compare on the dependencies. So, perhaps changing it so useEffect is dependent on the length of the array will help?

useEffect(() => {
  // Imagine some code here to
  // fetch things from an API
  setArrayOfThings(loadedArrayOfThings);
}, [arrayOfThings.length])

Well, it does, but it still requests the data from the API twice – once on first render, and again after the first fetch because the length of the array changes. The fix I found is to use a second useState variable:

const Thing = () => {
  const [arrayOfThings, setArrayOfThings] = useState([]);
  const [doFetchData, setDoFetchData] = useState(true);

  useEffect(() => {
    if (doFetchData) {
      // Imagine some code here to 
      // fetch things from an API
      setArrayOfThings(loadedArrayOfThings);
      setDoFetchData(false)
    }
  }, [doFetchData])

  return (
    <ul>
      {arrayOfThings.map(el => (<li>{el.title}</li>))}
    </ul>
  )
}

The ensures the data is only loaded once. Additionally, it gives us a way to precisely control when to go and re-fetch the data (using setDoFetchData(true)). Better.