Skip to content

Typing Remote Data

Philip London edited this page Jul 16, 2020 · 3 revisions

Displaying Async Data

All data loaded asynchronously to the Wallet is treated as a RemoteDataType by convention. Some components require multiple data sources to load before being displayed. Take for example the CRYPTO_SELECTION SimpleBuy step:

Crypto selection

This component is comprised of at least 3 data types:

RED: pairs

BLUE: rates

GREEN: price24h

Fetching the data

Let's say we wanted to wait for all of the data to be loaded before displaying the component. First, we need to request the data from the backend. In some cases the data has already been loaded by another component, but let's say for now that every time we load the CRYPTO_SELECTION component we want to reload all 3 data types. We can do that in componentDidMount

componentDidMount () {
  this.props.simpleBuyActions.fetchSBPairs()
  this.props.miscActions.fetchRates()
  this.props.miscActions.fetchPrice24H()
}

Storing the data

These actions kick off a saga, let's take the fetchSBPairs saga as an example:

const fetchSBPairs = function * ({
  currency
}: ReturnType<typeof A.fetchSBPairs>) {
  try {
    // pairs state => Remote.Loading() ⏳
    yield put(A.fetchSBPairsLoading())
    const { pairs }: ReturnType<typeof api.getSBPairs> = yield call(
      api.getSBPairs,
      currency
    )
    // pairs state => Remote.Success(pairs) 😎
    yield put(A.fetchSBPairsSuccess(pairs))
  } catch (e) {
    const error = errorHandler(e)
    // pairs state => Remote.Failure(error) 😭
    yield put(A.fetchSBPairsFailure(error))
  }
}

The saga will send the pairs data type through 3 possible states, from it's default Remote.NotAsked => Remote.Loading => Remote.Success(pairs) || Remote.Failure(error)

While this happens for each of the 3 data types our component will show it's Loading state, once each data type is in a Success state our component will render the image seen above. If one of the data types fails to load we can't display our Success state, so we show our Failure component instead. But how does React know when each data type is done Loading?

Lifting our data

lift is defined as:

"lifts" a function of arity > 1 so that it may "map over" a list, Function or other object that satisfies the FantasyLand Apply spec.

So basically we are "mapping" over our remote data types and lift/react interprets this as:

  • if every remote is success => show success
  • if any remote is loading => show loading
  • if any remote is failure => show failure
  • if any remote is notasked => show notasked

Here's an example of lifting our data:

selectors.ts

export const getData = state => {
  const pairsR = selectors.components.simpleBuy.getSBPairs(state)
  const ratesR = selectors.core.misc.getRates(state)
  const price24HrR = selectors.core.misc.getPrice24HrR(state)

  return lift(
    // pass lift a function w/ our data, now in it's successful state
    (
      // Extract the 'successful' data types from our Remote Types
      pairs: ExtractSuccess<typeof pairsR>,
      rates: ExtractSuccess<typeof ratesR>
      price24H: ExtractSuccess<typeof price24HrR>
    ) => ({
      pairs,
      rates,
      price24H
    })
  )(eligibilityR, pairsR, supportedCoinsR)
}

Because of our custom type definition for lift. TypeScript will interpret the return type of getData as:

RemoteDataType<any, {
  pairs: SBPairType[];
  rates: RatesType;
  price24H: {
    change: string;
    movement: PriceMovementDirType;
  };
}>

Success!!