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

execute functions when Gatsby has rendered

February 10, 2020 at 3:32am

execute functions when Gatsby has rendered

February 10, 2020 at 3:32am
I'm trying to implement locomotive-scroll on my gatsby repo.
The problem is, the scroll doesn't correctly calculate the height, I think this has to do with the timing of execution, e.g. it's executed too early and there calculates an incorrect height.
I've looked at the Gatsby Browser API and it seems {onInitialClientRender} is what I want. Now I need a reference to the element that the scroll needs to be initiated on (a DOM element or a useRef hook). I don't believe I can have access to this (correct me if I'm wrong), so thought of using Context for this. I was thinking of creating a loaded boolean and set this to true when it's loaded. Then in the component with the reference for the scroll, I create a useEffect that re-renders when loaded is set to true.
Am I overthinking this? I'm hoping there's a simpler solution :)

February 10, 2020 at 4:54pm
I'm curious why you would need a Context component to trigger a re-render. useEffect only runs when the component renders client-side, so to get the component to re-render, you would just set some state in useEffect. Doesn't necessarily have to be Context state, unless it's a different, sibling component that is mounting that needs to trigger the re-render of some other component; or maybe if it's some deeply nested child that needs to call back to a parent and you don't want to pass props way down the tree.
I'm also curious to know why you would need onInitialClientRender for this. I've never used locomotive-scroll, so I'm not entirely clear on its implementation details.
Do you happen to have an example you could share? Hard to tell precisely what is going on and troubleshoot it without a reproduction.
Edited
  • reply
  • like

February 11, 2020 at 4:59am
thanks for your reply
You're right, I won't need the context component to retrigger, was way overthinking it. locomotive-scroll is a "virtual scroll". You assign it a "scroll"-container (dom el) and it will scroll using translateY instead of the native scroll. It measures the height of the page in order to set the limit on the scroll (else you could scroll forever). This limit is incorrect and my assumption is that it triggers too early. I don't have any images set, so it's not that they're still loading. I do render out a list of items.
So I went looking for a hook in Gatsby that might be fired after all rendering is done. Which I thought onInitialClientRender would be, but I don't have access to Dom elements there.
Another challenge is that I need to pass an instance of locomotiveScroll around, to use its methods on different pages. I would only want one instance. Perhaps I should destroy it on each page and recreate it (since the content changes, it needs to be updated anyhow)
The code of locomotive-scroll is imperative, so perhaps this won't work well with React's declarative nature?
Edited
  • reply
  • like

February 12, 2020 at 2:39am
If it is imperative, and you want to pass around an instance of it, I wonder if you could make it a singleton 🤔 (assuming you can change the element it's attached to during the lifetime of the instance). You could use useRef on a base Element, and the current property should reflect the dom node. Pass that to the singleton during useEffect to assign it to the scroll container... might work? You'd have to remember to detach it when each component unmounts, otherwise you might wind up with some exceptions being thrown. If you wanted to wrap all of that up into a single function that could be used easily in every scroll container across your site, just put it all in a custom hook.
As for it not calculating the height correctly: I wonder what would happen if you used requestAnimationFrame inside of useEffect. While useEffect occurs after DOM layout (so you should theoretically be able to calculate the drawn member's dimensions) it does happen asynchronously, so perhaps it is still missing something that is needed when locomotive-scroll actually attaches to the element? requestAnimationFrame should fire after content has painted.
I'd also try useLayoutEffect just to see if maybe it makes a difference... it shouldn't, because it is like useEffect, but synchronous (so painting would wait for it to complete)
  • reply
  • like

February 24, 2020 at 1:47pm
Hi thanks so much for the suggestions. I tried the latter fews (raf & layout) but alas. Not sure what you mean with making it into a singleton and passing it around with a custom hook.. I'll try to figure it out, you have an example of something like this?
  • reply
  • like
A singleton is an object/class that exists as a single instance. With ES modules, it's easy to do because modules are only ever compiled once; any time they are accessed after that, the cached value is accessed instead of the module being compiled again. This process carries over to the browser; it's not quite the same as ES modules, but the effect is the same.
So, if I did something like this:
// foo-test.js
class Foo {
constructor() {
console.log('bar');
}
}
export const myFoo = new Foo();
This should only ever echo one 'bar', no matter how many times you import the module:
import { myFoo } from './foo-test';
So, if I wanted to make a singleton that managed something that interacts with the DOM directly, I could do something like this:
// (typescript)
class DomController {
private controller;
constructor() {
this.controller = new Foo(
/* options */
);
}
public start() {
this.controller.init(
/* options */
);
}
public stop() {
this.controller.destroy();
}
}
export default new DomController();
Now I can import that anywhere, and I will always be using the same instance. As long as I call start() from somewhere like useEffect, then it should have access to the correct dom members. This may not be particularly feasible, however... it all depends. For example, if I set up a process for when a component dismounts, it may cause a race condition with whatever component is currently trying to start() the object from its own mount event.
A custom hook is just a function, prefixed with use, that encapsulates certain functionality that utilizes React hooks. For example, I could write something like this:
import LocomotiveController from './locomotive-controller.ts';
export const useLocomotiveScroll = () => {
useEffect(
() => {
LocomotiveController.start();
return () => { // This is the function that will run on dismount.
LocomotiveController.destroy();
}
},
[] // This means this will only be run on the first render, not any subsequent renders.
);
}
Then I can use it kind of like this:
import { useLocomotiveScroll } from './my-custom-hook';
export const MyPage = () => {
useLocomotiveScroll();
return (
/* Some long scrollable component */
);
}
Obviously, all of this is very general. I don't know enough about how your project is designed to give you any more specific help. If you had a simplified reproduction you could share, I might be able to give more targeted advice.
Edited
  • reply
  • like