how do I declare additional property for React class component? - reactjs

I rewrite my React Project with TypeScript. now I know how to declare property interface and state interface for a Class Component, simple like this:
export interface ComponentProps {
propName: string
}
interface ComponentState {
stateName: number
}
class myComponent extends React.Component<ComponentProps, ComponentState> {
...
}
but error accur when I do something in componentDidMount() lifecycle:
componentDidMount() {
this.customProperty = 26; // here I could save an id returned by setInterval to cancel it in the componentWillUnmount() for example.
}
[ts] Property 'customProperty' does not exist on type 'MyComponent'. [2339]
What can I do to declare additional property correctly, not just simple silence the error.
I've learned the basic of typescript.
import React, { Component } from 'react';
export interface CheckoutProps {
total: number;
customer: string;
}
interface State {
isChecking: boolean;
}
class Checkout extends Component<CheckoutProps, State> {
state = {
isChecking: false
};
componentDidMount() {
this.customProperty = 'sean';
}
render() {
return <div>hello</div>;
}
}
export default Checkout;

Class level properties can be defined after the class declartion,
They can be initialized either at the time of declartion or in constructor.
Change your Class To :-
class Checkout extends Component<CheckoutProps, State> {
customProperty:string=''; //Here,you can define your class level properties
state = {
isChecking: false
};
componentDidMount() {
this.customProperty = 'sean';
}
render() {
return <div>hello</div>;
}
}
Hope this helps,
Cheers !!

You can declare it within the class:
class myComponent extends React.Component<ComponentProps, ComponentState> {
private customProperty: string = '';
componentDidMount() {
this.customProperty = 'sean';
}
}

Related

Typing a class component with no props

I am trying to type a class component with no props
import React, { Component } from "react";
type Props = {};
class MyComponent extends Component<Props> {
constructor(props: Props) {
super(props);
this.state = { hasError: false };
}
render() {
return <h1>hello</h1>;
}
}
export default MyComponent;
It seems like I just cannot ommit them
import React, { Component } from 'react';
class ErrorBoundary extends Component {
constructor() {
super();
this.state = { hasError: false };
}
What is the correct way to type a class component with no props?
You can initialize the state like this, avoiding to (manually) overwrite the constructor.
class ErrorBoundary extends Component {
state = {
hasError: false
}
render() {
return <h1>hello</h1>;
}
}
I think that's what you're talking about, not the typings.

How to declare different state and props for subclasses on react with typescript

Super new to typescript and static type in general with React, so
Given that I have this parent component:
interface ParentProps {
something: string
}
interface ParentState {
somethingElse: string
}
export default class ParentComponent extends React.Component<ParentProps, ParentSate> {
// Component Logic.
sharedFunction() {
// Some shared function.
}
}
How can I declare different props and state on child component, for example:
interface ChildProps {
something: string
}
interface ChildState {
somethingElse: string
}
export default class ChildComponent extends ParentComponent<ChildProps, ChildState> {
// Child component logic.
}
When I try the above I get:
[ts] Type 'ParentComponent' is not generic.
So, what is the best way of achieving this separation?, thanks.
You can make ParentComponent generic as well so that the ChildComponent can specify different props and state:
interface ParentProps {
something: string
}
interface ParentState {
somethingElse: string
}
export class ParentComponent<TProps extends ParentProps = ParentProps, TState extends ParentState = ParentState>
extends React.Component<TProps, TState> {
// Component Logic.
sharedFunction() {
// Some shared function.
}
}
interface ChildProps {
something: string
}
interface ChildState {
somethingElse: string
}
export class ChildComponent extends ParentComponent<ChildProps, ChildState> {
// Child component logic.
}

ReactJS TS, Property 'match' does not exist on type 'Readonly<{children?:ReactNode}> & Readonly<MyProps>'

I am trying to type the props of my component and use an URL param at the same time. I get the following error:
Property 'match' does not exist on type
'Readonly<{children?:ReactNode}> & Readonly'
Here is some of my code:
import Api from '../../api/Api';
interface MyProps {
api: Api
}
interface MyState {
someString: string,
loading: boolean
}
export class MyComponent extends React.Component<MyProps, MyState> {
constructor(props: MyProps) {
super(props);
this.state = {
someString: this.props.match.params.someString,//<-- Error is here on this line
loading: true
}
}
componentDidMount() {
this.props.api.getSomeInfo(this.state.someString, this.callback)
}
callback() {
let interval = setInterval(function () {
this.setState({loading: false});
clearInterval(interval)
}, 3000);
}
render() {
return (
<div>
<p>{this.someString}</p>
</div>
);
}
}
As you can see all I am trying to do is:
1- Go to:
http://localhost:8080/someStrings/:someString
2- Grab the value of :someString in my component's constructor and store in state
3- Use the value of someString in my state to be able to pass it as an argument to my API module to do stuff
4- When the callback is executed in the API I remove the loading animation
My question is basically, how do I declare my MyProps to be able to acheive this?
This is an open issue in type definition. https://github.com/DefinitelyTyped/DefinitelyTyped/issues/17355
Workaround
import { RouteProps } from 'react-router';
import React from 'react';
interface MyProps {
api: Api
}
interface MyState {
someString: string,
loading: boolean
}
class MyComponent extends React.Component<Props & RouteProps, State> // Pay attention here.
{
// your code...
}
ref: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/17355#issuecomment-336022780
This is how I solved it
import {RouteComponentProps} from 'react-router';
interface IMyProps {}
interface IReactRouterParams {
roomName: string;
username: string;
}
export class MyComponent extends React.Component<
IMyProps & RouteComponentProps<IReactRouterParams> {
constructor(props: any) {
super(props);
//everything works here
const {roomName, username} = this.props.match.params;
}
}
Try to add RouteComponentProps inteface to your props. Change:
export class MyComponent extends React.Component<MyProps, MyState> {
to
export class MyComponent extends React.Component<MyProps & RouteComponentProps, MyState> {
This is my script to handle the match problem, I believe you can borrow something from here.
class MyComponent extends React.Component<Props, State> {
private params: any;
constructor(props: any) {
super(props);
this.state = {
paramId: null
};
}
componentDidMount = () => {
this.getParams();
};
getParams = () => {
this.params = this.props;
this.setState({
paramId: this.params.match.params.id
});
};
render() {
const { paramId } = this.state;
return (
<React.Fragment></React.Fragment>
);
}
}
export default MyComponent;
I adapted the answer of Ritwick Dey into something I find a bit better.
Here it is:
import React, { Component } from 'react';
import { RouteComponentProps } from 'react-router';
interface Props {
// your custom props go here
api: Api
}
interface State {
someString: string,
isLoading: boolean
}
class MyComponent extends Component<RouteComponentProps<Props>, State>
{
// your code...
}
OR
import React, { Component } from 'react';
import { RouteComponentProps } from 'react-router';
interface Props
extends RouteComponentProps <{
// your custom props go here
api: Api
}> {}
interface State {
someString: string,
isLoading: boolean
}
class MyComponent extends Component<Props, State>
{
// your code...
}
They are the same thing. It depends on your preference.
I had a similar issue and that was due to the App.test.jsx file. There, there was already a test case(below code) that I had totally forgotten about and none of the props was utilized in that test case scenario.
When I introduced the props to the test case, it worked. So in nature test was definitely guiding to the right direction since it was trying to use props that do not exists. But after updating the test case with my default props, it works.
Hopefully this helps.
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App **propName1={propwasnotinthetest} propName2={propwasnotinthetest}**/>, div);
ReactDOM.unmountComponentAtNode(div);
});
I experienced a similar issue and found the least type breaking fix is to locally cast the params as any.
someString: (this.props.match.params as any).someString
import * as React from 'react';
import { RouteComponentProps } from 'react-router';
interface RouteComponetPath {
path?: string
}
interface ArticleContainerProps {
another: number
}
interface ArticleContainerState {
path?: string;
}
class ArticleContainer extends React.Component<ArticleContainerProps | RouteComponentProps<RouteComponetPath>, ArticleContainerState> {
constructor(props: ArticleContainerProps | RouteComponentProps<RouteComponetPath>) {
super(props);
this.state = {
path: (this.props as RouteComponentProps<RouteComponetPath>).match.params.path
};
}
componentDidMount() {
console.log("mount! Path is: ", this.state.path);
}
render() {
return (
<h1>This is a page with path {this.state.path} </h1>
)
}
}
export default ArticleContainer;
Which actually makes sense as you can have one interface to handle the paths and use a Union type and a Type Gueard as per TS documentation https://www.typescriptlang.org/docs/handbook/advanced-types.html#union-types
So, no hacks and remains strongly typed (not using any any anywhere).
Now I don't see any reason to pass both the path and another type of params, but you know... just to prove it can be done.
RouteComponentProps should help in typescript
import { RouteComponentProps } from 'react-router';
export class Edit extends React.Component<MyProps & RouteComponentProps, MyState> {
constructor(props: MyProps & RouteComponentProps) {
super(props);
...
}
...
(this.props.match.params as any).someString
or because you are using this.props.match but not adding match to the MyProps interface
Easy Workaround would be:
Home extends React.Component<any, any>

flowtype: inheritance and generic

I want to inherit from a React component, and define new Props. Something like that (got a lot of errors with Bar, should completely be wrong):
// #flow
class Component<Props> {
props: Props;
constructor(props: Props) {
this.props = props;
}
}
type FooProps = {
x: number;
}
class Foo extends Component<FooProps> {
_render(value: string) {
return `hello, ${value}`;
}
render() {
return this._render(`${this.props.x}`);
}
};
type BarProps = {
x: number;
y: number;
}
class Bar extends Foo<BarProps> {
render() {
return this._render(`${this.props.x} ${this.props.y}`);
}
}
const foo: Foo = new Foo({x: 1});
const bar: Bar = new Bar({x: 1, y: 2});
How should I use flow generics with inheritance? (in the context of React components, if it matters).
Using flow 0.57.2 and react 16.0.0.
the simplest case, creating a new react component and typing the props would look like this in ES:
// #flow
import React from 'react'
type Props = { books: Array<String>, search: (string) => void }
class SearchList extends React.Component {
static defaultProps: Props
props: Props
render () { /*...*/ }
}
SearchList.defaultProps = { books: [], search: (_) => undefined }
EDIT: forgot to mention this is using the class fields proposal that is in stage 3. If you are using a bootstrapper like create-react-app you can use it. Otherwise you can do:
// #flow
import React from 'react'
type Props = { books: Array<String>, search: (string) => void }
class SearchList extends React.Component<Props> {
render () { /*...*/ }
}

React/TypeScript: extending a component with additional properties

I am trying to use react to recreate my currents components (written in pure typescript) but I can't find a way to give additional props to a component extending an other.
export interface DataTableProps {
columns: any[];
data: any[];
}
export class DataTable extends React.Component<DataTableProps, {}> {
render() {
// -- I can use this.props.columns and this.props.data --
}
}
export class AnimalTable extends DataTable {
render() {
// -- I would need to use a this.props.onClickFunction --
}
}
My problem is that I need to give AnimalTable some props that would be irrelevant to DataTable. How can I do that ?
You'll need to make DataTable generic so that you'll be able to use an interface which extends DataTableProps:
export interface AnimalTableProps extends DataTableProps {
onClickFunction: Function;
}
export class DataTable<T extends DataTableProps> extends React.Component<T, {}> { }
export class AnimalTable extends DataTable<AnimalTableProps> {
render() {
// this.props.onClickFunction should be available
}
}
as a rule of thumb it is probably better to avoid inheritance. luckily TS and react are great tools allowing that (unlike c# for example, where inheritance often saves you a bunch of boilerplate)
export interface DataTableProps {
columns: any[];
data: any[];
}
export class DataTable extends React.Component<DataTableProps, {}> {
render() {
// -- I can use this.props.columns and this.props.data --
}
}
export type AnimalTableProps = DataTableProps & {
onClickFunction: () => void;
};
export class AnimalTable extends React.Component<AnimalTableProps, {}> {
render() {
const {onClickFunction, ...tableProps} = this.props;
// use onClickFunction however you need it
return <DataTable {...tableProps}></DataTable>
}
}
The most elegant solution that I found (without extra generic class) is
interface IBaseProps {
name: string;
}
class Base<P> extends React.Component<P & IBaseProps, {}>{
}
interface IChildProps extends IBaseProps {
id: number;
}
class Child extends Base<IChildProps> {
render(): JSX.Element {
return (
<div>
{this.props.id}
{this.props.name}
</div>
);
}
}
For those who need, base classes can declare required/abstract methods that all instances must implement:
import { Component } from 'react'
abstract class TestComponent<P = {}, S = {}, SS = any> extends Component<P, S, SS> {
abstract test(): string
}
type Props = {
first: string,
last: string,
}
type State = {
fullName: string,
}
class MyTest extends TestComponent<Props, State> {
constructor(props: Props) {
super(props)
this.state = {
fullName: `${props.first} ${props.last}`
}
}
test() {
const { fullName } = this.state
return fullName
}
}
Complete example of creating a component that you can extend off of and maintain state and props
import { Component } from "react";
// Props for the Base Component
export interface BaseComponentProps { }
// State for the Base Component
export interface BaseComponentState {
isLoaded?: boolean
}
// The Base Component that your components can extend
export class BaseComponent<Props extends BaseComponentProps, State extends BaseComponentState, SS = any> extends Component<Props, State, SS> {
State: BaseComponentState = {
isLoaded: false
}
constructor(props: Props) {
super(props);
}
componentDidMount() {
this.setState({ isLoaded: true })
}
}
// Props for your specialized component
export interface MainComponentProps extends BaseComponentProps {
}
// State for your specialized component
export interface MainComponentState extends BaseComponentState {
CanRead: boolean
}
// Your component which now extends the BaseComponent
export class MainComponent extends BaseComponent<MainComponentProps, MainComponentState> {
state: MainComponentState = {
CanRead: false
}
componentDidMount() {
super.componentDidMount();
if (this.state.isLoaded) {
this.setState({ CanRead: true })
}
}
}

Resources