Neat React Hooks

This hook comes courtesy of a tweet by Alex / KATT 🐱 It's a small custom hook to return the last 'non-nullable' version of value, i.e. ignore undefined and null for a variable.

/**
 * @alexdotjs https://gist.github.com/KATT/b6e155394213a88df6de68c4fc9a4460
 * Use the last value that isn't `null` or `undefined`.
 * Useful for instance in modals where you don't want the selected item
 * to flicker when the modal is closed.
 */
export function useLastNonNullableValue<T>(currentValue: T) {
  const [value, setValue] = React.useState(currentValue);

  useEffect(() => {
    setValue((stored) => currentValue ?? stored);
  }, [currentValue]);

  return value;
}

Pretty neat huh, thanks Alex!

The one thing I don't like in it is the useEffect. It works but it's not really a side-effect, in the sense that nothing outside React is being synced to value state here. I am something of a stickler for only true effects in useEffect. Therefore, I think a 'derived state from props'-like approach is a better solution here. With function components, we can do this by setting state in render.

I also like more verbose variable names, let's take a look at my version:

export function useLastNonNullableValue<ValueType>(currentValue: ValueType) {
  const [nonNullableValue, setNonNullableValue] = React.useState(currentValue);

  if (
    nonNullableValue !== currentValue &&  // !Object.is(nonNullableValue, currentValue) would work too
    typeof currentValue !== "undefined" &&
    currentValue !== null
  ) {
    setNonNullableValueValue(currentValue);
  }

  return nonNullableValue;
}

Alex gives one use case: Useful for instance in modals where you don't want the selected item to flicker when the modal is closed.. I think it's an all-round useful hook to avoid displaying undefined or an value disappearing because it is null.

import * as React from "react";

export function usePermission(permissionName: PermissionName) {
  const [permissionState, setPermissionState] = React.useState<PermissionState>('denied');

  React.useEffect(() => {
    if (navigator.permissions) { 
      navigator.permissions.query({ name: permissionName }).then((permissionStatus) => {
        setPermissionState(permissionStatus.state);

        permissionStatus.onchange = function () {
          setPermissionState(this.state);
        };
      });
    } else {
      console.warn("Can't access 'navigator.permissions'")
    }
  }, [permissionName]);
  
  return [permissionState, setPermissionState]
}

  1. Note that if you call a set function while rendering, it must be inside a condition like prevCount !== count, and there must be a call like setPrevCount(count) inside of the condition. Otherwise, your component would re-render in a loop until it crashes. Also, you can only update the state of the currently rendering component like this. Calling the set function of another component during rendering is an error.