Docs / ReasonReact / ComponentsAndProps

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 advanced topic React JSX Transformation

Component Example

Let's start with a first example to see how a ReScript React component looks like:

ReScriptJS Output
 
// src/Greeting.res
@react.component
let make = () => {
  <div>
    {React.string("Hello ReScripters!")}
  </div>
}

Important: Always make sure to name your component function make and don't forget to add the @react.component attribute.

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 <div> Hello ReScripters! </div> 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 <div> transforms into a React.createElement("div",...) call in JavaScript.

Defining Props

In ReactJS, props are usually described as a single props object. In ReScript, we use labeled arguments to define our props parameters instead. Here's an example:

ReScriptJS Output
 
// src/Article.res
@react.component
let make = (~title: string, ~visitorCount: int, ~children: React.element) => {
  let visitorCountMsg = "You are visitor number: " ++ Belt.Int.toString(visitorCount);
  <div>
    <div> {React.string(title)} </div>
    <div> {React.string(vistorCount)} </div>
    children
  </div>
}

Optional Props

We can leverage the full power of labeled arguments to define optional props as well:

ReScriptJS Output
 
// Greeting.res
@react.component
let make = (~name: option<string>=?) => {
  let greeting = switch name {
    | Some(name) => "Hello " ++ name ++ "!"
    | None => "Hello stranger!"
  }
  <div> {React.string(greeting)} </div>
}

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:

ReScriptJS Output
 
let name = Some("Andrea")

<Greeting ?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 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 and Forwarding React Refs sections for more details.

Children Props

In React props.children is a special attribute to represent the nested elements within a parent element:

RES
let element = <div> child1 child2 </div>

By default, whenever you are passing children like in the expression above, children will be treated as a React.element:

ReScriptJS Output
 
module MyList = {
  @react.component
  let make = (~children: React.element) => {
    <ul>
      children
    </ul>
  }
}

<MyList>
  <li> {React.string("Item 1")} </li>
  <li> {React.string("Item 2")} </li>
</MyList>

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) => { <div> {React.string(children)} </div> } } <StringChildren> "My Child" </StringChildren> // This will cause a type check error <StringChildren/>

Component with an optional React.element as children:

RES
module OptionalChildren = { @react.component let make = (~children: option<React.element>=?) => { <div> {switch children { | Some(element) => element | None => React.string("No children provided") }} </div> } } <div> <OptionalChildren /> <OptionalChildren> <div /> </OptionalChildren> </div>

Component that doesn't allow children at all:

RES
module NoChildren = { @react.component let make = () => { <div> {React.string("I don't accept any children params")} </div> } } // The compiler will raise a type error here <NoChildren> <div/> </NoChildren>

Children props are really tempting to be abused as a way to model hierarchies, e.g. <List> <ListHeader/> <Item/> </List> (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. <List header="..." items=[{id: "...", text: "..."}]/>. 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.elements 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) => { <div onClick> {React.string(msg)} children </div> }

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 tough 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:

ReScriptJS Output
 
// src/App.re

@react.component
let make = () => {
  <div>
    <Greeting/>
  </div>
}

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 section in our language manual.

More details on the @react.component decorator and its generated interface can be found in our Advanced React JSX Transformation 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) => { <div className="myLabel"> {React.string(title)} </div> } } @react.component let make = (~children) => { <div> <Label title="Getting Started" /> children </div> }

The Button.res file defined in above is now containing a Label component, that can also be used by other components, either by writing the fully qualified module name (<Button.Label title="My Button"/>) or by using a module alias to shortcut the full qualifier:

RES
module Label = Button.Label let content = <Label title="Test"/>

Tips & Tricks

  • Start with one component file and utilize submodule components as your component grows. Consider splitting up in multiple files when really necessary.

  • Keep your directory hierarchy flat. Instead of article/Header.res use ArticleHeader.res etc. Filenames are unique across the codebase, so filenames tend to be very specific ArticleUserHeaderCard.res, which is not necessarily a bad thing, since it clearly expresses the intent of the component within its name, and makes it also very easy to find, match and refactor across the whole codebase.