React.js

React Utils 🛠️

Everything is heavily copied from the utils.js files in the different workshops

useLocalStorage(key, defaultValue, {serialize, deserialize})

use it just like setState with integrated saving to localStorage:

import {useLocalStorageState} from './utils'
// ...
const [squares, setSquares] = useLocalStorageState(
    'squares',
    Array(9).fill(null),
)
// ...
setSquares(Array(9).fill(null))


from react-hooks workshop - exercise #2 (react-hooks/src/utils.js)

import * as React from 'react'

/**
 *
 * @param {String} key The key to set in localStorage for this value
 * @param {Object} defaultValue The value to use if it is not already in localStorage
 * @param {{serialize: Function, deserialize: Function}} options The serialize and deserialize functions to use
 * (defaults to JSON.stringify and JSON.parse respectively)
 */

function useLocalStorageState(
  key,
  defaultValue = '',
  {serialize = JSON.stringify, deserialize = JSON.parse} = {},
) {
  const [state, setState] = React.useState(() => {
    const valueInLocalStorage = window.localStorage.getItem(key)
    if (valueInLocalStorage) {
      return deserialize(valueInLocalStorage)
    }
    return typeof defaultValue === 'function' ? defaultValue() : defaultValue
  })

  const prevKeyRef = React.useRef(key)

  React.useEffect(() => {
    const prevKey = prevKeyRef.current
    if (prevKey !== key) {
      window.localStorage.removeItem(prevKey)
    }
    prevKeyRef.current = key
    window.localStorage.setItem(key, serialize(state))
  }, [key, state, serialize])

  return [state, setState]
}

export {useLocalStorageState}

useAsync(initialState)

Hook for managing the state of a data-fetching-promise. Managing async state is something every app does all the time so this abstracts that away into a custom hook.

Simple example usage:

import {useAsync} from './utils'

const {
    // using the same names that react-query uses for convenience
    isIdle,
    isLoading,
    isError,
    isSuccess,

    setData,
    setError,
    error,
    status,
    data,
    run,
    reset,
} = useAsync()

React.useEffect(() => {
  run(fetchPokemon(pokemonName))
}, [pokemonName, run])

Example for fetching pokemon-data with it in a PokemonInfo component:

import {useAsync} from './utils'
function PokemonInfo({pokemonName}) {
  const [cache, dispatch] = usePokemonCache()

  const {data: pokemon, status, error, run, setData} = useAsync({
    status: pokemonName ? 'pending' : 'idle',
  })

  React.useEffect(() => {
    if (!pokemonName) {
      return
    } else if (cache[pokemonName]) {
      setData(cache[pokemonName])
    } else {
      run(
        fetchPokemon(pokemonName).then(pokemonData => {
          dispatch({type: 'ADD_POKEMON', pokemonName, pokemonData})
          return pokemonData
        }),
      )
    }
  }, [cache, dispatch, pokemonName, run, setData])

  if (status === 'idle') {
    return 'Submit a pokemon'
  } else if (status === 'pending') {
    return <PokemonInfoFallback name={pokemonName} />
  } else if (status === 'rejected') {
    throw error
  } else if (status === 'resolved') {
    return <PokemonDataView pokemon={pokemon} />
  }

  throw new Error('This should be impossible')
}


from advanced-react-hooks workshop - exercise #2 and this issue to only dispatch the most recent fetch
(react-performance/src/utils.js, see also 03.extra-2.js)

import * as React from 'react'

function useSafeDispatch(dispatch) {
  const mounted = React.useRef(false)
  React.useLayoutEffect(() => {
    mounted.current = true
    return () => (mounted.current = false)
  }, [])
  return React.useCallback(
    (...args) => (mounted.current ? dispatch(...args) : void 0),
    [dispatch],
  )
}

const defaultInitialState = {status: 'idle', data: null, error: null}

function useAsync(initialState) {
  const initialStateRef = React.useRef({
    ...defaultInitialState,
    ...initialState,
  })
  const [{status, data, error}, setState] = React.useReducer(
    (s, a) => ({...s, ...a}),
    initialStateRef.current,
  )

  const safeSetState = useSafeDispatch(setState)
  const mostRecentPromise = React.useRef(null)

  const run = React.useCallback(
    promise => {
      if (!promise || !promise.then) {
        throw new Error(
          `The argument passed to useAsync().run must be a promise. 
 Maybe a function that's passed isn't returning anything?`,
        )
      }
      safeSetState({ status: 'pending' })
      mostRecentPromise.current = promise
      return promise.then(
        data => {
          if (promise === mostRecentPromise.current) {
            safeSetState({ data, status: 'resolved' })
          }
          return data
        },
        error => {
          if (promise === mostRecentPromise.current) {
            safeSetState({ status: 'rejected', error })
          }
          return error
        },
      )
    },
    [safeSetState],
  )

  const setData = React.useCallback(data => safeSetState({data}), [
    safeSetState,
  ])
  const setError = React.useCallback(error => safeSetState({error}), [
    safeSetState,
  ])
  const reset = React.useCallback(() => safeSetState(initialStateRef.current), [
    safeSetState,
  ])

  return {
    // using the same names that react-query uses for convenience
    isIdle: status === 'idle',
    isLoading: status === 'pending',
    isError: status === 'rejected',
    isSuccess: status === 'resolved',

    setData,
    setError,
    error,
    status,
    data,
    run,
    reset,
  }
}

createResource(promise)

A generic "resource factory" to be used in conjunction with <React.Suspense />.

Useful for Render as you fetch.

See also the comment on the right.

Example usage:

import {createResource} from './utils'

function PokemonInfo({pokemonResource}) {
  const pokemon = pokemonResource.read()
  return (
    <div>
      <div className="pokemon-info__img-wrapper">
        <img src={pokemon.image} alt={pokemon.name} />
      </div>
      <PokemonDataView pokemon={pokemon} />
    </div>
  )
}
// ...
fucntion App() {
  const [pokemonResource, setPokemonResource] = React.useState(null)
  // ...
  setPokemonResource(
    createResource(fetch(pokemonName).then((response) => response.json()))
  );

  // ...
  return (
    <React.Suspense
      fallback={<PokemonInfoFallback name={pokemonName} />}
    >
      <PokemonInfo pokemonResource={pokemonResource} />
    </React.Suspense>
  )
}


from react-suspense workshop - exercise #1.extra-2 (react-suspense/src/utils.js)

// 🚨 This should NOT be copy/pasted for production code and is only here
// for experimentation purposes. The API for suspense (currently throwing a
// promise) is likely to change before suspense is officially released.
// This was strongly inspired by work done in the React Docs by Dan Abramov

function createResource(promise) {
  let status = 'pending'
  let result = promise.then(
    resolved => {
      status = 'success'
      result = resolved
    },
    rejected => {
      status = 'error'
      result = rejected
    },
  )
  return {
    read() {
      if (status === 'pending') throw result
      if (status === 'error') throw result
      if (status === 'success') return result
      throw new Error('This should be impossible')
    },
  }
}

export {createResource}

preloadImage(src)

Example usage to create a <Img /> component in conjunction with createResource(promise) from above:

function Img({src, alt, ...props}) {
    const imgSrcResource = createResource(preloadImage(src))
  return <img src={imgSrcResource.read()} alt={alt} {...props} />
}

// and the Example code from createResource(promise), 
// but with the native <img src={pokemon.image} alt={pokemon.name} //> replaced by this component


from react-suspense workshop - exercise #5 (react-suspense/src/utils.js)

function preloadImage(src) {
  return new Promise(resolve => {
    const img = document.createElement('img')
    img.src = src
    img.onload = () => resolve(src)
  })
}

export {preloadImage}