pshrmn / curi

A JavaScript router for single-page applications
https://curi.js.org
MIT License
269 stars 10 forks source link

Apollo Integration - Prefetching #240

Closed martavis closed 4 years ago

martavis commented 4 years ago

I am integrating apollo into the routes file and am stumbling upon an issue with programmatically navigating. I have this code in my route, call it routeB.

resolve({ params }, client) {
    return client.query({
        query: GET_SOMETHING,
        variables: { userId: params ? params.userId : userId } // if I don't send the userId, get the userId from the top of the file
    });
},
respond({ error, resolved }) {
    return {
        body: ResponseBody,
        data: resolved.data.getSomething[0].prop[0]
    };
}

On one page (routeA), I submit a form and want to navigate to routeB programmatically using router.navigate. My first instinct was to do as normal router.navigate({ url });. But upon doing that, the resolve for routeB above didn't get the data.

Then I finally found the prefetch module but I'm stuck on how to use it. Basically, after the data has been pre-fetched, how do I send the data from the resolved response to routeB without triggering the resolve function in the route file?

This prefetch code is in routeA but I don't know what to do after it resolves, and there is no documentation on it.

 prefetch(
    pRoute, 
    { match: { params: { userId: userId }}, 
    external: router.external 
}).then((resolved: any) => {
    console.log(resolved); 
});

Or am I doing this all wrong? Thanks for the help!

martavis commented 4 years ago

Still trying to understand what to do. I started going down the rabbit hole of researching Curi's code and I basically almost reenacted what happens in the CreateRouter function. It seemed like too much. So I'm taking a step back...

Looking at the documentation, I'm mimicking this function: image

What would go into the then callback that would allow me to navigate to my new route with the data that was resolved?

pshrmn commented 4 years ago

I don't think that prefetch is actually what you want here.

prefetch is used for prefetching content for another route that the user might navigate to (i.e. loading code split routes early). The resolve function is always called when the route matches, so there needs to be some level of caching to prevent duplicate network requests (import() caches requests). I should probably update the docs/write a guide on using that.


As for your problem, are you saying that the resolved value in route B's respond function doesn't have the correct data? Your route definition looks correct to me. Do you see the network request for the query in dev tools? Does this issue only exist when you programmatically navigate (i.e. does it work if you navigate directly to that location)?

I'm trying to get a sense of the problem since what you shared looks fine to me. I haven't touched graphql in a minute, but I may try to whip together an example this weekend.

martavis commented 4 years ago

Your questions jogged some ideas. I verified that it was a race condition by placing a wait function (wrapped setTimeout) just before the route's query for 5 seconds, and the data was retrieved after programmatically navigating.

At that point, I realized that this was more of an Apollo issue and, lo and behold, it was a user error 😑. I'm fairly new to Graphql/Apollo but the gist of it is I didn't make use of the loading function provided by the useMutation hook. Once I tapped into that, I was able to effectively know when the mutation completed. FYI, the loading function completion is not the same as when await mutationName finishes...which is a different thought process than using a REST API. So here's the code to help someone understand someday:

const [isNewTeamCreated, setIsNewTeamCreated] = useState<boolean>(false);
const [createTeam, { loading: isCreateTeamLoading }] = useMutation(CREATE_TEAM);

useEffect(() => {
    // isNewTeamCreated is listening to when the await call is finished, since 
    // isCreateTeamLoading is false on page load

    if (!isCreateTeamLoading && isNewTeamCreated) {
        let url = router.url({ name: 'AddOrganizationInfo',  });
        router.navigate({ url });
    }
}, [isCreateTeamLoading, isNewTeamCreated]);

const onSubmit = async () => {
    // wrapped in try/catch of course
    const newTeamId = cryptoRandomString({ length: 16 });
    await createTeam({
        variables: {
            objects: {
                admins: `{${userId}}`, // db syntax - arrays use curly braces
                creatorId: userId,
                members: `{${userId}}`,
                seatCount: planHeadCount,
                teamId: newTeamId
            }
        }
    });

    setIsNewTeamCreated(true);
};

Thank you for your help (again) 🙌

martavis commented 4 years ago

Unfortunately, I was mistaken. I didn't fix the issue but it still can be classified as a race condition to no fault of Curi.