Current section
Files
Jump to
Current section
Files
src/redraw.gleam
import gleam/function
import gleam/javascript/promise.{type Promise}
import gleam/option.{type Option}
import gleam/string
import redraw/error.{type Error}
import redraw/internals/coerce.{coerce}
// Component creation
/// Default Node in Redraw. Use `component`-family functions to create components.
/// Forwarded ref can be constructed using `forward_ref`-family functions, while
/// external components can be used with `to_component`-family functions.
pub type Component
/// Create a Redraw component, with a `name`, and a `render` function. `render`
/// will accept props, and a list of children.
pub fn component(
name name: String,
render render: fn(props, List(Component)) -> Component,
) -> fn(props, List(Component)) -> Component {
render
|> set_function_name(name)
|> add_children_proxy
}
/// Create a Redraw component, with a `name`, and a `render` function. This
/// component does not accept children.
pub fn component_(
name name: String,
render render: fn(props) -> Component,
) -> fn(props) -> Component {
render
|> set_function_name(name)
|> add_proxy
}
/// Create a Redraw component, with a `name` and a `render` function. This
/// component does not accept children nor props.
pub fn component__(
name name: String,
render render: fn() -> Component,
) -> fn() -> Component {
render
|> set_function_name(name)
|> add_empty_proxy
}
@external(javascript, "./external.ffi.mjs", "convertProps")
fn convert_props(gleam_props: gleam_props) -> props
/// Convert a React component to a React-redraw component with children. Give it a
/// name, and send directly the FFI. Don't worry about the snake_case over
/// camelCase, redraw take care of it for you.
///
/// ```gleam
/// import redraw
///
/// pub type MyComponentProps {
/// MyComponentProps(
/// first_prop: Bool,
/// second_prop: String,
/// )
/// }
///
/// @external(javascript, "my_library", "MyComponent")
/// fn do_my_component(props: MyComponentProps) -> redraw.Component
///
/// pub fn my_component() -> fn(MyComponentProps, List(Component)) -> redraw.Component {
/// redraw.to_component("MyComponent", do_my_component)
/// }
/// ```
pub fn to_component(
name name: String,
component render: fn(props) -> Component,
) -> fn(props, List(Component)) -> Component {
fn(props, children) { jsx(render, convert_props(props), children) }
|> set_function_name(name)
}
/// Convert a React Component to a redraw Component without children. Give it a
/// name, and send directly the FFI. Don't worry about the snake_case over
/// camelCase, redraw take care of it for you.
///
/// ```gleam
/// import redraw
///
/// pub type MyComponentProps {
/// MyComponentProps(
/// first_prop: Bool,
/// second_prop: String,
/// )
/// }
///
/// @external(javascript, "my_library", "MyComponent")
/// fn do_my_component(props: MyComponentProps) -> redraw.Component
///
/// pub fn my_component() -> fn(MyComponentProps) -> redraw.Component {
/// redraw.to_component_("MyComponent", do_my_component)
/// }
/// ```
pub fn to_component_(
name name: String,
component render: fn(props) -> Component,
) -> fn(props) -> Component {
fn(props) { jsx(render, convert_props(props), Nil) }
|> set_function_name(name)
}
/// Create a Redraw component with children with forwarded ref. \
/// [Documentation](https://fr.react.dev/reference/react/forwardRef)
pub fn forward_ref(
name name: String,
render render: fn(props, Ref(ref), List(Component)) -> Component,
) -> fn(props, Ref(ref), List(Component)) -> Component {
render
|> set_function_name(name)
|> add_children_forward_ref
}
/// Create a Redraw component without children with forwarded ref. \
/// [Documentation](https://react.dev/reference/react/forwardRef)
pub fn forward_ref_(
name name: String,
render render: fn(props, Ref(ref)) -> Component,
) -> fn(props, Ref(ref)) -> Component {
render
|> set_function_name(name)
|> add_forward_ref
}
/// Memoizes a Redraw component with children. \
/// [Documentation](https://react.dev/reference/react/memo)
@external(javascript, "react", "memo")
pub fn memo(
component: fn(props, List(Component)) -> Component,
) -> fn(props, List(Component)) -> Component
/// Memoizes a Redraw component without children. \
/// [Documentation](https://react.dev/reference/react/memo)
@external(javascript, "react", "memo")
pub fn memo_(component: fn(props) -> Component) -> fn(props) -> Component
// Components
/// Strict Mode should be enabled during development. \
/// [Documentation](https://react.dev/reference/react/StrictMode)
@external(javascript, "./redraw.ffi.mjs", "strictMode")
pub fn strict_mode(children: List(Component)) -> Component
/// Fragment allow to group children, without creating a node in the DOM. \
/// [Documentation](https://react.dev/reference/react/Fragment)
@external(javascript, "./redraw.ffi.mjs", "fragment")
pub fn fragment(children: List(Component)) -> Component
/// Profile allows to measure code performance for a component tree. \
/// [Documentation](https://react.dev/reference/react/Profiler)
@external(javascript, "./redraw.ffi.mjs", "strictMode")
pub fn profiler(children: List(Component)) -> Component
pub type Suspense {
Suspense(fallback: Component)
}
/// Suspense allow to display a fallback content while waiting for children to
/// finish loading. \
/// [Documentation](https://fr.react.dev/reference/react/Suspense)
@external(javascript, "./redraw.ffi.mjs", "fragment")
pub fn suspense(props: Suspense, children: List(Component)) -> Component
// Hooks
/// Let you cache a function definition between re-renders.
/// `dependencies` should be a tuple. \
/// [Documentation](https://react.dev/reference/react/useCallback)
@external(javascript, "react", "useCallback")
pub fn use_callback(fun: function, dependencies: dependencies) -> function
/// Let you add a label to a custom Hook in React DevTools. \
/// [Documentation](https://react.dev/reference/react/useDebugValue)
@external(javascript, "react", "useDebugValue")
pub fn use_debug_value(value: a) -> Nil
/// Let you add a label to a custom Hook in React DevTools, but allow to format
/// it before. \
/// [Documentation](https://react.dev/reference/react/useDebugValue)
@external(javascript, "react", "useDebugValue")
pub fn use_debug_value_(value: a, formatter: fn(a) -> String) -> Nil
/// Let you defer updating a part of the UI. \
/// [Documentation](https://react.dev/reference/react/useDeferredValue)
@external(javascript, "react", "useDeferredValue")
pub fn use_deferred_value(value: a) -> a
/// Let you synchronize a component with an external system. \
/// [Documentation](https://react.dev/reference/react/useEffect)
@external(javascript, "react", "useEffect")
pub fn use_effect(value: fn() -> Nil, dependencies: a) -> Nil
/// Let you synchronize a component with an external system. Allow to return
/// a cleanup function. \
/// [Documentation](https://react.dev/reference/react/useEffect)
@external(javascript, "react", "useEffect")
pub fn use_effect_(value: fn() -> fn() -> Nil, dependencies: a) -> Nil
/// Version of useEffect that fires before the browser repaints the screen. \
/// [Documentation](https://react.dev/reference/react/useLayoutEffect)
@external(javascript, "react", "useLayoutEffect")
pub fn use_layout_effect(value: fn() -> Nil, dependencies: a) -> Nil
/// Generate unique IDs that can be passed to accessibility attributes. \
/// [Documentation](https://react.dev/reference/react/useId)
@external(javascript, "react", "useId")
pub fn use_id() -> String
/// Let you cache the result of a calculation between re-renders. \
/// [Documentation](https://react.dev/reference/react/useMemo)
@external(javascript, "react", "useMemo")
pub fn use_memo(calculate_value: fn() -> a, dependencies: b) -> a
/// Let you add a [reducer](https://react.dev/learn/extracting-state-logic-into-a-reducer) to your component. \
/// [Documentation](https://react.dev/reference/react/useReducer)
@external(javascript, "react", "useReducer")
pub fn use_reducer(
reducer: fn(state, action) -> state,
initial_state: state,
) -> #(state, fn(action) -> Nil)
/// Let you add a [reducer](https://react.dev/learn/extracting-state-logic-into-a-reducer) to your component.
/// Allow to initialize the store in a custom way. \
/// [Documentation](https://react.dev/reference/react/useReducer)
@external(javascript, "react", "useReducer")
pub fn use_reducer_(
reducer: fn(state, action) -> state,
initializer: initializer,
init: fn(initializer) -> state,
) -> #(state, fn(action) -> Nil)
/// Let you add a [state variable](https://react.dev/learn/state-a-components-memory) to your component. \
/// [Documentation](https://react.dev/reference/react/useState)
@external(javascript, "react", "useState")
pub fn use_state(initial_value: a) -> #(a, fn(a) -> Nil)
/// Let you add a [state variable](https://react.dev/learn/state-a-components-memory) to your component.
/// Give an `updater` function instead of a state setter. \
/// [Documentation](https://react.dev/reference/react/useState)
@external(javascript, "react", "useState")
pub fn use_state_(initial_value: a) -> #(a, fn(fn(a) -> a) -> Nil)
/// Let you add a [state variable](https://react.dev/learn/state-a-components-memory) to your component.
/// Allow to create the initial value in a lazy way. \
/// [Documentation](https://react.dev/reference/react/useState)
@external(javascript, "react", "useState")
pub fn use_lazy_state(initial_value: fn() -> a) -> #(a, fn(a) -> Nil)
/// Let you add a [state variable](https://react.dev/learn/state-a-components-memory) to your component.
/// Allow to create the initial value in a lazy way.
/// Give an `updater` function instead of a state setter. \
/// [Documentation](https://react.dev/reference/react/useState)
@external(javascript, "react", "useState")
pub fn use_lazy_state_(initial_value: fn() -> a) -> #(a, fn(fn(a) -> a) -> Nil)
/// Let you update the state without blocking the UI. \
/// [Documentation](https://react.dev/reference/react/useTransition)
@external(javascript, "react", "useTransition")
pub fn use_transition() -> #(Bool, fn() -> Nil)
// Refs
/// A Ref is a mutable data stored in React, persisted across renders.
/// They allow to keep track of a DOM node, a component data, or to store a
/// mutable variable in the component, outside of every component lifecycle. \
/// [Documentation](https://react.dev/learn/referencing-values-with-refs)
pub type Ref(a)
/// Set the current value of a ref, overriding its existing content.
@external(javascript, "./redraw.ffi.mjs", "setCurrent")
pub fn set_current(of ref: Ref(a), with value: a) -> Nil
/// Get the current value of a ref.
@external(javascript, "./redraw.ffi.mjs", "getCurrent")
pub fn get_current(from ref: Ref(a)) -> a
/// Let you reference a value that’s not needed for rendering.
/// Most used ref you'll want to create. They're automatically created to `None`,
/// and can be passed to `attribute.ref` or `use_imperative_handle`.
/// You probably don't want the ref value to be anything than `Option(a)`, unless
/// you have really strong reasons. \
/// [Documentation](https://react.dev/reference/react/useRef)
pub fn use_ref() -> Ref(Option(a)) {
use_ref_(option.None)
}
/// Let you reference a value that’s not needed for rendering.
/// Use `use_ref` if you're trying to acquire a reference to a child or to a
/// component. Use `use_ref_` when you want to keep track of a data, like if
/// you're doing some side-effects, in conjuction with `get_current` and
/// `set_current`. \
/// [Documentation](https://react.dev/reference/react/useRef)
@external(javascript, "react", "useRef")
pub fn use_ref_(initial_value: a) -> Ref(a)
/// Let you customize the handle exposed as a [ref](https://react.dev/learn/manipulating-the-dom-with-refs).
/// Use `use_imperative_handle` when you want to customize the data stored in
/// a ref. It's mostly used in conjuction with `forward_ref`. \
/// [Documentation](https://react.dev/reference/react/useImperativeHandle)
pub fn use_imperative_handle(
ref: Ref(Option(a)),
handler: fn() -> a,
dependencies: b,
) -> Nil {
use_imperative_handle_(ref, fn() { option.Some(handler()) }, dependencies)
}
/// Let you customize the handle exposed as a [ref](https://react.dev/learn/manipulating-the-dom-with-refs).
/// Use `use_imperative_handle` by default, unless you really know what you're
/// doing. \
/// [Documentation](https://react.dev/reference/react/useImperativeHandle)
@external(javascript, "react", "useImperativeHandle")
pub fn use_imperative_handle_(
ref: Ref(a),
handler: fn() -> a,
dependencies: b,
) -> Nil
// Contexts
/// Pass data without props drilling. \
/// [Documentation](https://react.dev/learn/passing-data-deeply-with-context)
pub type Context(a)
/// Let you read and subscribe to [context](https://react.dev/learn/passing-data-deeply-with-context) from your component. \
/// [Documentation](https://react.dev/reference/react/useContext)
@external(javascript, "react", "useContext")
pub fn use_context(context: Context(a)) -> a
/// Let you create a [context](https://react.dev/learn/passing-data-deeply-with-context) that components can provide or read. \
/// [Documentation](https://react.dev/reference/react/createContext)
@deprecated("Use redraw/create_context_ instead. redraw/create_context will be removed in 2.0.0. Unusable right now, due to how React handles Context.")
@external(javascript, "react", "createContext")
pub fn create_context(default_value default_value: Option(a)) -> Context(a)
/// Wrap your components into a context provider to specify the value of this context for all components inside. \
/// [Documentation](https://react.dev/reference/react/createContext#provider)
@external(javascript, "./context.ffi.mjs", "contextProvider")
pub fn provider(
context context: Context(a),
value value: a,
children children: List(Component),
) -> Component
/// Create a [context](https://react.dev/learn/passing-data-deeply-with-context)
/// that components can provide or read.
/// Each context is referenced by its name, a little bit like actors in OTP
/// (if you're familiar with Erlang). Because Gleam cannot execute code outside of
/// `main` function, creating a context should do some side-effect at startup.
///
/// In traditional React code, Context usage is usually written like this.
///
/// ```javascript
/// import * as react from 'react'
///
/// // Create your Context in a side-effectful way.
/// const MyContext = react.createContext(defaultValue)
///
/// // Create your own provider, wrapping your context.
/// export function MyProvider(props) {
/// return <MyContext.Provider>{props.children}</MyContext.Provider>
/// }
///
/// // Create your own hook, to simplify usage of your context.
/// export function useMyContext() {
/// return react.useContext(MyContext)
/// }
/// ```
///
/// To simplify and mimic that usage, Redraw wraps Context creation with some
/// caching, to emulate a similar behaviour.
///
/// ```gleam
/// import redraw
///
/// const context_name = "MyContextName"
///
/// pub fn my_provider(children) {
/// let assert Ok(context) = redraw.create_context_(context_name, default_value)
/// redraw.provider(context, value, children)
/// }
///
/// pub fn use_my_context() {
/// let assert Ok(context) = redraw.get_context(context_name)
/// redraw.use_context(context)
/// }
/// ```
///
/// Be careful, `create_context_` fails if the Context is already defined.
/// Choose a full qualified name, hard to overlap with inattention. If
/// you want to get a Context in an idempotent way, take a look at [`context()`](#context).
///
/// [Documentation](https://react.dev/reference/react/createContext)
@external(javascript, "./context.ffi.mjs", "createContext")
pub fn create_context_(
name: String,
default_value: a,
) -> Result(Context(a), Error)
/// Get a context. Because of FFI, `get_context` breaks the type-checker. It
/// should be considered as unsafe code. As a library author, never exposes
/// your context and expect users will call `get_context` themselves, but rather
/// exposes a `use_my_context()` function, handling the type-checking for the
/// user.
///
/// ```gleam
/// import redraw
///
/// pub type MyContext {
/// MyContext(value: Int)
/// }
///
/// /// `use_context` returns `Context(a)`, should it can be safely returned as
/// /// `Context(MyContext)`.
/// pub fn use_my_context() -> redraw.Context(MyContext) {
/// let context = case redraw.get_context("MyContextName") {
/// // Context has been found in the context cache, use it as desired.
/// Ok(context) -> context
/// // Context has not been found. It means the user did not initialised it.
/// Error(_) -> panic as "Unitialised context."
/// }
/// redraw.use_context(context)
/// }
/// ```
@external(javascript, "./context.ffi.mjs", "getContext")
pub fn get_context(name: String) -> Result(Context(a), Error)
/// `context` emulates classic Context usage in React. Instead of calling
/// `create_context_` and `get_context`, it's possible to simply call `context`,
/// which will get or create the context directly, and allows to write code as
/// if Context is globally available. `context` also tries to preserve
/// type-checking at most. `context.default_value` is lazily evaluated, meaning
/// no additional computations will ever be run.
///
/// ```gleam
/// import redraw
///
/// const context_name = "MyContextName"
///
/// pub type MyContext {
/// MyContext(count: Int, set_count: fn (Int) -> Nil)
/// }
///
/// fn default_value() {
/// let count = 0
/// les set_count = fn (_) { Nil }
/// MyContext(count:)
/// }
///
/// pub fn provider() {
/// use _, children <- redraw.component()
/// let context = redraw.context(context_name, default_value)
/// let #(count, set_count) = redraw.use_state(0)
/// redraw.provider(context, MyContext(count:, set_count:), children)
/// }
///
/// pub fn use_my_context() {
/// let context = redraw.context(context_name, default_value)
/// redraw.use_context(context)
/// }
/// ```
///
/// `context` should never fail, but it can be wrong if you use an already used
/// name.
pub fn context(name: String, default_value: fn() -> a) -> Context(a) {
case get_context(name) {
Ok(context) -> context
Error(get) ->
case create_context_(name, default_value()) {
Ok(context) -> context
Error(create) -> {
let get = " get_context: " <> string.inspect(get)
let create = " create_context_: " <> string.inspect(create)
let head = "[Redraw Internal Error] Unable to find or create context."
let body =
function.flip(string.join)(" ", [
"context should never panic.",
"Please, open an issue on https://github.com/ghivert/redraw,",
"and join the error details.\n",
])
let details = "Error details:"
let msg = string.join([head, body, details, get, create], "\n")
panic as msg
}
}
}
}
// API
/// Test helper to apply pending React updates before making assertions. \
/// [Documentation](https://react.dev/reference/react/act)
@external(javascript, "react", "act")
pub fn act(act_fn: fn() -> Promise(Nil)) -> Promise(Nil)
/// Let you update the state without blocking the UI. \
/// [Documentation](https://react.dev/reference/react/startTransition)
@external(javascript, "react", "startTransition")
pub fn start_transition(scope scope: fn() -> Nil) -> Nil
// Helpers
/// Redraw does not support passing key element to components in an easy way like
/// React does. To simplify this, it uses the same API than [Lustre](lustre.build)
/// to put keys on children.
/// ```gleam
/// fn my_component(props, children) {
/// redraw.keyed(my_other_component(props, _), {
/// use item <- list.map(children)
/// #("my-key", item)
/// })
/// }
/// ```
pub fn keyed(
element: fn(List(Component)) -> Component,
content: List(#(String, Component)),
) {
content
|> coerce
|> element
}
// FFI
// Those functions are used internally by Redraw, to setup things correctly.
// They should not be accessible from the outside world.
@external(javascript, "./redraw.ffi.mjs", "jsx")
@internal
pub fn jsx(value: a, props: props, children: b) -> Component
@external(javascript, "./redraw.ffi.mjs", "setFunctionName")
fn set_function_name(a: a, name: String) -> a
@external(javascript, "./redraw.ffi.mjs", "addProxy")
fn add_proxy(a: fn(props) -> Component) -> fn(props) -> Component
@external(javascript, "./redraw.ffi.mjs", "addEmptyProxy")
fn add_empty_proxy(a: fn() -> Component) -> fn() -> Component
@external(javascript, "./redraw.ffi.mjs", "addChildrenForwardRef")
fn add_children_forward_ref(
a: fn(props, Ref(ref), List(Component)) -> Component,
) -> fn(props, Ref(ref), List(Component)) -> Component
@external(javascript, "./redraw.ffi.mjs", "addForwardRef")
fn add_forward_ref(
a: fn(props, Ref(ref)) -> Component,
) -> fn(props, Ref(ref)) -> Component
@external(javascript, "./redraw.ffi.mjs", "addChildrenProxy")
fn add_children_proxy(
a: fn(props, List(Component)) -> Component,
) -> fn(props, List(Component)) -> Component