-
-
Save tejacques/54997ef2d6f672314d53 to your computer and use it in GitHub Desktop.
import * as React from 'react'; | |
import { Component } from 'react'; | |
export default function HOCBaseRender<Props, State, ComponentState>( | |
Comp: new() => Component<Props & State, ComponentState>) { | |
return class HOCBase extends Component<Props, State> { | |
render() { | |
return <Comp {...this.props} {...this.state}/>; | |
} | |
} | |
} |
import * as React from 'react'; | |
import { Component } from 'react'; | |
import HOCBaseRender from './HOCBaseRender'; | |
export default function HOCMounted<Props, ComponentState>( | |
Comp: new() => Component<Props, ComponentState>, onMount: () => void, onUnmount: () => void) { | |
return class HOCWrapper extends HOCBaseRender<Props, void, ComponentState>(Comp) { | |
// ... Implementation | |
componentWillMount() { | |
onMount.call(this); | |
} | |
componentWillUnmount() { | |
onUnmount.call(this); | |
} | |
} | |
} |
import * as React from 'react'; | |
import NameAndAge from './NameAndAge'; | |
import HOCStateToProps from './HOCStateToProps'; | |
export default HOCStateToProps< | |
{ name: string }, | |
{ age: number }, | |
void>(NameAndAge, () => ({ age: 12 })); |
import * as React from 'react'; | |
import { Component } from 'react'; | |
import HOCBaseRender from './HOCBaseRender'; | |
export default function HOCStateToProps<Props, State, ComponentState>( | |
Comp: new() => Component<Props & State, ComponentState>, getState: () => State) { | |
return class HOCWrapper extends HOCBaseRender<Props, State, ComponentState>(Comp) { | |
// ... Implementation | |
constructor() { | |
super(); | |
this.state = getState(); | |
} | |
} | |
} |
import * as React from 'react'; | |
import { Component } from 'react'; | |
export default class NameAndAge extends Component<{ name: string, age: number }, void> { | |
render() { | |
return <div>Name: {this.props.name}, Age: {this.props.age}</div>; | |
} | |
} |
import * as React from 'react'; | |
import * as ReactDOM from 'react-dom'; | |
import HOCNameAndAge from './HOCNameAndAge'; | |
ReactDOM.render(<HOCNameAndAge name='Hello'/>, document.getElementById('root')); |
If someone is interested in a introduction of HOCs before using them with TypeScript, you could read the gentle introduction to higher order components.
@NicholasBoll your example throw this error [ts] Spread types may only be created from object types.
at line return <Comp {...this.props} />
. Any ideas on how to fix that?
@AlgusDark - i'm having the same issue. did you find any solutions?
This works...
export const Logger = (WrapperedComponent: typeof React.Component, name?: string) =>
class extends React.Component<any, any>{
public render() {
console.log((name ? name : '') + ' logging things... @' + new Date().getTime());
return (<WrapperedComponent {...this.props} />);
}
};
@AlgusDark - i'm having the same issue. It looks like the typescript bug
@AlgusDark @MrKou47 It is a TypeScript bug tracked here: microsoft/TypeScript#13288. You can fix by doing {...this.props as object}
. It is incorrectly casting P
.
This works for me with [email protected] and React:
// HOC for use case:
// every component connected to some external data source has prop, which has shape:
// { pending: boolean, data: object, error: object, done: boolean }
// name of that prop is not defined, so UsersList has "users" prop, ImagesList has "images" prop, but
// shape is always the same. I want to display loader, while resource is fetching.
import * as React from "react";
import Loader from "../Loader/Loader"; // spinner!
interface DisplayLoaderOptions {
resourceName: string; // example: "users" for UsersList, "images" for ImagesList
}
interface ComponentWithResourceProps {
[resourceName: string]: {
pending: boolean // otherwise TS will not know about "pending" property
}
}
function displayLoader({resourceName}: DisplayLoaderOptions) {
return <OriginalProps extends {},
Component extends React.ComponentClass<OriginalProps>>(WrappedComponent: Component): Component => {
class WithLoader extends React.Component<OriginalProps & ComponentWithResourceProps> {
public render() {
return this.props[resourceName] && this.props[resourceName].pending === true
? <Loader center/>
: <WrappedComponent {...props} />;
}
}
return WithLoader as Component; // otherwise WrappedComponent is not assignable to Component returned from HOC. It smells...
};
}
export default displayLoader;
Test:
import "jest";
import * as React from "react";
import {mount} from "enzyme";
import displayLoader from "./DisplayLoader";
import Loader from "../Loader/Loader";
describe("Shared - Layout - enhancements - displayLoader", () => {
test("should display loader when resource is fetching", () => {
@displayLoader({
resourceName: "myResource"
})
class TestComponent extends React.Component<any, any> {
public render() {
return <p>Test</p>
}
}
const testComponentWithLoader = mount(<TestComponent myResource={{pending: true}} />);
expect(testComponentWithLoader.find("p").length).toBe(0);
expect(testComponentWithLoader.find(Loader).length).toBe(1);
});
test("display component when resource is not fetching", () => {
@displayLoader({
resourceName: "resource"
})
class TestComponent extends React.Component<any, any> {
public render() {
return <p>Test</p>
}
}
const testComponentWithLoader = mount(<TestComponent resource={{pending: false}} />);
expect(testComponentWithLoader.find("p").length).toBe(1);
expect(testComponentWithLoader.find(Loader).length).toBe(0);
});
});
A little bit tricky (return XXX as XX
??).
Any better ideas (I'm starting with TS)?
Works with decorators and component classes for now. I did not test it with stateless components and "no-decorator" approach.
I believe that due to microsoft/TypeScript#6559 and microsoft/TypeScript#12215 optimal solution (without casting the result) is not possible yet.
Hi ,
could you please help me to find books or resource on how to learn typescript programming am using now same tech you used also same file ext.
but i am learning through shredded information from diffrent websites
here are sample of code i wrote it
import * as React from 'react';
import 'isomorphic-fetch';
interface IDataTunel {
name: string;
desription: string;
forchild: string;
}
interface IContentTunel {
empdata: EmployeeContacts;
}
class EmployeeContacts {
public mobile: string;
public email: string;
public others: string;
constructor() {
this.mobile = "mobile";
this.email = "email";
this.others = "others";
}
}
export class ListItems extends React.Component<IDataTunel, IContentTunel > {
public constructor() {
super();
this.state = { empdata: null };
}
componentWillMount() {
return (
this.state = { empdata: new EmployeeContacts() }
);
};
public render() {
return (
<div className="List_Items">
<h1>{this.props.forchild} </h1>
<span><h4>{this.props.name} with type {this.props.desription}</h4></span>
<br/>
<button type="Submitt" id="gd" onClick={() => {this.readdata((document.getElementById("id") as HTMLInputElement).value)}}>Display other information</button>
<ul>
<li>Mobile : {this.state.empdata.mobile} </li>
<li>Email : {this.state.empdata.email} </li>
<li>Others :{this.state.empdata.others} </li>
</ul>
</div>
);
}
public readdata(id): EmployeeContacts {
fetch('/api/SampleData/GetEmployeesContacts?id=' + id).then(response => response.json() as Promise<EmployeeContacts>).then(data => {
this.setState({ empdata: data });
});
return this.state.empdata;
};
}
export default ListItems
{
}
The limitations of declaration emit prevent inferred return types from being compilable to declaration files.
I've opened a request to fix issues like these in TypeScript by bringing declaration files to parity with language features. microsoft/TypeScript#35822
Your examples worked until I tried to add the
declarations: true
in the compiler options which will create type definitions files in your output.For the first example (
HOCBaseRender.tsx
), the type error I got wasTS4060: Return type of exported function has or is using private name 'HOCBase'.
The solution is pretty simple - add an explicit return type ofReact.ComponentClass<P>
I also lost type information with the
new() => Component...
part in TypeScript 2.1. I created a HOC that had more types implied through generics:simpleComponentWrap.tsx
:Button.tsx
:No explicit typing is needed when wrapping, but strict type checking is preserved.