Home > database >  Interface with default key value types that can be extended TypeScript
Interface with default key value types that can be extended TypeScript

Time:02-11

In many of my projects, I have a common pattern which repeats over and over again.

type RequestStatus =
  | 'pending'
  | 'requesting'
  | 'successful'
  | 'failed'

type AValue = string | undefined

type OtherValue = number

interface State {
  requestStatus: RequestStatus;
  aValue?: AValue;
  otherValue?: OtherValue;
}

const [requestState, setRequestState] = useState<State>({
  requestStatus: 'pending',
  aValue: 'aValue',
  otherValue: 11
});

const appendToState = (state: Partial<State>) =>
  setRequestState(previousState => ({ ...previousState, ...state }));

In such a scenario, I am fetching values and changing the UI based on the requestStatus and the values.

This repeats over and over again in my projects and in an attempt to reduce the repetitions, I am considering wrapping all this in a hook. My main problem is dealing with types.

This was one of my attempts.

export const useExtendedRequestStatus = <T>(state: T) => {
  const [requestState, setRequestState] = useState<{ requestStatus: RequestStatus } & T>({
    requestStatus: 'pending',
    ...state,
  });

  const appendToState = (state: Partial<T>) =>
    setRequestState(previousState => ({ ...previousState, ...state }));

  return { requestState, setRequestState, appendToState };
};

And i would implement it like this

const {
  requestStatus,
  appendToState
} = useExtendedRequestStatus<{ requestStatus: RequestStatus; aValue: AValue }>({
  aValue: aValue,
  requestStatus: 'successful',
});

It works but not perfectly. There are instances if I am statically type checking the hook, I have to re-define the RequestStatus type which is already defined in the hook. I am wondering if there is a way, RequestStatus will still be in the hook when manually type checking without having to re-define it.

I am open to any ideas.

CodePudding user response:

You indeed use a T generic in your useExtendedRequestStatus custom hook, which describes the possible members of the state you want to use it with.

But since you specify that the state argument, when you call your custom hook, is of type T directly, if you want it to include an initial requestStatus, then you are forced to also mention this requestStatus member in your explicit concrete type (of course you could rely on automatic type inference, but I guess you have your reasons to manually type check).

You can easily "embed" this predefined member in your custom hook, and have it available for your argument (same for appendToState), in a very similar manner you have already done with your inner useState<{ requestStatus: RequestStatus } & T>({...}): simply specify that your state argument is also of type T & { requestStatus: RequestStatus } (or maybe even better T & Partial<{ requestStatus: RequestStatus }> in case requestStatus can be initially omitted, as suggested by your initial default value in your custom hook). That way, T no longer needs to contain the requestStatus member. Same for appendToState.

interface IRequestStatus {
  requestStatus: RequestStatus;
}

export const useExtendedRequestStatus = <T>(
  state: T & Partial<IRequestStatus> // Initial state can contain requestStatus, even if not mentioned in concrete T
) => {
  const [requestState, setRequestState] = useState<IRequestStatus & T>({
    requestStatus: 'pending',
    ...state,
  });

  const appendToState = (state: Partial<T & IRequestStatus>) =>
    setRequestState((previousState) => ({ ...previousState, ...state }));

  return { requestState, setRequestState, appendToState };
};

Now in your React functional component, you can use it like this:

const { requestState, appendToState } = useExtendedRequestStatus<{
  //requestStatus: RequestStatus; // Can now be omitted
  aValue: AValue;
}>({
  aValue: aValue,
  requestStatus: 'successful', // Okay
});

requestState.requestStatus; // Okay

appendToState({
  requestStatus: 'successful', // Okay
});

Demo: https://stackblitz.com/edit/react-ts-eyyzhg?file=Hello.tsx

  • Related