diff --git a/components/generate/GenerateStoryComponent.tsx b/components/generate/GenerateStoryComponent.tsx index f14b59718..17fc59ea2 100644 --- a/components/generate/GenerateStoryComponent.tsx +++ b/components/generate/GenerateStoryComponent.tsx @@ -1,33 +1,13 @@ 'use-client'; -import chatOperations from 'operations/chatOperations'; -import { useState } from 'react'; import GenerateStoryContextProvider, { IGenerateStoryContext } from './GenerateStoryContext'; export default function GenerateStoryComponent() { - const [loading, setLoading] = useState(false); - const [data, setData] = useState(); - - const getStory = async () => { - setLoading(true); - const data = await chatOperations.createStoryAsync(); - setData(data); - setLoading(false); - }; - return ( - {({ story }: IGenerateStoryContext) => { + {({ story, loading }: IGenerateStoryContext) => { return (
- - {loading ?
Loading...
: null} - {JSON.stringify(data)}
); }} diff --git a/components/generate/GenerateStoryContext.tsx b/components/generate/GenerateStoryContext.tsx index 79b048c88..d7c6b82e7 100644 --- a/components/generate/GenerateStoryContext.tsx +++ b/components/generate/GenerateStoryContext.tsx @@ -1,23 +1,22 @@ 'use client'; -import { IStory } from 'operations/chatOperations'; +import chatOperations, { IStory } from 'operations/chatOperations'; import { PropsWithChildren, createContext, useContext, useMemo, useState } from 'react'; export interface IGenerateStoryContext { story?: IStory; - setStory: (story: IStory) => void; images: string[]; - setImages: (images: string[]) => void; + loading: boolean; } const GenerateStoryContext = createContext({ story: undefined, - setStory: () => {}, images: [], - setImages: () => {} + loading: false }); function GenerateStoryContextProvider({ children }: { children: PropsWithChildren }) { + const [loading, setLoading] = useState(false); const [story, setStory] = useState(); /* Note(Benson): For now images is an array of urls where each index in the array @@ -27,13 +26,26 @@ function GenerateStoryContextProvider({ children }: { children: PropsWithChildre const [images, setImages] = useState([]); const value = useMemo( - () => ({ story, setStory, images, setImages }), - [story, setStory, images, setImages] + () => ({ story, images, loading }), + [story, images, loading] ); return ( - {typeof children === 'function' ? children(value) : children} + <> + + {typeof children === 'function' ? children(value) : children} + ); } diff --git a/lib/utils/usePromiseMemo.ts b/lib/utils/usePromiseMemo.ts new file mode 100644 index 000000000..6018842b3 --- /dev/null +++ b/lib/utils/usePromiseMemo.ts @@ -0,0 +1,56 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import dependenciesMatch from 'utils/dependenciesMatch'; + +const usePromiseMemo = ( + promise: () => Promise, + nextDeps: unknown[] +): { results?: T; error?: E; loading: boolean; refetch: () => void } => { + const [results, setResults] = useState(); + const [error, setError] = useState(); + const [hasFinished, setHasFinished] = useState(false); + const dependencies = useRef(nextDeps); + + const isMounted = useRef(true); + + const checkIfPromiseIsStillValid = useCallback( + (dependenciesAtTimeOfPromise: unknown[]): boolean => { + return ( + isMounted.current && dependenciesMatch(dependenciesAtTimeOfPromise, dependencies.current) + ); + }, + [] + ); + + const run = useCallback(() => { + setHasFinished(false); + promise() + .then((r) => checkIfPromiseIsStillValid(nextDeps) && setResults(r)) + .catch((e) => checkIfPromiseIsStillValid(nextDeps) && setError(e)) + .finally(() => checkIfPromiseIsStillValid(nextDeps) && setHasFinished(true)); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, nextDeps); + + useEffect(() => { + isMounted.current = true; + dependencies.current = nextDeps; + run(); + return () => { + isMounted.current = false; + }; + // nextDeps is already a dependency of run + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [run]); + + return useMemo( + () => ({ + results, + error, + loading: !hasFinished, + refetch: run + }), + // eslint-disable-next-line react-hooks/exhaustive-deps + [hasFinished, results] + ); +}; + +export default usePromiseMemo; diff --git a/operations/chatOperations.ts b/operations/chatOperations.ts index 2dd1efdaf..aa3bd31c2 100644 --- a/operations/chatOperations.ts +++ b/operations/chatOperations.ts @@ -40,9 +40,13 @@ async function createStoryAsync( content: userPrompt } ]; - const data = await post('/api/open-ai/chat', generateRequestPayload(messages)); - // const data = await post('/api/revalidate', generateRequestPayload(messages)); - return getFunctionCallArguments(data); + try { + const data = await post('/api/open-ai/chat', generateRequestPayload(messages)); + return getFunctionCallArguments(data); + } catch (e) { + console.error(e); + throw e; + } } export default { createStoryAsync };