diff --git a/data/sidebar_react_v0110.json b/data/sidebar_react_v0110.json new file mode 100644 index 000000000..f7e96a22c --- /dev/null +++ b/data/sidebar_react_v0110.json @@ -0,0 +1,31 @@ +{ + "Overview": [ + "introduction", + "installation", + "migrate-react" + ], + "Main Concepts": [ + "elements-and-jsx", + "rendering-elements", + "components-and-props", + "arrays-and-keys", + "refs-and-the-dom", + "context", + "styling", + "router" + ], + "Hooks & State Management": [ + "hooks-overview", + "hooks-effect", + "hooks-state", + "hooks-reducer", + "hooks-context", + "hooks-ref", + "hooks-custom" + ], + "Guides": [ + "beyond-jsx", + "forwarding-refs", + "extensions-of-props" + ] +} \ No newline at end of file diff --git a/pages/docs/react/v0.11.0/arrays-and-keys.mdx b/pages/docs/react/v0.11.0/arrays-and-keys.mdx new file mode 100644 index 000000000..56be3f290 --- /dev/null +++ b/pages/docs/react/v0.11.0/arrays-and-keys.mdx @@ -0,0 +1,127 @@ +--- +title: Arrays and Keys +description: "Rendering arrays and handling keys in ReScript and React" +canonical: "/docs/react/latest/arrays-and-keys" +--- + +# Arrays and Keys + + + +Whenever we are transforming data into an array of elements and put it in our React tree, we need to make sure to give every element an unique identifier to help React distinguish elements for each render. This page will explain the `key` attribute and how to apply it whenever we need to map data to `React.element`s. + + + +## Keys & Rendering Arrays + +Keys help React identify which elements have been changed, added, or removed throughout each render. Keys should be given to elements inside the array to give the elements a stable identity: + +```res +let numbers = [1, 2, 3, 4, 5]; + +let items = Belt.Array.map(numbers, (number) => { +
  • {React.int(number)}
  • +}) +``` + +The best way to pick a key is to use a string that uniquely identifies a list item among its siblings. Most often you would use IDs from your data as keys: + +```res +type todo = {id: string, text: string} + +let todos = [ + {id: "todo1", text: "Todo 1"}, + {id: "todo2", text: "Todo 2"} +] + +let items = Belt.Array.map(todos, todo => { +
  • {React.string(todo.text)}
  • +}) +``` + +If you don’t have stable IDs for rendered items, you may use the item index as a key as a last resort: + +```res {1..3} +let items = Belt.Array.mapWithIndex(todos, (i, todo) => { + // Only do this if items have no stable id +
  • + {todo.text} +
  • +}); +``` + +### Keys Must Only Be Unique Among Siblings + +Keys used within arrays should be unique among their siblings. However they don’t need to be globally unique. We can use the same keys when we produce two different arrays: + +```res {6,10,17,18,25,27} +type post = {id: string, title: string, content: string} + +module Blog = { + @react.component + let make = (~posts: array) => { + let sidebar = + + + let content = Belt.Array.map(posts, (post) => { +
    +

    {React.string(post.title)}

    +

    {React.string(post.content)}

    +
    + }); + +
    + {sidebar} +
    + {React.array(content)} +
    + } +} + +let posts = [ + {id: "1", title: "Hello World", content: "Welcome to learning ReScript & React!"}, + {id: "2", title: "Installation", content: "You can install reason-react from npm."} +] + +let blog = +``` + + +## Rendering `list` Values + +In case you ever want to render a `list` of items, you can do something like this: + +```res +type todo = {id: string, text: string} + +@react.component +let make = () => { + let todoList = list{ + {id: "todo1", text: "Todo 1"}, + {id: "todo2", text: "Todo 2"}, + } + + let items = + todoList + ->Belt.List.toArray + ->Belt.Array.map(todo => { +
  • {React.string(todo.text)}
  • + }) + +
    {React.array(items)}
    +} + +``` + +We use `Belt.List.toArray` to convert our list to an array before creating our `array`. Please note that using `list` has performance impact due to extra conversion costs. + +99% of the time you'll want to use arrays (seamless interop, faster JS code), but in some cases it might make sense to use a `list` to leverage advanced pattern matching features etc. + diff --git a/pages/docs/react/v0.11.0/beyond-jsx.mdx b/pages/docs/react/v0.11.0/beyond-jsx.mdx new file mode 100644 index 000000000..bb4a12411 --- /dev/null +++ b/pages/docs/react/v0.11.0/beyond-jsx.mdx @@ -0,0 +1,220 @@ +--- +title: Beyond JSX +description: "Details on how to use ReScript and React without JSX" +canonical: "/docs/react/latest/beyond-jsx" +--- + +# Beyond JSX + + + +JSX is a syntax sugar that allows us to use React components in an HTML like manner. A component needs to adhere to certain interface conventions, otherwise it can't be used in JSX. This section will go into detail on how the JSX transformation works and what React APIs are used underneath. + + + +**Note:** This section requires knowledge about the low level apis for [creating elements](./elements-and-jsx#creating-elements-from-component-functions), such as `React.createElement` or `ReactDOM.createDOMElementVariadic`. + +> **Note:** This page assumes your `bsconfig.json` to be set to `"jsx": { "version": 4 }` to apply the right JSX transformations. + +## Component Types + +A plain React component is defined as a `('props) => React.element` function. You can also express a component more efficiently with our shorthand type `React.component<'props>`. + +Here are some examples on how to define your own component types (often useful when interoping with existing JS code, or passing around components): + +```res +// Plain function type +type friend = {name: string, online: bool} +type friendComp = friend => React.element + +// Equivalent to +// ({padding: string, children: React.element}) => React.element +type props = {padding: string, children: React.element} +type containerComp = React.component +``` +The types above are pretty low level (basically the JS representation of a React component), but since ReScript React has its own ways of defining React components in a more language specific way, let's have a closer look on the anatomy of such a construct. + +## JSX Component Interface + +A ReScript React component needs to be a (sub-)module with a `make` function and `props` type to be usable in JSX. To make things easier, we provide a `@react.component` decorator to create those functions for you: + + + +```res +module Friend = { + @react.component + let make = (~name: string, ~children) => { +
    + {React.string(name)} + children +
    + } +} +``` +```res +module Friend = { + type props<'name, 'children> = { + name: 'name, + children: 'children, + } + + let make = ({name, children, _}: props) => { + ReactDOM.createDOMElementVariadic("div", [{React.string(name)}, children]) + } +} +``` + +
    + +In the expanded output: + +- `props`: A generated record type that has fields according to the labeled arguments of the `make` function +- `make`: A converted `make` function that complies to the component interface `(props) => React.element` + +### Special Case React.forwardRef + +The `@react.component` decorator also works for `React.forwardRef` calls: + + + + +```res +module FancyInput = { + @react.component + let make = React.forwardRef((~className=?, ~children, ref) => +
    + // use ref here +
    + ) +} +``` + +```res +// Simplified Output +type props<'className, 'children, 'ref> = { + className?: 'className, + children: 'children, + ref?: 'ref, +} + +let make = ( + {?className, children, _}: props<'className, 'children, ReactRef.currentDomRef>, + ref: Js.Nullable.t, +) => + make(~className, ~children, ~ref, ()) +``` + +
    + +As shown in the expanded output above, our decorator desugars the function passed to `React.forwardRef` in the same manner as a typical component `make` function. It also creates a `props` type with an optional `ref` field, so we can use it in our JSX call (``). + +So now that we know how the ReScript React component transformation works, let's have a look on how ReScript transforms our JSX constructs. + +## JSX Under the Hood + +Whenever we are using JSX with a custom component ("capitalized JSX"), we are actually using `React.createElement` to create a new element. Here is an example of a React component without children: + + + +```res + +``` +```res +// classic +React.createElement(Friend.make, {name: "Fred", age:20}) + +// automatic +React.jsx(Friend.make, {name: "Fred", age: 20}) +``` +```js +React.createElement(Playground$Friend, { name: "Fred", age: 20 }); +``` + + + +As you can see, it uses `Friend.make` to call the `React.createElement` API. In case you are providing children, it will use `React.createElementVariadic` instead (which is just a different binding for `React.createElement`): + + + +```res + + {React.string("Hello")} + {React.string("World")} + +``` + +```res +// classic +React.createElementVariadic( + Container.make, + {width: 200, children: React.null}, + [{React.string("Hello")}, {React.string("World")}], +) + +// automatic +React.jsxs( + Container.make, + {width: 200, children: React.array([{React.string("Hello")}, {React.string("World")}])}, +) +``` + +```js +React.createElement(Container, { width: 200, children: null }, "Hello", "World"); +``` + + + +Note that the `children: React.null` field has no relevance since React will only care about the children array passed as a third argument. + + +### Dom Elements + +"Uncapitalized JSX" expressions are treated as DOM elements and will be converted to `ReactDOM.createDOMElementVariadic` calls: + + + +```res +
    +``` + +```res +// classic +ReactDOM.createDOMElementVariadic("div", ~props={title: "test"}, []) + +// automatic +ReactDOM.jsx("div", {title: "test"}) +``` + +```js +React.createElement("div", { title: "test" }); +``` + + + +The same goes for uncapitalized JSX with children: + + + +```res +
    + +
    +``` + +```res +// classic +ReactDOM.createDOMElementVariadic( + "div", + ~props={title: "test"}, + [ReactDOM.createDOMElementVariadic("span", [])], +) + +// automatic +ReactDOM.jsx("div", {title: "test", children: ?ReactDOM.someElement(ReactDOM.jsx("span", {}))}) +``` + +```js +React.createElement("div", { title: "test" }, React.createElement("span", undefined)); +``` + +
    diff --git a/pages/docs/react/v0.11.0/components-and-props.mdx b/pages/docs/react/v0.11.0/components-and-props.mdx new file mode 100644 index 000000000..7d21b1d68 --- /dev/null +++ b/pages/docs/react/v0.11.0/components-and-props.mdx @@ -0,0 +1,424 @@ +--- +title: Components and Props +description: "Basic concepts for components and props in ReScript & React" +canonical: "/docs/react/latest/components-and-props" +--- + +# Components and Props + + + +Components let you split the UI into independent, reusable pieces, and think about each piece in isolation. This page provides an introduction to the idea of components. + + + +## What is a Component? + +A React component is a function describing a UI element that receives a `props` object as a parameter (data describing the dynamic parts of the UI) and returns a `React.element`. + +The nice thing about this concept is that you can solely focus on the input and output. The component function receives some data and returns some opaque `React.element` that is managed by the React framework to render your UI. + +> If you want to know more about the low level details on how a component interface is implemented, refer to the [Beyond JSX](./beyond-jsx) page. + +## Component Example + +Let's start with a first example to see how a ReScript React component looks like: + + + +```res +// src/Greeting.res +@react.component +let make = () => { +
    + {React.string("Hello ReScripters!")} +
    +} +``` +```js +import * as React from "react"; + +function Greeting(props) { + return React.createElement("div", undefined, "Hello ReScripters!"); +} + +var make = Greeting; +``` + +
    + +**Important:** Always make sure to name your component function `make`. + +We've created a `Greeting.res` file that contains a `make` function that doesn't receive any props (the function doesn't receive any parameters), and returns a `React.element` that represents `
    Hello ReScripters!
    ` in the rendered DOM. + +You can also see in the the JS output that the function we created was directly translated into the pure JS version of a ReactJS component. Note how a `
    ` transforms into a `React.createElement("div",...)` call in JavaScript. + +## Defining Props + +In ReactJS, props are usually described as a single `props` record. In ReScript, we use [labeled arguments](/docs/manual/latest/function#labeled-arguments) to define our props parameters instead. Here's an example: + + + +```res +// src/Article.res +@react.component +let make = (~title: string, ~visitorCount: int, ~children: React.element) => { + let visitorCountMsg = "You are visitor number: " ++ Belt.Int.toString(visitorCount); +
    +
    {React.string(title)}
    +
    {React.string(visitorCountMsg)}
    + children +
    +} +``` +```js +import * as React from "react"; + +function Article(props) { + var visitorCountMsg = "You are visitor number: " + String(props.visitorCount); + return React.createElement("div", undefined, React.createElement("div", undefined, props.title), React.createElement("div", undefined, visitorCountMsg), props.children); +} + +var make = Article; +``` + +
    + +### Optional Props + +We can leverage the full power of labeled arguments to define optional props as well: + + + +```res +// Greeting.res +@react.component +let make = (~name: option=?) => { + let greeting = switch name { + | Some(name) => "Hello " ++ name ++ "!" + | None => "Hello stranger!" + } +
    {React.string(greeting)}
    +} +``` + +```js +function Greeting(props) { + var name = props.name; + var greeting = name !== undefined ? "Hello " + name + "!" : "Hello stranger!"; + return React.createElement("div", undefined, greeting); +} +``` + +
    + +**Note:** The `@react.component` attribute implicitly adds the last `()` parameter to our `make` function for us (no need to do it ourselves). + +In JSX, you can apply optional props with some special syntax: + + + +```res +let name = Some("Andrea") + + +``` + +```js +var name = "Andrea"; + +React.createElement(Greeting, { + name: name +}); +``` + + + +### Special Props `key` and `ref` + +You can't define any props called `key` or `ref`. React treats those props differently and the compiler will yield an error whenever you try to define a `~key` or `~ref` argument in your component function. + +Check out the corresponding [Arrays and Keys](./arrays-and-keys) and [Forwarding React Refs](./forwarding-refs) sections for more details. + +### Handling Invalid Prop Names (e.g. keywords) + +Prop names like `type` (as in ``) aren't syntactically valid; `type` is a reserved keyword in ReScript. Use `` instead. + +For `aria-*` use camelCasing, e.g., `ariaLabel`. For DOM components, we'll translate it to `aria-label` under the hood. + +For `data-*` this is a bit trickier; words with `-` in them aren't valid in ReScript. When you do want to write them, e.g., `
    `, check out the [React.cloneElement](./elements-and-jsx#cloning-elements) or [React.createDOMElementVariadic](./elements-and-jsx#creating-dom-elements) section. + + +## Children Props + +In React `props.children` is a special attribute to represent the nested elements within a parent element: + +```res +let element =
    child1 child2
    +``` + +By default, whenever you are passing children like in the expression above, `children` will be treated +as a `React.element`: + + + +```res +module MyList = { + @react.component + let make = (~children: React.element) => { +
      + children +
    + } +} + + +
  • {React.string("Item 1")}
  • +
  • {React.string("Item 2")}
  • +
    +``` + +```js +function MyList(props) { + return React.createElement("ul", undefined, props.children); +} + +var MyList = { + make: MyList +}; + +React.createElement(MyList, { + children: null + }, React.createElement("li", undefined, "Item 1"), + React.createElement("li", undefined, "Item 2")); +``` + +
    + +Interestingly, it doesn't matter if you are passing just one element, or several, React will always collapse its children to a single `React.element`. + +It is also possible to redefine the `children` type as well. Here are some examples: + +**Component with a mandatory `string` as children:** + +```res +module StringChildren = { + @react.component + let make = (~children: string) => { +
    + {React.string(children)} +
    + } +} + + "My Child" + +// This will cause a type check error + +``` + +**Component with an optional `React.element` as children:** + +```res +module OptionalChildren = { + @react.component + let make = (~children: option=?) => { +
    + {switch children { + | Some(element) => element + | None => React.string("No children provided") + }} +
    + } +} + +
    + +
    +
    +``` + +**Component that doesn't allow children at all:** + +```res +module NoChildren = { + @react.component + let make = () => { +
    + {React.string("I don't accept any children params")} +
    + } +} + +// The compiler will raise a type error here +
    +``` + +Children props are really tempting to be abused as a way to model hierarchies, e.g. ` ` (`List` should only allow `Item` / `ListHeader` elements), but this kind of constraint is hard to enforce because all components end up being a `React.element`, so it would require notorious runtime checking within `List` to verify that all children are in fact of type `Item` or `ListHeader`. + +The best way to approach this kind of issue is by using props instead of children, e.g. ``. This way it's easy to type check the constraints, and it also spares component consumers from memorizing and remembering component constraints. + +**The best use-case for `children` is to pass down `React.element`s without any semantic order or implementation details!** + + +## Props & Type Inference + +The ReScript type system is really good at inferring the prop types just by looking at its prop usage. + +For simple cases, well-scoped usage, or experimentation, it's still fine to omit type annotations: + + +```res +// Button.res + +@react.component +let make = (~onClick, ~msg, ~children) => { +
    + {React.string(msg)} + children +
    +} +``` + +In the example above, `onClick` will be inferred as `ReactEvent.Mouse.t => unit`, `msg` as `string` and `children` as `React.element`. Type inference is especially useful when you just forward values to some smaller (privately scoped) functions. + +Even though type inference spares us a lot of keyboard typing, we still recommend to explicitly type your props (just like with any public API) for better type visibility and to prevent confusing type errors. + +## Using Components in JSX + +Every ReScript component can be used in JSX. For example, if we want to use our `Greeting` component within our `App` component, we can do this: + + + +```res +// src/App.res + +@react.component +let make = () => { +
    + +
    +} +``` +```js +var React = require("react"); +var Greeting = require("./Greeting.js") + +function App(Props) { + return React.createElement("div", undefined, React.createElement(Greeting.make, {})); +} + +var make = App; +``` + +
    + +**Note:** React components are capitalized; primitive DOM elements like `div` or `button` are uncapitalized. More infos on the JSX specifics and code transformations can be found in our [JSX language manual section](/docs/manual/latest/jsx#capitalized-tag). + + +### Handwritten Components + +You don't need to use the `@react.component` decorator to write components that can be used in JSX. Instead you can write the `make` function with type `props` and these will always work as React components. But then you will have the issue with the component name being "make" in the React dev tools. + +For example: + + + +```res +module Link = { + type props = {href: string, children: React.element}; + + let make = (props: props) => { + + {props.children} + + } +} + + {React.string("Docs")} +``` +```js +function make(props) { + return React.createElement( + "a", + { href: props.href }, + props.children + ); +} + +var Link = { + make: make, +}; + +React.createElement(make, { + href: "/docs", + children: "Docs", +}); +``` + + + +More details on the `@react.component` decorator and its generated interface can be found in our [Beyond JSX](./beyond-jsx) page. + + +## Submodule Components + +We can also represent React components as submodules, which makes it very convenient to build more complex UI without the need to create multiple files for each composite component (that's probably only used by the parent component anyways): + +```res +// src/Button.res +module Label = { + @react.component + let make = (~title: string) => { +
    {React.string(title)}
    + } +} + +@react.component +let make = (~children) => { +
    +
    +} +``` + +The `Button.res` file defined above is now containing a `Label` component, that can also be used by other components, either by writing the fully qualified module name (``) or by using a module alias to shortcut the full qualifier: + + +```res +module Label = Button.Label + +let content =