Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Code Block
languagejs
// .../api-src/types.ts
export interface UserData { ...myUserDataTypeDefinition }
export interface SystemData { ...mySystemDataTypeDefinition }
export interface SubmitEventData { ...mySubmitEventDataTypeDefinition }

...

2

...

Add query types for react-query queries.

Note: react-query does provide exported types here, but in general I found those a bit deeper/more complex than needed for this basic usage. In particular, I am relying on a snake_case upstream data source and converting it into camelCase for consumption. The TS wrapping of the useQuery calls in this pattern is not optimized for that specific interface, so much as it is for the consuming selectors.

...

languagejs

...

:

...

Define API hooks

Code Block
languagejs
// .../api-src/hooks/api.ts
import { useQuery, useMutation } from '@tanstack/react-query';

import { camelCaseObject } from '@edx/frontend-platform'; // or lodash
import * as types from '../types';
import { queryKeys } from '../constants';

export const useUserData = (userId: string): types.QueryData<types.UserData> => {
  const { data, ...status } =(
  useQuery({
    queryKey: [queryKeys.userData, userId],
    queryFn: () => fetch(`my-urls/user-data/${userId}`),
  });   return { ...status, data: data ? camelCaseObject(data) : {} };
}.then(camelCaseObject),
  })
);

export const useSystemData = (): types.QueryData<types.SystemData> => {
 (
const { data, ...status } = useQuery({
    queryKey: [queryKeys.systemData],
    queryFn: () => fetch('my-urls/system-data'),
  });   return { ...status, data: data ? camelCaseObject(data) : {} };
}then(camelCaseObject),
  });
);

export const useSubmit = () => useMutation({
  mutationFn: (data: types.SubmitEventData),
});

...

3: Define Selectors for the API hooks

Code Block
languagejs
// ...api-src/hooks/selectors.ts
import * as api from './api';
import * as types from './types';

// generic accessor
export const useUserDataStatus = (userId: string): types.QueryStatus => {
  const {
    isLoading,
    isFetching,
    isInitialLoading,
    status,
    error,
  } = api.useUserData(userId);
  return {
    isLoading,
    isFetching,
    isInitialLoading,
    status,
    error,
  };
};

export const useIsUserDataLoaded = (userId: string): boolean => (
  api.useUserData().status === 'success'
);
export const useUserDataResponse = (userId: string): types.UserData => (
  api.useUserData().data
);

/*
 * add more, increasingly granular UI selectors here, until all "questions"
 * asked about the data by the UI for presentation are answered in this module.
 */

...

4: Consume (Profit!)

When consuming data, ALWAYS consume with selectors. The reasoning is that this separates your UI code from the raw details of the incoming data. At all times, the consumers of the data should be asking “Questions” that can be moved around and fixed, even if the incoming data shape changes.

...