diff --git a/.gitignore b/.gitignore index 8d17ffe2f..44fe5e025 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ finalOutput/*.js .merlin .install /lib/bs/ +/lib/js/ /lib/ocaml/ *.log .bsb.lock @@ -11,6 +12,7 @@ finalOutput/*.js _esy _build *.install +*.bs.js # Editor /.idea/ diff --git a/HISTORY.md b/HISTORY.md index c7f0ca92e..05fd92d10 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -12,7 +12,7 @@ Thanks to all of our contributors for making this happen! ### Breaking Changes: -* `bs-platform` has a minimum version of 7.0.1 (^7.0.1) [@imbsky in #503](https://github.com/reasonml/reason-react/pull/503) +* `bs-platform` has a minimum version of 7.1.1 (^7.1.1) [@imbsky in #503](https://github.com/reasonml/reason-react/pull/503) * Adds `onInvalid` prop [@bsansouci in #364](https://github.com/reasonml/reason-react/pull/364) * Adds `React.float` and `React.int` [@utkarshkukreti in #420](https://github.com/reasonml/reason-react/pull/420) * Fix `crossOrigin` case and type [@schmavery in #469](https://github.com/reasonml/reason-react/pull/469) diff --git a/README.md b/README.md index 569085d4a..107f9f3e0 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,88 @@ -# [ReasonReact](https://reasonml.github.io/reason-react/) +# [ReasonReact](https://reasonml.github.io/reason-react/) - ReasonML / BuckleScript bindings for React.js -[![Chat](https://img.shields.io/discord/235176658175262720.svg?logo=discord&colorb=blue)](https://discord.gg/reasonml) +[![npm version](https://badge.fury.io/js/reason-react.svg)](https://www.npmjs.com/package/reason-react) +![npm](https://img.shields.io/npm/dt/reason-react) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) +![contributors](https://img.shields.io/github/contributors/reasonml/reason-react) +[![discord](https://img.shields.io/discord/235176658175262720.svg?logo=discord&colorb=blue)](https://discord.gg/reasonml) +[![twitter](https://img.shields.io/twitter/follow/reasonml?style=social)](https://twitter.com/reasonml) + +ReasonReact is a safer, simpler way to build React components. You get a great type system with an even better developer experience. Why choose ReasonReact? Read more [here](https://reasonml.github.io/reason-react/docs/en/what-and-why) + +ReasonReact is just React.js under the hood. This makes it super easy to integrate with your current Next.js, Create React App, JavaScript, Flowtype or TypeScript project. Learn more about getting started [here](https://reasonml.github.io/reason-react/docs/en/installation#adding-reason-to-an-existing-reactjs-project-create-react-app-nextjs-etc) + +> Watch Ricky Vetter's Reason Conf talk, ["Why React is Just Better in Reason"](https://www.youtube.com/watch?v=i9Kr9wuz24g) to learn more about how Facebook & Messenger are using ReasonReact + +> Watch Jordan Walke's Reason Conf talk, ["React to the Future"](https://www.youtube.com/watch?v=5fG_lyNuEAw) to learn more about the future of ReasonML and React ## Example ```reason /* Greeting.re */ + [@react.component] let make = (~name) =>

{React.string("Hello " ++ name)}

``` -in another file: +See all of our examples [here](https://reasonml.github.io/reason-react/docs/en/simple). For a full application, check out [reason-react-hacker-news](https://github.com/reasonml-community/reason-react-hacker-news). -```reason -ReactDOMRe.renderToElementWithId(, "root"); -``` +## Getting Started -For a more in-depth example, see: https://github.com/reasonml-community/reason-react-hacker-news +[BuckleScript](http://bucklescript.github.io/) is how your ReasonML code gets compiled to Javascript. Every project that uses BuckleScript will have a `bsconfig.json` file (the same way you'd have tsconfig.json in a Typescript project) with project specific settings. -## Quick start - -[BuckleScript](http://bucklescript.github.io/) compiles ReasonML code to JavaScript. You can get it with: +You can install BuckleScript globally or keep it project specific by adding it as a `devDependency`: ```sh +yarn global add bs-platform + +# or npm npm install --global bs-platform +``` + +If you install BuckleScript globally, you can quickly generate a ReasonReact project template (similar to `create-react-app`): + +```sh bsb -init my-react-app -theme react-hooks cd my-react-app && npm install && npm start + # in another tab npm run server ``` +If you're interested in adding ReasonReact to your current project, it's a simple 2 step process: + +``` +yarn add bs-platform --dev --exact + +# or npm +npm install bs-platform -D -S +``` + +Add the appropriate script tags to package.json: + +```json +"scripts": { + "re:build": "bsb -make-world -clean-world", + "re:watch": "bsb -make-world -clean-world -w" +} +``` + +Copy the `bsconfig.json` file from our docs located [here](https://reasonml.github.io/reason-react/docs/en/installation#adding-reason-to-an-existing-reactjs-project-create-react-app-nextjs-etc) + +Then add some files somewhere (don't forget to change `bsconfig.json`, if needed). + +## Using Your Favorite Javascript Libraries + +The same way that TypeScript has `type annotations`, we have `bindings`. Bindings are libraries that allow you to import a popular project (like lodash) or to import your own local file. ReasonReact is in fact an example of a binding! + ## Documentation -See https://reasonml.github.io/reason-react/ +See [https://reasonml.github.io/reason-react](https://reasonml.github.io/reason-react) ## Contribute +We welcome all contributors! Anything from docs to issues to pull requests. Please help us :smile: + ```sh git clone https://github.com/reasonml/reason-react.git cd reason-react @@ -43,6 +90,17 @@ npm install npm start ``` -Then add some files somewhere (don't forget to change `bsconfig.json`, if needed). - See the README inside `src` for more info! + +## Editor Support + +Looking for syntax highlighting for your favorite editor? Check out [ReasonML Editor Plugins](https://reasonml.github.io/docs/en/editor-plugins) + +## Friends of ReasonReact + +- [genType](https://github.com/cristianoc/genType) - genType automatically generates bindings for your TypeScript / vanilla JS code. +- [reason-react-native](https://github.com/reason-react-native/reason-react-native) - ReasonML / Bucklescript bindings for React Native. Allows you to use Reason to build an iOS, Android or Web app! +- [reasonml.org](https://reasonml.org/) - An effort by the Reason Association to improve documentation for ReasonML & BuckleScript +- [redex.github.io](https://redex.github.io/) - Find bindings for your favorite libraries here +- [ReasonTown Podcast](https://anchor.fm/reason-town) - ReasonML Podcast +- [ReasonConf Youtube](https://www.youtube.com/channel/UCtFP_Hn5nIbZY4Xi47qfHhw/videos) Reason Conf on Youtube diff --git a/bsconfig.json b/bsconfig.json index 794d4871f..4e16b5f07 100644 --- a/bsconfig.json +++ b/bsconfig.json @@ -1,10 +1,8 @@ -/* This is the BuckleScript configuration file. Note that this is a comment; - BuckleScript comes with a JSON parser that supports comments and trailing - comma. If this screws with your editor highlighting, please tell us by filing - an issue! */ { - "name" : "reason-react", + "name": "reason-react", "sources": "src", + "package-specs": [{ "module": "es6", "in-source": true }], + "suffix": ".bs.js", "refmt": 3, "bsc-flags": ["-bs-no-version-header"] } diff --git a/docs/intro-example.md b/docs/intro-example.md index d08566147..93d999ca8 100644 --- a/docs/intro-example.md +++ b/docs/intro-example.md @@ -26,7 +26,7 @@ ReactDOMRe.renderToElementWithId(, "root"); This is how you used to write this in plain Javascript (index.js): ```js /* file: index.js */ -ReactDOM.render( document.getElementById("root")); +ReactDOM.render(, document.getElementById("root")); ``` ### Using Greeting in an existing Javascript/Typescript App diff --git a/docs/simple.md b/docs/simple.md index 136997f39..198afb28c 100644 --- a/docs/simple.md +++ b/docs/simple.md @@ -4,7 +4,11 @@ title: A List of Simple Examples ### A Basic Greeting Component -Reason's returns are implicit so you don't need to write `return`, it'll be the last item in the block: +**Some things to note**: + +- Reason's `return` statements are implicit so you don't need to write `return`. It'll always be the last item in the block +- Reason has labelled parameters (arguments) that are prefixed by a tilde, eg: `~message` +- Since everything in ReasonReact is typed, we need to wrap our message in `React.string(message)` ```reason /* Greeting.re */ diff --git a/docs/tailwind-css.md b/docs/tailwind-css.md new file mode 100644 index 000000000..5e86e402b --- /dev/null +++ b/docs/tailwind-css.md @@ -0,0 +1,82 @@ +--- +title: Styling: Tailwind CSS +--- + +[Tailwind CSS](https://tailwindcss.com) is a new CSS framework that is rapidly +growing in popularity. It's completely customizable and lightweight, making it +a perfect companion to React. If you're not familiar with Tailwind, we recommend +checking out [their docs](https://tailwindcss.com/#what-is-tailwind) for +a gentle introduction before moving forward. + +## Setting up Tailwind + +Now let's see how we can use Tailwind within ReasonReact and start building an +app! + +First, you'll need to create a new ReasonReact project -- we recommend [this +template](https://github.com/bodhish/create-reason-react-tailwind) (select the +`tailwind-starter` option) which has Tailwind set up out of the box. Once you've +installed the dependencies with `yarn install` or `npm install`, you should be +ready to go. + +Let's see an example of a React component using Tailwind: + +```reason +[@react.component] +let make = () => +
+
+ Sunset in the mountains +
+
{React.string("Tailwind")}
+

+ {React.string("A reason react starter with tailwind")} +

+
+
+
; +``` + +which gives us the following UI: + + + +## tailwind-ppx + +Often times when you're writing with Tailwind and ReasonReact, you may find +yourself wondering why the UI isn't working like it should, only to find out you +had a typo in a class name. For example, + +```reason +
+ ... +
+``` + +Wouldn't it be nice to get some validation _while_ you're writing the Tailwind +classes? Better yet, how about preventing your code from even compiling if the +classes aren't correct? Well, enter +[`tailwind-ppx`](https://github.com/dylanirlbeck/tailwind-ppx): a compile-time +validator for Tailwind CSS. Using this PPX, you can get immediate compiler +errors if you happen to misspell class names, along with a nice suggestion of +what you may have meant to write! + +```reason +
/* ERROR: Class name not found: flex-ro. Did you mean flex-row? */ + ... +
+``` + +Moreover, in a large codebase where components may have many class names, you +may find yourself duplicating some class names. `tailwind-ppx` solves this +issue, too! + +```reason +
/* ERROR: Duplicate class name: flex */ + ... +
+``` + +Wrapping the class names in a PPX allows for some powerful integrations with +Tailwind in addition to validation. For more information, check out +`tailwind-ppx`'s [other features](https://github.com/dylanirlbeck/tailwind-ppx#features) diff --git a/docs/use-state-use-effect.md b/docs/use-state-use-effect.md index 2bf9beb01..4fc9d9fd5 100644 --- a/docs/use-state-use-effect.md +++ b/docs/use-state-use-effect.md @@ -30,7 +30,12 @@ let make = (~label, ~onSubmit) => { onChange(ReactEvent.Form.target(event)##value)} + onChange={ + event => { + let value = ReactEvent.Form.target(event)##value; + onChange(_ => value) + } + } value /> ; diff --git a/docs/usestate-event-value.md b/docs/usestate-event-value.md new file mode 100644 index 000000000..e3fae88f4 --- /dev/null +++ b/docs/usestate-event-value.md @@ -0,0 +1,75 @@ +--- +title: Using Event Values With useState +--- + +In ReactJS, it's common to update a component's state based on an event's +value. Because ReasonReact's `useState` is slightly different than ReactJS, +directly translating JavaScript components to Reason can lead to a common bug. + +```js +/* App.js */ +function App() { + const [name, setName] = React.useState("John"); + return ( + setName(event.target.value)} + /> + ); +} +``` + +If we convert this component to reason, we may attempt to write this: + +```reason +/* App.re */ +/* WRONG! Don't do this! */ +[@react.component] +let make = () => { + let (name, setName) = React.useState(() => "John"); + setName(_ => ReactEvent.Form.target(event)##value) + />; +}; +``` + +Can you spot the bug? + +In the Reason version, the `onChange` callback won't correctly update the state. +This happens because the callback passed to `setName` is run *after* the `event` +variable is cleaned up by React, so the `value` field won't exist when it's +needed. + +This isn't actually any different than how events and `useState` hooks work in +ReactJS when you choose to use a callback with `setName`. The only difference +is that ReasonReact enforces that we always use callbacks. + +## Solution + +Fortunately, fixing this bug is simple: + +```reason +/* App.re */ +/* CORRECT! This code is bug-free. 👍 */ +[@react.component] +let make = () => { + let (name, setName) = React.useState(() => "John"); + { + let value = ReactEvent.Form.target(event)##value; + setName(_ => value) + } + } + />; +}; +``` + +The key is to extract the `value` from the `event` *before* we send it to +`setName`. Even if React cleans up the event, we don't lose access to the +value we need. diff --git a/docs/what-and-why.md b/docs/what-and-why.md index 5d1be5e49..18aa1e771 100644 --- a/docs/what-and-why.md +++ b/docs/what-and-why.md @@ -2,13 +2,14 @@ title: What & Why --- -ReasonReact is a safer, simpler way to build [React](https://reactjs.org/) components, in [Reason](http://reasonml.github.io/). +ReasonReact helps you use [Reason](http://reasonml.github.io/) to build [React](https://reactjs.org/) components with deeply integrated, strong, static type safety. -By leveraging the latter's great type system, expressive language features and smooth interoperability with JS, ReasonReact packs ReactJS' features into an API that is: +It is designed and built by people using Reason and React in large, mission critical production React codebases. -- Safe and statically typed -- Simple and lean -- Familiar and easy to insert into an existing ReactJS codebase -- Well thought-out (made by the creator of ReactJS himself!) +ReasonReact uses Reason's expressive language features, along with smooth JavaScript interop to provide a React API that is: -It is often said that writing ReactJS code feels like "just using JavaScript". The same applies to ReasonReact, but we push it further; writing routing, data management, component composition and components themselves feel like "just using Reason". +- Safe and statically typed (with full type inference). +- Simple and lean. +- Familiar and easy to insert into an existing ReactJS codebase. + +One of ReactJS's strengths is that it uses the programming language's features to implement framework features. It is often said that ReactJS feels like an extension of the programming language. ReasonReact pushes that philosophy further because the Reason syntax and language features are a better match for React programming patterns. ReasonReact also uses built-in language features to seamlessly integrate into other UI framework patterns left unaddressed by ReactJS. Routing, data management, component composition and components themselves all feel like "just using Reason". diff --git a/lib/js/src/React.js b/lib/js/src/React.js deleted file mode 100644 index 98d1b74e6..000000000 --- a/lib/js/src/React.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - - -var Ref = { }; - -var Children = { }; - -var Context = { }; - -var Fragment = { }; - -var Suspense = { }; - -var SuspenseList = { }; - -exports.Ref = Ref; -exports.Children = Children; -exports.Context = Context; -exports.Fragment = Fragment; -exports.Suspense = Suspense; -exports.SuspenseList = SuspenseList; -/* No side effect */ diff --git a/lib/js/src/ReactDOMServerRe.js b/lib/js/src/ReactDOMServerRe.js deleted file mode 100644 index ae1b9f17e..000000000 --- a/lib/js/src/ReactDOMServerRe.js +++ /dev/null @@ -1 +0,0 @@ -/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ diff --git a/lib/js/src/ReactEvent.js b/lib/js/src/ReactEvent.js deleted file mode 100644 index 3b4a9a18f..000000000 --- a/lib/js/src/ReactEvent.js +++ /dev/null @@ -1,49 +0,0 @@ -'use strict'; - - -var Synthetic = { }; - -var Clipboard = { }; - -var Composition = { }; - -var Keyboard = { }; - -var Focus = { }; - -var Form = { }; - -var Mouse = { }; - -var $$Selection = { }; - -var $$Touch = { }; - -var UI = { }; - -var Wheel = { }; - -var Media = { }; - -var $$Image = { }; - -var Animation = { }; - -var Transition = { }; - -exports.Synthetic = Synthetic; -exports.Clipboard = Clipboard; -exports.Composition = Composition; -exports.Keyboard = Keyboard; -exports.Focus = Focus; -exports.Form = Form; -exports.Mouse = Mouse; -exports.$$Selection = $$Selection; -exports.$$Touch = $$Touch; -exports.UI = UI; -exports.Wheel = Wheel; -exports.Media = Media; -exports.$$Image = $$Image; -exports.Animation = Animation; -exports.Transition = Transition; -/* No side effect */ diff --git a/lib/js/src/ReactEventRe.js b/lib/js/src/ReactEventRe.js deleted file mode 100644 index 3b4a9a18f..000000000 --- a/lib/js/src/ReactEventRe.js +++ /dev/null @@ -1,49 +0,0 @@ -'use strict'; - - -var Synthetic = { }; - -var Clipboard = { }; - -var Composition = { }; - -var Keyboard = { }; - -var Focus = { }; - -var Form = { }; - -var Mouse = { }; - -var $$Selection = { }; - -var $$Touch = { }; - -var UI = { }; - -var Wheel = { }; - -var Media = { }; - -var $$Image = { }; - -var Animation = { }; - -var Transition = { }; - -exports.Synthetic = Synthetic; -exports.Clipboard = Clipboard; -exports.Composition = Composition; -exports.Keyboard = Keyboard; -exports.Focus = Focus; -exports.Form = Form; -exports.Mouse = Mouse; -exports.$$Selection = $$Selection; -exports.$$Touch = $$Touch; -exports.UI = UI; -exports.Wheel = Wheel; -exports.Media = Media; -exports.$$Image = $$Image; -exports.Animation = Animation; -exports.Transition = Transition; -/* No side effect */ diff --git a/lib/js/src/ReasonReact.js b/lib/js/src/ReasonReact.js deleted file mode 100644 index 61c00f8c4..000000000 --- a/lib/js/src/ReasonReact.js +++ /dev/null @@ -1,416 +0,0 @@ -'use strict'; - -var Curry = require("bs-platform/lib/js/curry.js"); -var React = require("react"); -var Caml_option = require("bs-platform/lib/js/caml_option.js"); -var Caml_builtin_exceptions = require("bs-platform/lib/js/caml_builtin_exceptions.js"); -var ReasonReactOptimizedCreateClass = require("./ReasonReactOptimizedCreateClass.js"); - -function createDomElement(s, props, children) { - var vararg = [ - s, - props - ].concat(children); - return React.createElement.apply(null, vararg); -} - -function anyToUnit(param) { - return /* () */0; -} - -function anyToTrue(param) { - return true; -} - -function willReceivePropsDefault(param) { - return param.state; -} - -function renderDefault(_self) { - return "RenderNotImplemented"; -} - -function initialStateDefault(param) { - return /* () */0; -} - -function reducerDefault(_action, _state) { - return /* NoUpdate */0; -} - -function convertPropsIfTheyreFromJs(props, jsPropsToReason, debugName) { - var match = props.reasonProps; - if (match == null) { - if (jsPropsToReason !== undefined) { - return /* Element */[Caml_option.valFromOption(jsPropsToReason)(props)]; - } else { - throw [ - Caml_builtin_exceptions.invalid_argument, - "A JS component called the Reason component " + (debugName + " which didn't implement the JS->Reason React props conversion.") - ]; - } - } else { - return match; - } -} - -function createClass(debugName) { - return ReasonReactOptimizedCreateClass.createClass({ - displayName: debugName, - subscriptions: null, - self: (function (state, retainedProps) { - var $$this = this ; - return { - handle: $$this.handleMethod, - state: state, - retainedProps: retainedProps, - send: $$this.sendMethod, - onUnmount: $$this.onUnmountMethod - }; - }), - getInitialState: (function () { - var thisJs = this; - var convertedReasonProps = convertPropsIfTheyreFromJs(thisJs.props, thisJs.jsPropsToReason, debugName); - return { - reasonState: Curry._1(convertedReasonProps[0].initialState, /* () */0) - }; - }), - componentDidMount: (function () { - var $$this = this ; - var thisJs = this; - var convertedReasonProps = convertPropsIfTheyreFromJs(thisJs.props, thisJs.jsPropsToReason, debugName); - var component = convertedReasonProps[0]; - var curTotalState = thisJs.state; - var curReasonState = curTotalState.reasonState; - var self = $$this.self(curReasonState, component.retainedProps); - if (component.didMount !== anyToUnit) { - return Curry._1(component.didMount, self); - } else { - return 0; - } - }), - componentDidUpdate: (function (prevProps, prevState) { - var $$this = this ; - var thisJs = this; - var curState = thisJs.state; - var curReasonState = curState.reasonState; - var newJsProps = thisJs.props; - var newConvertedReasonProps = convertPropsIfTheyreFromJs(newJsProps, thisJs.jsPropsToReason, debugName); - var newComponent = newConvertedReasonProps[0]; - if (newComponent.didUpdate !== anyToUnit) { - var oldConvertedReasonProps = prevProps === newJsProps ? newConvertedReasonProps : convertPropsIfTheyreFromJs(prevProps, thisJs.jsPropsToReason, debugName); - var prevReasonState = prevState.reasonState; - var newSelf = $$this.self(curReasonState, newComponent.retainedProps); - var oldSelf_handle = newSelf.handle; - var oldSelf_retainedProps = oldConvertedReasonProps[0].retainedProps; - var oldSelf_send = newSelf.send; - var oldSelf_onUnmount = newSelf.onUnmount; - var oldSelf = { - handle: oldSelf_handle, - state: prevReasonState, - retainedProps: oldSelf_retainedProps, - send: oldSelf_send, - onUnmount: oldSelf_onUnmount - }; - return Curry._1(newComponent.didUpdate, { - oldSelf: oldSelf, - newSelf: newSelf - }); - } else { - return 0; - } - }), - componentWillUnmount: (function () { - var $$this = this ; - var thisJs = this; - var convertedReasonProps = convertPropsIfTheyreFromJs(thisJs.props, thisJs.jsPropsToReason, debugName); - var component = convertedReasonProps[0]; - var curState = thisJs.state; - var curReasonState = curState.reasonState; - if (component.willUnmount !== anyToUnit) { - Curry._1(component.willUnmount, $$this.self(curReasonState, component.retainedProps)); - } - var match = $$this.subscriptions; - if (match !== null) { - match.forEach((function (unsubscribe) { - return Curry._1(unsubscribe, /* () */0); - })); - return /* () */0; - } else { - return /* () */0; - } - }), - componentWillUpdate: (function (nextProps, nextState) { - var $$this = this ; - var thisJs = this; - var newConvertedReasonProps = convertPropsIfTheyreFromJs(nextProps, thisJs.jsPropsToReason, debugName); - var newComponent = newConvertedReasonProps[0]; - if (newComponent.willUpdate !== anyToUnit) { - var oldJsProps = thisJs.props; - var oldConvertedReasonProps = nextProps === oldJsProps ? newConvertedReasonProps : convertPropsIfTheyreFromJs(oldJsProps, thisJs.jsPropsToReason, debugName); - var curState = thisJs.state; - var curReasonState = curState.reasonState; - var nextReasonState = nextState.reasonState; - var newSelf = $$this.self(nextReasonState, newComponent.retainedProps); - var oldSelf_handle = newSelf.handle; - var oldSelf_retainedProps = oldConvertedReasonProps[0].retainedProps; - var oldSelf_send = newSelf.send; - var oldSelf_onUnmount = newSelf.onUnmount; - var oldSelf = { - handle: oldSelf_handle, - state: curReasonState, - retainedProps: oldSelf_retainedProps, - send: oldSelf_send, - onUnmount: oldSelf_onUnmount - }; - return Curry._1(newComponent.willUpdate, { - oldSelf: oldSelf, - newSelf: newSelf - }); - } else { - return 0; - } - }), - componentWillReceiveProps: (function (nextProps) { - var $$this = this ; - var thisJs = this; - var newConvertedReasonProps = convertPropsIfTheyreFromJs(nextProps, thisJs.jsPropsToReason, debugName); - var newComponent = newConvertedReasonProps[0]; - if (newComponent.willReceiveProps !== willReceivePropsDefault) { - var oldJsProps = thisJs.props; - var oldConvertedReasonProps = nextProps === oldJsProps ? newConvertedReasonProps : convertPropsIfTheyreFromJs(oldJsProps, thisJs.jsPropsToReason, debugName); - var oldComponent = oldConvertedReasonProps[0]; - return thisJs.setState((function (curTotalState, param) { - var curReasonState = curTotalState.reasonState; - var oldSelf = $$this.self(curReasonState, oldComponent.retainedProps); - var nextReasonState = Curry._1(newComponent.willReceiveProps, oldSelf); - if (nextReasonState !== curTotalState) { - return { - reasonState: nextReasonState - }; - } else { - return curTotalState; - } - }), null); - } else { - return 0; - } - }), - shouldComponentUpdate: (function (nextJsProps, nextState, param) { - var $$this = this ; - var thisJs = this; - var curJsProps = thisJs.props; - var oldConvertedReasonProps = convertPropsIfTheyreFromJs(thisJs.props, thisJs.jsPropsToReason, debugName); - var newConvertedReasonProps = nextJsProps === curJsProps ? oldConvertedReasonProps : convertPropsIfTheyreFromJs(nextJsProps, thisJs.jsPropsToReason, debugName); - var newComponent = newConvertedReasonProps[0]; - var nextReasonState = nextState.reasonState; - var newSelf = $$this.self(nextReasonState, newComponent.retainedProps); - if (newComponent.shouldUpdate !== anyToTrue) { - var curState = thisJs.state; - var curReasonState = curState.reasonState; - var oldSelf_handle = newSelf.handle; - var oldSelf_retainedProps = oldConvertedReasonProps[0].retainedProps; - var oldSelf_send = newSelf.send; - var oldSelf_onUnmount = newSelf.onUnmount; - var oldSelf = { - handle: oldSelf_handle, - state: curReasonState, - retainedProps: oldSelf_retainedProps, - send: oldSelf_send, - onUnmount: oldSelf_onUnmount - }; - return Curry._1(newComponent.shouldUpdate, { - oldSelf: oldSelf, - newSelf: newSelf - }); - } else { - return true; - } - }), - onUnmountMethod: (function (subscription) { - var $$this = this ; - var match = $$this.subscriptions; - if (match !== null) { - match.push(subscription); - return /* () */0; - } else { - $$this.subscriptions = [subscription]; - return /* () */0; - } - }), - handleMethod: (function (callback) { - var $$this = this ; - var thisJs = this; - return (function (callbackPayload) { - var curState = thisJs.state; - var curReasonState = curState.reasonState; - var convertedReasonProps = convertPropsIfTheyreFromJs(thisJs.props, thisJs.jsPropsToReason, debugName); - return Curry._2(callback, callbackPayload, $$this.self(curReasonState, convertedReasonProps[0].retainedProps)); - }); - }), - sendMethod: (function (action) { - var $$this = this ; - var thisJs = this; - var convertedReasonProps = convertPropsIfTheyreFromJs(thisJs.props, thisJs.jsPropsToReason, debugName); - var component = convertedReasonProps[0]; - if (component.reducer !== reducerDefault) { - var sideEffects = { - contents: (function (prim) { - return /* () */0; - }) - }; - var partialStateApplication = Curry._1(component.reducer, action); - return thisJs.setState((function (curTotalState, param) { - var curReasonState = curTotalState.reasonState; - var reasonStateUpdate = Curry._1(partialStateApplication, curReasonState); - if (reasonStateUpdate === /* NoUpdate */0) { - return null; - } else { - var nextTotalState; - if (typeof reasonStateUpdate === "number") { - nextTotalState = curTotalState; - } else { - switch (reasonStateUpdate.tag | 0) { - case /* Update */0 : - nextTotalState = { - reasonState: reasonStateUpdate[0] - }; - break; - case /* SideEffects */1 : - sideEffects.contents = reasonStateUpdate[0]; - nextTotalState = curTotalState; - break; - case /* UpdateWithSideEffects */2 : - sideEffects.contents = reasonStateUpdate[1]; - nextTotalState = { - reasonState: reasonStateUpdate[0] - }; - break; - - } - } - if (nextTotalState !== curTotalState) { - return nextTotalState; - } else { - return null; - } - } - }), $$this.handleMethod((function (param, self) { - return Curry._1(sideEffects.contents, self); - }))); - } else { - return 0; - } - }), - render: (function () { - var $$this = this ; - var thisJs = this; - var convertedReasonProps = convertPropsIfTheyreFromJs(thisJs.props, thisJs.jsPropsToReason, debugName); - var created = convertedReasonProps[0]; - var curState = thisJs.state; - var curReasonState = curState.reasonState; - return Curry._1(created.render, $$this.self(curReasonState, created.retainedProps)); - }) - }); -} - -function basicComponent(debugName) { - return { - debugName: debugName, - reactClassInternal: createClass(debugName), - handedOffState: { - contents: undefined - }, - willReceiveProps: willReceivePropsDefault, - didMount: anyToUnit, - didUpdate: anyToUnit, - willUnmount: anyToUnit, - willUpdate: anyToUnit, - shouldUpdate: anyToTrue, - render: renderDefault, - initialState: initialStateDefault, - retainedProps: /* () */0, - reducer: reducerDefault, - jsElementWrapped: undefined - }; -} - -var statelessComponent = basicComponent; - -var statelessComponentWithRetainedProps = basicComponent; - -var reducerComponent = basicComponent; - -var reducerComponentWithRetainedProps = basicComponent; - -function element($staropt$star, $staropt$star$1, component) { - var key = $staropt$star !== undefined ? $staropt$star : undefined; - var ref = $staropt$star$1 !== undefined ? $staropt$star$1 : undefined; - var element$1 = /* Element */[component]; - var match = component.jsElementWrapped; - if (match !== undefined) { - return Curry._2(match, key, ref); - } else { - return React.createElement(component.reactClassInternal, { - key: key, - ref: ref, - reasonProps: element$1 - }); - } -} - -function wrapReasonForJs(component, jsPropsToReason) { - var uncurriedJsPropsToReason = Curry.__1(jsPropsToReason); - component.reactClassInternal.prototype.jsPropsToReason = Caml_option.some(uncurriedJsPropsToReason); - return component.reactClassInternal; -} - -var dummyInteropComponent = basicComponent("interop"); - -function wrapJsForReason(reactClass, props, children) { - var jsElementWrapped = (function (param, param$1) { - var reactClass$1 = reactClass; - var props$1 = props; - var children$1 = children; - var key = param; - var ref = param$1; - var props$2 = Object.assign(Object.assign({ }, props$1), { - ref: ref, - key: key - }); - var varargs = [ - reactClass$1, - props$2 - ].concat(children$1); - return React.createElement.apply(null, varargs); - }); - return { - debugName: dummyInteropComponent.debugName, - reactClassInternal: dummyInteropComponent.reactClassInternal, - handedOffState: dummyInteropComponent.handedOffState, - willReceiveProps: dummyInteropComponent.willReceiveProps, - didMount: dummyInteropComponent.didMount, - didUpdate: dummyInteropComponent.didUpdate, - willUnmount: dummyInteropComponent.willUnmount, - willUpdate: dummyInteropComponent.willUpdate, - shouldUpdate: dummyInteropComponent.shouldUpdate, - render: dummyInteropComponent.render, - initialState: dummyInteropComponent.initialState, - retainedProps: dummyInteropComponent.retainedProps, - reducer: dummyInteropComponent.reducer, - jsElementWrapped: jsElementWrapped - }; -} - -var Router = /* alias */0; - -exports.statelessComponent = statelessComponent; -exports.statelessComponentWithRetainedProps = statelessComponentWithRetainedProps; -exports.reducerComponent = reducerComponent; -exports.reducerComponentWithRetainedProps = reducerComponentWithRetainedProps; -exports.element = element; -exports.wrapReasonForJs = wrapReasonForJs; -exports.createDomElement = createDomElement; -exports.wrapJsForReason = wrapJsForReason; -exports.Router = Router; -/* dummyInteropComponent Not a pure module */ diff --git a/lib/js/src/ReasonReactCompat.js b/lib/js/src/ReasonReactCompat.js deleted file mode 100644 index c55ec2a97..000000000 --- a/lib/js/src/ReasonReactCompat.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -var ReasonReact = require("./ReasonReact.js"); - -var wrapReactForReasonReact = ReasonReact.wrapJsForReason; - -var wrapReasonReactForReact = ReasonReact.wrapReasonForJs; - -exports.wrapReactForReasonReact = wrapReactForReasonReact; -exports.wrapReasonReactForReact = wrapReasonReactForReact; -/* ReasonReact Not a pure module */ diff --git a/lib/js/src/ReasonReactOptimizedCreateClass.js b/lib/js/src/ReasonReactOptimizedCreateClass.js deleted file mode 100644 index c9f073f53..000000000 --- a/lib/js/src/ReasonReactOptimizedCreateClass.js +++ /dev/null @@ -1,892 +0,0 @@ -'use strict'; - -var React = require("react"); - -function _assign(prim, prim$1) { - return Object.assign(prim, prim$1); -} - -var emptyObject = { }; - -/** - * Copyright 2013-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - */ - -// 'use strict'; - -// var _assign = require('object-assign'); - -// var emptyObject = require('emptyObject'); -// var _invariant = require('invariant'); - -// if (process.env.NODE_ENV !== 'production') { -// var warning = require('fbjs/lib/warning'); -// } - -var MIXINS_KEY = 'mixins'; - -// Helper function to allow the creation of anonymous functions which do not -// have .name set to the name of the variable being assigned to. -function identity(fn) { - return fn; -} - -var ReactPropTypeLocationNames; -// if (process.env.NODE_ENV !== 'production') { -// ReactPropTypeLocationNames = { -// prop: 'prop', -// context: 'context', -// childContext: 'child context' -// }; -// } else { - ReactPropTypeLocationNames = {}; -// } -; - -var factory = (function factory(ReactComponent, isValidElement, ReactNoopUpdateQueue) { - /** - * Policies that describe methods in `ReactClassInterface`. - */ - - var injectedMixins = []; - - /** - * Composite components are higher-level components that compose other composite - * or host components. - * - * To create a new type of `ReactClass`, pass a specification of - * your new class to `React.createClass`. The only requirement of your class - * specification is that you implement a `render` method. - * - * var MyComponent = React.createClass({ - * render: function() { - * return
Hello World
; - * } - * }); - * - * The class specification supports a specific protocol of methods that have - * special meaning (e.g. `render`). See `ReactClassInterface` for - * more the comprehensive protocol. Any other properties and methods in the - * class specification will be available on the prototype. - * - * @interface ReactClassInterface - * @internal - */ - var ReactClassInterface = { - /** - * An array of Mixin objects to include when defining your component. - * - * @type {array} - * @optional - */ - mixins: 'DEFINE_MANY', - - /** - * An object containing properties and methods that should be defined on - * the component's constructor instead of its prototype (static methods). - * - * @type {object} - * @optional - */ - statics: 'DEFINE_MANY', - - /** - * Definition of prop types for this component. - * - * @type {object} - * @optional - */ - propTypes: 'DEFINE_MANY', - - /** - * Definition of context types for this component. - * - * @type {object} - * @optional - */ - contextTypes: 'DEFINE_MANY', - - /** - * Definition of context types this component sets for its children. - * - * @type {object} - * @optional - */ - childContextTypes: 'DEFINE_MANY', - - // ==== Definition methods ==== - - /** - * Invoked when the component is mounted. Values in the mapping will be set on - * `this.props` if that prop is not specified (i.e. using an `in` check). - * - * This method is invoked before `getInitialState` and therefore cannot rely - * on `this.state` or use `this.setState`. - * - * @return {object} - * @optional - */ - getDefaultProps: 'DEFINE_MANY_MERGED', - - /** - * Invoked once before the component is mounted. The return value will be used - * as the initial value of `this.state`. - * - * getInitialState: function() { - * return { - * isOn: false, - * fooBaz: new BazFoo() - * } - * } - * - * @return {object} - * @optional - */ - getInitialState: 'DEFINE_MANY_MERGED', - - /** - * @return {object} - * @optional - */ - getChildContext: 'DEFINE_MANY_MERGED', - - /** - * Uses props from `this.props` and state from `this.state` to render the - * structure of the component. - * - * No guarantees are made about when or how often this method is invoked, so - * it must not have side effects. - * - * render: function() { - * var name = this.props.name; - * return
Hello, {name}!
; - * } - * - * @return {ReactComponent} - * @required - */ - render: 'DEFINE_ONCE', - - // ==== Delegate methods ==== - - /** - * Invoked when the component is initially created and about to be mounted. - * This may have side effects, but any external subscriptions or data created - * by this method must be cleaned up in `componentWillUnmount`. - * - * @optional - */ - componentWillMount: 'DEFINE_MANY', - - /** - * Invoked when the component has been mounted and has a DOM representation. - * However, there is no guarantee that the DOM node is in the document. - * - * Use this as an opportunity to operate on the DOM when the component has - * been mounted (initialized and rendered) for the first time. - * - * @param {DOMElement} rootNode DOM element representing the component. - * @optional - */ - componentDidMount: 'DEFINE_MANY', - - /** - * Invoked before the component receives new props. - * - * Use this as an opportunity to react to a prop transition by updating the - * state using `this.setState`. Current props are accessed via `this.props`. - * - * componentWillReceiveProps: function(nextProps, nextContext) { - * this.setState({ - * likesIncreasing: nextProps.likeCount > this.props.likeCount - * }); - * } - * - * NOTE: There is no equivalent `componentWillReceiveState`. An incoming prop - * transition may cause a state change, but the opposite is not true. If you - * need it, you are probably looking for `componentWillUpdate`. - * - * @param {object} nextProps - * @optional - */ - componentWillReceiveProps: 'DEFINE_MANY', - - /** - * Invoked while deciding if the component should be updated as a result of - * receiving new props, state and/or context. - * - * Use this as an opportunity to `return false` when you're certain that the - * transition to the new props/state/context will not require a component - * update. - * - * shouldComponentUpdate: function(nextProps, nextState, nextContext) { - * return !equal(nextProps, this.props) || - * !equal(nextState, this.state) || - * !equal(nextContext, this.context); - * } - * - * @param {object} nextProps - * @param {?object} nextState - * @param {?object} nextContext - * @return {boolean} True if the component should update. - * @optional - */ - shouldComponentUpdate: 'DEFINE_ONCE', - - /** - * Invoked when the component is about to update due to a transition from - * `this.props`, `this.state` and `this.context` to `nextProps`, `nextState` - * and `nextContext`. - * - * Use this as an opportunity to perform preparation before an update occurs. - * - * NOTE: You **cannot** use `this.setState()` in this method. - * - * @param {object} nextProps - * @param {?object} nextState - * @param {?object} nextContext - * @param {ReactReconcileTransaction} transaction - * @optional - */ - componentWillUpdate: 'DEFINE_MANY', - - /** - * Invoked when the component's DOM representation has been updated. - * - * Use this as an opportunity to operate on the DOM when the component has - * been updated. - * - * @param {object} prevProps - * @param {?object} prevState - * @param {?object} prevContext - * @param {DOMElement} rootNode DOM element representing the component. - * @optional - */ - componentDidUpdate: 'DEFINE_MANY', - - /** - * Invoked when the component is about to be removed from its parent and have - * its DOM representation destroyed. - * - * Use this as an opportunity to deallocate any external resources. - * - * NOTE: There is no `componentDidUnmount` since your component will have been - * destroyed by that point. - * - * @optional - */ - componentWillUnmount: 'DEFINE_MANY', - - // ==== Advanced methods ==== - - /** - * Updates the component's currently mounted DOM representation. - * - * By default, this implements React's rendering and reconciliation algorithm. - * Sophisticated clients may wish to override this. - * - * @param {ReactReconcileTransaction} transaction - * @internal - * @overridable - */ - updateComponent: 'OVERRIDE_BASE' - }; - - /** - * Mapping from class specification keys to special processing functions. - * - * Although these are declared like instance properties in the specification - * when defining classes using `React.createClass`, they are actually static - * and are accessible on the constructor instead of the prototype. Despite - * being static, they must be defined outside of the "statics" key under - * which all other static methods are defined. - */ - var RESERVED_SPEC_KEYS = { - displayName: function(Constructor, displayName) { - Constructor.displayName = displayName; - }, - mixins: function(Constructor, mixins) { - if (mixins) { - for (var i = 0; i < mixins.length; i++) { - mixSpecIntoComponent(Constructor, mixins[i]); - } - } - }, - childContextTypes: function(Constructor, childContextTypes) { - // if (process.env.NODE_ENV !== 'production') { - // validateTypeDef(Constructor, childContextTypes, 'childContext'); - // } - Constructor.childContextTypes = _assign( - {}, - Constructor.childContextTypes, - childContextTypes - ); - }, - contextTypes: function(Constructor, contextTypes) { - // if (process.env.NODE_ENV !== 'production') { - // validateTypeDef(Constructor, contextTypes, 'context'); - // } - Constructor.contextTypes = _assign( - {}, - Constructor.contextTypes, - contextTypes - ); - }, - /** - * Special case getDefaultProps which should move into statics but requires - * automatic merging. - */ - getDefaultProps: function(Constructor, getDefaultProps) { - if (Constructor.getDefaultProps) { - Constructor.getDefaultProps = createMergedResultFunction( - Constructor.getDefaultProps, - getDefaultProps - ); - } else { - Constructor.getDefaultProps = getDefaultProps; - } - }, - propTypes: function(Constructor, propTypes) { - // if (process.env.NODE_ENV !== 'production') { - // validateTypeDef(Constructor, propTypes, 'prop'); - // } - Constructor.propTypes = _assign({}, Constructor.propTypes, propTypes); - }, - statics: function(Constructor, statics) { - mixStaticSpecIntoComponent(Constructor, statics); - }, - autobind: function() {} - }; - - function validateTypeDef(Constructor, typeDef, location) { - for (var propName in typeDef) { - // if (typeDef.hasOwnProperty(propName)) { - // // use a warning instead of an _invariant so components - // // don't show up in prod but only in __DEV__ - // // if (process.env.NODE_ENV !== 'production') { - // // warning( - // // typeof typeDef[propName] === 'function', - // // '%s: %s type `%s` is invalid; it must be a function, usually from ' + - // // 'React.PropTypes.', - // // Constructor.displayName || 'ReactClass', - // // ReactPropTypeLocationNames[location], - // // propName - // // ); - // // } - // } - } - } - - function validateMethodOverride(isAlreadyDefined, name) { - var specPolicy = ReactClassInterface.hasOwnProperty(name) - ? ReactClassInterface[name] - : null; - - // Disallow overriding of base class methods unless explicitly allowed. - if (ReactClassMixin.hasOwnProperty(name)) { - // _invariant( - // specPolicy === 'OVERRIDE_BASE', - // 'ReactClassInterface: You are attempting to override ' + - // '`%s` from your class specification. Ensure that your method names ' + - // 'do not overlap with React methods.', - // name - // ); - } - - // Disallow defining methods more than once unless explicitly allowed. - if (isAlreadyDefined) { - // _invariant( - // specPolicy === 'DEFINE_MANY' || specPolicy === 'DEFINE_MANY_MERGED', - // 'ReactClassInterface: You are attempting to define ' + - // '`%s` on your component more than once. This conflict may be due ' + - // 'to a mixin.', - // name - // ); - } - } - - /** - * Mixin helper which handles policy validation and reserved - * specification keys when building React classes. - */ - function mixSpecIntoComponent(Constructor, spec) { - if (!spec) { - // if (process.env.NODE_ENV !== 'production') { - // var typeofSpec = typeof spec; - // var isMixinValid = typeofSpec === 'object' && spec !== null; - // - // if (process.env.NODE_ENV !== 'production') { - // warning( - // isMixinValid, - // "%s: You're attempting to include a mixin that is either null " + - // 'or not an object. Check the mixins included by the component, ' + - // 'as well as any mixins they include themselves. ' + - // 'Expected object but got %s.', - // Constructor.displayName || 'ReactClass', - // spec === null ? null : typeofSpec - // ); - // } - // } - - return; - } - - // _invariant( - // typeof spec !== 'function', - // "ReactClass: You're attempting to " + - // 'use a component class or function as a mixin. Instead, just use a ' + - // 'regular object.' - // ); - // _invariant( - // !isValidElement(spec), - // "ReactClass: You're attempting to " + - // 'use a component as a mixin. Instead, just use a regular object.' - // ); - - var proto = Constructor.prototype; - var autoBindPairs = proto.__reactAutoBindPairs; - - // By handling mixins before any other properties, we ensure the same - // chaining order is applied to methods with DEFINE_MANY policy, whether - // mixins are listed before or after these methods in the spec. - if (spec.hasOwnProperty(MIXINS_KEY)) { - RESERVED_SPEC_KEYS.mixins(Constructor, spec.mixins); - } - - for (var name in spec) { - if (!spec.hasOwnProperty(name)) { - continue; - } - - if (name === MIXINS_KEY) { - // We have already handled mixins in a special case above. - continue; - } - - var property = spec[name]; - var isAlreadyDefined = proto.hasOwnProperty(name); - validateMethodOverride(isAlreadyDefined, name); - - if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) { - RESERVED_SPEC_KEYS[name](Constructor, property); - } else { - // Setup methods on prototype: - // The following member methods should not be automatically bound: - // 1. Expected ReactClass methods (in the "interface"). - // 2. Overridden methods (that were mixed in). - var isReactClassMethod = ReactClassInterface.hasOwnProperty(name); - var isFunction = typeof property === 'function'; - var shouldAutoBind = - isFunction && - !isReactClassMethod && - !isAlreadyDefined && - spec.autobind !== false; - - if (shouldAutoBind) { - autoBindPairs.push(name, property); - proto[name] = property; - } else { - if (isAlreadyDefined) { - var specPolicy = ReactClassInterface[name]; - - // These cases should already be caught by validateMethodOverride. - // _invariant( - // isReactClassMethod && - // (specPolicy === 'DEFINE_MANY_MERGED' || - // specPolicy === 'DEFINE_MANY'), - // 'ReactClass: Unexpected spec policy %s for key %s ' + - // 'when mixing in component specs.', - // specPolicy, - // name - // ); - - // For methods which are defined more than once, call the existing - // methods before calling the new property, merging if appropriate. - if (specPolicy === 'DEFINE_MANY_MERGED') { - proto[name] = createMergedResultFunction(proto[name], property); - } else if (specPolicy === 'DEFINE_MANY') { - proto[name] = createChainedFunction(proto[name], property); - } - } else { - proto[name] = property; - // if (process.env.NODE_ENV !== 'production') { - // // Add verbose displayName to the function, which helps when looking - // // at profiling tools. - // if (typeof property === 'function' && spec.displayName) { - // proto[name].displayName = spec.displayName + '_' + name; - // } - // } - } - } - } - } - } - - function mixStaticSpecIntoComponent(Constructor, statics) { - if (!statics) { - return; - } - for (var name in statics) { - var property = statics[name]; - if (!statics.hasOwnProperty(name)) { - continue; - } - - var isReserved = name in RESERVED_SPEC_KEYS; - // _invariant( - // !isReserved, - // 'ReactClass: You are attempting to define a reserved ' + - // 'property, `%s`, that shouldn\'t be on the "statics" key. Define it ' + - // 'as an instance property instead; it will still be accessible on the ' + - // 'constructor.', - // name - // ); - - var isInherited = name in Constructor; - // _invariant( - // !isInherited, - // 'ReactClass: You are attempting to define ' + - // '`%s` on your component more than once. This conflict may be ' + - // 'due to a mixin.', - // name - // ); - Constructor[name] = property; - } - } - - /** - * Merge two objects, but throw if both contain the same key. - * - * @param {object} one The first object, which is mutated. - * @param {object} two The second object - * @return {object} one after it has been mutated to contain everything in two. - */ - function mergeIntoWithNoDuplicateKeys(one, two) { - // _invariant( - // one && two && typeof one === 'object' && typeof two === 'object', - // 'mergeIntoWithNoDuplicateKeys(): Cannot merge non-objects.' - // ); - - for (var key in two) { - if (two.hasOwnProperty(key)) { - // _invariant( - // one[key] === undefined, - // 'mergeIntoWithNoDuplicateKeys(): ' + - // 'Tried to merge two objects with the same key: `%s`. This conflict ' + - // 'may be due to a mixin; in particular, this may be caused by two ' + - // 'getInitialState() or getDefaultProps() methods returning objects ' + - // 'with clashing keys.', - // key - // ); - one[key] = two[key]; - } - } - return one; - } - - /** - * Creates a function that invokes two functions and merges their return values. - * - * @param {function} one Function to invoke first. - * @param {function} two Function to invoke second. - * @return {function} Function that invokes the two argument functions. - * @private - */ - function createMergedResultFunction(one, two) { - return function mergedResult() { - var a = one.apply(this, arguments); - var b = two.apply(this, arguments); - if (a == null) { - return b; - } else if (b == null) { - return a; - } - var c = {}; - mergeIntoWithNoDuplicateKeys(c, a); - mergeIntoWithNoDuplicateKeys(c, b); - return c; - }; - } - - /** - * Creates a function that invokes two functions and ignores their return vales. - * - * @param {function} one Function to invoke first. - * @param {function} two Function to invoke second. - * @return {function} Function that invokes the two argument functions. - * @private - */ - function createChainedFunction(one, two) { - return function chainedFunction() { - one.apply(this, arguments); - two.apply(this, arguments); - }; - } - - /** - * Binds a method to the component. - * - * @param {object} component Component whose method is going to be bound. - * @param {function} method Method to be bound. - * @return {function} The bound method. - */ - function bindAutoBindMethod(component, method) { - var boundMethod = method.bind(component); - // if (process.env.NODE_ENV !== 'production') { - // boundMethod.__reactBoundContext = component; - // boundMethod.__reactBoundMethod = method; - // boundMethod.__reactBoundArguments = null; - // var componentName = component.constructor.displayName; - // var _bind = boundMethod.bind; - // boundMethod.bind = function(newThis) { - // for ( - // var _len = arguments.length, - // args = Array(_len > 1 ? _len - 1 : 0), - // _key = 1; - // _key < _len; - // _key++ - // ) { - // args[_key - 1] = arguments[_key]; - // } - // - // // User is trying to bind() an autobound method; we effectively will - // // ignore the value of "this" that the user is trying to use, so - // // let's warn. - // if (newThis !== component && newThis !== null) { - // if (process.env.NODE_ENV !== 'production') { - // warning( - // false, - // 'bind(): React component methods may only be bound to the ' + - // 'component instance. See %s', - // componentName - // ); - // } - // } else if (!args.length) { - // if (process.env.NODE_ENV !== 'production') { - // warning( - // false, - // 'bind(): You are binding a component method to the component. ' + - // 'React does this for you automatically in a high-performance ' + - // 'way, so you can safely remove this call. See %s', - // componentName - // ); - // } - // return boundMethod; - // } - // var reboundMethod = _bind.apply(boundMethod, arguments); - // reboundMethod.__reactBoundContext = component; - // reboundMethod.__reactBoundMethod = method; - // reboundMethod.__reactBoundArguments = args; - // return reboundMethod; - // }; - // } - return boundMethod; - } - - /** - * Binds all auto-bound methods in a component. - * - * @param {object} component Component whose method is going to be bound. - */ - function bindAutoBindMethods(component) { - var pairs = component.__reactAutoBindPairs; - for (var i = 0; i < pairs.length; i += 2) { - var autoBindKey = pairs[i]; - var method = pairs[i + 1]; - component[autoBindKey] = bindAutoBindMethod(component, method); - } - } - - var IsMountedPreMixin = { - componentDidMount: function() { - this.__isMounted = true; - } - }; - - var IsMountedPostMixin = { - componentWillUnmount: function() { - this.__isMounted = false; - } - }; - - /** - * Add more to the ReactClass base class. These are all legacy features and - * therefore not already part of the modern ReactComponent. - */ - var ReactClassMixin = { - /** - * TODO: This will be deprecated because state should always keep a consistent - * type signature and the only use case for this, is to avoid that. - */ - replaceState: function(newState, callback) { - this.updater.enqueueReplaceState(this, newState, callback); - }, - - /** - * Checks whether or not this composite component is mounted. - * @return {boolean} True if mounted, false otherwise. - * @protected - * @final - */ - isMounted: function() { - // if (process.env.NODE_ENV !== 'production') { - // warning( - // this.__didWarnIsMounted, - // '%s: isMounted is deprecated. Instead, make sure to clean up ' + - // 'subscriptions and pending requests in componentWillUnmount to ' + - // 'prevent memory leaks.', - // (this.constructor && this.constructor.displayName) || - // this.name || - // 'Component' - // ); - // this.__didWarnIsMounted = true; - // } - return !!this.__isMounted; - } - }; - - var ReactClassComponent = function() {}; - _assign( - ReactClassComponent.prototype, - ReactComponent.prototype, - ReactClassMixin - ); - - /** - * Creates a composite component class given a class specification. - * See https://facebook.github.io/react/docs/top-level-api.html#react.createclass - * - * @param {object} spec Class specification (which must define `render`). - * @return {function} Component constructor function. - * @public - */ - function createClass(spec) { - // To keep our warnings more understandable, we'll use a little hack here to - // ensure that Constructor.name !== 'Constructor'. This makes sure we don't - // unnecessarily identify a class without displayName as 'Constructor'. - var Constructor = identity(function(props, context, updater) { - // This constructor gets overridden by mocks. The argument is used - // by mocks to assert on what gets mounted. - - // if (process.env.NODE_ENV !== 'production') { - // warning( - // this instanceof Constructor, - // 'Something is calling a React component directly. Use a factory or ' + - // 'JSX instead. See: https://fb.me/react-legacyfactory' - // ); - // } - - // Wire up auto-binding - if (this.__reactAutoBindPairs.length) { - bindAutoBindMethods(this); - } - - this.props = props; - this.context = context; - this.refs = emptyObject; - this.updater = updater || ReactNoopUpdateQueue; - - this.state = null; - - // ReactClasses doesn't have constructors. Instead, they use the - // getInitialState and componentWillMount methods for initialization. - - var initialState = this.getInitialState ? this.getInitialState() : null; - // if (process.env.NODE_ENV !== 'production') { - // // We allow auto-mocks to proceed as if they're returning null. - // if ( - // initialState === undefined && - // this.getInitialState._isMockFunction - // ) { - // // This is probably bad practice. Consider warning here and - // // deprecating this convenience. - // initialState = null; - // } - // } - // _invariant( - // typeof initialState === 'object' && !Array.isArray(initialState), - // '%s.getInitialState(): must return an object or null', - // Constructor.displayName || 'ReactCompositeComponent' - // ); - - this.state = initialState; - }); - Constructor.prototype = new ReactClassComponent(); - Constructor.prototype.constructor = Constructor; - Constructor.prototype.__reactAutoBindPairs = []; - - injectedMixins.forEach(mixSpecIntoComponent.bind(null, Constructor)); - - mixSpecIntoComponent(Constructor, IsMountedPreMixin); - mixSpecIntoComponent(Constructor, spec); - mixSpecIntoComponent(Constructor, IsMountedPostMixin); - - // Initialize the defaultProps property after all mixins have been merged. - if (Constructor.getDefaultProps) { - Constructor.defaultProps = Constructor.getDefaultProps(); - } - - // if (process.env.NODE_ENV !== 'production') { - // // This is a tag to indicate that the use of these method names is ok, - // // since it's used with createClass. If it's not, then it's likely a - // // mistake so we'll warn you to use the static property, property - // // initializer or constructor respectively. - // if (Constructor.getDefaultProps) { - // Constructor.getDefaultProps.isReactClassApproved = {}; - // } - // if (Constructor.prototype.getInitialState) { - // Constructor.prototype.getInitialState.isReactClassApproved = {}; - // } - // } - - // _invariant( - // Constructor.prototype.render, - // 'createClass(...): Class specification must implement a `render` method.' - // ); - - // if (process.env.NODE_ENV !== 'production') { - // warning( - // !Constructor.prototype.componentShouldUpdate, - // '%s has a method called ' + - // 'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' + - // 'The name is phrased as a question because the function is ' + - // 'expected to return a value.', - // spec.displayName || 'A component' - // ); - // warning( - // !Constructor.prototype.componentWillRecieveProps, - // '%s has a method called ' + - // 'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?', - // spec.displayName || 'A component' - // ); - // } - - // Reduce time spent doing lookups by setting these on the prototype. - for (var methodName in ReactClassInterface) { - if (!Constructor.prototype[methodName]) { - Constructor.prototype[methodName] = null; - } - } - - return Constructor; - } - - return createClass; -}); - -var reactNoopUpdateQueue = new React.Component().updater; - -var createClass = factory(React.Component, React.isValidElement, reactNoopUpdateQueue); - -exports._assign = _assign; -exports.emptyObject = emptyObject; -exports.factory = factory; -exports.reactNoopUpdateQueue = reactNoopUpdateQueue; -exports.createClass = createClass; -/* Not a pure module */ diff --git a/lib/js/src/ReasonReactRouter.js b/lib/js/src/ReasonReactRouter.js deleted file mode 100644 index ba1af1781..000000000 --- a/lib/js/src/ReasonReactRouter.js +++ /dev/null @@ -1,203 +0,0 @@ -'use strict'; - -var Curry = require("bs-platform/lib/js/curry.js"); -var React = require("react"); - -function safeMakeEvent(eventName) { - if (typeof Event === "function") { - return new Event(eventName); - } else { - var $$event = document.createEvent("Event"); - $$event.initEvent(eventName, true, true); - return $$event; - } -} - -function path(param) { - var match = typeof window === "undefined" ? undefined : window; - if (match !== undefined) { - var raw = match.location.pathname; - switch (raw) { - case "" : - case "/" : - return /* [] */0; - default: - var raw$1 = raw.slice(1); - var match$1 = raw$1[raw$1.length - 1 | 0]; - var raw$2 = match$1 === "/" ? raw$1.slice(0, -1) : raw$1; - var a = raw$2.split("/"); - var _i = a.length - 1 | 0; - var _res = /* [] */0; - while(true) { - var res = _res; - var i = _i; - if (i < 0) { - return res; - } else { - _res = /* :: */[ - a[i], - res - ]; - _i = i - 1 | 0; - continue ; - } - }; - } - } else { - return /* [] */0; - } -} - -function hash(param) { - var match = typeof window === "undefined" ? undefined : window; - if (match !== undefined) { - var raw = match.location.hash; - switch (raw) { - case "" : - case "#" : - return ""; - default: - return raw.slice(1); - } - } else { - return ""; - } -} - -function search(param) { - var match = typeof window === "undefined" ? undefined : window; - if (match !== undefined) { - var raw = match.location.search; - switch (raw) { - case "" : - case "?" : - return ""; - default: - return raw.slice(1); - } - } else { - return ""; - } -} - -function push(path) { - var match = typeof history === "undefined" ? undefined : history; - var match$1 = typeof window === "undefined" ? undefined : window; - if (match !== undefined && match$1 !== undefined) { - match.pushState(null, "", path); - match$1.dispatchEvent(safeMakeEvent("popstate")); - return /* () */0; - } else { - return /* () */0; - } -} - -function replace(path) { - var match = typeof history === "undefined" ? undefined : history; - var match$1 = typeof window === "undefined" ? undefined : window; - if (match !== undefined && match$1 !== undefined) { - match.replaceState(null, "", path); - match$1.dispatchEvent(safeMakeEvent("popstate")); - return /* () */0; - } else { - return /* () */0; - } -} - -function urlNotEqual(a, b) { - if (a.hash !== b.hash || a.search !== b.search) { - return true; - } else { - var _aList = a.path; - var _bList = b.path; - while(true) { - var bList = _bList; - var aList = _aList; - if (aList) { - if (bList && aList[0] === bList[0]) { - _bList = bList[1]; - _aList = aList[1]; - continue ; - } else { - return true; - } - } else if (bList) { - return true; - } else { - return false; - } - }; - } -} - -function url(param) { - return { - path: path(/* () */0), - hash: hash(/* () */0), - search: search(/* () */0) - }; -} - -function watchUrl(callback) { - var match = typeof window === "undefined" ? undefined : window; - if (match !== undefined) { - var watcherID = function (param) { - return Curry._1(callback, url(/* () */0)); - }; - match.addEventListener("popstate", watcherID); - return watcherID; - } else { - return (function (param) { - return /* () */0; - }); - } -} - -function unwatchUrl(watcherID) { - var match = typeof window === "undefined" ? undefined : window; - if (match !== undefined) { - match.removeEventListener("popstate", watcherID); - return /* () */0; - } else { - return /* () */0; - } -} - -function useUrl(serverUrl, param) { - var match = React.useState((function () { - if (serverUrl !== undefined) { - return serverUrl; - } else { - return url(/* () */0); - } - })); - var setUrl = match[1]; - var url$1 = match[0]; - React.useEffect((function () { - var watcherId = watchUrl((function (url) { - return Curry._1(setUrl, (function (param) { - return url; - })); - })); - var newUrl = url(/* () */0); - if (urlNotEqual(newUrl, url$1)) { - Curry._1(setUrl, (function (param) { - return newUrl; - })); - } - return (function (param) { - return unwatchUrl(watcherId); - }); - }), ([])); - return url$1; -} - -var dangerouslyGetInitialUrl = url; - -exports.push = push; -exports.replace = replace; -exports.watchUrl = watchUrl; -exports.unwatchUrl = unwatchUrl; -exports.dangerouslyGetInitialUrl = dangerouslyGetInitialUrl; -exports.useUrl = useUrl; -/* react Not a pure module */ diff --git a/package.json b/package.json index d36fc53b3..ac04f20e5 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,15 @@ { "name": "reason-react", - "version": "0.8.0-dev.4", + "version": "0.8.0", "description": "React bindings for Reason", - "main": "lib/js/src/ReasonReact.js", + "main": "src/React.bs.js", + "files": [ + "README.md", + "HISTORY.md", + "LICENSE", + "bsconfig.json", + "src" + ], "scripts": { "build": "bsb -make-world", "start": "bsb -make-world -w", diff --git a/src/React.re b/src/React.re index cbb4945ea..312bf78de 100644 --- a/src/React.re +++ b/src/React.re @@ -101,6 +101,14 @@ module Fragment = { external make: component({. "children": element}) = "Fragment"; }; +module StrictMode = { + [@bs.obj] + external makeProps: + (~children: element, ~key: 'key=?, unit) => {. "children": element}; + [@bs.module "react"] + external make: component({. "children": element}) = "StrictMode"; +}; + module Suspense = { [@bs.obj] external makeProps: @@ -173,7 +181,7 @@ external useReducerWithMapState: ( [@bs.uncurry] (('state, 'action) => 'state), 'initialState, - 'initialState => 'state + [@bs.uncurry] ('initialState => 'state) ) => ('state, 'action => unit) = "useReducer"; diff --git a/website/blog/2020-05-05-080-release.md b/website/blog/2020-05-05-080-release.md new file mode 100644 index 000000000..a0533e4f7 --- /dev/null +++ b/website/blog/2020-05-05-080-release.md @@ -0,0 +1,24 @@ +--- +title: ReasonReact 0.8.0 🎉 BuckleScript Upgrade & More! +--- + +We're excited to share Version 0.8.0 of ReasonReact with the world today! ReasonReact adds a huge number of quality of life improvements and new api changes. This is our first big release since introducing hooks in 0.7.0. + +It's a breaking change primarily because it enforces a minimum BuckleScript version of 7.1.1. This is to ensure that we get consistent record and object runtime representation and to unlock more changes in the future. Going forward you can expect that ReasonReact will track BuckleScript more closely. The PPX lives in and ships with BuckleScript, so in order to make sweeping changes we have to work in both codebases. + +There are a number of additional breaking changes: + +* crossOrigin casing was incorrect +* maxDuration removed from suspense api +* Ref type has been changed to be modelled as a record instead of abstract type with get and set +* The min attribute on dom nodes in now a string to match max + +As well as new additions: + +* Support for concurrent mode with createRoot, Suspense, SuspenseList, useTransition +* React.float and int +* Better Children mapping + +This is not an exhaustive list - I encourage you to check out the full set in https://github.com/reasonml/reason-react/blob/master/HISTORY.md. + +This release has been a long time coming and is the huge effort of the ReasonReact community. A heartfelt thanks to everyone in the HISTORY and to everyone who has created or interacted with issues! The first step to upgrading here is to make sure you're on BuckleScript ^7.1.1. From there you can visit [upgrade-reason-react](https://github.com/rickyvetter/upgrade-reason-react) for a script that will handle the ref upgrade, crossOrigin capitalization, and the type of min when used in JSX. Happy hacking! diff --git a/website/i18n/en.json b/website/i18n/en.json index 9f43b151a..a35cde07b 100644 --- a/website/i18n/en.json +++ b/website/i18n/en.json @@ -146,6 +146,9 @@ "subscriptions-helper": { "title": "Subscriptions Helper" }, + "tailwind-css": { + "title": "Tailwind CSS" + }, "ternary-shortcut": { "title": "Ternary Shortcut" }, @@ -161,6 +164,9 @@ "usereducer-hook": { "title": "useReducer Hook" }, + "usestate-event-value": { + "title": "Using Event Values With useState" + }, "usestate-hook": { "title": "useState Hook" }, diff --git a/website/sidebars.json b/website/sidebars.json index 9a5810efe..6b3b3e805 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -21,7 +21,8 @@ "component-as-prop", "ternary-shortcut", "context-mixins", - "custom-class-component-property" + "custom-class-component-property", + "usestate-event-value" ], "Record API (deprecated)": [ "jsx-2", @@ -63,7 +64,8 @@ "js-using-reason", "gentype", "example-projects", - "graphql-apollo" + "graphql-apollo", + "tailwind-css" ] }, "community-menu": { diff --git a/website/static/img/tailwind-example.png b/website/static/img/tailwind-example.png new file mode 100644 index 000000000..2040a88de Binary files /dev/null and b/website/static/img/tailwind-example.png differ