/use-data

A React hook for async fetching of data, data manipulation, and take latest vs take every functionality.

Primary LanguageTypeScriptMIT LicenseMIT

Use Data Build Status NPM Version

  1. Fetches data from an async function
  2. Allows you to manipulate the data à la Redux
  3. Will throw out outdated API calls by default

Basic usage:

import React, {FC} from 'react';
import useData from 'use-data';
import {someApi} from './someApi';

const FunctionalComponent: FC<{someProp: string}> = ({someProp}) => {
  const {loading, error, data} = useData(() => someApi(someProp));

  if (error) return <p>Error...</p>;
  if (!data || loading) return <p>Loading...</p>;
  return <p>{data.someString}</p>;
};
Expand for more advanced usage:
import React, {FC, useEffect} from 'react';
import useData from 'use-data';
import {getUser} from './getUserAPI';

const FunctionalComponent: FC<{userId: string}> = ({userId}) => {
  const {loading, error, data, fireFetch, setData} = useData(
    () => getUser(userId),
    {username: '', age: 0},
    {fireOnMount: false, takeEvery: true},
  );

  useEffect(() => {
    // Wait for userId to fetch (see fireOnMount is false).
    if (userId) fireFetch();
  }, [fireFetch, userId]);

  const handleSetUsername = () => {
    // Uses the function option for newData in order to only change username.
    setData(oldUser => ({...oldUser!, username: 'John Doe'}));
  };

  if (error) return <p>Error...</p>;
  if (!data || loading) return <p>Loading...</p>;
  return (
    <>
      <p>{data.username}</p>
      <button onClick={handleSetUsername}>
        {"Set username to 'John Doe'"}
      </button>
    </>
  );
};
Expand for setting up hook with Context:
import React, {createContext, FC} from 'react';
import useData, {StatusObject} from 'use-data';
import {getName} from './getNameAPI';

interface ContextData {
  firstName: string;
  lastName: string;
}

export const NameContext = createContext<StatusObject<ContextData>>({
  loading: false,
  error: new Error('No Provider'),
  data: null,
  fireFetch: () => new Error('No Provider'),
  setData: () => new Error('No Provider'),
});

export const NameProvider: FC = ({children}) => {
  const statusObject = useData(getName);

  return (
    <NameContext.Provider value={statusObject}>{children}</NameContext.Provider>
  );
};

API

The useData hook is the only non-type export. The type definition is as follows:

declare function useData<D>(
  asyncFetch: () => Promise<D>,
  initialData?: D,
  options?: UseDataOptions,
): StatusObject<D>;

If you are using this in a TypeScript project, you do not need to provide a type for the generic D, as it will be automatically parsed from the return type of asyncFetch. However in some cases it may be desirable to define it (see more information in the initialData section).

It accepts three arguments:

asyncFetch - () => Promise<D>

Any async fetch function that you want to use to fetch data from. The type will be automatically parsed from the return type.

Example usage:

// If your function takes no arguments, you can pass it in directly:
const {loading, error, data} = useData(asyncFetch);

// If it requires arguments, wrap it in an arrow function:
const {loading, error, data} = useData(() => asyncFetch(someValue));

initialData - D

Initial data must match the return type of asyncFetch, or the generic D. For example, the following will result in an error:

const {loading, error, data} = useData(
  async () => {
    return {a: {b: 'b contents'}};
  },
  {a: null},
);

This is because it expects initialData to be of type { a: { b: string; }; }. To fix this, you will need to define the generic:

const {loading, error, data} = useData<{a: null | {b: string}}>(
  async () => {
    return {a: {b: 'b contents'}};
  },
  {a: null},
);

This will allow the property a to be either null or { b: string; }.

options - UseDataOptions

Options is an optional object that has the following structure:

export interface UseDataOptions {
  fireOnMount?: boolean;
  takeEvery?: boolean;
}
  1. fireOnMount - Default: true. Should the hook fire the asyncFetch on mount.
  2. takeEvery - Default: false. Should the hook take every call rather than throwing out active calls when new ones are made.

@returns - StatusObject<D>

The hook returns an object with 5 properties:

  1. loading - True if currently fetching.
  2. error - The error object if your fetch fails, or null if not failed.
  3. data - The data from your async fetch, or null if not fetched.
  4. fireFetch - Fire the async function that was provided to useData. You may pass it an async function to call instead of asyncFetch,
  5. setData - Mutate the data. Either a function that takes old data and returns the new data, or data of type D. Calling this will turn error to null. Additionally takes a parameter to stop loading when called (loading will continue by default).
interface UseDataState<D> {
  loading: boolean;
  error: Error | null;
  data: D | null;
}

interface StatusObject<D> extends UseDataState<D> {
  fireFetch: (newAsyncFetch?: () => Promise<D>) => void;
  setData: (
    newData: D | ((oldData: D | null) => D),
    stopLoading?: boolean,
  ) => void;
}