Resolving handleSubmit's promise by observing state change

121 Views Asked by At

I have augmented Context's dispatch similar to what is described in this blog post. In addition, much like Redux, I can create a middleware chain which all my actions will be run through. Based on this middleware sample from Real World Redux, I have a middleware that centrally handles all API consumption.

This means that my components dispatch actions to initiate API requests, and can not easily observe the promise that represents that async operation. As such, I can't pass that promise to RFF's handleSubmit. Instead, I need to create my own async function to pass to handleSubmit and resolve its promise by observing changes in application state.

@erikras created the Redux Promise Listener which solves a similar problem using middleware observing dispatched actions.

I took a first pass at creating a React hook that would settle promises by observing state changes, but it feels awkward:

/**
 * usePromiseSettler invokes an async executor function, and then will observe changes in
 * state to determine when the async function's promise has settled. Once settled,
 * either the success or failure callback will be invoked.
 *
 * @param executor Function that initiates state change we wish to observe
 * @param successCondition Condition that indicates executor operation succeeded
 * @param successCallback Callback to invoke when executor operation succeeds
 * @param failureCondition Condition that indicates executor operation has failed
 * @param failureCallback Callback to invoke when executor operation fails
 * @returns An async function returning a promise which will be settled when
 * a success or error condition is observed.
 */
export const usePromiseSettler = (
  executor: (...args: any[]) => any,
  successCondition: boolean,
  successCallback: (value?: any) => void,
  failureCondition: boolean,
  failureCallback: (value?: any) => void,
) => {
  const resolveRef = useRef<(value?: any) => void>();
  const successConditionRef = useRef<boolean>(false);
  const failureConditionRef = useRef<boolean>(false);

  const staticExecutor = React.useCallback(executor, []);

  const handlePromise = React.useCallback(
    (value) => {
      staticExecutor(value);
      return new Promise((resolve) => {
        resolveRef.current = resolve;
      });
    },
    [staticExecutor],
  );

  if (!successConditionRef.current && successCondition) {
    successCallback(resolveRef.current);
  }

  if (!failureConditionRef.current && failureCondition) {
    failureCallback(resolveRef.current);
  }

  successConditionRef.current = successCondition;
  failureConditionRef.current = failureCondition;

  return handlePromise;
};

Usage:

const previousDidCreateSucceed = usePrevious(
    state.targetState.didCreateSucceed,
  );

const previousError = usePrevious(state.targetState.error);

const handleSubmit = usePromiseSettler(
    (target: TargetData) => {
      const action = requestPostTarget(target);
      dispatch(action);
    },
    !previousDidCreateSucceed && state.targetState.didCreateSucceed,
    (resolve) => {
      resolve(state.targetState.data);
      history.push("/targets");
    },
    !previousError && !!state.targetState.error,
    (resolve) => {
      resolve({ [FORM_ERROR]: state.targetState.error });
    },
  );

  ...
  return (
      <Form
        onSubmit={handleSubmit}

Has this problem been solved elsewhere with less complexity?

0

There are 0 best solutions below