Importing namespace and interface with methods in TypeScript (TSX) in pdfjs-dist (PDFJS) - reactjs

I am trying to use pdfjs-dist in my React project, but get a lot of problems trying to import the module and the functions in the project.
The pdfjs-dist module index.d.ts in #types/node_modules is defined so that it contains a namespace "PDF" and a module "pdfjs-dist" which exports "PDF".
The file has interfaces, which contains methods such as "getDocument(name:string)" which I want to call from my other classes.
In short; the file consists of a lot of interfaces and methods that are implemented through this interface, on the form:
declare module "pdfjs-dist" {
export = PDF;
}
declare namespace PDF {
interface PDFJSStatic {
getDocument(
source: string,
pdfDataRangeTransport ? : any,
passwordCallback ? : (fn: (password: string) => void, reason: string) => string,
progressCallback ? : (progressData: PDFProgressData) => void): PDFPromise < PDFDocumentProxy > ;
}
I have tried to use the regular import statements, such as:
import * as PDF from "pdfjs-dist"
and
import { PDFJSStatic } from "pdfjs-dist"
However, it does not seem to respond very well. VS Code gives me all the interfaces, so I can see what they are, but this is where my knowledge of React and Typescript falls a bit short.
How would I go about calling the methods and actually using the "getDocument()" method?

For some reason the fix seems to be to import the interface first, so that the PDFJSStatic and other interfaces are available when using the require statement on line 2.
The import statements that I used were;
import { PDFJSStatic, PDFPageProxy } from "pdfjs-dist";
let PDFJS: PDFJSStatic = require("pdfjs-dist");
This is probably not the correct way of doing it, but it works.

Related

Using external type definitions with some types not exported

I am using typescript in ReactJs together with Leaflet library (for displaying world maps). I needed to provide the typescript definitions for the library, but they already exist, so I installed them as a node module under #types.
In some place the library requires type of:
(feature: Feature<Geometry, any>, layer: Layer) => void
so I thought it will not be a problem, I created such function:
import L from 'leaflet';
...
function f(feature: L.Feature<L.Geometry, any>, layer: L.Layer){}
the Layer type works without any problems, but for Feature or Geometry I get:
Namespace '"/.../node_modules/#types/leaflet/index"' has no exported member 'Feature'
so I checked what is inside the type definition file:
export as namespace L;
import * as geojson from 'geojson';
...
onEachFeature?(feature: geojson.Feature<geojson.GeometryObject, P>, layer: Layer): void;
it's hidden under geojson, but when I try to access it, like L.geojson.Feature I get has no exported member named 'geojson', so I changed my own code to:
import L from 'leaflet';
import * as geojson from 'geojson';
...
(feature: geojson.Feature<geojson.Geometry, any>, layer: L.Layer
and it works but I am confused, because I thought you don't import type definitions, but only your code and the types living in x.d.ts will be imported for you (this is how the type L.Layer works). However, there is no geojson module in the main node_modules, it's nested under #types module. Am I doing something wrong here?
I thought you don't import type definitions
Actually we do, it is just that TypeScript does it automatically for us with the actual import:
import L from 'leaflet'; // Bundler will import the Leaflet library,
// while TypeScript will look for types in the library, or in "#types"
There are several tsconfig modules options to configure this automatic behaviour, like types, typeRoots (by default the #types folder) and paths (for aliases).
As you figured out, it is also possible to import just types. You can use import type to make it explicit (and to hint the bundler that there is no code to actually import):
import type * as geojson from 'geojson';
It is also possible to access non exported types by deep access:
type TonEachFeature = L.GeoJSONOptions["onEachFeature"];
// ^? ((feature: geojson.Feature<geojson.GeometryObject, any>, layer: Layer): void) | undefined
// You can type the function and let TS infer the argument types automatically
const myOnEachFeature: TonEachFeature = (feature, layer) => {
feature
// ^? geojson.Feature<geojson.GeometryObject, any>
layer
// ^? L.Layer
}
And using utility types:
function myOnEachFeature2(feature: Parameters<NonNullable<TonEachFeature>>[0], layer: L.Layer) {
feature
// ^? geojson.Feature<geojson.GeometryObject, any>
}
Playground Link

Unable to get type-safety (CustomTypeOptions) working with react-i18next

I recently successfully implemented using react-18next for localization needs inside my app. I have a small package that contains the localization files, react-i18next setup, and exports a class which is referenced in another application to get the i18n instance and pass it to the which wraps my components.
This has been deployed and works as expected.
I stumbled upon the documentation here (https://react.i18next.com/latest/typescript#create-a-declaration-file), which says that you are able to make the t function fully type safe. I would love to implement this, so that I am able to catch mis-matched key errors at compilation time, rather than needing to hunt for each case within the application.
I am having some trouble achieving this desired type-safety though, and wasnt sure if it was something that I am doing wrong or possibly a bug in the typing (I assume the former, as others seem to get the safety working without any issue).
Versions:
react-i18next “^11.15.6”
i18next “^21.6.14”
react “16.14.0”
typescript 4.1+
Repo structure (excluding package.json, tsconfig.json, etc) :
src
translations
translations_en.json
translations_es.json
MyTranslationManager.ts
react-i18next.d.ts
The translation files do not use any nested strings, and are separated by language (”_en” vs “_es”). Each language has all the needed strings in their localized format. The files are in this format:
{
"string1": "First string",
"string2": "Second string"
}
In my live (working) setup, this is how I initialize my instance:
import translationEN from "./translations/translations_en.json";
export class MyTranslationManager {
private readonly i18nInstance: i18nType;
constructor() {
this.i18nInstance = i18n.createInstance();
const defaultResources = {
en: { translation: { ...translationEN } },
};
this.language = "en";
this.i18nInstance
.use(initReactI18next)
.init({
resources: defaultResources,
lng: "en",
keySeparator: false, // we do not use nested translation resources
interpolation: {
escapeValue: false, // React already prevents XSS
},
});
}
// WORKING ON TYPE SAFETY
As directed in the docs, I create a react-i18next.d.s file to redeclare the “react-i18next” module - specifically the CustomTypeOptions interface:
import "react-i18next";
import translationEN from "./translations/translations_en.json";
declare module "react-i18next" {
interface CustomTypeOptions {
resources: typeof translationEN;
}
}
I do not declare a “defaultNS” option to CustomTypeOptions because I rely on the default namespace, “translation”.
When I attempt to compile the project with the above code, I get the following TS2344 issue:
node_modules/react-i18next/ts4.1/index.d.ts:203:25 - error TS2344:
Type 'string' does not satisfy the constraint 'Namespace<"btn_cancel" | "btn_save" | ... 86 more ... | "msg_unsavedChanges">'.
203 N extends Namespace = DefaultNamespace,
The error is thrown from each line in react-i18next/ts4.1/index.d.ts that attempts to set Namespace = DefaultNamespace.
I copied over as much of the code in index.d.ts as I could into the Typescript playground to try and get some insight into what is happening here, and I am able to get the compilation error to repro.
Hovering over the following items in the Typescript playground gives some interesting insight:
DefaultResources will resolve to { “btn_cancel”: string; “btn_ok”: string; }
DefaultNamespace will resolve to “ type DefaultNamespace<T = "translation"> = T extends "btn_cancel" | "btn_ok" ? T : string “
I assume this gets set by the use of the Fallback type, which gets passed in each key from DefaultResources via the keyof...
Link to playground.
My question is, why are the keys for the language files being set as the namespace? Is this by design? Am I importing the resources in an incorrect manner?
I noticed that the example here (https://github.com/i18next/react-i18next/blob/master/example/react-typescript4.1/no-namespaces/%40types/react-i18next/index.d.ts) only shows what the docs point to as an older version, i.e using the DefaultResources type instead of CustomTypeOptions. Any guidance on using the new method without namespaces would be greatly appreciated :)
For anyone working with i18next#v22.0.0 and react-i18next#v12.0.0, following the documentation here was successful for me (as of the time this was posted):
https://www.i18next.com/overview/typescript
I am now getting types for my translations inside of the t() function

TypeDI: class in injected service is not a function or service is not found

I'm using TypeDI in a Typescript ReactJS project and I need to generalize the User Repository/retrieval.
So what I did up to now is having an interface:
export interface IAdminUserService{
getUsers():User[];
getUser(id:string):User;
...
}
Have an implementation:
#Service('aws-user-service')
class AWSUserService implements IAdminUserService {
...various implementations for interface methods...
}
And finally, the class I'll be using, the one I should inject the other one in:
#Service('user-service')
export class AdminUserService{
constructor(
#Inject('aws-user-service') public service:AWSUserService
){
}
}
Then when I need to actually use that stuff, I try to run the following:
const adminUserService: AdminUserService = Container.get<AdminUserService>('user-service'); // throw ServiceNotFound error
or the following:
const adminUserService: AdminUserService = Container.get<AdminUserService>(AdminUserService); // kinda good
adminUserSerivce.service.getUsers() // throws 'adminUserSerivce.service.getUsers' is not a function
I did put import 'reflect-metadata'; on top of each file using TypeDI.
So now I really don't know what to do/try to make this work.
I even try to use Container.set(<id>, <class>) before using the Container.get(<class|id>) but it doesn't change anything at all.
Also tried not putting strings into the #Service() decorator or even putting an object like {id:<id>} but still, nothing seems to work properly.
Am I doing something wrong? should I fix something, somewhere?
EDIT:
Just tried the sample code from the TypeDI page and I'm getting the same exact error: serviceInstance.injectedService.printMessage is not a function so it might probably be something related to the library, more than my code itself.

How to include mxFloorplan.js in mxGraph inside React application using Typescript?

So I'm trying to create a simple react application to render a mxGraph that I'm loading from a file. I can load the model, but some shapes aren't rendering correctly. The problem is that they are a specific shape, that are part of the floorplan package, and I can't find a way to include those shapes in my code.
ps.: I'm new to working with mxGraph.
Things I tried
First thing I tried was downloading the mxFloorplan.js file into my application, and import it, like so:
// App.tsx
import './models/mxFloorplan'
const mx = factory({
mxBasePath: './models'
})
let graph: mxGraph
...
Because the docs on extending mxShape show that I should register a new shape: mxCellRenderer.registerShape('customShape', CustomShape); and the mxFloorplan.js file does that.
I then simply added this to the beggining of the file:
// mxFloorplan.js
import Graph, {
mxShape,
mxUtils,
mxCellRenderer,
mxPoint
} from 'mxgraph'
...
But then I get this error:
Then I thought that I needed mxCellRenderer to be linked to my graph instance? So I tried moving one of the shape definitions into App.jsx to test:
// App.jsx
const mx = factory({
mxBasePath: './models'
})
let graph: mxGraph
function mxFloorplanWall(bounds: any, fill: any, stroke: any, strokewidth: any)
{
mx.mxShape.call(this); <-- Error: "Expected 2 args, but got one"
this.bounds = bounds;
this.fill = fill;
this.stroke = stroke;
this.strokewidth = (strokewidth != null) ? strokewidth : 1;
};
/**
* Extends mxShape.
*/
mx.mxUtils.extend(mxFloorplanWall, mxShape); <-- Error: "Property 'extend' does not exist on type mxUtils
// ... more code
mx.mxCellRenderer.registerShape(mxFloorplanWall.prototype.cst.WALL, mxFloorplanWall); <-- Error: mxFloorplanWall type not compatible with expected.
Really don't know how to solve these ones. On my research I only find references to
mxCellRenderer.registerShape('name', CustomShape), so not really sure on the rest.
How it looks
Here is how the diagram looks like (ignore the arrow and labels, please):
Here is what I'm actually rendering (the "black boxes" have shape=shape=mxgraph.floorplan.wallU):
As described in https://jgraph.github.io/mxgraph/docs/js-api/files/shape/mxShape-js.html, you must pass a constructor to mxCellRenderer.registerShape
function CustomShape() { }
CustomShape.prototype = new mxShape();
CustomShape.prototype.constructor = CustomShape;
// To register a custom shape in an existing graph instance,
// one must register the shape under a new name in the graph’s cell renderer
// as follows:
mxCellRenderer.registerShape('customShape', CustomShape);
}
I guess your issues come from a wrong port of drawio code (that use a very old Javscript syntax) and is not related to React at all. It is currently unclear to me what you are exactly have implemented. Here are some hints.
If you use TypeScript, the mxCellRenderer.registerShape signature is enforced by the mxgraph types https://github.com/typed-mxgraph/typed-mxgraph/blob/v0.0.5-0/view/mxCellRenderer.d.ts#L83.
When porting the mxFloorplanWall code to Typescript you should have a constructor like in the following (please avoid any!)
export class mxFloorplanWall extends mxShape { // or extends mx.mxShape depending how you manage mxgraph imports
public constructor(bounds: mxRectangle, fill: string, stroke: string, strokewidth: number) {
super(bounds, fill, stroke, strokewidth);
}
...
}
Calling super directly set the arguments in super class and avoid the errors
// mx.mxUtils.extend(mxFloorplanWall, mxShape); <-- Error: "Property 'extend' does not exist on type mxUtils
// avoid mx.mxShape.call(this); <-- Error: "Expected 2 args, but got one"
Same if you use Javascript, prefer the ES6 class syntax to declare the mxFloorplanWall class.

Import class in nested module typescript

I'm trying to create hybrid angular 1 + angular 2 with webpack
I have a problem with nested module
//a.ts
module a.b.c {
export class A {
}
}
//b.ts
module a.b.c {
export class B extends A {
}
}
Code compiling but I'm getting A is undefined
I tried import in various of ways but nothing seems to work
What am I'd doing wrong?
You need to add a reference to a.ts in b.ts.
So, just edit your b.ts as following:
/// <reference path="./a.ts" />
module a.b.c {
export class B extends A {
public second;
}
}
In addition, you need to include the a.ts before b.ts in your html file:
<script src="./a.js"></script>
<script src="./b.js"></script>
Otherwise, you will get an error.
That's the way it worked before, but now when we added webpack i read that using reference is not best practice, and webpack not compiling it.
If i just remove the module, than there is not problem. but like you said this is a legacy code with lots of internal modules.
If there is no good way of doing that, and the best practice is to remove the module than we may just do that.
Ok, so i'll start by saying again there is indeed no really good way to do this with webpack, and unless this is an enormous project, moving to external modules is the way to go.
if most of your files look like this:
//a.ts
module a.b.c {
export class A {}
}
//b.ts
module d.e.f {
export class B extends a.b.c.A
}
Than it's relatively easier, you just do:
//a.ts
export module a.b.c {
export class A {}
}
//b.ts
import {a} from './a';
module d.e.f {
export class B extends a.b.c.A
}
However, in the example you gave, you have two files composing the same namespace\internal module - a.b.c. This is harder to deal with and you'll need to do some ugly stuff like:
//b.ts
import {aImported} from './a';
module a.b.c {
export class B extends aImported.b.c.A
}
In this solution, your files are external modules and they export internal modules.
There is also a different approach, which is to take all your old code, and compile + concat it old school using Gulp\Grunt, before passing the resulting js file to webpack. I did this in the past and I regret it and has since moved away from it. But it does require the least amount of changes to code which is the main benefit of it. Overall, I think it's even worse than the previous solution...

Resources