Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix and document client lifecycle events (onRouteUpdate...) #3399

Closed
slorber opened this issue Sep 3, 2020 · 35 comments · Fixed by #6732
Closed

Fix and document client lifecycle events (onRouteUpdate...) #3399

slorber opened this issue Sep 3, 2020 · 35 comments · Fixed by #6732
Labels
bug An error in the Docusaurus core causing instability or issues with its execution documentation The issue is related to the documentation of Docusaurus
Milestone

Comments

@slorber
Copy link
Collaborator

slorber commented Sep 3, 2020

📚 Documentation

We have client lifecycle events that allow to hook into the navigation, but it's not documented.

#1591

Example usage in the google analytics plugin:

image

@slorber slorber added help wanted Asking for outside help and/or contributions to this particular issue or PR. good first issue If you are just getting started with Docusaurus, this issue should be a good place to begin. documentation The issue is related to the documentation of Docusaurus difficulty: starter Issues that are starter difficulty level, e.g. minimal tweaking with a clear test plan. labels Sep 3, 2020
@imabp
Copy link

imabp commented Sep 4, 2020

I would like to work on this issue. Can you guide me @slorber about this.

@slorber
Copy link
Collaborator Author

slorber commented Sep 5, 2020

Hi

Unfortunately I don't have more Infos. This requires reading the source code, understanding what it does exactly, and documenting the exposed api

@saadpasta
Copy link

@slorber I like to take this up.

@imabp
Copy link

imabp commented Sep 10, 2020

@slorber can you help me with the file locations where to start
I am ready to read the source code and contribute.

@saadpasta
Copy link

saadpasta commented Sep 10, 2020

onRouteChange is a navigation hook that would help you do run something when a route change.
For E.g If you want to show a pic of a cat on every route change then you would use this hook. (A very silly example I know 😜)
Make sure you document this in Navigation Guide

@imabp
Copy link

imabp commented Sep 10, 2020

🥺 Thankyou @saadpasta for giving me the chance to contribute.😊
😅 As it will be my first contribution in Docusaurus. (if merged)

Yes I noted it. I will get back here itself, if I need any help .
Thank you once again 🤗

@slorber
Copy link
Collaborator Author

slorber commented Sep 10, 2020

@saadpasta thanks for helping @imabp :)

You can look for "onRouteUpdate" in the project's source code to find the relevant entry points to document :)

However this is a v2 feature, so this must be documented in the v2 website ;) (not in the v1 navigation guide)

I'd rather see it there: http://localhost:3000/docs/docusaurus-core

@slorber
Copy link
Collaborator Author

slorber commented Oct 6, 2020

Note: we should rather wait for documenting these things.

onRouteUpdate fires too early (before new route actually has rendered), and the PendingNavigation component implementation looks not very robust (particularly due to doing things in shouldComponentUpdate).

Until we have a better implementation, we should probably keep this undocumented.

@imabp
Copy link

imabp commented Oct 6, 2020

we can do this by putting onRouteUpdate inside componentDidMount()

But finally its actually, whenever we create a new route, ga analytics are set up for that route, so that whenever user clicks on that page, the analytics data is sent to the Google Analytics. right @slorber ?

@slorber
Copy link
Collaborator Author

slorber commented Oct 6, 2020

@imabp yes, but maybe we should wait for the page to render before sending the analytics.

onRouteUpdate inside componentDidMount()

Just tried that and it didn't work, as comparing prevProps.location to this.props.location always returned true, due to the hacky code in shouldComponentUpdate, so it never fired.

Looks like a deeper refactor is needed.

@slorber slorber changed the title Document client lifecycle events Fix and document client lifecycle events (onRouteUpdate...) Oct 6, 2020
@slorber slorber added better engineering Not a bug or feature request bug An error in the Docusaurus core causing instability or issues with its execution and removed difficulty: starter Issues that are starter difficulty level, e.g. minimal tweaking with a clear test plan. good first issue If you are just getting started with Docusaurus, this issue should be a good place to begin. help wanted Asking for outside help and/or contributions to this particular issue or PR. labels Oct 6, 2020
@imabp
Copy link

imabp commented Oct 6, 2020

Yaa we need to deep dive into this. May I know, where you looked this ?

@slorber
Copy link
Collaborator Author

slorber commented Oct 7, 2020

Note, events are inspired from Gatsby (that also has a onRouteUpdateDelayed) https://www.gatsbyjs.com/docs/browser-apis/#onRouteUpdateDelayed

@mcat
Copy link

mcat commented Jun 22, 2021

Wondering if anyone has figured out a workaround for this? We've just implemented a docs site with Docusaurus and we're at the point where we want to integrate our Segment analytics and noticed this bug. From what we can tell it seems like anyone currently using Docusaurus v2 and any of the analytics plugins are getting incorrect tracking data.

@axe312ger
Copy link

axe312ger commented Jun 23, 2021

For now I use a 0ms timeout, that worked fine, at least when I tested it on my machine 🤷

https://github.com/hashbite/consent-manager/blob/main/packages/docs/src/plugins/client-plugin.js

@Josh-Cena Josh-Cena removed the better engineering Not a bug or feature request label Jan 1, 2022
@zeropaper
Copy link

It feels to me like the naming of onRouteUpdate is misleading / confusing because it is a function that is called when the browser (DOM) location (history?) changes and that generally, in modern web apps, a "route" is considered / perceived as the "pathname" only (because you couldn't render a page containing a query or hash - SSR thingy).

@slorber
Copy link
Collaborator Author

slorber commented Jan 28, 2022

generally, in modern web apps, a "route" is considered / perceived as the "pathname" only

I can't really agree with that, this is quite subjective

because you couldn't render a page containing a query or hash - SSR thingy

It is definitively possible to SSR routes with a predefined set of query strings, I've done this in the past.

We don't provide Docusaurus guides for that and it's probably not widely used, but it is technically possible.


Note that some apis like history.listen() (https://github.com/remix-run/history/blob/dev/docs/getting-started.md#listening) (used internally) won't care about how location is changed: it will just emit whenever there's any change (including a replace event with just history entry state changed, is the URL is the same).


IMHO onRouteUpdate naming implies that it's a Docusaurus route change, and it should only fire when we detect that Docusaurus is now using a different route (from .docusaurus/routes.js file) + a different set of route params (in case someone implements dynamic routes like /product/:id which is also technically possible)

Does it make sense?

@aeneasr
Copy link
Contributor

aeneasr commented Feb 9, 2022

IMHO onRouteUpdate naming implies that it's a Docusaurus route change, and it should only fire when we detect that Docusaurus is now using a different route (from .docusaurus/routes.js file) + a different set of route params (in case someone implements dynamic routes like /product/:id which is also technically possible)

Does it make sense?

To me it makes sense :)

aeneasr added a commit to ory/docs that referenced this issue Feb 9, 2022
aeneasr added a commit to ory/docs that referenced this issue Feb 9, 2022
@matkoch
Copy link
Contributor

matkoch commented Feb 9, 2022

I'm trying to use this with a different analytics provider, but it never gets triggered. I'm a bit lost. Frontend code is not my area of expertise :/

  plugins: [
    async function myPlugin(context, options) {
      return {
        name: 'my-plugin',
        async onRouteUpdate({location}) {
          alert('foo');
//          setTimeout(() => fathom.trackPageview(), 5000);
        }
      }
    },

Any help would be greatly appreciated.

@zeropaper
Copy link

sorry @slorber for the late reply... it makes sense.

@matkoch could it be that you are missing the loading of the client side part?

For the config part we're using
https://github.com/ory/docs/blob/62abd0d6cae464adf5e7cec99878e7734eaeec86/src/plugins/analytics/index.js#L14

And in the client part we have
https://github.com/ory/docs/blob/62abd0d6cae464adf5e7cec99878e7734eaeec86/src/plugins/analytics/analytics.js#L18

@Josh-Cena
Copy link
Collaborator

@matkoch The onRouteUpdate is not a plugin lifecycle. It's a client module return value. You need to register a client module using getClientModules and return an object containing onRouteUpdate within that module.

aeneasr added a commit to ory/docs that referenced this issue Feb 10, 2022
aeneasr added a commit to ory/docs that referenced this issue Feb 10, 2022
aeneasr added a commit to ory/docs that referenced this issue Feb 10, 2022
aeneasr added a commit to ory/docs that referenced this issue Feb 10, 2022
aeneasr added a commit to ory/docs that referenced this issue Feb 10, 2022
@Josh-Cena Josh-Cena added this to the 2.0.0 milestone Feb 27, 2022
@chrtze
Copy link

chrtze commented Mar 15, 2022

For anyone interested in tracking page views in Docusaurus, we have solved it like this (using fathom analytics):

in theme/Root.js:

import React, { useEffect, useRef } from 'react';
import { useHistory } from '@docusaurus/router';
import * as Fathom from 'fathom-client';

export default function Root({ children }) {
  const history = useHistory();
  const pathname = useRef(history?.location?.pathname);

  useEffect(() => {
    Fathom.load('YOUR-FATHOM-CODE');

    return history.listen((location) => {
      if (location.pathname !== pathname.current) {
        Fathom.trackPageview({ url: `https://your-domain.com${location.pathname}` });
      }
      pathname.current = location.pathname;
    });
  }, []);

  return (
    <div>
      {children}
    </div>
  );
}

Please note that we are passing the url of the next page to the trackPageview function. Otherwise, it will track the previous page as the history.listen function fires before the actual pageload. This code is adapted from the fathom nextjs integration.

@krillboi
Copy link

So I've been implementing some analytics as well and have been using your GA plugin as a help (onRouteUpdate etc) to create my own. Now I am kinda stuck on retrieving the current version and language. Are there any hooks available for these or should I just infer them from the location?

@slorber
Copy link
Collaborator Author

slorber commented Mar 25, 2022

The client modules can't access hooks because they do not render React components

If you want to track dynamic things with a hook, there's no other choice than to add a component in the tree (for example swizzle the layout) to put a hook here afaik.

Now you can also access static things in the client modules with:

import siteConfig from '@generated/docusaurus.config';
import globalData from '@generated/globalData';
import i18n from '@generated/i18n';

Those are not documented but it's quite stable so it's probably not too unsafe to be considered as public API. It's the same data you get when you use useDocusaurusContext or useGlobalData


Regarding the data you want to access with hooks:

  • The i18n language is available in the Docusaurus context
  • The docs version can be accessed globally but it's an internal API: import {useActivePluginAndVersion} from '@docusaurus/plugin-content-docs/client'; => it's a hook that read globalData

@krillboi
Copy link

Cheers @slorber, exactly what I was looking for

@slorber
Copy link
Collaborator Author

slorber commented Apr 29, 2022

We did several changes to client module lifecycles in #6732

Read the doc at https://docusaurus.io/docs/advanced/client#client-module-lifecycles


TL.DR:

import type {ClientModule} from '@docusaurus/types';

const module: ClientModule = {
  onRouteUpdate({location, previousLocation}) {
    // this fires just after the user link click`
    return () => {
      // this fires when all the JS bundles for the page transition are ready,
      // just before React renders the next page
    }
  },
  onRouteDidUpdate({location, previousLocation}) {
    // this fires after React renders and can access to updated DOM
  },
};

export default module;

Breaking changes:

  • onRouteUpdateDelayed has been removed (breaking change)
  • onRouteUpdate is now also called on first render/hydration

Note: both onRouteUpdate and onRouteDidUpdate fire on first browser page render.

If you implement things like analytics and those analytics send pageview events on script load, it's your responsibility to filter the first call to avoid a duplicate pageview analytics event. On first event, previousLocation will be unavailable so it's easy to detect.

Note: those lifecycles fire for any location change

IE these will also fire for changes such as hash, querystring, history state... It is also your responsibility to filter calls according to your need.


Example analytics plugin implementation that:

  • ignore first call
  • only send pageview events for pathname changes
import type {ClientModule} from '@docusaurus/types';

const clientModule: ClientModule = {
  onRouteDidUpdate({location, previousLocation}) {
    if (previousLocation && location.pathname !== previousLocation.pathname) {
      emitAnalyticsEvent('pageview', {
        page_title: document.title,
        page_location: window.location.href,
        page_path: location.pathname,
      });
    }
  },
};

export default clientModule;

@tisonkun
Copy link

tisonkun commented Nov 1, 2022

Hi @slorber!

I noticed that even I use onRouteDidUpdate it seems the value of document.title is still the previous one:

image

You can read the source code here and browse online at https://town.korandoru.io/.

const module: ClientModule = {
    onRouteDidUpdate({location, previousLocation}) {
        if (ExecutionEnvironment.canUseDOM) {
            if (location.pathname != previousLocation?.pathname) {
                _paq.push(['setCustomUrl', location.pathname])
                _paq.push(['setDocumentTitle', document.title])
                _paq.push(['trackPageView'])
                console.log(`path = ${location.pathname}`)
                console.log(`title = ${document.title}`)
            }
        }
    }
}

Looking forward to your feedback >_<

@tisonkun
Copy link

tisonkun commented Nov 2, 2022

Resolved as korandoru/town@eb510df.

This seems the same as #7424 that can be common.

@slorber
Copy link
Collaborator Author

slorber commented Nov 2, 2022

Thanks @tisonkun, can you please open a dedicated issue so that we can investigate?

Not 100% sure but I believe this is due to an undocumented behavior of react-helmet-async which "defers" the updates by default, maybe for perf reasons 🤷‍♂️ or just to replicate the behavior of the original Helmet.

Using <Head defer={false}> probably fixes it

Edit: actually created the issue: #8278

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug An error in the Docusaurus core causing instability or issues with its execution documentation The issue is related to the documentation of Docusaurus
Projects
None yet
Development

Successfully merging a pull request may close this issue.