diff --git a/Changes.md b/Changes.md index 0922744f0..ff04a1502 100644 --- a/Changes.md +++ b/Changes.md @@ -1,3 +1,14 @@ +# master +- Support import of `[@react.component]` components whose props require conversion. + The conversion is performed by wrapping the component with a `React.createElement` call, + so it works whether the component is a function or a class. + + All the TypeScript components are now typed with `React.ComponentType<...>`. + If existing code was using e.g. render props of type `React.FC<...>`, + direct function calls `foo(props)` should now be replaced with JSX calls ``. + + See https://github.com/cristianoc/genType/pull/226. + # 2.32.0 - Fix issue where conversion functions are not be generated for types defined in other files when `"importPath": "node",` is set in `gentypeconfig`. diff --git a/examples/commonjs-react-example/src/ImportHookDefault.gen.js b/examples/commonjs-react-example/src/ImportHookDefault.gen.js index 65b8d89b8..351d86916 100644 --- a/examples/commonjs-react-example/src/ImportHookDefault.gen.js +++ b/examples/commonjs-react-example/src/ImportHookDefault.gen.js @@ -9,7 +9,7 @@ const hookExample = require('./hookExample'); // In case of type error, check the type of 'make' in 'ImportHookDefault.re' and './hookExample'. -const makeTypeChecked: ({| +show: boolean, +Message?: string |}) => React$Node = hookExample;; +const makeTypeChecked: React$ComponentType<{| +show: boolean, +Message?: string |}> = hookExample;; exports.makeTypeChecked = makeTypeChecked // Export 'make' early to allow circular import from the '.bs.js' file. @@ -17,7 +17,7 @@ const make: mixed = makeTypeChecked;; exports.make = make // In case of type error, check the type of 'default' in 'ImportHookDefault.re' and './hookExample'. -const defaultTypeChecked: ({| +show: boolean, +Message?: string |}) => React$Node = hookExample;; +const defaultTypeChecked: React$ComponentType<{| +show: boolean, +Message?: string |}> = hookExample;; exports.defaultTypeChecked = defaultTypeChecked // Export '$$default' early to allow circular import from the '.bs.js' file. diff --git a/examples/flow-react-example/src/Hooks.bs.js b/examples/flow-react-example/src/Hooks.bs.js index ff332c849..8a4f63ee9 100644 --- a/examples/flow-react-example/src/Hooks.bs.js +++ b/examples/flow-react-example/src/Hooks.bs.js @@ -24,8 +24,8 @@ function Hooks(Props) { /* age */71 ], children: null, - renderMe: (function (param) { - return null; + renderMe: (function (x) { + return x.randomString; }) }, "child1", "child2"), React.createElement(ImportHookDefault.make, { person: /* record */[ @@ -33,8 +33,8 @@ function Hooks(Props) { /* age */42 ], children: null, - renderMe: (function (param) { - return null; + renderMe: (function (x) { + return x.randomString; }) }, "child1", "child2")); } @@ -118,6 +118,16 @@ function Hooks$functionReturningReactElement(Props) { return Props.name; } +function Hooks$RenderPropRequiresConversion(Props) { + var renderVehicle = Props.renderVehicle; + return Curry._1(renderVehicle, { + vehicle: /* record */[/* name */"Car"], + number: 42 + }); +} + +var RenderPropRequiresConversion = /* module */[/* make */Hooks$RenderPropRequiresConversion]; + var make = Hooks; var $$default = Hooks; @@ -145,6 +155,7 @@ export { testForwardRef , polymorphicComponent , functionReturningReactElement , + RenderPropRequiresConversion , } /* testForwardRef Not a pure module */ diff --git a/examples/flow-react-example/src/Hooks.gen.js b/examples/flow-react-example/src/Hooks.gen.js index f3c084fee..cabba8b15 100644 --- a/examples/flow-react-example/src/Hooks.gen.js +++ b/examples/flow-react-example/src/Hooks.gen.js @@ -7,6 +7,9 @@ // $FlowExpectedError: Reason checked type sufficiently type $any = any; +// $FlowExpectedError: Reason checked type sufficiently +import * as React from 'react'; + // $FlowExpectedError: Reason checked type sufficiently import * as Curry from 'bs-platform/lib/es6/curry.js'; @@ -130,3 +133,14 @@ export const polymorphicComponent: typeof(polymorphicComponent$$forTypeof) = fun const functionReturningReactElement$$forTypeof = function (_: {| +name: string |}) : React$Node { return null }; export const functionReturningReactElement: typeof(functionReturningReactElement$$forTypeof) = HooksBS.functionReturningReactElement; + +// Type annotated function components are not checked by Flow, but typeof() works. +const RenderPropRequiresConversion_make$$forTypeof = function (_: {| +renderVehicle: React$ComponentType<{| +number: number, +vehicle: vehicle |}> |}) : React$Node { return null }; + +export const RenderPropRequiresConversion_make: typeof(RenderPropRequiresConversion_make$$forTypeof) = function Hooks_RenderPropRequiresConversion(Arg1: $any) { + const result = HooksBS.RenderPropRequiresConversion[0]({renderVehicle:function (Arg11: $any) { + const result1 = React.createElement(Arg1.renderVehicle, {number:Arg11.number, vehicle:{name:Arg11.vehicle[0]}}); + return result1 + }}); + return result +}; diff --git a/examples/flow-react-example/src/Hooks.re b/examples/flow-react-example/src/Hooks.re index 23e8ee7f4..7af6547ee 100644 --- a/examples/flow-react-example/src/Hooks.re +++ b/examples/flow-react-example/src/Hooks.re @@ -17,12 +17,15 @@ let make = (~vehicle) => { - React.null}> + React.string(x##randomString)}> {React.string("child1")} {React.string("child2")} React.null}> + person={name: "DefaultImport", age: 42} + renderMe={x => React.string(x##randomString)}> {React.string("child1")} {React.string("child2")} @@ -116,4 +119,22 @@ let polymorphicComponent = (~p as (x, _)) => React.string(x.name); [@genType] [@react.component] -let functionReturningReactElement = (~name) => React.string(name); \ No newline at end of file +let functionReturningReactElement = (~name) => React.string(name); + +module RenderPropRequiresConversion = { + [@genType] + [@react.component] + let make = + ( + ~renderVehicle: + { + . + "vehicle": vehicle, + "number": int, + } => + React.element, + ) => { + let car = {name: "Car"}; + renderVehicle({"vehicle": car, "number": 42}); + }; +}; \ No newline at end of file diff --git a/examples/flow-react-example/src/ImportHookDefault.gen.js b/examples/flow-react-example/src/ImportHookDefault.gen.js index 8750c3093..48a8746bf 100644 --- a/examples/flow-react-example/src/ImportHookDefault.gen.js +++ b/examples/flow-react-example/src/ImportHookDefault.gen.js @@ -13,29 +13,32 @@ import {default as makeNotChecked} from './hookExample'; // flowlint-next-line nonstrict-import:off import {default as defaultNotChecked} from './hookExample'; +// $FlowExpectedError: Reason checked type sufficiently +import * as React from 'react'; + // In case of type error, check the type of 'make' in 'ImportHookDefault.re' and './hookExample'. -export const makeTypeChecked: ({| +export const makeTypeChecked: React$ComponentType<{| +person: person, +children: React$Node, +renderMe: ImportHooks_renderMe -|}) => React$Node = makeNotChecked; +|}> = makeNotChecked; // Export 'make' early to allow circular import from the '.bs.js' file. export const make: mixed = function hookExample(Arg1: $any) { - const result = makeTypeChecked({person:{name:Arg1.person[0], age:Arg1.person[1]}, children:Arg1.children, renderMe:Arg1.renderMe}); + const result = React.createElement(makeTypeChecked, {person:{name:Arg1.person[0], age:Arg1.person[1]}, children:Arg1.children, renderMe:Arg1.renderMe}); return result }; // In case of type error, check the type of 'default' in 'ImportHookDefault.re' and './hookExample'. -export const defaultTypeChecked: ({| +export const defaultTypeChecked: React$ComponentType<{| +person: person, +children: React$Node, +renderMe: ImportHooks_renderMe -|}) => React$Node = defaultNotChecked; +|}> = defaultNotChecked; // Export '$$default' early to allow circular import from the '.bs.js' file. export const $$default: mixed = function hookExample(Arg1: $any) { - const result = defaultTypeChecked({person:{name:Arg1.person[0], age:Arg1.person[1]}, children:Arg1.children, renderMe:Arg1.renderMe}); + const result = React.createElement(defaultTypeChecked, {person:{name:Arg1.person[0], age:Arg1.person[1]}, children:Arg1.children, renderMe:Arg1.renderMe}); return result }; diff --git a/examples/flow-react-example/src/ImportHooks.gen.js b/examples/flow-react-example/src/ImportHooks.gen.js index b89d2b119..94a08c7a9 100644 --- a/examples/flow-react-example/src/ImportHooks.gen.js +++ b/examples/flow-react-example/src/ImportHooks.gen.js @@ -13,16 +13,19 @@ import {makeRenamed as makeRenamedNotChecked} from './hookExample'; // flowlint-next-line nonstrict-import:off import {foo as fooNotChecked} from './hookExample'; +// $FlowExpectedError: Reason checked type sufficiently +import * as React from 'react'; + // In case of type error, check the type of 'makeRenamed' in 'ImportHooks.re' and './hookExample'. -export const makeRenamedTypeChecked: ({| +export const makeRenamedTypeChecked: React$ComponentType<{| +person: person, +children: React$Node, - +renderMe: renderMe -|}) => React$Node = makeRenamedNotChecked; + +renderMe: renderMe<$any> +|}> = makeRenamedNotChecked; // Export 'makeRenamed' early to allow circular import from the '.bs.js' file. export const makeRenamed: mixed = function hookExample(Arg1: $any) { - const result = makeRenamedTypeChecked({person:{name:Arg1.person[0], age:Arg1.person[1]}, children:Arg1.children, renderMe:Arg1.renderMe}); + const result = React.createElement(makeRenamedTypeChecked, {person:{name:Arg1.person[0], age:Arg1.person[1]}, children:Arg1.children, renderMe:Arg1.renderMe}); return result }; @@ -37,4 +40,4 @@ export const foo: mixed = function (Argperson: $any) { export type person = {| +name: string, +age: number |}; -export type renderMe = ({| +randomString: string, +poly: a |}) => React$Node; +export type renderMe = React$ComponentType<{| +randomString: string, +poly: a |}>; diff --git a/examples/flow-react-example/src/ImportHooks.re b/examples/flow-react-example/src/ImportHooks.re index dbf613186..d79fb58f2 100644 --- a/examples/flow-react-example/src/ImportHooks.re +++ b/examples/flow-react-example/src/ImportHooks.re @@ -6,12 +6,11 @@ type person = { [@genType] type renderMe('a) = - { + React.component({ . "randomString": string, "poly": 'a, - } => - React.element; + }); [@genType.import "./hookExample"] [@react.component] external make: diff --git a/examples/flow-react-example/src/hookExample.js b/examples/flow-react-example/src/hookExample.js index 27a4f89b7..6b8e86577 100644 --- a/examples/flow-react-example/src/hookExample.js +++ b/examples/flow-react-example/src/hookExample.js @@ -6,15 +6,27 @@ export const foo = function(x: { +person: { +name: string, +age: number } }) { return x.person.name; }; -export const make = (x: { +type Props = {| +person: { +name: string, +age: number }, - +children: React.Node -}) => ( -
- {" "} - {x.person.name} {x.children}{" "} -
-); + +children: React.Node, + +renderMe: React.ComponentType<{| + +randomString: string, + +poly: string + |}> +|}; + +export class make extends React.Component { + render() { + const RenderMe = this.props.renderMe; + return ( +
+ {" "} + {this.props.person.name} {this.props.children}{" "} + +
+ ); + } +} export const makeRenamed = make; diff --git a/examples/typescript-react-example/src/Hooks.bs.js b/examples/typescript-react-example/src/Hooks.bs.js index ff332c849..8a4f63ee9 100644 --- a/examples/typescript-react-example/src/Hooks.bs.js +++ b/examples/typescript-react-example/src/Hooks.bs.js @@ -24,8 +24,8 @@ function Hooks(Props) { /* age */71 ], children: null, - renderMe: (function (param) { - return null; + renderMe: (function (x) { + return x.randomString; }) }, "child1", "child2"), React.createElement(ImportHookDefault.make, { person: /* record */[ @@ -33,8 +33,8 @@ function Hooks(Props) { /* age */42 ], children: null, - renderMe: (function (param) { - return null; + renderMe: (function (x) { + return x.randomString; }) }, "child1", "child2")); } @@ -118,6 +118,16 @@ function Hooks$functionReturningReactElement(Props) { return Props.name; } +function Hooks$RenderPropRequiresConversion(Props) { + var renderVehicle = Props.renderVehicle; + return Curry._1(renderVehicle, { + vehicle: /* record */[/* name */"Car"], + number: 42 + }); +} + +var RenderPropRequiresConversion = /* module */[/* make */Hooks$RenderPropRequiresConversion]; + var make = Hooks; var $$default = Hooks; @@ -145,6 +155,7 @@ export { testForwardRef , polymorphicComponent , functionReturningReactElement , + RenderPropRequiresConversion , } /* testForwardRef Not a pure module */ diff --git a/examples/typescript-react-example/src/Hooks.gen.tsx b/examples/typescript-react-example/src/Hooks.gen.tsx index f93fd4291..2e1928777 100644 --- a/examples/typescript-react-example/src/Hooks.gen.tsx +++ b/examples/typescript-react-example/src/Hooks.gen.tsx @@ -2,6 +2,8 @@ /* eslint-disable import/first */ +import * as React from 'react'; + // tslint:disable-next-line:no-var-requires const Curry = require('bs-platform/lib/es6/curry.js'); @@ -23,39 +25,39 @@ export type testReactContext = React.Context; // tslint:disable-next-line:interface-over-type-literal export type testReactRef = React.Ref; -export const $$default: React.FC<{ readonly vehicle: vehicle }> = function Hooks(Arg1: any) { +export const $$default: React.ComponentType<{ readonly vehicle: vehicle }> = function Hooks(Arg1: any) { const result = HooksBS.default({vehicle:[Arg1.vehicle.name]}); return result }; export default $$default; -export const anotherComponent: React.FC<{ readonly callback: (_1:void) => void; readonly vehicle: vehicle }> = function Hooks_anotherComponent(Arg1: any) { +export const anotherComponent: React.ComponentType<{ readonly callback: (_1:void) => void; readonly vehicle: vehicle }> = function Hooks_anotherComponent(Arg1: any) { const result = HooksBS.anotherComponent({callback:Arg1.callback, vehicle:[Arg1.vehicle.name]}); return result }; -export const Inner_make: React.FC<{ readonly vehicle: vehicle }> = function Hooks_Inner(Arg1: any) { +export const Inner_make: React.ComponentType<{ readonly vehicle: vehicle }> = function Hooks_Inner(Arg1: any) { const result = HooksBS.Inner[0]({vehicle:[Arg1.vehicle.name]}); return result }; -export const Inner_anotherComponent: React.FC<{ readonly vehicle: vehicle }> = function Hooks_Inner_anotherComponent(Arg1: any) { +export const Inner_anotherComponent: React.ComponentType<{ readonly vehicle: vehicle }> = function Hooks_Inner_anotherComponent(Arg1: any) { const result = HooksBS.Inner[1]({vehicle:[Arg1.vehicle.name]}); return result }; -export const Inner_Inner2_make: React.FC<{ readonly vehicle: vehicle }> = function Hooks_Inner_Inner2(Arg1: any) { +export const Inner_Inner2_make: React.ComponentType<{ readonly vehicle: vehicle }> = function Hooks_Inner_Inner2(Arg1: any) { const result = HooksBS.Inner[2][0]({vehicle:[Arg1.vehicle.name]}); return result }; -export const Inner_Inner2_anotherComponent: React.FC<{ readonly vehicle: vehicle }> = function Hooks_Inner_Inner2_anotherComponent(Arg1: any) { +export const Inner_Inner2_anotherComponent: React.ComponentType<{ readonly vehicle: vehicle }> = function Hooks_Inner_Inner2_anotherComponent(Arg1: any) { const result = HooksBS.Inner[2][1]({vehicle:[Arg1.vehicle.name]}); return result }; -export const NoProps_make: React.FC<{}> = HooksBS.NoProps[0]; +export const NoProps_make: React.ComponentType<{}> = HooksBS.NoProps[0]; export const functionWithRenamedArgs: (_1:{ readonly to: vehicle; @@ -69,7 +71,7 @@ export const functionWithRenamedArgs: (_1:{ return result }; -export const componentWithRenamedArgs: React.FC<{ +export const componentWithRenamedArgs: React.ComponentType<{ readonly Type: vehicle; readonly to: vehicle; readonly cb: cb @@ -86,14 +88,22 @@ export const makeWithRef: (_1:{ readonly vehicle: vehicle }, _2:(null | undefine return result }; -export const testForwardRef: React.FC<{ readonly vehicle: vehicle }> = function Hooks_testForwardRef(Arg1: any) { +export const testForwardRef: React.ComponentType<{ readonly vehicle: vehicle }> = function Hooks_testForwardRef(Arg1: any) { const result = HooksBS.testForwardRef({vehicle:[Arg1.vehicle.name]}); return result }; -export const polymorphicComponent: React.FC<{ readonly p: [vehicle, any] }> = function Hooks_polymorphicComponent(Arg1: any) { +export const polymorphicComponent: React.ComponentType<{ readonly p: [vehicle, any] }> = function Hooks_polymorphicComponent(Arg1: any) { const result = HooksBS.polymorphicComponent({p:[[Arg1.p[0].name], Arg1.p[1]]}); return result }; -export const functionReturningReactElement: React.FC<{ readonly name: string }> = HooksBS.functionReturningReactElement; +export const functionReturningReactElement: React.ComponentType<{ readonly name: string }> = HooksBS.functionReturningReactElement; + +export const RenderPropRequiresConversion_make: React.ComponentType<{ readonly renderVehicle: React.ComponentType<{ readonly number: number; readonly vehicle: vehicle }> }> = function Hooks_RenderPropRequiresConversion(Arg1: any) { + const result = HooksBS.RenderPropRequiresConversion[0]({renderVehicle:function (Arg11: any) { + const result1 = React.createElement(Arg1.renderVehicle, {number:Arg11.number, vehicle:{name:Arg11.vehicle[0]}}); + return result1 + }}); + return result +}; diff --git a/examples/typescript-react-example/src/Hooks.re b/examples/typescript-react-example/src/Hooks.re index 23e8ee7f4..7af6547ee 100644 --- a/examples/typescript-react-example/src/Hooks.re +++ b/examples/typescript-react-example/src/Hooks.re @@ -17,12 +17,15 @@ let make = (~vehicle) => { - React.null}> + React.string(x##randomString)}> {React.string("child1")} {React.string("child2")} React.null}> + person={name: "DefaultImport", age: 42} + renderMe={x => React.string(x##randomString)}> {React.string("child1")} {React.string("child2")} @@ -116,4 +119,22 @@ let polymorphicComponent = (~p as (x, _)) => React.string(x.name); [@genType] [@react.component] -let functionReturningReactElement = (~name) => React.string(name); \ No newline at end of file +let functionReturningReactElement = (~name) => React.string(name); + +module RenderPropRequiresConversion = { + [@genType] + [@react.component] + let make = + ( + ~renderVehicle: + { + . + "vehicle": vehicle, + "number": int, + } => + React.element, + ) => { + let car = {name: "Car"}; + renderVehicle({"vehicle": car, "number": 42}); + }; +}; \ No newline at end of file diff --git a/examples/typescript-react-example/src/ImportHookDefault.gen.tsx b/examples/typescript-react-example/src/ImportHookDefault.gen.tsx index eaef1b044..dd992067d 100644 --- a/examples/typescript-react-example/src/ImportHookDefault.gen.tsx +++ b/examples/typescript-react-example/src/ImportHookDefault.gen.tsx @@ -6,8 +6,10 @@ import {default as makeNotChecked} from './hookExample'; import {default as defaultNotChecked} from './hookExample'; +import * as React from 'react'; + // In case of type error, check the type of 'make' in 'ImportHookDefault.re' and './hookExample'. -export const makeTypeChecked: React.FC<{ +export const makeTypeChecked: React.ComponentType<{ readonly person: person; readonly children: JSX.Element; readonly renderMe: ImportHooks_renderMe @@ -15,16 +17,16 @@ export const makeTypeChecked: React.FC<{ // Export 'make' early to allow circular import from the '.bs.js' file. export const make: unknown = function hookExample(Arg1: any) { - const result = makeTypeChecked({person:{name:Arg1.person[0], age:Arg1.person[1]}, children:Arg1.children, renderMe:Arg1.renderMe}); + const result = React.createElement(makeTypeChecked, {person:{name:Arg1.person[0], age:Arg1.person[1]}, children:Arg1.children, renderMe:Arg1.renderMe}); return result -} as React.FC<{ +} as React.ComponentType<{ readonly person: person; readonly children: JSX.Element; readonly renderMe: ImportHooks_renderMe }>; // In case of type error, check the type of 'default' in 'ImportHookDefault.re' and './hookExample'. -export const defaultTypeChecked: React.FC<{ +export const defaultTypeChecked: React.ComponentType<{ readonly person: person; readonly children: JSX.Element; readonly renderMe: ImportHooks_renderMe @@ -32,9 +34,9 @@ export const defaultTypeChecked: React.FC<{ // Export '$$default' early to allow circular import from the '.bs.js' file. export const $$default: unknown = function hookExample(Arg1: any) { - const result = defaultTypeChecked({person:{name:Arg1.person[0], age:Arg1.person[1]}, children:Arg1.children, renderMe:Arg1.renderMe}); + const result = React.createElement(defaultTypeChecked, {person:{name:Arg1.person[0], age:Arg1.person[1]}, children:Arg1.children, renderMe:Arg1.renderMe}); return result -} as React.FC<{ +} as React.ComponentType<{ readonly person: person; readonly children: JSX.Element; readonly renderMe: ImportHooks_renderMe diff --git a/examples/typescript-react-example/src/ImportHooks.gen.tsx b/examples/typescript-react-example/src/ImportHooks.gen.tsx index aeddeb759..aeea42a03 100644 --- a/examples/typescript-react-example/src/ImportHooks.gen.tsx +++ b/examples/typescript-react-example/src/ImportHooks.gen.tsx @@ -6,8 +6,10 @@ import {makeRenamed as makeRenamedNotChecked} from './hookExample'; import {foo as fooNotChecked} from './hookExample'; +import * as React from 'react'; + // In case of type error, check the type of 'makeRenamed' in 'ImportHooks.re' and './hookExample'. -export const makeRenamedTypeChecked: React.FC<{ +export const makeRenamedTypeChecked: React.ComponentType<{ readonly person: person; readonly children: JSX.Element; readonly renderMe: renderMe @@ -15,9 +17,9 @@ export const makeRenamedTypeChecked: React.FC<{ // Export 'makeRenamed' early to allow circular import from the '.bs.js' file. export const makeRenamed: unknown = function hookExample
(Arg1: any) { - const result = makeRenamedTypeChecked({person:{name:Arg1.person[0], age:Arg1.person[1]}, children:Arg1.children, renderMe:Arg1.renderMe}); + const result = React.createElement(makeRenamedTypeChecked, {person:{name:Arg1.person[0], age:Arg1.person[1]}, children:Arg1.children, renderMe:Arg1.renderMe}); return result -} as React.FC<{ +} as React.ComponentType<{ readonly person: person; readonly children: JSX.Element; readonly renderMe: renderMe @@ -36,4 +38,4 @@ export const foo: unknown = function (Argperson: any) { export type person = { readonly name: string; readonly age: number }; // tslint:disable-next-line:interface-over-type-literal -export type renderMe = React.FC<{ readonly randomString: string; readonly poly: a }>; +export type renderMe = React.ComponentType<{ readonly randomString: string; readonly poly: a }>; diff --git a/examples/typescript-react-example/src/ImportHooks.re b/examples/typescript-react-example/src/ImportHooks.re index dbf613186..d79fb58f2 100644 --- a/examples/typescript-react-example/src/ImportHooks.re +++ b/examples/typescript-react-example/src/ImportHooks.re @@ -6,12 +6,11 @@ type person = { [@genType] type renderMe('a) = - { + React.component({ . "randomString": string, "poly": 'a, - } => - React.element; + }); [@genType.import "./hookExample"] [@react.component] external make: diff --git a/examples/typescript-react-example/src/ReasonComponent.gen.tsx b/examples/typescript-react-example/src/ReasonComponent.gen.tsx index 878e5e6f2..de51e0251 100644 --- a/examples/typescript-react-example/src/ReasonComponent.gen.tsx +++ b/examples/typescript-react-example/src/ReasonComponent.gen.tsx @@ -48,7 +48,7 @@ export type Props = { readonly children?: unknown }; -export const ReasonComponent: React.ComponentClass = ReasonReact.wrapReasonForJs( +export const ReasonComponent: React.ComponentType = ReasonReact.wrapReasonForJs( ReasonComponentBS.component, (function _(jsProps: Props) { return Curry._4(ReasonComponentBS.make, jsProps.message, [jsProps.person.name, jsProps.person.surname, jsProps.person.type, jsProps.person.polymorphicPayload], jsProps.intList, jsProps.children); diff --git a/examples/typescript-react-example/src/TestImport.gen.tsx b/examples/typescript-react-example/src/TestImport.gen.tsx index 97e7050d7..136933608 100644 --- a/examples/typescript-react-example/src/TestImport.gen.tsx +++ b/examples/typescript-react-example/src/TestImport.gen.tsx @@ -12,10 +12,10 @@ import {default as defaultValueNotChecked} from './exportNestedValues'; import {TopLevelClass as TopLevelClass} from './MyBanner'; -import * as React from 'react'; - import {default as defaultValue2NotChecked} from './exportNestedValues'; +import * as React from 'react'; + // tslint:disable-next-line:no-var-requires const ReasonReact = require('reason-react/src/ReasonReact.js'); diff --git a/examples/typescript-react-example/src/components/ComponentAsProp.gen.tsx b/examples/typescript-react-example/src/components/ComponentAsProp.gen.tsx index 0722b9061..fb4c7c81f 100644 --- a/examples/typescript-react-example/src/components/ComponentAsProp.gen.tsx +++ b/examples/typescript-react-example/src/components/ComponentAsProp.gen.tsx @@ -19,7 +19,7 @@ export type Props = { readonly children?: unknown }; -export const ComponentAsProp: React.ComponentClass = ReasonReact.wrapReasonForJs( +export const ComponentAsProp: React.ComponentType = ReasonReact.wrapReasonForJs( ComponentAsPropBS.component, (function _(jsProps: Props) { return Curry._4(ComponentAsPropBS.make, jsProps.title, jsProps.description, jsProps.button, jsProps.children); diff --git a/examples/typescript-react-example/src/components/ManyComponents.gen.tsx b/examples/typescript-react-example/src/components/ManyComponents.gen.tsx index e5f4d5097..86e6794f7 100644 --- a/examples/typescript-react-example/src/components/ManyComponents.gen.tsx +++ b/examples/typescript-react-example/src/components/ManyComponents.gen.tsx @@ -14,7 +14,7 @@ const ReasonReact = require('reason-react/src/ReasonReact.js'); // tslint:disable-next-line:interface-over-type-literal export type InnerComponent_Props = { readonly children?: unknown }; -export const InnerComponent: React.ComponentClass = ReasonReact.wrapReasonForJs( +export const InnerComponent: React.ComponentType = ReasonReact.wrapReasonForJs( ManyComponentsBS.InnerComponent[1], (function _(jsProps: InnerComponent_Props) { return ManyComponentsBS.InnerComponent[2](jsProps.children); @@ -33,7 +33,7 @@ export type ManyProps_Props = { readonly children?: unknown }; -export const ManyProps: React.ComponentClass = ReasonReact.wrapReasonForJs( +export const ManyProps: React.ComponentType = ReasonReact.wrapReasonForJs( ManyComponentsBS.ManyProps[0], (function _(jsProps: ManyProps_Props) { return Curry.app(ManyComponentsBS.ManyProps[1], [jsProps.a, jsProps.b, jsProps.c, jsProps.d, jsProps.e, jsProps.f, jsProps.g, jsProps.h, jsProps.children]); @@ -42,7 +42,7 @@ export const ManyProps: React.ComponentClass = ReasonReact.wrap // tslint:disable-next-line:interface-over-type-literal export type Props = { readonly children?: unknown }; -export const ManyComponents: React.ComponentClass = ReasonReact.wrapReasonForJs( +export const ManyComponents: React.ComponentType = ReasonReact.wrapReasonForJs( ManyComponentsBS.component, (function _(jsProps: Props) { return ManyComponentsBS.make(jsProps.children); diff --git a/examples/typescript-react-example/src/hookExample.tsx b/examples/typescript-react-example/src/hookExample.tsx index 3671c92ae..851301482 100644 --- a/examples/typescript-react-example/src/hookExample.tsx +++ b/examples/typescript-react-example/src/hookExample.tsx @@ -7,16 +7,36 @@ export const foo = (x: { type Props = { readonly person: { readonly name: string; readonly age: number }; readonly children: JSX.Element; - readonly renderMe: React.FC<{ randomString: string; readonly poly: string }>; + readonly renderMe: React.ComponentType<{ + randomString: string; + readonly poly: string; + }>; }; -export const make: React.FC = (x: Props) => ( -
- {" "} - {x.person.name} {x.children}{" "} -
-); +export const make: React.FC = (x: Props) => { + const RenderMe = x.renderMe; + return ( +
+ {" "} + {x.person.name} {x.children}{" "} + +
+ ); +}; + +class AsClassComponent extends React.PureComponent { + public render() { + const RenderMe = this.props.renderMe; + return ( +
+ {" "} + {this.props.person.name} {this.props.children}{" "} + +
+ ); + } +} -export const makeRenamed = make; +export const makeRenamed = AsClassComponent; export default make; diff --git a/src/Config_.re b/src/Config_.re index 71ff1852c..43bb823db 100644 --- a/src/Config_.re +++ b/src/Config_.re @@ -22,6 +22,7 @@ type config = { mutable emitFlowAny: bool, mutable emitImportCurry: bool, mutable emitImportPropTypes: bool, + mutable emitImportReact: bool, exportInterfaces: bool, generatedFileExtension: option(string), importPath, @@ -40,6 +41,7 @@ let default = { emitFlowAny: false, emitImportCurry: false, emitImportPropTypes: false, + emitImportReact: false, exportInterfaces: false, generatedFileExtension: None, importPath: Relative, @@ -257,6 +259,7 @@ let readConfig = (~getConfigFile, ~getBsConfigFile, ~namespace) => { emitFlowAny: false, emitImportCurry: false, emitImportPropTypes: false, + emitImportReact: false, exportInterfaces, generatedFileExtension, importPath, diff --git a/src/Converter.re b/src/Converter.re index b40dbefa8..1776df8ba 100644 --- a/src/Converter.re +++ b/src/Converter.re @@ -18,7 +18,8 @@ and groupedArgConverter = and fieldsC = list((string, t)) and functionC = { argConverters: list(groupedArgConverter), - functionName: option(string), + componentName: option(string), + isHook: bool, retConverter: t, typeVars: list(string), uncurried: bool, @@ -130,15 +131,23 @@ let typeGetConverterNormalized = let (tConverter, tNormalized) = t |> visit(~visited); (ArrayC(tConverter), Array(tNormalized, mutable_)); - | Function({argTypes, retType, typeVars, uncurried} as function_) => + | Function( + {argTypes, componentName, retType, typeVars, uncurried} as function_, + ) => let argConverted = argTypes |> List.map(typeToGroupedArgConverter(~visited)); let argConverters = argConverted |> List.map(fst); let (retConverter, retNormalized) = retType |> visit(~visited); + let isHook = + switch (argTypes) { + | [Object(_)] => retType |> EmitType.isTypeReactElement(~config) + | _ => false + }; ( FunctionC({ argConverters, - functionName: None, + componentName, + isHook, retConverter, typeVars, uncurried, @@ -451,7 +460,8 @@ let rec apply = | FunctionC({ argConverters, - functionName, + componentName, + isHook, retConverter, typeVars, uncurried, @@ -555,8 +565,14 @@ let rec apply = let mkBody = bodyArgs => { let useCurry = !uncurried && toJS && List.length(bodyArgs) > 1; config.emitImportCurry = config.emitImportCurry || useCurry; + let hookToReason = !toJS && isHook; + let args = hookToReason ? [value, ...bodyArgs] : bodyArgs; + let functionName = hookToReason ? "React.createElement" : value; + if (hookToReason) { + config.emitImportReact = true; + }; Indent.break(~indent=indent1) - ++ (value |> EmitText.funCall(~args=bodyArgs, ~useCurry) |> mkReturn); + ++ (functionName |> EmitText.funCall(~args, ~useCurry) |> mkReturn); }; let convertedArgs = argConverters |> List.mapi(convertArg); @@ -565,7 +581,7 @@ let rec apply = let bodyArgs = convertedArgs |> List.map(snd) |> List.concat; EmitText.funDef( ~bodyArgs, - ~functionName, + ~functionName=componentName, ~funParams, ~indent, ~mkBody, diff --git a/src/EmitJs.re b/src/EmitJs.re index 191470269..f7807f7de 100644 --- a/src/EmitJs.re +++ b/src/EmitJs.re @@ -280,7 +280,7 @@ let rec emitCodeItem = let componentNameTypeChecked = lastNameInPath ++ "TypeChecked"; /* Check the type of the component */ - let emitters = EmitType.emitRequireReact(~early=true, ~emitters, ~config); + config.emitImportReact = true; let emitters = emitExportType( ~early=true, @@ -438,22 +438,16 @@ let rec emitCodeItem = |> requireModule(~import=true, ~env, ~importPath, ~strict=true); (emitters, importedAsName, env); }; - let converter = type_ |> typeGetConverter; - let isHook = + let type_ = switch (type_) { - | Function({argTypes: [Object(_)], retType}) + | Function({argTypes: [Object(_)], retType} as function_) when retType |> EmitType.isTypeReactElement(~config) => - true - | _ => false + Function({...function_, componentName: Some(importFile)}) + | _ => type_ }; - let converter = - switch (converter) { - | FunctionC(functionC) when isHook => - Converter.FunctionC({...functionC, functionName: Some(importFile)}) - | _ => converter - }; + let converter = type_ |> typeGetConverter; let valueNameTypeChecked = valueName ++ "TypeChecked"; @@ -678,26 +672,16 @@ let rec emitCodeItem = let fileNameBs = fileName |> ModuleName.forBsFile; let envWithRequires = fileNameBs |> requireModule(~import=false, ~env, ~importPath); - let converter = type_ |> typeGetConverter; let default = "default"; let make = "make"; - let hookType = + let (type_, hookType) = switch (type_) { - | Function({argTypes: [Object(_) as propsT], retType, typeVars}) + | Function( + {argTypes: [Object(_) as propsT], retType, typeVars} as function_, + ) when retType |> EmitType.isTypeReactElement(~config) => - Some((propsT, retType, typeVars)) - | _ => None - }; - /* Work around Flow issue with function components. - If type annotated direcly, they are not checked. But typeof() works. */ - let flowFunctionTypeWorkaround = - hookType != None && config.language == Flow; - - let converter = - switch (converter) { - | FunctionC(functionC) when hookType != None => let chopSuffix = suffix => resolvedName == suffix ? "" : @@ -714,9 +698,19 @@ let rec emitCodeItem = let hookName = (fileName |> ModuleName.toString) ++ (suffix == "" ? suffix : "_" ++ suffix); - Converter.FunctionC({...functionC, functionName: Some(hookName)}); - | _ => converter + + ( + Function({...function_, componentName: Some(hookName)}), + Some((propsT, retType, typeVars)), + ); + | _ => (type_, None) }; + /* Work around Flow issue with function components. + If type annotated direcly, they are not checked. But typeof() works. */ + let flowFunctionTypeWorkaround = + hookType != None && config.language == Flow; + + let converter = type_ |> typeGetConverter; let name = originalName == default ? Runtime.default : resolvedName; let hookNameForTypeof = name ++ "$$forTypeof"; @@ -1246,6 +1240,10 @@ let emitTranslationAsString = ~typeNameIsInterface=typeNameIsInterface(~env), ~variantTables, ); + let emitters = + config.emitImportReact ? + EmitType.emitImportReact(~emitters, ~config) : emitters; + let env = config.emitImportCurry ? ModuleName.curry diff --git a/src/EmitType.re b/src/EmitType.re index afc461b7a..0a9e57db2 100644 --- a/src/EmitType.re +++ b/src/EmitType.re @@ -46,9 +46,9 @@ let shimExtension = (~config) => let interfaceName = (~config, name) => config.exportInterfaces ? "I" ++ name : name; -let typeReactComponent = (~config, ~propsTypeName) => - (config.language == Flow ? "React$ComponentType" : "React.ComponentClass") - |> ident(~builtin=true, ~typeArgs=[ident(propsTypeName)]); +let typeReactComponent = (~config, ~propsType) => + (config.language == Flow ? "React$ComponentType" : "React.ComponentType") + |> ident(~builtin=true, ~typeArgs=[propsType]); let typeReactContext = (~config, ~type_) => (config.language == Flow ? "React$Context" : "React.Context") @@ -58,9 +58,6 @@ let typeReactElementFlow = ident(~builtin=true, "React$Node"); let typeReactElementTypeScript = ident(~builtin=true, "JSX.Element"); -let typeReactFunctionComponentTypeScript = (~propsType) => - ident(~builtin=true, ~typeArgs=[propsType], "React.FC"); - let typeReactElement = (~config) => config.language == Flow ? typeReactElementFlow : typeReactElementTypeScript; @@ -117,10 +114,7 @@ let rec renderType = }; | Function({argTypes: [Object(closedFlag, fields)], retType, typeVars}) - when - config.language == TypeScript - && retType - |> isTypeReactElement(~config) => + when retType |> isTypeReactElement(~config) => let fields = fields |> List.map(field => @@ -137,11 +131,9 @@ let rec renderType = ), } ); - let functionComponentType = - typeReactFunctionComponentTypeScript( - ~propsType=Object(closedFlag, fields), - ); - functionComponentType + let componentType = + typeReactComponent(~config, ~propsType=Object(closedFlag, fields)); + componentType |> renderType(~config, ~indent, ~typeNameIsInterface, ~inFunType); | Function({argTypes, retType, typeVars}) => @@ -624,12 +616,12 @@ let emitRequire = let require = (~early) => early ? Emitters.requireEarly : Emitters.require; -let emitRequireReact = (~early, ~emitters, ~config) => +let emitImportReact = (~emitters, ~config) => switch (config.language) { | Flow => emitRequire( ~importedValueOrComponent=false, - ~early, + ~early=true, ~emitters, ~config, ~moduleName=ModuleName.react, @@ -637,7 +629,7 @@ let emitRequireReact = (~early, ~emitters, ~config) => ImportPath.react, ) | TypeScript => - "import * as React from 'react';" |> require(~early, ~emitters) + "import * as React from 'react';" |> require(~early=true, ~emitters) | Untyped => emitters }; diff --git a/src/EmitType.rei b/src/EmitType.rei index 8002246b9..8fa7a8096 100644 --- a/src/EmitType.rei +++ b/src/EmitType.rei @@ -69,6 +69,8 @@ let emitHookTypeAsFunction: ) => Emitters.t; +let emitImportReact: (~emitters: Emitters.t, ~config: config) => Emitters.t; + let emitImportTypeAs: ( ~emitters: Emitters.t, @@ -112,9 +114,6 @@ let emitRequire: ) => Emitters.t; -let emitRequireReact: - (~early: bool, ~emitters: Emitters.t, ~config: config) => Emitters.t; - let emitTypeCast: ( ~config: config, @@ -146,9 +145,7 @@ let outputFileSuffix: (~config: config) => string; let shimExtension: (~config: config) => string; -let typeReactComponent: (~config: config, ~propsTypeName: string) => type_; - -let typeReactFunctionComponentTypeScript: (~propsType: type_) => type_; +let typeReactComponent: (~config: config, ~propsType: type_) => type_; let typeReactContext: (~config: config, ~type_: type_) => type_; diff --git a/src/GenTypeCommon.re b/src/GenTypeCommon.re index 55519b4e5..2c35c982c 100644 --- a/src/GenTypeCommon.re +++ b/src/GenTypeCommon.re @@ -71,6 +71,7 @@ and field = { } and function_ = { argTypes: list(type_), + componentName: option(string), retType: type_, typeVars: list(string), uncurried: bool, diff --git a/src/TranslateCoreType.re b/src/TranslateCoreType.re index 4dcc2d07d..2f139d09e 100644 --- a/src/TranslateCoreType.re +++ b/src/TranslateCoreType.re @@ -149,7 +149,13 @@ let rec translateArrowType = let argTypes = labeledConvertableTypes |> NamedArgs.group; let functionType = - Function({argTypes, retType, typeVars: [], uncurried: false}); + Function({ + argTypes, + componentName: None, + retType, + typeVars: [], + uncurried: false, + }); {dependencies: allDeps, type_: functionType}; } diff --git a/src/TranslateTypeExprFromTypes.re b/src/TranslateTypeExprFromTypes.re index 25010c793..cb6582b67 100644 --- a/src/TranslateTypeExprFromTypes.re +++ b/src/TranslateTypeExprFromTypes.re @@ -195,6 +195,7 @@ let translateConstr = type_: Function({ argTypes: [fromTranslation.type_], + componentName: None, retType: toTranslation.type_, typeVars: [], uncurried: false, @@ -210,6 +211,7 @@ let translateConstr = type_: Function({ argTypes: [propsTranslation.type_], + componentName: None, retType: retTranslation.type_, typeVars: [], uncurried: false, @@ -221,6 +223,7 @@ let translateConstr = type_: Function({ argTypes: [propsTranslation.type_], + componentName: None, retType: EmitType.typeReactElement(~config), typeVars: [], uncurried: false, @@ -308,6 +311,7 @@ let translateConstr = type_: Function({ argTypes: ts, + componentName: None, retType: ret.type_, typeVars: [], uncurried: true, @@ -328,6 +332,7 @@ let translateConstr = type_: Function({ argTypes: [], + componentName: None, retType: ret.type_, typeVars: [], uncurried: true, @@ -348,6 +353,7 @@ let translateConstr = type_: Function({ argTypes, + componentName: None, retType: ret.type_, typeVars: [], uncurried: true, @@ -523,7 +529,13 @@ let rec translateArrowType = let argTypes = labeledConvertableTypes |> NamedArgs.group; let functionType = - Function({argTypes, retType, typeVars: [], uncurried: false}); + Function({ + argTypes, + componentName: None, + retType, + typeVars: [], + uncurried: false, + }); {dependencies: allDeps, type_: functionType}; } diff --git a/src/Translation.re b/src/Translation.re index d75c1f23c..e97948607 100644 --- a/src/Translation.re +++ b/src/Translation.re @@ -35,8 +35,7 @@ let combine = (translations: list(t)): t => /* Applies type parameters to types (for all) */ let abstractTheTypeParameters = (~typeVars, type_) => switch (type_) { - | Function({argTypes, retType, uncurried, _}) => - Function({argTypes, retType, typeVars, uncurried}) + | Function(function_) => Function({...function_, typeVars}) | _ => type_ }; @@ -199,13 +198,13 @@ let translateComponent = "ReasonReact_component" | "React_component", typeArgs: [_state, ..._], - } as ident, + } as id, ), _, } as function_, ) => let type_ = - Function({...function_, retType: Ident({...ident, typeArgs: []})}); + Function({...function_, retType: Ident({...id, typeArgs: []})}); /* Add children?:any to props type */ let propsType = @@ -240,7 +239,8 @@ let translateComponent = }; let resolvedTypeName = "Props" |> TypeEnv.addModulePath(~typeEnv); let propsTypeName = resolvedTypeName |> ResolvedName.toString; - let componentType = EmitType.typeReactComponent(~config, ~propsTypeName); + let componentType = + EmitType.typeReactComponent(~config, ~propsType=ident(propsTypeName)); let nestedModuleName = typeEnv |> TypeEnv.getNestedModuleName;