Issue
I am new to typescript and I've been wrapping my head around an issue for a couple of hours.
To make it simple, the scenario is that I have a react custom hook which receives some arguments and in the body of the hook I define a class in whose constructor I use the arguments passed to the hook.
In case it's relevant, in the constructor I also use variables that are derived from the arguments of the hook (eg: the hook receives an id
, then finds the corresponding DOM element and finally the element is used in the constructor of the class; it is not passed as argument to the constructor, it is just used cause it already exists in the scope of the hook).
Now the issue is to move the declaration of the class outside of the hook (cause I declared tons of types for the hook and I am moving all of them into another file).
The only approach I was able to find in a couple of hours alone with Google, that maybe could work is to add parameters to the constructor corresponding to the variables that were available inside of the hook and then in the hook bind the constructor with the available variables (passing the arguments directly when creating an instance of the class would mean having redundant code duplication, also because those variables are literally constants).
I suppose that this issue is common enough that most TypeScript developers had to face and resolve it at least once in their life, so I would like to know what are the options here? Do I have to go for binding to the constructor? What other approaches could work?
EDIT:
Below there is some code that should reproduce the logic of my hook well enough.
import { useEffect, useRef } from 'react';
type SomeFancyType = { a: string; b: boolean };
const defaultConfig = { a: 'default', b: true } as SomeFancyType;
function MyCustomHook(id: string, anotherId: string, configObject: SomeFancyType) {
const elementRef = useRef<HTMLElement | null>(null);
class ThatsMyIssue {
element: HTMLElement | null;
constructor(id = '') {
const candidate = document.getElementById(id) as HTMLElement | null;
this.element = elementRef.current!.contains(candidate) // && another check involving `configObject`
? candidate
: null;
}
triggerClick() {
this.element?.click();
}
}
useEffect(() => {
elementRef.current = document.getElementById(id);
if (elementRef.current) 'all good';
}, []);
useEffect(() => {
const element = new ThatsMyIssue(anotherId);
if ('some condition') element.triggerClick();
const anotherElement = new ThatsMyIssue('sourceNotEasyToReproduce');
if (anotherElement) 'very good';
}, [elementRef.current]);
}
export default MyCustomHook;
I need to move the class ThatsMyIssue
outside of the hook.
The problem is that the class constructor is using elementRef.current
and configObject
, which are provided as arguments to the hook.
The solution I found is to modify the constructor to accept those variables as argument:
constructor(id = '', elementRef: HTMLElement | null, configObject: SomeFancyType) { ... }
And than in the hook bind the variables to the constructor, so that than I can create instances by providing only the first argument, (which is the only one that actually changes):
const BindedConstructor = ThatsMyIssue.bind(null, elementRef.current, configObject || defaultConfig);
const element = new BindedConstructor(anotherId);
const anotherElement = new BindedConstructor('sourceNotEasyToReproduce');
But I wonder if there is another way to solve the issue.
I am just starting off with TypeScript and I don't want to teach myself how to overcomplicate things, while the JavaScript community is possibly using some well thought pattern for this kind of scenarios.
Solution
Well, yeah, if you want to use values from a different scope, you have to pass them.
But rather than binding the constructor, which is sort of complicated and strange, I'd probably just declare a function that knows how to instantiate these objects, and then just call that function.
function MyCustomHook(id: string, anotherId: string, configObject: SomeFancyType) {
const elementRef = useRef<HTMLElement | null>(null);
function makeThing(id: string): ThatsMyIssue {
return new ThatsMyIssue(id, elementRef)
}
useEffect(() => {
const element = makeThing(anotherId);
if ('some condition') element.triggerClick();
const anotherElement = makeThing('sourceNotEasyToReproduce');
if (anotherElement) 'very good';
}, [elementRef.current]);
}
Answered By - Alex Wayne
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.