I made a React hook to get the current date

February 24, 2019 React React hooks JavaScript TypeScript

React Hooks are a fantastic new feature in React 16.8. It’s my favorite thing about React since React. Somehow the developers found a way to make side effects work in functional React components, with the plainest Javascript, and ten times less code than the class-based equivalent.

You can apply hooks to all kinds of use cases, but I had a quite simple one on my mind.

I’m working on an app where there’s a lot of business logic tied to the current date. Generally, when you need the current date in your app, you do a new Date() or a DateTime.local() in Luxon, which is what I’ve been doing so far. From a theoretical FP perspective, this is wrong because it makes your components (or worse, reducers) impure. From a practical perspective, this is also wrong, because your components won’t redraw when the date changes: if you leave the app open and go to sleep, it’s going to be stuck in yesterday until some other change forces the components to redraw. What could be even worse is if the date is inconsistent across the app, because some components had a reason to change, but others did not.

I had planned to make a React context to distribute the current date through the application, and hooks presented an opportunity to do so with the cleanest possible API.

Provider

First of all, we declare our context. Nothing new here:

// CalendarDate is my custom class with `today()` and `equal(otherDate)` methods
const Today = React.createContext(CalendarDate.today());

Now, we build a provider that will take care of updating the context value.

Typically the provider would be a class component because it has state. (And in fact, I only use class components when I need state management.)

However, with hooks, we can make the provider a function and keep the code cleaner than a class component!

import { useState, useEffect } from "react";

export const TodayProvider: React.FunctionComponent<{}> = ({ children }) => {
  // Allocate a state to keep the current date in
  const [currentDate, setDate] = useState(CalendarDate.today());

  // This function will be called every minute to check for changes
  function updateDateIfChanged() {
    const newDate = CalendarDate.today();
    // setDate would always trigger a render in our case: dates are objects,
    // and newDate will never be equal to currentDate if they are not the
    // same instance.
    // So, only setDate when we know that the date has changed meaningfully.
    if (!newDate.equal(currentDate)) {
      setDate(newDate);
    }
  }

  useEffect(() => {
    // Set up polling for date changes every minute
    // (A sidenote about JS timers - they run on CPU time, not calendar time. This means that
    // while the computer is in sleep mode, the timer is not ticking. That's why we need to
    // check once a minute instead of calculating time to midnight and setting a precise alarm.)
    const intervalID = setInterval(updateDateIfChanged, 60000);
    // setInterval provides us with a cleanup function, pass that on
    return () => clearInterval(intervalID);
    // the effect is good as long as the component lives, no need to ever run it twice
  }, []);

  // Render the React context provider
  return <Today.Provider value={currentDate}>{children}</Today.Provider>;
};

What I like about hooks is that they are simple single-purpose building blocks. You take them and assemble whatever business logic you need, with no boilerplate at all. Hooks are classic Javascript, just as I like it.

(What I don’t like is that hooks turn functional components impure and make testing harder. However, you can deal with that by injecting hooks with a HOC.)

Getting back to the example, first, we call a useState hook to make some place to put the current date. That’s what the hook does - it gives you a persistent local variable and a setter for it.

(You might wonder - local and persisted to where? The code looks magical at first sight, but it’s not — the state is attached to the current instance of the component. That’s why hooks are only allowed within component code.)

Then we call a useEffect hook. It attaches side effects to the component lifecycle. In this case, we use it to set up a polling mechanism with a familiar subscribe-cleanup idiom. (You return a cleanup function from the effect function if you need to.)

One thing you have to know about useEffect is that it runs the cleanup and effect functions whenever the component is rendered, and not only when it’s mounted and unmounted. This is good for some side effects, but not for subscriptions. To avoid unnecessary churn, we pass a second value to useEffect, which is a cache key: the effect is only rerun when the key changes. In our case, the key is a never changing empty array: [].

The typical subscription effect retrieves some data and puts it into a variable managed by useState.

Consumer

Finally, there’s a useContext hook that gives access to a context without having to wrap the component into a <Context.Consumer>. We make a custom wrapper for the hook to hide the implementation details:

const useToday = () => useContext(Today);

export default useToday;

Now we have created our little hook (all functions that call hooks are technically hooks themselves, and the useSomething name is a convention). Using it in a component could not be simpler:

const MyComponent: React.FunctionComponent<{}> = () => {
  const today = useToday();

  return <div>Today is: {today.toString()}</div>;
};

Now our component will be rerendered whenever the date changes.

As to using dates in reducers and other business logic — I recommend always passing the date as an argument. In my case, I get by with passing the date from UI components to actions and selectors.

Buy me a coffee Liked the post? Treat me to a coffee