or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

core-react.mdcss-styling.mddom-integration.mderror-boundaries.mdevent-system.mdindex.mdreact-hooks.mdrouting.mdserver-side-rendering.mdtesting-utilities.md
tile.json

react-hooks.mddocs/

React Hooks

Complete React hooks implementation providing state management, side effects, memoization, and lifecycle hooks with full type safety and dependency array support.

State Hooks

useState

[@bs.module "react"]
external useState: ([@bs.uncurry] (unit => 'state)) => ('state, ('state => 'state) => unit) = "useState";

The useState hook requires a lazy initializer function to handle any type of state safely.

Usage Example:

[@react.component]
let make = () => {
  let (count, setCount) = React.useState(() => 0);
  let (name, setName) = React.useState(() => "");
  
  <div>
    <p> {React.string("Count: " ++ string_of_int(count))} </p>
    <button onClick={_ => setCount(c => c + 1)}>
      {React.string("Increment")}
    </button>
  </div>
}

useReducer

[@bs.module "react"]
external useReducer: ([@bs.uncurry] (('state, 'action) => 'state), 'state) => ('state, 'action => unit) = "useReducer";

[@bs.module "react"]
external useReducerWithMapState: (
  [@bs.uncurry] (('state, 'action) => 'state),
  'initialState,
  [@bs.uncurry] ('initialState => 'state)
) => ('state, 'action => unit) = "useReducer";

Usage Example:

type action = 
  | Increment 
  | Decrement 
  | Reset;

type state = {count: int};

let reducer = (state, action) => {
  switch (action) {
  | Increment => {count: state.count + 1}
  | Decrement => {count: state.count - 1}
  | Reset => {count: 0}
  }
};

[@react.component]
let make = () => {
  let (state, dispatch) = React.useReducer(reducer, {count: 0});
  
  <div>
    <p> {React.string("Count: " ++ string_of_int(state.count))} </p>
    <button onClick={_ => dispatch(Increment)}>
      {React.string("+")}
    </button>
    <button onClick={_ => dispatch(Decrement)}>
      {React.string("-")}
    </button>
    <button onClick={_ => dispatch(Reset)}>
      {React.string("Reset")}
    </button>
  </div>
}

Effect Hooks

useEffect Variants

/* Run on every render */
[@bs.module "react"]
external useEffect: ([@bs.uncurry] (unit => option(unit => unit))) => unit = "useEffect";

/* Run once on mount */
[@bs.module "react"]
external useEffect0: ([@bs.uncurry] (unit => option(unit => unit)), [@bs.as {json|[]|json}] _) => unit = "useEffect";

/* Run when dependencies change */
[@bs.module "react"]
external useEffect1: ([@bs.uncurry] (unit => option(unit => unit)), array('a)) => unit = "useEffect";

[@bs.module "react"]
external useEffect2: ([@bs.uncurry] (unit => option(unit => unit)), ('a, 'b)) => unit = "useEffect";

[@bs.module "react"]
external useEffect3: ([@bs.uncurry] (unit => option(unit => unit)), ('a, 'b, 'c)) => unit = "useEffect";

[@bs.module "react"]
external useEffect4: ([@bs.uncurry] (unit => option(unit => unit)), ('a, 'b, 'c, 'd)) => unit = "useEffect";

[@bs.module "react"]
external useEffect5: ([@bs.uncurry] (unit => option(unit => unit)), ('a, 'b, 'c, 'd, 'e)) => unit = "useEffect";

[@bs.module "react"]
external useEffect6: ([@bs.uncurry] (unit => option(unit => unit)), ('a, 'b, 'c, 'd, 'e, 'f)) => unit = "useEffect";

[@bs.module "react"]
external useEffect7: ([@bs.uncurry] (unit => option(unit => unit)), ('a, 'b, 'c, 'd, 'e, 'f, 'g)) => unit = "useEffect";

Usage Examples:

[@react.component]
let make = (~userId: int) => {
  let (user, setUser) = React.useState(() => None);
  
  /* Effect with cleanup */
  React.useEffect1(() => {
    let timer = setInterval(() => {
      fetchUser(userId, setUser);
    }, 5000);
    
    /* Cleanup function */
    Some(() => clearInterval(timer))
  }, [|userId|]);
  
  /* Effect that runs once */
  React.useEffect0(() => {
    setupAnalytics();
    None /* No cleanup needed */
  }, ());
  
  /* Multiple dependencies */
  React.useEffect2(() => {
    updateTitle(user, userId);
    None
  }, (user, userId));
  
  <div>
    {switch (user) {
     | Some(u) => <UserProfile user=u />
     | None => React.string("Loading...")
     }}
  </div>
}

useLayoutEffect Variants

[@bs.module "react"]
external useLayoutEffect: ([@bs.uncurry] (unit => option(unit => unit))) => unit = "useLayoutEffect";

[@bs.module "react"]
external useLayoutEffect0: ([@bs.uncurry] (unit => option(unit => unit)), [@bs.as {json|[]|json}] _) => unit = "useLayoutEffect";

[@bs.module "react"]
external useLayoutEffect1: ([@bs.uncurry] (unit => option(unit => unit)), array('a)) => unit = "useLayoutEffect";

[@bs.module "react"]
external useLayoutEffect2: ([@bs.uncurry] (unit => option(unit => unit)), ('a, 'b)) => unit = "useLayoutEffect";

[@bs.module "react"]
external useLayoutEffect3: ([@bs.uncurry] (unit => option(unit => unit)), ('a, 'b, 'c)) => unit = "useLayoutEffect";

[@bs.module "react"]
external useLayoutEffect4: ([@bs.uncurry] (unit => option(unit => unit)), ('a, 'b, 'c, 'd)) => unit = "useLayoutEffect";

[@bs.module "react"]
external useLayoutEffect5: ([@bs.uncurry] (unit => option(unit => unit)), ('a, 'b, 'c, 'd, 'e)) => unit = "useLayoutEffect";

[@bs.module "react"]
external useLayoutEffect6: ([@bs.uncurry] (unit => option(unit => unit)), ('a, 'b, 'c, 'd, 'e, 'f)) => unit = "useLayoutEffect";

[@bs.module "react"]
external useLayoutEffect7: ([@bs.uncurry] (unit => option(unit => unit)), ('a, 'b, 'c, 'd, 'e, 'f, 'g)) => unit = "useLayoutEffect";

Memoization Hooks

useMemo Variants

[@bs.module "react"]
external useMemo: ([@bs.uncurry] (unit => 'any)) => 'any = "useMemo";

[@bs.module "react"]
external useMemo0: ([@bs.uncurry] (unit => 'any), [@bs.as {json|[]|json}] _) => 'any = "useMemo";

[@bs.module "react"]
external useMemo1: ([@bs.uncurry] (unit => 'any), array('a)) => 'any = "useMemo";

[@bs.module "react"]
external useMemo2: ([@bs.uncurry] (unit => 'any), ('a, 'b)) => 'any = "useMemo";

[@bs.module "react"]
external useMemo3: ([@bs.uncurry] (unit => 'any), ('a, 'b, 'c)) => 'any = "useMemo";

[@bs.module "react"]
external useMemo4: ([@bs.uncurry] (unit => 'any), ('a, 'b, 'c, 'd)) => 'any = "useMemo";

[@bs.module "react"]
external useMemo5: ([@bs.uncurry] (unit => 'any), ('a, 'b, 'c, 'd, 'e)) => 'any = "useMemo";

[@bs.module "react"]
external useMemo6: ([@bs.uncurry] (unit => 'any), ('a, 'b, 'c, 'd, 'e, 'f)) => 'any = "useMemo";

[@bs.module "react"]
external useMemo7: ([@bs.uncurry] (unit => 'any), ('a, 'b, 'c, 'd, 'e, 'f, 'g)) => 'any = "useMemo";

Usage Example:

[@react.component]
let make = (~items: array(item), ~filter: string) => {
  let filteredItems = React.useMemo2(() => {
    items->Array.keep(item => String.includes(item.name, filter))
  }, (items, filter));
  
  let itemCount = React.useMemo1(() => {
    Array.length(filteredItems)
  }, [|filteredItems|]);
  
  <div>
    <p> {React.string("Found " ++ string_of_int(itemCount) ++ " items")} </p>
    {filteredItems->Array.map(item => 
      <ItemCard key=item.id item />
    )->React.array}
  </div>
}

useCallback Variants

type callback('input, 'output) = 'input => 'output;

[@bs.module "react"]
external useCallback: ([@bs.uncurry] ('input => 'output)) => callback('input, 'output) = "useCallback";

[@bs.module "react"]
external useCallback0: ([@bs.uncurry] ('input => 'output), [@bs.as {json|[]|json}] _) => callback('input, 'output) = "useCallback";

[@bs.module "react"]
external useCallback1: ([@bs.uncurry] ('input => 'output), array('a)) => callback('input, 'output) = "useCallback";

[@bs.module "react"]
external useCallback2: ([@bs.uncurry] ('input => 'output), ('a, 'b)) => callback('input, 'output) = "useCallback";

[@bs.module "react"]
external useCallback3: ([@bs.uncurry] ('input => 'output), ('a, 'b, 'c)) => callback('input, 'output) = "useCallback";

[@bs.module "react"]
external useCallback4: ([@bs.uncurry] ('input => 'output), ('a, 'b, 'c, 'd)) => callback('input, 'output) = "useCallback";

[@bs.module "react"]
external useCallback5: ([@bs.uncurry] ('input => 'output), ('a, 'b, 'c, 'd, 'e)) => callback('input, 'output) = "useCallback";

[@bs.module "react"]
external useCallback6: ([@bs.uncurry] ('input => 'output), ('a, 'b, 'c, 'd, 'e, 'f)) => callback('input, 'output) = "useCallback";

[@bs.module "react"]
external useCallback7: ([@bs.uncurry] ('input => 'output), ('a, 'b, 'c, 'd, 'e, 'f, 'g)) => callback('input, 'output) = "useCallback";

Usage Example:

[@react.component]
let make = (~onItemClick: item => unit, ~selectedId: option(int)) => {
  let handleClick = React.useCallback2((item) => {
    onItemClick(item);
  }, (onItemClick, selectedId));
  
  let handleKeyPress = React.useCallback1((event) => {
    if (ReactEvent.Keyboard.key(event) === "Enter") {
      handleClick(currentItem);
    }
  }, [|handleClick|]);
  
  <div onClick=handleClick onKeyPress=handleKeyPress>
    {React.string("Click me")}
  </div>
}

Imperative Handle Hook

[@bs.module "react"]
external useImperativeHandle0: (
  Js.Nullable.t(ref('value)),
  [@bs.uncurry] (unit => 'value),
  [@bs.as {json|[]|json}] _
) => unit = "useImperativeHandle";

[@bs.module "react"]
external useImperativeHandle1: (Js.Nullable.t(ref('value)), [@bs.uncurry] (unit => 'value), array('a)) => unit = "useImperativeHandle";

[@bs.module "react"]
external useImperativeHandle2: (Js.Nullable.t(ref('value)), [@bs.uncurry] (unit => 'value), ('a, 'b)) => unit = "useImperativeHandle";

[@bs.module "react"]
external useImperativeHandle3: (
  Js.Nullable.t(ref('value)),
  [@bs.uncurry] (unit => 'value),
  ('a, 'b, 'c)
) => unit = "useImperativeHandle";

[@bs.module "react"]
external useImperativeHandle4: (
  Js.Nullable.t(ref('value)),
  [@bs.uncurry] (unit => 'value),
  ('a, 'b, 'c, 'd)
) => unit = "useImperativeHandle";

[@bs.module "react"]
external useImperativeHandle5: (
  Js.Nullable.t(ref('value)),
  [@bs.uncurry] (unit => 'value),
  ('a, 'b, 'c, 'd, 'e)
) => unit = "useImperativeHandle";

[@bs.module "react"]
external useImperativeHandle6: (
  Js.Nullable.t(ref('value)),
  [@bs.uncurry] (unit => 'value),
  ('a, 'b, 'c, 'd, 'e, 'f)
) => unit = "useImperativeHandle";

[@bs.module "react"]
external useImperativeHandle7: (
  Js.Nullable.t(ref('value)),
  [@bs.uncurry] (unit => 'value),
  ('a, 'b, 'c, 'd, 'e, 'f, 'g)
) => unit = "useImperativeHandle";

Uncurried Hooks Module

Alternative hooks with uncurried function signatures for performance optimization:

module Uncurried = {
  type callback('input, 'output) = (. 'input) => 'output;
  
  [@bs.module "react"]
  external useState: ([@bs.uncurry] (unit => 'state)) => ('state, (. ('state => 'state)) => unit) = "useState";

  [@bs.module "react"]
  external useReducer: ([@bs.uncurry] (('state, 'action) => 'state), 'state) => ('state, (. 'action) => unit) = "useReducer";

  [@bs.module "react"]
  external useReducerWithMapState: (
    [@bs.uncurry] (('state, 'action) => 'state),
    'initialState,
    [@bs.uncurry] ('initialState => 'state)
  ) => ('state, (. 'action) => unit) = "useReducer";

  [@bs.module "react"]
  external useCallback: ([@bs.uncurry] ('input => 'output)) => callback('input, 'output) = "useCallback";

  [@bs.module "react"]
  external useCallback0: ([@bs.uncurry] ('input => 'output), [@bs.as {json|[]|json}] _) => callback('input, 'output) = "useCallback";

  /* ... useCallback1 through useCallback7 with same pattern */
};

Hook Usage Patterns

Custom Hooks

let useCounter = (~initialValue=0, ()) => {
  let (count, setCount) = React.useState(() => initialValue);
  
  let increment = React.useCallback0(() => {
    setCount(c => c + 1);
  }, ());
  
  let decrement = React.useCallback0(() => {
    setCount(c => c - 1);
  }, ());
  
  let reset = React.useCallback1(() => {
    setCount(_ => initialValue);
  }, [|initialValue|]);
  
  (count, increment, decrement, reset);
};

/* Usage */
[@react.component]
let make = () => {
  let (count, increment, decrement, reset) = useCounter(~initialValue=10, ());
  
  <div>
    <p> {React.string(string_of_int(count))} </p>
    <button onClick={_ => increment()}> {React.string("+")} </button>
    <button onClick={_ => decrement()}> {React.string("-")} </button>
    <button onClick={_ => reset()}> {React.string("Reset")} </button>
  </div>
}

Complex State Management

type appState = {
  user: option(user),
  isLoading: bool,
  error: option(string),
  theme: string,
};

type appAction = 
  | SetUser(option(user))
  | SetLoading(bool)
  | SetError(option(string))
  | SetTheme(string);

let appReducer = (state, action) => {
  switch (action) {
  | SetUser(user) => {...state, user, isLoading: false}
  | SetLoading(isLoading) => {...state, isLoading}
  | SetError(error) => {...state, error, isLoading: false}
  | SetTheme(theme) => {...state, theme}
  }
};

[@react.component]
let make = () => {
  let (state, dispatch) = React.useReducer(appReducer, {
    user: None,
    isLoading: false,
    error: None,
    theme: "light"
  });
  
  React.useEffect0(() => {
    dispatch(SetLoading(true));
    fetchCurrentUser()
    |> Promise.then_(user => {
         dispatch(SetUser(Some(user)));
         Promise.resolve();
       })
    |> Promise.catch(error => {
         dispatch(SetError(Some("Failed to load user")));
         Promise.resolve();
       })
    |> ignore;
    None;
  }, ());
  
  <div className={state.theme}>
    {switch (state.user, state.isLoading, state.error) {
     | (Some(user), false, None) => <UserDashboard user />
     | (None, true, None) => React.string("Loading...")
     | (None, false, Some(error)) => <ErrorMessage error />
     | _ => React.string("Something went wrong")
     }}
  </div>
}