React-router v6. How is matchPath supposed to work now - react-router

In version 5 I could perform check this way https://v5.reactrouter.com/web/api/matchPath
import { matchPath } from "react-router";
const match = matchPath("/users/123", {
path: "/users/:id",
exact: true,
strict: false
});
In version 6 I get an error on the screen. How should this work now?
error: pathname.match is not a function

It looks like in react-router-dom#6 the order of the arguments passed was inverted.
matchPath
declare function matchPath<
ParamKey extends string = string
>(
pattern: PathPattern | string,
pathname: string
): PathMatch<ParamKey> | null;
interface PathMatch<ParamKey extends string = string> {
params: Params<ParamKey>;
pathname: string;
pattern: PathPattern;
}
interface PathPattern {
path: string;
caseSensitive?: boolean;
end?: boolean;
}
pattern is the first argument, pathname is the second.
You've passed pathname then pattern:
const match = matchPath("/users/123", {
path: "/users/:id",
exact: true,
strict: false
});
To resolve swap the order of arguments passed to matchPath:
const match = matchPath(
{ path: "/users/:id" },
"/users/123",
);

Related

TypeScript: Generate type or interface of enum values

I'd like to have an interface generated out of the values of an enum. I have the following use-case in React:
I have an enum with potentially a lot of key value pairs. Each of the enum values is used as form IDs, so I get the name and the value of the input element in an event listener. I'd like to set the state to this.setState({ name: value }), but the name, description, etc. should be type-safe.
So I somehow need to generate an interface out of the values of the enum (because an interface cannot inherit from an enum) to be able to do the following for example: this.setState({ name: 'hello world' }) and this.setState({ description: 'a description' })
enum InputIDs {
NAME = 'name',
DESCRIPTION = 'description',
// ... many more items ...
}
interface IMyReactComponentState {
alreadyExisting: boolean;
[InputIDs.NAME]: string;
// ... all other items from the Test enum should go here but I'd like to generate it somehow ...
}
class MyReactComponent extends React.Component< ISomeProps, IMyReactComponentState > {
constructor(props: ISomeProps) {
super(props);
this.state = {
alreadyExisting: false,
[InputIDs.NAME]: '',
// more default values
}
}
private handleChange = (event: React.FormEvent<HTMLDivElement>) => {
// TODO make type safe
const {name, value}: {name: any, value: string} = (event.target as any); // event.target is either HTMLInputElement, HTMLSelectElement or HTMLTextAreaElement
// store the value of the corresponding input in state to preserve it when changing tabs
this.setState({
[name]: value
});
}
}
My problem is that something along these lines is not possible:
interface IMyReactComponentState extends InputIDs {
alreadyExisting: boolean;
}
Any ideas how I can keep the enum with the typings of IMyReactComponentState in sync without writing an interface myself?
Thanks in advance! Not sure if this has been asked already - if so I haven't found the answer yet!
EDIT (May 8th, 2019):
We're using TypeScript 2.8.1 in our project
You need to use an interasection and a mapped type (the predefined Record mapped type should do)
enum InputIDs {
NAME = 'name',
DESCRIPTION = 'description',
// ... many more items ...
}
type IMyReactComponentState = {
alreadyExisting: boolean;
} & Record<InputIDs, string>
class MyReactComponent { // simplified
state:IMyReactComponentState
constructor() {
this.state = {
alreadyExisting: false,
[InputIDs.NAME]: '',
[InputIDs.DESCRIPTION]: ''
}
}
}
You can use mapped types to produce type with enum values as a keys, then use intersection or extend it with additional properties:
type InputValues = {
[key in InputIDs]: string
}
Then
type IMyReactComponentState = InputValues & {
alreadyExisting: boolean
};
Or:
interface IMyReactComponentState extends InputValues {
alreadyExisting: boolean
}
You can define your IMyReactComponentState as
interface IMyReactComponentState {
alreadyExisting: boolean;
[key in keyof typeof InputIDs]: string
}

Typescript - generics in function property does not get resolved

i'm pretty new to typescript and i'm trying to play with nested generics. By now I can't make them work as i would expect, probably i'm missing something obvious. Here is my sample code:
type GenericServiceResponse<T> = {
status: number;
payload: T;
}
type myServicePayload = {
name: string;
surname: string;
}
type myServiceResponse = GenericServiceResponse<myServicePayload>;
class GenericServiceRequest {
callback?: <T>(data:T) => void;
}
let request = new GenericServiceRequest();
request.callback = <myServiceResponse>(data:myServiceResponse) => {
console.info(data.payload.name);
};
The output of the tsc compiler (target es5) is:
main.ts(20,23): error TS2339: Property 'payload' does not exist on type 'myServiceResponse'.
Try it this way. The main problem were the casts you had in the definition of callback and where it is assigned. Besides, the class needs to be generic, too. Otherwise, Typescript does not know what T means.
type GenericServiceResponse<T> = {
status: number;
payload: T;
}
type myServicePayload = {
name: string;
surname: string;
}
type myServiceResponse = GenericServiceResponse<myServicePayload>;
class GenericServiceRequest<T> {
callback?: (data:T) => void;
}
let request = new GenericServiceRequest<myServiceResponse>();
request.callback = data => {
console.info(data.payload.name);
};
As you see, now you don't have to speficy the type of data in the las sentence, it`s inferred by the definition of request.

How to convert a json to a typescript interface?

Given a JSON output of an api:
{
"id": 13,
"name": "horst",
"cars": [{
"brand": "VW",
"maxSpeed": 120,
"isWastingGazoline": true
}]
}
I would like to define interfaces for typescript:
export interface Car {
brand: string;
maxSpeed: number;
isWastingGazoline: boolean;
}
export interface RaceCarDriver {
id: number;
name: string;
cars: Car[];
}
Yet I don't want them to type them manually, I rather have a script generate them for me.
How can I convert json into typescript interfaces? I also don't want to use webservices like MakeTypes or json2ts.
You can write the script using typescript compiler API and its ability to infer types. I was really surprised how easy it was.
You have to wrap your sample data to make it compile-able as typescript code. The script will pick all variable declarations and try to print inferred types for them. It uses variable names and property names for assigning names to types, and if two objects have a property with the same name, it will pick the type from the first one. So it will not work in case these types are actually different (the fix is left as an exercise). For your JSON output, the data sample will look like
file sample.ts
let raceCarDriver = {
"id": 13,
"name": "horst",
"cars": [{
"brand": "VW",
"maxSpeed": 120,
"isWastingGazoline": true,
}]
};
The script was tested with Typescript 2.1 (just released):
npm i typescript
npm i #types/node
./node_modules/.bin/tsc --lib es6 print-inferred-types.ts
node print-inferred-types.js sample.ts
output:
export interface RaceCarDriver {
id: number;
name: string;
cars: Car[];
}
export interface Car {
brand: string;
maxSpeed: number;
isWastingGazoline: boolean;
}
Here is the script: print-inferred-types.ts:
import * as ts from "typescript";
let fileName = process.argv[2];
function printInferredTypes(fileNames: string[], options: ts.CompilerOptions): void {
let program = ts.createProgram(fileNames, options);
let checker = program.getTypeChecker();
let knownTypes: {[name: string]: boolean} = {};
let pendingTypes: {name: string, symbol: ts.Symbol}[] = [];
for (const sourceFile of program.getSourceFiles()) {
if (sourceFile.fileName == fileName) {
ts.forEachChild(sourceFile, visit);
}
}
while (pendingTypes.length > 0) {
let pendingType = pendingTypes.shift();
printJsonType(pendingType.name, pendingType.symbol);
}
function visit(node: ts.Node) {
if (node.kind == ts.SyntaxKind.VariableStatement) {
(<ts.VariableStatement>node).declarationList.declarations.forEach(declaration => {
if (declaration.name.kind == ts.SyntaxKind.Identifier) {
let identifier = <ts.Identifier>declaration.name;
let symbol = checker.getSymbolAtLocation(identifier);
if (symbol) {
let t = checker.getTypeOfSymbolAtLocation(symbol, identifier);
if (t && t.symbol) {
pendingTypes.push({name: identifier.text, symbol: t.symbol});
}
}
}
});
}
}
function printJsonType(name: string, symbol: ts.Symbol) {
if (symbol.members) {
console.log(`export interface ${capitalize(name)} {`);
Object.keys(symbol.members).forEach(k => {
let member = symbol.members[k];
let typeName = null;
if (member.declarations[0]) {
let memberType = checker.getTypeOfSymbolAtLocation(member, member.declarations[0]);
if (memberType) {
typeName = getMemberTypeName(k, memberType);
}
}
if (!typeName) {
console.log(`// Sorry, could not get type name for ${k}!`);
} else {
console.log(` ${k}: ${typeName};`);
}
});
console.log(`}`);
}
}
function getMemberTypeName(memberName: string, memberType: ts.Type): string | null {
if (memberType.flags == ts.TypeFlags.String) {
return 'string';
} else if (memberType.flags == ts.TypeFlags.Number) {
return 'number';
} else if (0 !== (memberType.flags & ts.TypeFlags.Boolean)) {
return 'boolean';
} else if (memberType.symbol) {
if (memberType.symbol.name == 'Array' && (<ts.TypeReference>memberType).typeArguments) {
let elementType = (<ts.TypeReference>memberType).typeArguments[0];
if (elementType && elementType.symbol) {
let elementTypeName = capitalize(stripS(memberName));
if (!knownTypes[elementTypeName]) {
knownTypes[elementTypeName] = true;
pendingTypes.push({name: elementTypeName, symbol: elementType.symbol});
}
return `${elementTypeName}[]`;
}
} else if (memberType.symbol.name == '__object') {
let typeName = capitalize(memberName);
if (!knownTypes[typeName]) {
knownTypes[typeName] = true;
pendingTypes.push({name: typeName, symbol: memberType.symbol});
}
return typeName;
} else {
return null;
}
} else {
return null;
}
}
function capitalize(n: string) {
return n.charAt(0).toUpperCase() + n.slice(1);
}
function stripS(n: string) {
return n.endsWith('s') ? n.substring(0, n.length - 1) : n;
}
}
printInferredTypes([fileName], {
noEmitOnError: true, noImplicitAny: true,
target: ts.ScriptTarget.ES5, module: ts.ModuleKind.CommonJS
});
Found a npm package that converts a arbitrary JSON file without a schema into a TS interface:
https://www.npmjs.com/package/json-to-ts
The author also provided a VSCode plugin.
You can use an npm module instead of the web hosted solution:
https://www.npmjs.com/package/json-schema-to-typescript
If your JSON comes from an HTTP API and the API has an swagger code definition, you can generate a TypeScript client:
https://github.com/swagger-api/swagger-codegen#api-clients
If you json comes from a Java ot .Net backened, you can generate TypeScript from Java or C# classes:
http://type.litesolutions.net
https://github.com/raphaeljolivet/java2typescript
Using only sed and tsc
sed '1s#^#const foo = #' sample.json > sample.$$.ts
tsc sample.$$.ts --emitDeclarationOnly --declaration
Append const foo = to beginning of file
Using sed to replace (s) nothing (#^#) at the beginning of the first line (1) with const foo =
output to sample.$$.ts
the extension is the required to be .ts
$$ expands to the shells process id, handy for a temp file that is unlikely to overwrite stuff you care about
ask tsc to only emit a .d.ts typings file
this file has pretty much everything you want for the interface. You might need to replace a few strings and customize it in a way you want but most of the leg work is done

How can Typescript decorators be used for html labels?

I am having a difficult time understanding how to use Typescript decorators. I have this code:
class Address {
private street: string;
private city: string;
private state: string;
private zipCode: string;
#displayName("Street")
get streetHtml() { return this.street; }
#displayName("City")
get cityHtml() { return this.city; }
#displayName("State")
get stateHtml() { return this.state; }
#displayName("Zip Code")
get zipCodeHtml() { return this.zipCode; }
public static map(input: any) {
let address = new Address();
address.street = input.street;
address.city = input.city;
address.state = input.state;
address.zipCode = input.zipCode;
return address;
}
}
function displayName(name: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
let label: HTMLLabelElement = document.createElement('label');
label.innerHTML = name;
return label;
};
}
var address = Address.map({ street: "123 My St", city: "Boise", state: "ID", zipCode: "83709" })
console.log(address.cityHtml);
However, the only thing this is doing is returning "Boise". How do I get at the decorator stuff? I did enable "experimentalDecorators": true, in my tsconfig.
As commented by #JohnWhite, the getters in your class return a string (that's being inferred by the compiler from the return type), but the decorator you're asking for changes that to return a HTMLLabelElement.
This might create difficulties for you in compile time, or end up in runtime errors.
With that being said, to answer your question:
What you return in the decorator isn't the returned value of the accessor, as the docs say:
If the accessor decorator returns a value, it will be used as the
Property Descriptor for the member
What you need to do is to change the "Property Descriptor" so that it returns your desired value:
function displayName(name: string) {
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
descriptor.get = () => {
let label: HTMLLabelElement = document.createElement('label');
label.innerHTML = name;
return label;
}
};
}
(code in playground)
Edit
After posting my answer I noticed that you want to get:
<label>Boise</label>
But how you tried to implement it (and my answer) returns:
<label>City</label>
In order to get the desired result we need to change my code a bit:
function displayName(name: string) {
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
descriptor.get = function() {
let label: HTMLLabelElement = document.createElement('label');
label.innerHTML = this[name.toLowerCase()];
return label;
}
};
}
(code in playground)
Where the changes are:
The return function is now a regular anonymous function instead of an arrow function so that the this will point to the class instance instead of the window.
The label inner html is now this[name.toLowerCase()] instead of name

How to Create a Simple Typescript Metadata Annotation

I have some fields that need to be formatted before sent to server-side.
So, I would like to serialize some fields of my typescript classes using custom serializers, something like that would be ideal:
export class Person {
#serializeWith(MyDateSerializer)
private date: Date;
}
post(url, value) {
this.http.post(url, JSON.stringfy(value, (key, val) => {
if (//value has serializeWith annotation) {
return //serialize with custom serializer
}
}));
}
Anything close to this would be acceptable, any help is welcome.
Thanks
My solution below is built on top of:
TypeScript Decorators
Metadata spec (early stage/experimental)
Prerequisites:
Enable Decorator and decorator metadata support in TypeScript in you tsconfig.json or on the command line:
tsconfig.json
{
"compilerOptions": {
"target": "es5", // you have to target es5+
"experimentalDecorators": true,
"emitDecoratorMetadata": true
// ....
}
// ...
}
Install reflect-metadata package
npm install --save-dev reflect-metadata
serializerWith Decorator (factory)
A decorator factory is something that takes one or many parameters and returns a decorator function that TypeScript uses in the generated JavaScript code.
I have chosen to directly pass the serialization function into my decorator factory and use the reflect-metadata implementation of the metdata spec to associate the serializer function with the property. I will later retrieve it and use it at run-time.
function serializeWith(serializer: (input: any) => string) : (target: any, propertyKey: string) => void {
return function(target: any, propertyKey: string) {
// serialization here is the metadata key (something like a category)
Reflect.defineMetadata("serialization", serializer, target, propertyKey);
}
}
Use
Given this serializer:
function MyDateSerializer(value : any) : string {
console.log("MyDateSerializer called");
return "dummy value";
}
We can then apply the decorator factory like this:
import "reflect-metadata"; // has to be imported before any decorator which uses it is applied
class Greeter {
#serializeWith(MyDateSerializer)
public greeting : string;
constructor(message: string) {
this.greeting = message;
}
}
And we can get and use the serializer like this:
var greetingInstance = new Greeter("hi");
var serializerFunc : (input: any) => string = Reflect.getMetadata("serialization", greetingInstance, "greeting");
serializerFunc(greetingInstance.greeting);
Sample
main.ts
import "reflect-metadata";
function serializeWith(serializer: (input: any) => string) : (target: any, propertyKey: string) => void {
return function(target: any, propertyKey: string) {
console.log("serializeWith called: adding metadata");
Reflect.defineMetadata("serialization", serializer, target, propertyKey);
}
}
function MyDateSerializer(value : any) : string {
console.log("MyDateSerializer called");
return "bla";
}
class Greeter {
#serializeWith(MyDateSerializer)
public greeting : string;
constructor(message: string) {
console.log("Greeter constructor");
this.greeting = message;
}
}
var greetingInstance = new Greeter("hi");
var serializerFunc : (input: any) => string = Reflect.getMetadata("serialization", greetingInstance, "greeting");
var serializedValue = serializerFunc(greetingInstance.greeting);
console.log(serializedValue);
Outputs
c:\code\tmp\lll>node build\main.js
serializeWith called: adding metadata
Greeter constructor
MyDateSerializer called
bla