-
Notifications
You must be signed in to change notification settings - Fork 527
Typing Remote 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:
This component is comprised of at least 3 data types:
RED: pairs
BLUE: rates
GREEN: price24h
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()
}
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
?
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!!