Async Setters and Getters in Javascript Classes for Managing Dynamiccaly Mounted Components in React
Yes, it is a mouthful title, but I started with "async / await in classes". Then after started to think some examples it elevated quickly, sorry for that. So lets get to it.
When trying to write some complex animations on Next.js (or any other react framework) it is a good idea to wrap your component's animation functions with a class. But we know that writing an animation without blocking means writing asynchronous functions. And if you are using requestAnimationFrame with Next.js than you must have a class with async methods.
class Animator{
constructor(){
...
}
async doSomethingCool(){ // Its ok to use...
...
}
get async x(){ // Come on JS, what about this?
...
}
}
Its ok to use async method syntax in javascript but when you try to write async getter or setter in class, javascript doesnt understand what you are saying, so much for the syntactic sugar. We have to find another way to async/await in getters and setters.
As we know that async functions returns a promise in Javascript, so now we have to return a promise manually from our getter/setter methods. If you have a C# background and love to use async / await syntactic sugar like myself, you have to return a function that returns a promise and function must be invoked immediately. Generally I dont really like to use IIFE, but sometimes its convenient to use it.
Recommended by LinkedIn
class Animator
constructor(){
...
}
async doSomethingCool(){ // Its ok to use...
...
}
get x(){
return( async function(){
// Dont forget to try catch!
})();
}
}
Now you are all set for to get or set :) but... if you ever want to use "this" keyword as a class reference, than you have to use an arrow function. So let me show you and my future self a cool way to reference generically mounted components in Next.js that I used in one of my projects;
// You can define this class inside main screen or create a seperate file
// and import into your main view
class Animator{
constructor(){
...
// Create an unique id for the component
this.uid = Math.random()
.toString(36)
.replace(/[^a-z]+/g, "");
// Mounting detection key
this.loading = true;
}
...
// Here we wait for the component render
async waitForLoad() {
return new Promise((resolve) => {
const interval = setInterval(() => {
const el = document.querySelector("#" + this.uid);
if (el) {
this.loading = false;
clearInterval(interval);
resolve("OK");
}
}, 100);
});
}
get x(){
return (async () => {
if (this.loading) await this.waitForLoad();
const el = document.querySelector("#" + this.uid);
return parseFloat(getComputedStyle(el).getPropertyValue("--x"));
})();
}
}
// And in your main function
...
export default function Home(){
...
async function addAnimator(){
const newAnimator = new Animator(...);
console.log(await newAnimator.x);
}
...
useEffect(()=>{
addAnimator();
return () => { // Dont forget to clean up ;)
...
}
},[]);
...
return(
...
// You should create a animatorList with useState and add new animators to
// this array, its up to you how to do it... (Button press, trigger, etc..)
{animatorList.map(animator)=>{
return(
<AnimationComponent // Your component for the animating object
...
id={animator.uid} />
);
}}
);
So we can decouple the animations and the animating components by using asynchronous getter and setters in the class. If you are puzzled anywhere dont be shy just ask by the way, I will be happy to talk about it. Happy coding... :)\m/