Typescript react component name

Typing a dynamic tag in React with TypeScript?

You can pass in a string as a tag name and use that as you have, but you need to type it properly to get type checking to work. tag should be a key of JSX.IntrinsicElements .

interface CompProps < tag: keyof JSX.IntrinsicElements; >const MyComponent: React.FunctionComponent> = (< tag: Wrapper = "div", children, . rest >) => < return > ; >; 

late comment but for custom react components, maybe we can union their type together and then use the props to detect, or just dynamic import it (if conditionally, use io-ts to check the type could be an option IMO)

See my answer here to dynamically change the props based on its tag: stackoverflow.com/a/76178335/16649797

Using A Type definition For All HTML Elements

In order to allow all HTML elements to be used as your tag, you can utilize the keys of the IntrinsicElements interface defined in the JSX namespace. IntrinsicElements appears to contain a mapping of HTML element tags to their respective attributes (includes element-specific attributes). To utilize these keys we can do the following:

What if I want to allow React components to be used as the tag?

React defines two interfaces: ComponentClass and FunctionComponent . React also defines a union of these two interfaces that allows you to specify any React component: ComponentType . We can create a union of this and our last definition to allow both components and HTML tags.

import < ComponentType >from 'react'; interface Props

Well, now I have a tag, what about HTML attributes?

If you want to allow all other HTML attributes to be allowed you can either extend React.HTMLAttributes to get all of the shared HTML attributes (no element-specific ones) or you can introduce a generic and utilize JSX.IntrinsicElements .

The second option is more complex and comes with a few caveats. You have to use type instead of interface to extend/intersect your Props and the specific attributes defined on a key in JSX.IntrinsicElements . You will also need to use generics on your function so that you can pass them to your Props type which means you can no longer use React.FunctionComponent since that happens before access to any generics. This means you’ll want to add children to your Props definition.

That was a lot of words which I believe are better explained with this example:

// Define our Props type to allow the specifying of a Tag for HTML attributes // Also define children as React does with React.ReactNode type Props = < tag?: ComponentType | keyof JSX.IntrinsicElements; children?: ReactNode; >& JSX.IntrinsicElements[Tag]; // Define our generic (Tag) again here and give it our default value // Don't forget to specify the type Props at the end of your function's arguments // Then we can spread all props to the tag/Wrapper function MyComponent(< tag: Wrapper = 'div', . props >: Props) < return />; > // Example usage, noValidate is typed as // (JSX attribute) React.FormHTMLAttributes.noValidate?: boolean | undefined tag="form" noValidate> ; // You don't need to specify 'div' since it is the default Just a paragraph inside of a regular div

;

Источник

Читайте также:  Index php что означает

Force React component naming with TypeScript

There is React+TypeScript application, and all component classes should be upper-cased and have Component suffix, e.g:

export class FooBarComponent extends React.Component

The application is ejected create-react-application application, i.e. is build with Webpack. How can component naming be forced to be consistent with style guide, at least for component classes, with error being thrown on build when there are inconsistencies? I believe this cannot be achieved with TSLint/ESLint alone. If different methods should be used for TypeScript and JavaScript, solutions for both languages would be helpful.

2 Answers 2

I can offer you only solution for typescript.

I believe this cannot be achieved with TSLint/ESLint alone.

There is a so-called rule class-name that can solve your issue partially but seems you need to write custom rule for such case.

So let’s try writing such custom tslint rule. For that we need to use rulesDirectory option in tslint config to specify path to custom rules

"rulesDirectory": [ "./tools/tslint-rules/" ], 

Since I’m going to write custom rule in typescript I will be using one feature that was added in tslint@5.7.0

[enhancement] custom lint rules will be resolved using node’s path resolution to allow for loaders like ts-node (#3108)

We need to install ts-node package

Then add fake rule in tslint.json

and create file tsLoaderRule.js in our rulesDirectory:

const path = require('path'); const Lint = require('tslint'); // Custom rule that registers all of the custom rules, written in TypeScript, with ts-node. // This is necessary, because `tslint` and IDEs won't execute any rules that aren't in a .js file. require('ts-node').register(< project: path.join(__dirname, '../tsconfig.json') >); // Add a noop rule so tslint doesn't complain. exports.Rule = class Rule extends Lint.Rules.AbstractRule < apply() <>>; 

This is basically approach which is widely used in angular packages like angular material, universal etc

Now we can create our custom rule(expanded version of class-name rule) that will be written in typescript.

myReactComponentRule.ts

import * as ts from 'typescript'; import * as Lint from 'tslint'; export class Rule extends Lint.Rules.AbstractRule < /* tslint:disable:object-literal-sort-keys */ static metadata: Lint.IRuleMetadata = < ruleName: 'my-react-component', description: 'Enforces PascalCased React component class.', rationale: 'Makes it easy to differentiate classes from regular variables at a glance.', optionsDescription: 'Not configurable.', options: null, optionExamples: [true], type: 'style', typescriptOnly: false, >; /* tslint:enable:object-literal-sort-keys */ static FAILURE_STRING = (className: string) => `React component $ must be PascalCased and prefixed by Component`; static validate(name: string): boolean < return isUpperCase(name[0]) && !name.includes('_') && name.endsWith('Component'); >apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] < return this.applyWithFunction(sourceFile, walk); >> function walk(ctx: Lint.WalkContext) < return ts.forEachChild(ctx.sourceFile, function cb(node: ts.Node): void < if (isClassLikeDeclaration(node) && node.name !== undefined && isReactComponent(node)) < if (!Rule.validate(node.name!.text)) < ctx.addFailureAtNode(node.name!, Rule.FAILURE_STRING(node.name!.text)); >> return ts.forEachChild(node, cb); >); > function isClassLikeDeclaration(node: ts.Node): node is ts.ClassLikeDeclaration < return node.kind === ts.SyntaxKind.ClassDeclaration || node.kind === ts.SyntaxKind.ClassExpression; >function isReactComponent(node: ts.Node): boolean < let result = false; const classDeclaration = node; if (classDeclaration.heritageClauses) < classDeclaration.heritageClauses.forEach((hc) => < if (hc.token === ts.SyntaxKind.ExtendsKeyword && hc.types) < hc.types.forEach(type => < if (type.getText() === 'React.Component') < result = true; >>); > >); > return result; > function isUpperCase(str: string): boolean

and finally we should put our new rule to tsling.json :

// Custom rules "ts-loader": true, "my-react-component": true 
App extends React.Component 

enter image description here

I also created ejected react-ts application where you can try it.

Читайте также:  Python docx list bullet

Update

I guess tracking class names in grandparents won’t be a trivial task

Indeed we can handle inheritance. To do that we will need create rule extended from class Lint.Rules.TypedRule to have access to TypeChecker :

myReactComponentRule.ts

import * as ts from 'typescript'; import * as Lint from 'tslint'; export class Rule extends Lint.Rules.TypedRule < /* tslint:disable:object-literal-sort-keys */ static metadata: Lint.IRuleMetadata = < ruleName: 'my-react-component', description: 'Enforces PascalCased React component class.', rationale: 'Makes it easy to differentiate classes from regular variables at a glance.', optionsDescription: 'Not configurable.', options: null, optionExamples: [true], type: 'style', typescriptOnly: false, >; /* tslint:enable:object-literal-sort-keys */ static FAILURE_STRING = (className: string) => `React component $ must be PascalCased and prefixed by Component`; static validate(name: string): boolean < return isUpperCase(name[0]) && !name.includes('_') && name.endsWith('Component'); >applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] < return this.applyWithFunction(sourceFile, walk, undefined, program.getTypeChecker()); >> function walk(ctx: Lint.WalkContext, tc: ts.TypeChecker) < return ts.forEachChild(ctx.sourceFile, function cb(node: ts.Node): void < if ( isClassLikeDeclaration(node) && node.name !== undefined && containsType(tc.getTypeAtLocation(node), isReactComponentType) && !Rule.validate(node.name!.text)) < ctx.addFailureAtNode(node.name!, Rule.FAILURE_STRING(node.name!.text)); >return ts.forEachChild(node, cb); >); > /* tslint:disable:no-any */ function containsType(type: ts.Type, predicate: (symbol: any) => boolean): boolean < if (type.symbol !== undefined && predicate(type.symbol)) < return true; >const bases = type.getBaseTypes(); return bases && bases.some((t) => containsType(t, predicate)); > function isReactComponentType(symbol: any) < return symbol.name === 'Component' && symbol.parent && symbol.parent.name === 'React'; >/* tslint:enable:no-any */ function isClassLikeDeclaration(node: ts.Node): node is ts.ClassLikeDeclaration < return node.kind === ts.SyntaxKind.ClassDeclaration || node.kind === ts.SyntaxKind.ClassExpression; >function isUpperCase(str: string): boolean

Источник

Typescript react component name

A community for discussing anything related to the React UI framework and its ecosystem. Join the Reactiflux Discord ( https://www.reactiflux.com ) for additional React discussion and help.

I’ve been working on a TypeScript React app and have just come across the issue where I have several components whose names conflict with the names of some shared type definitions.

E.g. My simplified Bookshelf component and type:

import React, < FunctionComponent >from ‘react’ import < Bookshelf >from ‘../types’ interface BookshelfProps < bookshelf: Bookshelf >// Naming conflict here. const Bookshelf: FunctionComponent = (< bookshelf >) => < return ( Number of books: ) > export default Bookshelf

Are there any conventions or recommendations for handling this?

Some options that I’ve considered are:

  1. Importing types using import * as types from ‘../types’ – I find that having types. makes it a bit messy, especially for more complicated components.
  2. Importing just the conflicted types with an alias ( import < Bookshelf as BookshelfType >from ‘../types’ ) – This works in some places, but when I have a type I called BookshelfType that I want to import then it becomes a problem.
  3. Giving all components the suffix Component or View (e.g. BookshelfComponent or BookshelfView ) – I’m not entirely opposed to this but haven’t seen it in any other React app so wonder if there’s a reason that no-one else seems to do it.
  4. Giving only problematic components the suffix Component or View – Component names would then be inconsistent.
Читайте также:  Css overflow and scrollbars

Источник

Using a string as a component name in react and typescript

I have an input component that can either render a textarea component (from a library) or a regular input. This is the component:

import React, < useEffect, useRef, useState >from 'react' import './AppInput.css' interface Props < placeholder: string, value: string | number, setValue: React.Dispatch>, text: string, shouldFocusOnKey: boolean, type: string, name: string, className: string, Component: string > export const AppInput: React.FC = (props) => < const < placeholder, value, setValue, text, shouldFocusOnKey, type = 'text', name, className, Component='input' >= props let inputRef = useRef(null) useEffect(() => < if (shouldFocusOnKey) < window.onkeydown = (e) => < if (e.ctrlKey && e.key === 'm') < inputRef.current?.focus() >> > >, [shouldFocusOnKey]) return ( 
`>>
placeholder= value= onChange= < setValue(e.target.value) >> type= name= />
) >

In the case that Component === ‘ReactTextareaAutosize’ Then I want to render that component as depicted.This works perfectly well in ReactJS. But when using ReactTS I get type errors etc as typescript does not understand what I am trying to do or doing. Any idea how I can solve this?

You can check my answer, try use component name you want use (it just string, in my case, it was div ), and use className (string) which you want to use instead of my example. In my case I needed to generate className string for my purpose.

2 Answers 2

Code Sample

Here is example that used in my code:

Apart from the useRef type, simply resolve the actual component from the value of your Component prop, so that it is no longer a simple string , but either an input or an actual ReactTextareaAutosize React Component:

import ReactTextareaAutosize from 'react-textarea-autosize' interface Props < // other props Component: 'input' | 'ReactTextareaAutosize' >export const AppInput: React.FC = (props) => < const < // other props Component = 'input' >= props const ActualComponent = Component === 'ReactTextareaAutosize' ? ReactTextareaAutosize : Component return ( 
) >

As for the useRef type, if you just need for simple usage like .focus() , i.e. common properties/methods of and , then the easiest would be to type it as the intersection HTMLInputElement & HTMLTextAreaElement , so that it is compatible with both elements, and you can use all common properties/methods:

const inputRef = useRef< HTMLInputElement & HTMLTextAreaElement >(null) inputRef.current?.focus() // Okay // Okay /> 

Now if you absolutely need to be able to use specific properties and/or methods to and elements, then things get more verbose just for the sake of safe typing unfortunately.

The most straightforward technique is to separate the ref and actual component for each possibility:

const inputRef = useRef(null) const textareaRef = useRef(null) inputRef.current?.focus() // Okay textareaRef.current?.focus() // Okay 
// Okay /> : // Okay /> >

Источник

Оцените статью