menu

Gatsby.js

Fast in every way that matters. Gatsby is a free and open source framework based on React that helps developers build blazing fast websites and apps.

Channels
# All channels
view-forward
# General
view-forward
# I Made This
view-forward
# Meta
view-forward
# Themes
view-forward
Team

Update provider's location from onRouteUpdate

February 18, 2020 at 2:42pm

Update provider's location from onRouteUpdate

February 18, 2020 at 2:42pm (Edited 2 months ago)
Hi, I'm trying to set a listener for location change in gatsby-browser.js and updating the provider's value for using in other components, The code I'm currenlty using that didnt works:
import Provider,{ appContext } from './provider';
export const onRouteUpdate = ({ location, prevLocation }) => {
console.log('new pathname', location.pathname)
console.log('old pathname', prevLocation ? prevLocation.pathname : null)
// wanna set the new location for provider to use in other pages
// this code does not work
return(
<appContext.Consumer>
{context => {
context.changeLocation(location.pathname)
}})
</appContext.Consumer>
)
}

February 18, 2020 at 4:59pm
If you look here, you'll see how the onRouteUpdate and onPreRouteUpdate endpoints are called. The reason your code will not work is because it's not in a render block. Pay close attention to that module, though, because the details in it will get you where you need to be (for instance, look at where it uses the <Location> context)
Also, for more context, check out root.js to see where that RouteUpdates component is used. As you can see, it is rendered just inside the <Root /> element, so in order to have access to your own <Context> element, your Context.Provider would need to be added in the wrapRootElement endpoint. However, that still doesn't give you access to it in onRouteUpdate.
One easy way, that is also a single-entry point like onRouteUpdate, would be to add everything in wrapPageElement. I have a single wrapPageElement file that I export in both gatsby-browser, and gatsby-ssr that creates a global layout component (i.e. one that doesn't mount/unmount on each route change). To implement something like you're trying to do, I added a <Location /> context consumer to gatsby-browser:
// ./src/wrapPageElement.tsx
import { GatsbyBrowser } from 'gatsby';
import React from 'react';
import Layout from './layouts/main';
export const wrapPageElement: GatsbyBrowser['wrapPageElement'] = ({ element }) => (
<Layout>{element}</Layout>
);
// ./gatsby-browser.tsx
import { GatsbyBrowser } from 'gatsby';
import React from 'react';
import { Location } from '@reach/router';
import { wrapPageElement as WrapPageElement } from './src/wrapPageElement';
export const wrapPageElement: GatsbyBrowser['wrapPageElement'] = (props) => {
return (
<Location>
{(locationContext) => {
console.log(locationContext);
return <WrapPageElement {...props} />;
}}
</Location>
);
};
// ./gatsby-ssr.ts <- just for reference, to see how wrapPageElement is used without the extra <Location>
export { wrapPageElement } from './src/wrapPageElement';
Since wrapPageElement is underneath the <Root /> component, and therefore underneath the <Location> context provider, I am able to consume route changes there. Since this also wraps your Page components, it is a perfect place to also add your custom Context.Provider (unless you want to put that in wrapRootElement). You could put it above <Location>, along with a consumer, then include code inside of the <Location> component to handle updating your custom Context.
like-fill
1
  • reply
  • like
Or, you could add the locationContext to the props sent to WrapPageElement:
// ./gatsby-browser.tsx
import { GatsbyBrowser } from 'gatsby';
import React from 'react';
import { Location } from '@reach/router';
import { wrapPageElement as WrapPageElement } from './src/wrapPageElement';
export const wrapPageElement: GatsbyBrowser['wrapPageElement'] = (props) => {
return (
<Location>
{(locationContext) => {
console.log(locationContext);
return <WrapPageElement {...props} locationContext={locationContext} />;
}}
</Location>
);
};
// ./src/wrapPageElement.tsx
import { GatsbyBrowser, WrapPageElementBrowserArgs } from 'gatsby';
import { LocationContext } from '@reach/router';
import React from 'react';
import Layout from './layouts/main';
interface IWrapPageElementProps extends WrapPageElementBrowserArgs {
locationContext?: LocationContext;
}
export const wrapPageElement: GatsbyBrowser['wrapPageElement'] = ({ element, locationContext }: IWrapPageElementProps) => (
<Provider>
<appContext.Consumer>
{(context) => {
if (locationContext) {
context.updateRoute(locationContext.location.pathname);
/** Do something with new location */
}
return <Layout>{element}</Layout>;
}}
</appContext.Consumer>
</Provider>
);
);
Something like that
Edited
  • reply
  • like

February 19, 2020 at 6:14am
If you look here, you'll see how the onRouteUpdate and onPreRouteUpdate endpoints are called. The reason your code will not work is because it's not in a render block. Pay close attention to that module, though, because the details in it will get you where you need to be (for instance, look at where it uses the <Location> context)
Also, for more context, check out root.js to see where that RouteUpdates component is used. As you can see, it is rendered just inside the <Root /> element, so in order to have access to your own <Context> element, your Context.Provider would need to be added in the wrapRootElement endpoint. However, that still doesn't give you access to it in onRouteUpdate.
One easy way, that is also a single-entry point like onRouteUpdate, would be to add everything in wrapPageElement. I have a single wrapPageElement file that I export in both gatsby-browser, and gatsby-ssr that creates a global layout component (i.e. one that doesn't mount/unmount on each route change). To implement something like you're trying to do, I added a <Location /> context consumer to gatsby-browser:
// ./src/wrapPageElement.tsx
import { GatsbyBrowser } from 'gatsby';
import React from 'react';
import Layout from './layouts/main';
export const wrapPageElement: GatsbyBrowser['wrapPageElement'] = ({ element }) => (
<Layout>{element}</Layout>
);
// ./gatsby-browser.tsx
import { GatsbyBrowser } from 'gatsby';
import React from 'react';
import { Location } from '@reach/router';
import { wrapPageElement as WrapPageElement } from './src/wrapPageElement';
export const wrapPageElement: GatsbyBrowser['wrapPageElement'] = (props) => {
return (
<Location>
{(locationContext) => {
console.log(locationContext);
return <WrapPageElement {...props} />;
}}
</Location>
);
};
// ./gatsby-ssr.ts <- just for reference, to see how wrapPageElement is used without the extra <Location>
export { wrapPageElement } from './src/wrapPageElement';
Since wrapPageElement is underneath the <Root /> component, and therefore underneath the <Location> context provider, I am able to consume route changes there. Since this also wraps your Page components, it is a perfect place to also add your custom Context.Provider (unless you want to put that in wrapRootElement). You could put it above <Location>, along with a consumer, then include code inside of the <Location> component to handle updating your custom Context.
Thanks for complete explanation 👍
  • reply
  • like