{"id":13741043,"url":"https://github.com/ghivert/redraw","last_synced_at":"2025-04-06T09:31:18.330Z","repository":{"id":249537869,"uuid":"829451065","full_name":"ghivert/redraw","owner":"ghivert","description":"React bindings for Gleam! Supports everything modern React provides, with full Gleam Type-Checking system!","archived":false,"fork":false,"pushed_at":"2025-03-19T15:51:37.000Z","size":222,"stargazers_count":40,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-26T18:56:39.965Z","etag":null,"topics":["frontend","gleam","react"],"latest_commit_sha":null,"homepage":"https://hexdocs.pm/redraw","language":"Gleam","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ghivert.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["ghivert"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"lfx_crowdfunding":null,"polar":null,"buy_me_a_coffee":null,"custom":null}},"created_at":"2024-07-16T13:01:26.000Z","updated_at":"2025-03-19T15:51:40.000Z","dependencies_parsed_at":null,"dependency_job_id":"073f12ff-7305-45b3-b338-4f0d27c7d03b","html_url":"https://github.com/ghivert/redraw","commit_stats":null,"previous_names":["ghivert/redraw"],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ghivert%2Fredraw","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ghivert%2Fredraw/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ghivert%2Fredraw/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ghivert%2Fredraw/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ghivert","download_url":"https://codeload.github.com/ghivert/redraw/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247463744,"owners_count":20942935,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["frontend","gleam","react"],"created_at":"2024-08-03T04:00:54.835Z","updated_at":"2025-04-06T09:31:18.321Z","avatar_url":"https://github.com/ghivert.png","language":"Gleam","funding_links":["https://github.com/sponsors/ghivert"],"categories":["Packages","Gleam"],"sub_categories":["Frontend"],"readme":"# Redraw\n\nReact opinionated bindings for Gleam. Use React directly from Gleam, with a\nfriendly API that will never go in your path. Redraw tries to stick to React\nconventions, while providing idiomatic Gleam code. Write interoperable code\nbetween React and Gleam code, reuse existing components, and leverage type-safe\ncomponents \u0026 immutable data structure. Forget runtime errors, and write React\ncomponents that just works.\n\n\u003e [!TIP]\n\u003e\n\u003e Have you tried [Lustre](https://lustre.build)? Lustre requires almost no\n\u003e knowledge of JavaScript, no complicated runtimes to get like Node.js, few\n\u003e knowledge of HTML/CSS and other web technologies, less code interfaces to\n\u003e write than Redraw, and is supported by the entire Gleam community! Before\n\u003e launching into Redraw, you should take a look at Lustre, it provides a\n\u003e user-friendly, awesome experience right out-of-the-box for every gleamlins!\n\u003e\n\u003e As a bonus, Lustre is isomorphic, i.e. it can be used as well on client and on\n\u003e server with the same codebase!\n\u003e\n\u003e Redraw assumes you have minimal knowledge on frontend development, and will\n\u003e neither try to ease your learning curve nor simplify and hide the frontend\n\u003e technology stack and complexities. In case you're not sure what you really\n\u003e need, pick Lustre.\n\n## Overview\n\nRedraw is a package that let you use React in a frontend-only Gleam project. By\nleveraging on the entire JS ecosystem, Redraw help you interop with existing\ncurrent React codebases, or allows you to build your custom codebase and\ncherry-picking the existing components you know and love! Redraw tries to keep\neverything at the lowest level possible, turning all the React niceties into\nGleam niceties. Wherever possible, Redraw tries to stick with Lustre API, to\nhelp you create a codebase mixing the two frameworks. For instance, you could\nbuild libraries targeting both Lustre and Redraw, and using the same design\nsystem for all your products!\n\n## Prerequisites\n\nRedraw assumes that you're a fluent frontend developer and already understand\nhow React works. If you don't, it's best to first learn React and the frontend\necosystem, and come back here later. Meanwhile, you could also take a look at\n[Lustre](https://lustre.build) to create your own application. You'll find some\ngood tutorials on [react.dev](https://react.dev/), more specifically on\n[\"Get Started\" page](https://react.dev/learn).\n\nRedraw assumes you have [`node.js`](https://nodejs.org/en) or equivalent as well\nas a modern package manager, i.e. `npm`, `yarn`, `pnpm`, or even `bun`. Redraw\nalso assumes you're using [`Vite`](https://vitejs.dev/) or an equivalent as\nbuild tool, and will not provide any interface to build your application. In the\nrest of that README, `Vite` will be used as example. It's up to you to use\nanother bundler if you prefer. Redraw sticks with the modern, up-to-date\nfrontend stack.\n\n## Getting started\n\nCreate the project, and add everything needed to make it work. Choose your\nprefered bundler to start. Create a Vite application, and choose to use\nJavaScript and React. Vite should bundle everything for you directly.\n\n```sh\nnpm create vite@latest\n```\n\n```sh\nyarn create vite\n```\n\n```sh\npnpm create vite\n```\n\n```sh\nbun create vite\n```\n\nFrom the follow-on, `yarn` will be used to illustrate the commands, it's up to\nyou to see how to use your desired package manager. Then, it's time to setup the\nproject correctly.\n\n```sh\ncd [project-name]\nyarn install\n\n# Install the Vite Gleam plugin. That plugin is required to tell Vite how to\n# read Gleam files.\nyarn add -D vite-gleam\n\n# If you want to build the project on Vercel or Netlify.\n# @chouqueth/gleam provides a local version of the Gleam compiler installed in\n# your node_modules. You can freely skip that step if you don't need to build\n# your application remotely or if you're in control of the environment.\nyarn add -D @chouqueth/gleam\n\n# Remove the files needed for `gleam new` to work.\nmv README.md README.md.old\nmv .gitignore .gitignore.old\n\n# Setup the project.\ngleam new .\ngleam add redraw redraw_dom\n```\n\nNow that everyting is setup, you have to add the `vite-gleam` plugin in\n`vite.config.js`. An example of a `vite.config.js` should look like this.\n\n```javascript\nimport { defineConfig } from \"vite\"\nimport react from \"@vitejs/plugin-react\"\nimport gleam from \"vite-gleam\"\n\n// https://vitejs.dev/config/\nexport default defineConfig({\n  plugins: [react(), gleam()],\n})\n```\n\nYou're good to go!\n\n## Writing Redraw components\n\nWriting Redraw components is the same as writing React component, with one small\ndifference: wrap the component in `use \u003c- redraw.component()`!\n\n```gleam\nimport redraw\nimport redraw/dom/attribute\nimport redraw/dom/html\n\npub fn gleam_is_awesome() {\n  use \u003c- redraw.component__(\"GleamIsAwesome\")\n  html.div([attribute.class(\"oh-yeah\")], [\n    html.text(\"Yeah, for sure\")\n  ])\n}\n```\n\nWhile this could feels strange at first, you'll get used to it quickly. To call\nthe component, you'll need to call the function first, _before definining a new\ncomponent_.\n\n```gleam\nimport redraw\nimport redraw/dom/attribute\nimport redraw/dom/client\nimport redraw/dom/html\n\npub fn main() {\n  let root = root()\n  let assert Ok(root) = client.create_root(\"root\")\n  client.render(root, redraw.strict_mode([root()]))\n}\n\nfn root() {\n  // Call `gleam_is_awesome` here, before component creation, otherwise a new\n  // component will be created at each refresh.\n  let gleam_is_awesome = gleam_is_awesome()\n  use \u003c- redraw.component__(\"Root\")\n  html.div([], [\n    gleam_is_awesome()\n  ])\n}\n\nfn gleam_is_awesome() {\n  use \u003c- redraw.component__(\"GleamIsAwesome\")\n  html.div([attribute.class(\"oh-yeah\")], [\n    html.text(\"Yeah, for sure\")\n  ])\n}\n```\n\nAnd you know everything to create Redraw components!\n\n## What is the kind of components?\n\nComponents can have different kinds, accepting props, children, ref, or other\nthings. To provide a simpler, usable API in Gleam, props components in Gleam can\nbe List, Tuple, CustomTypes or Nil. It means you can totally define your\ncomponent like this:\n\n```gleam\npub type CounterProps {\n  CounterProps(\n    count: Int,\n    set_count: fn(fn(Int) -\u003e Int) -\u003e Nil,\n  )\n}\n\npub fn counter() {\n  use props: CounterProps \u003c- redraw.component_(\"Counter\")\n  html.button(\n    [events.on_click(fn(_) { props.set_count(fn(count) { count + 1 }) })],\n    [html.text(\"count is \" \u003c\u003e int.to_string(props.count))],\n  )\n}\n```\n\nbut also like this:\n\n```gleam\npub type CounterProps = #(Int, fn(fn(Int) -\u003e Int) -\u003e Nil)\n\npub fn counter() {\n  use #(count, set_count): CounterProps \u003c- redraw.component_(\"Counter\")\n  html.button(\n    [events.on_click(fn(_) { set_count(fn(count) { count + 1 }) })],\n    [html.text(\"count is \" \u003c\u003e int.to_string(count))],\n  )\n}\n```\n\nor even props-less components:\n\n```gleam\npub fn counter() {\n  use Nil \u003c- redraw.component_(\"Counter\")\n  let #(count, set_count) = redraw.use_state(0)\n  html.button(\n    [events.on_click(fn(_) { set_count(fn(count) { count + 1 }) })],\n    [html.text(\"count is \" \u003c\u003e int.to_string(count))],\n  )\n}\n```\n\nDon't worry about the translation of data from and to React, Redraw handles the\nhard task for you!\n\n### `component`-family functions\n\nTo define components, you should use `component`, `component_` or `component__`.\nThe difference between the three is the signature of the resulting component.\n`component` accepts props and children, `component_` accepts only props, and\n`component__` do not accept anything. See it as a way to create an empty\ncomponent, used with contexts or internal state for instance. You cannot create\na component that accept children but no props. While it can feel boilerplaty at\nfirst, that is a design decision. Most of the time, components that accept\nchildren also accept props, so it's not worth creating another API and add\noverhead for a function that will almost not be used.\n\n### `forward_ref`-family functions\n\nDefining components sometimes involves to forward a ref to internal component.\nReact uses the mechanism of `forwardRef` to push a ref, from the parent to a\nnested child. Redraw fully implements forwarded ref components! Use\n`forward_ref` or `forward_ref_` to create a component with props, ref and\nchildren, or only with props and children!\n\n## Some reminders on hooks\n\nNever use hooks outside of custom hooks (functions named `use_[something]`) or\nin components! It means you should never use something like `use_effect` or\n`use_state` outside of the body of `component`-related functions. If you break\nthat rule, while it could seem to work, it's actually breaking the runtime, and\nit can explode at any time. So keep that rule anytime: no hooks outside of\ncustom hooks or component body.\n\n## Type-checking of hooks\n\nYou could see that hooks often use dependencies array, to determine if they have\nto rerun or not. This is totally supported by Redraw, and leverages on Gleam\nabilities! Always pass a tuple of dependencies to hooks. No type-checking are\ndone at this stage, and probably none will be implemented later, exactly like\nit's done with React currently. Be careful to provide the correct dependencies.\n\n```gleam\nimport gleam/io\nimport redraw\nimport redraw/attribute\nimport redraw/html\n\nfn gleam_is_awesome() {\n  use \u003c- redraw.component__(\"GleamIsAwesome\")\n  redraw.use_effect(fn() {\n    io.println(\"Hello from component!\")\n  }, #()) // Passing an empty tuple here is like passing [] in JavaScript.\n  html.div([attribute.class(\"oh-yeah\")], [\n    html.text(\"Yeah, for sure\")\n  ])\n}\n```\n\n## Using external components\n\nReact is greatly used everywhere. It means a lot of components are already\nusable out-of-the-box. Happily, Redraw provides a way to interop directly with\nthem! Use `to_component` and `to_component_` to integrate a foreign function\ndirectly. Define the correct props, and your work is done! Don't worry about\nsnake_case and camelCase of props name, Redraw take care of the translation for\nyou. Everytime you put an `Option(a)`, Redraw will also translate it to\n`a | null`, because React use the convention to pass `null` everywhere instead\nof optionals.\n\n```gleam\nimport gleam/option.{type Option}\nimport redraw\nimport redraw/dom/html\n\n// This type will be converted to correct JS props.\npub type ExternalComponentProps {\n  ExternalComponentProps(\n    first_field: Bool,             // firstField: bool\n    second_field: Bool,            // secondField: bool\n    optional_field: Option(String) // optionalField: string | null\n  )\n}\n\n@external(javascript, \"external_module\", \"ExternalComponent\")\nfn external_component_ffi(props: a) -\u003e redraw.Component\n\nfn external_component() -\u003e fn(ExternalComponentProps) -\u003e redraw.Component {\n  redraw.to_component_(\"ExternalComponent\", external_component_ffi)\n}\n\npub fn my_other_component() {\n  let external_component = external_component()\n  use \u003c- redraw.component__(\"OtherComponent\")\n  html.div([], [\n    external_component(\n      ExternalComponentProps(\n        first_field: True,\n        second_field: False,\n        optional_field: option.None,\n      )\n    )\n  ])\n}\n```\n\n## Miscellaneous\n\nSome questions, answers, and various informations.\n\n### Is there no linter for Redraw?\n\nAt the moment, Redraw leverages on the Gleam compiler and does not offer linter\nsupport for critical parts like hooks dependencies. A future, complementary\nlinter is planned, and should bridge that gap between Gleam and React. While\nGleam compiler provides all useful information about Gleam code, Redraw linter\nwill focus on specific Redraw requirements.\n\n### What is the state of support for React Native, or any other React flavor?\n\nReact is an isolated packages, and renderers can be various. Redraw has\nsuccessfully been used with Raycast. You can also easily add a `redraw_native`\npackage, and provide an interface for native components. Everything can be done\nquickly and easily, because the entire package is written with as few JS as\npossible. Everything should work almost out-of-the box, because React is already\nworking there. You should take inspiration at how is working `redraw/html`, and\nit could work in the exact same way. It's only a matter of providing the correct\nComponent to `jsx`.\n\n### Contributing\n\nDo you love the package? You can contribute! Feel free to open a PR, or open an\nissue!\n\n### Why wrapping every components in `use props \u003c- component(name)`?\n\nIf you're used to React, you know a component is no more than a function,\nreturning a `ReactNode`. Something like this:\n\n```javascript\nfunction GleamIsAwesome(props) {\n  return \u003cdiv className=\"oh-yeah\"\u003eYeah, for sure\u003c/div\u003e\n}\n```\n\nActually, because of JSX, React is a bit lying to all of us, and compiles it to:\n\n```javascript\nimport { jsx } from \"react/runtime-jsx\"\n\nfunction GleamIsAwesome(props) {\n  return jsx(\"div\", { className: \"oh-yeah\", children: \"Yeah, for sure\" })\n}\n```\n\nReact injects a call to `jsx` before every JSX render. This allows for the\nruntime to determine if the function should be called once again with the new\nprovided props. It's also true for functions.\n\n```javascript\n// That code\nfunction ILoveBeam(props) {\n  return \u003cdiv\u003eBEAM 💜\u003c/div\u003e\n}\n\nfunction GleamIsAwesome(props) {\n  return (\n    \u003cdiv className=\"oh-yeah\"\u003e\n      \u003cILoveBeam /\u003e\n      Yeah, for sure\n    \u003c/div\u003e\n  )\n}\n\n// Turns into\nimport { jsx, jsxs } from \"react/runtime-jsx\"\n\nfunction ILoveBeam(props) {\n  return jsx(\"div\", { children: \"BEAM 💜\" })\n}\n\nfunction GleamIsAwesome(props) {\n  return jsxs(\"div\", {\n    className: \"oh-yeah\",\n    children: [jsx(ILoveBeam, {}), \"Yeah, for sure\"],\n  })\n}\n```\n\nHere, we got a problem: we cannot inject the `jsx` call before `ILoveBeam` in\nGleam. What we could do is write a function that generate the `jsx` call with an\nother function with `use`, but anonymous functions cannot be used with React:\nReact is doing referential equality for Functional Components. Every component\nshould be defined once and for all.\n\nTo inject the `jsx` call properly, it would requires an additional\ncompilation-step. Because we cannot do this, we pass by a generator function.\nThat's what `use props \u003c- component(name)` is doing. To get the correct result,\nwe go to a _Component creator_, a function that create a component once and for\nall from a `render` function. Everytime we create a component, we use a render\nfunction and turns it into a proper component to be used with React.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fghivert%2Fredraw","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fghivert%2Fredraw","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fghivert%2Fredraw/lists"}