sonner icon indicating copy to clipboard operation
sonner copied to clipboard

Bug: falsy infer a http request when there is ok and Error in the return data strucutre

Open LatentDream opened this issue 1 year ago • 4 comments

Describe the feature / bug 📝:

Bug using Error in the return data strucutre of a promise. While there is no error raise, the toast take the error branch with the following message HTTP error! status: undefined

Steps to reproduce the bug 🔁:

promise data structure return

export type Result<T, E = Error> =
  | { ok: true; value: T }
  | { ok: false; error: E };

export function Ok<T, E>(value: T): Result<T, E> {
  return { ok: true, value };
}
export function Err<T, E>(error: E): Result<T, E> {
  return { ok: false, error };
}

Code to reproduce the bug

async function whatWillHappen(): Promise<Result<null, Error>> {
  return Err(new Error("Not implemented"));
}

export function useSaveProject() {
  const handle = async () => {
    toast.promise(whatWillHappen, {
    loading: "Saving project...",
    success: (result) => {
      if (result.ok) {
        return "Project saved";
      } else {
        return `Result Error: ${result.error}`;
      }
    },
    error: (e) => `Error Raise: ${e}`,
    });
  };

  return (handle);
}

This return: Error Raise: HTTP error! status: undefined

LatentDream avatar Mar 04 '24 16:03 LatentDream

There is a miss understanding. The library infer that all data structure with .ok and .error is a http request: problem here

LatentDream avatar Mar 04 '24 17:03 LatentDream

We catch errors in our backend and return a structured response with an error key to indicate an error. It'd be great to be able to escape hatch by throwing an error and propagating the specific message to the error toast.

toast.promise(response, {
	loading: messages.saveLoading,
	success: (response) => {
		if ('errorKey' in response) {
			const errorMessage = getTranslatedError(response.errorKey, lang);
			throw new Error(errorMessage ?? messages.fallbackErrorMessage);
		} else {
			return selectedEntity === ''
				? messages.successRemovedDescription
				: messages.successDescription;
		}
	},
        // catch error and deconstruct the message
	error: (e) => e.message ?? messages.fallbackErrorMessage
});

Edit: just did a little more digging, pretty easy to achieve this if anyone else stumbles here by using the id returned by the loading toast:

const id = toast.loading(messages.saveLoading);

const response = await request(request, configLocator, lang);

if ('errorKey' in response) {
	const errorMessage = getTranslatedError(response.errorKey, lang);
	toast.error(errorMessage ?? messages.fallbackErrorMessage, { id });
} else {
	toast.success(
		selectedEntity === ''
			? messages.successRemovedDescription
			: messages.successDescription,
		{ id },
	);
}

dclark27 avatar Mar 27 '24 14:03 dclark27

That works but I've noticed that we lose subtle on-mount animations during the transition process.

toast.promise(sleep(1000), {
    loading: "Loading..",
    success: "Success!",
})
const id = toast.loading("Loading...")
await sleep(1000)
toast.success("Success!", {
    id
})

https://github.com/emilkowalski/sonner/assets/24833827/b86571f5-a6bb-484b-95b6-f4031e86d113

origamisage avatar Jun 05 '24 15:06 origamisage

Found the culprit:

:where([data-sonner-toast][data-promise='true']) :where([data-icon]) > svg {
  opacity: 0;
  transform: scale(0.8);
  transform-origin: center;
  animation: sonner-fade-in 300ms ease forwards;
}

Animation only applies when the element also contains data-promise='true'.

It makes sense not to apply it to normal toast calls, since the slight scale animation wouldn't be visible due to on-mount opacity animation applied to the parent (and thus to children as well).

Easy workaround is adding a custom class, something like:

.sonner-toast-promise-flow [data-icon] > svg {
  opacity: 0;
  transform: scale(0.8);
  transform-origin: center;
  animation: sonner-fade-in 300ms ease forwards;
}

And applying it like so:

toast.success("Success", {
  id,
  duration: 1000,
  className: "sonner-toast-promise-flow",
  })

origamisage avatar Jun 05 '24 17:06 origamisage