{"id":25232953,"url":"https://github.com/londek/reactea","last_synced_at":"2025-10-26T09:31:00.795Z","repository":{"id":60155721,"uuid":"538661616","full_name":"londek/reactea","owner":"londek","description":"Rather simple Bubble Tea companion for handling hierarchy and support for lifting state up 🚀","archived":false,"fork":false,"pushed_at":"2024-08-19T13:06:40.000Z","size":306,"stargazers_count":47,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-01-09T18:10:39.158Z","etag":null,"topics":["bubbletea","go","golang","react","routing","tui"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/londek/reactea","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/londek.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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}},"created_at":"2022-09-19T19:18:49.000Z","updated_at":"2024-12-28T08:35:39.000Z","dependencies_parsed_at":"2023-02-13T04:01:38.477Z","dependency_job_id":"c64931a8-70fd-40d6-93f0-c5a5274dc393","html_url":"https://github.com/londek/reactea","commit_stats":{"total_commits":113,"total_committers":2,"mean_commits":56.5,"dds":0.008849557522123908,"last_synced_commit":"6fbbbfade3c14fd1609cb29ec9b1291065faf354"},"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/londek%2Freactea","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/londek%2Freactea/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/londek%2Freactea/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/londek%2Freactea/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/londek","download_url":"https://codeload.github.com/londek/reactea/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238302239,"owners_count":19449565,"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":["bubbletea","go","golang","react","routing","tui"],"created_at":"2025-02-11T13:37:28.287Z","updated_at":"2025-10-26T09:31:00.789Z","avatar_url":"https://github.com/londek.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# \u003cp align=\"center\"\u003eReactea\u003c/p\u003e\n\n\u003cdiv align=\"center\"\u003e\n\n[![Latest](https://img.shields.io/github/v/tag/londek/reactea?label=latest)](https://img.shields.io/github/v/tag/londek/reactea?label=latest)\n[![build](https://github.com/londek/reactea/actions/workflows/build.yml/badge.svg)](https://github.com/londek/reactea/actions/workflows/build.yml)\n![Codecov](https://img.shields.io/codecov/c/github/Londek/reactea)\n[![Go Reference](https://pkg.go.dev/badge/github.com/londek/reactea.svg)](https://pkg.go.dev/github.com/londek/reactea)\n[![Go Report Card](https://goreportcard.com/badge/github.com/londek/reactea)](https://goreportcard.com/report/github.com/londek/reactea)\n\n\u003cp align=\"center\"\u003e⚠️ I'm rewriting project for 2025! ⚠️\u003c/p\u003e\n\nRather simple **Bubbletea companion** for **handling hierarchy**, support for **lifting state up.**\\\nIt Reactifies Bubbletea philosophy and makes it especially easy to work with in bigger projects.\n\nFor me, personally - **It's a must** in project with multiple pages and component communication\n\nCheck our quickstart [right here](#quickstart) or other examples [here!](/examples)\n\n`go get -u github.com/londek/reactea`\n\u003c/div\u003e\n\n## General info\n\nThe goal is to create components that are\n\n- dimensions-aware (especially unify all setSize conventions)\n- scallable\n- more robust\n- easier to code\n- all of that without code duplication\n\nExtreme performance is not main goal of this package, however it should not be\nthat far off actual Bubbletea which is already blazing fast.\nMost info is currently in source code so I suggest checking it out\n\nAlways return `reactea.Destroy` instead of `tea.Quit` in order to follow our convention (that way Destroy() will be called on your components)\n\nAs of now Go doesn't support type aliases for generics, so `Renderer[TProps]` has to be explicitly casted.\n\n## [Quickstart](/examples/quickstart)\n\nReactea unlike Bubbletea implements two-way communication, very React-like communication.\\\nIf you have experience with React you are gonna love Reactea straight away!\n\nIn this tutorial we are going to make application that consists of 2 pages.\n\n- The `/input` (aka `index`, in reactea `default`) page for inputting your name\n- The `/displayname` page for displaying your name\n\n### [Lifecycle](#component-lifecycle)\n\nMore detailed docs about component lifecycle can be found [here](#component-lifecycle), we are only gonna go through basics.\n\nReactea component lifecycle consists of 6 methods (while Bubbletea only 3)\n|Method|Purpose|\n|-|-|\n| `Init() tea.Cmd` | It's called first. All critical stuff should happen here. It also supports IO through tea.Cmd |\n| `Update(tea.Msg) tea.Cmd` | It reacts to Bubbletea IO and updates state accordingly |\n| `Render(int, int) string` | It renders the UI. The two arguments are width and height, they should be calculated by parent |\n| `Destroy()` | It's called whenever Component is about to end it's lifecycle. Please note that it's parent's responsibility to call `Destroy()` |\n\nLet's get to work!\n\n### The `/input` page\n\n`/pages/input/input.go`\n\n```go\ntype Component struct {\n    reactea.BasicComponent                // It implements all reactea's core functionalities\n\n    // Props\n    SetText func(string)\n\n    textinput textinput.Model             // Input for inputting name\n}\n\nfunc New() *Component {\n    return \u0026Component{textinput: textinput.New()}\n}\n\nfunc (c *Component) Init() tea.Cmd {\n    return c.textinput.Focus()\n}\n\nfunc (c *Component) Update(msg tea.Msg) tea.Cmd {\n    switch msg := msg.(type) {\n    case tea.KeyMsg:\n        if msg.Type == tea.KeyEnter {\n            // Lifted state power! Woohooo\n            c.SetText(c.textinput.Value())\n\n            // Navigate to displayname, please\n            reactea.SetRoute(\"/displayname\")\n            return nil\n        }\n    }\n\n    var cmd tea.Cmd\n    c.textinput, cmd = c.textinput.Update(msg)\n    return cmd\n}\n\n// Here we are not using width and height, but you can!\nfunc (c *Component) Render(int, int) string {\n    return fmt.Sprintf(\"Enter your name: %s\\nAnd press [ Enter ]\", c.textinput.View())\n}\n```\n\n#### The `/displayname` page\n\n`/pages/displayname/displayname.go`\n\n```go\nimport (\n \"fmt\"\n)\n\n// Our prop(s) is a string itself!\ntype Props = string\n\n// Stateless components?!?!\nfunc Renderer(text Props, width, height int) string {\n    return fmt.Sprintf(\"OMG! Hello %s!\", text)\n}\n```\n\n### Main component\n\n`/app/app.go`\n\n```go\ntype Component struct {\n    reactea.BasicComponent                // It implements all reactea's core functionalities\n\n    mainRouter reactea.Component[router.Props]      // Our router\n\n    text string // The name\n}\n\nfunc New() *Component {\n    return \u0026Component{\n        mainRouter: router.New(),\n    }\n}\n\nfunc (c *Component) Init(reactea.NoProps) tea.Cmd {\n    // Does it remind you of something? react-router!\n    return c.mainRouter.Init(map[string]router.RouteInitializer{\n        \"default\": func(router.Params) reactea.Component {\n\t\t\tcomponent := input.New()\n\n\t\t\tcomponent.SetText = c.setText\n\n\t\t\treturn component\n\t\t},\n\t\t\"/displayname\": func(router.Params) reactea.Component {\n            // RouteInitializer requires Component so we have to convert\n            // Stateless component (renderer) to Component\n\t\t\treturn reactea.Componentify(displayname.Render, c.text)\n\t\t},\n    })\n}\n\nfunc (c *Component) Update(msg tea.Msg) tea.Cmd {\n    switch msg := msg.(type) {\n    case tea.KeyMsg:\n        // ctrl+c support \n        if msg.String() == \"ctrl+c\" {\n            return reactea.Destroy\n        }\n    }\n\n    return c.mainRouter.Update(msg)\n}\n\nfunc (c *Component) Render(width, height int) string {\n    return c.mainRouter.Render(width, height)\n}\n\nfunc (c *Component) setText(text string) {\n    c.text = text\n}\n```\n\n#### Main\n\n`main.go`\n\n```go\n// reactea.NewProgram initializes program with\n// \"translation layer\", so Reactea components work\nprogram := reactea.NewProgram(app.New())\n\nif _, err := program.Run(); err != nil {\n    panic(err)\n}\n```\n\n## Component lifecycle\n\n![Component lifecycle image](.github/lifecycle-diagram.png)\n\nReactea component lifecycle consists of 6 methods (while Bubbletea only 3)\n|Method|Purpose|\n|-|-|\n| `Init() tea.Cmd` | It's called first. All critical stuff should happen here. It also supports IO through tea.Cmd |\n| `Update(tea.Msg) tea.Cmd` | It reacts to Bubbletea IO and updates state accordingly |\n| `Render(int, int) string` | It renders the UI. The two arguments are width and height, they should be calculated by parent |\n| `Destroy()` | It's called whenever Component is about to end it's lifecycle. Please note that it's parent's responsibility to call `Destroy()` |\n\nReactea takes pointer approach for components making state modifiable in any lifecycle method\\\n\n### Notes\n\n`Update()` **IS NOT** guaranteed to be called on first-run, `Init()` for most part is, and critical logic should be there\n\n## Stateless components\n\nStateless components are represented by following function types\n\n|                | Renderer[TProps any]     | ProplessRenderer   | DumbRenderer  |\n|----------------|:------------------------:|:------------------:|:-------------:|\n| **Properties** | ✅                       | ❌                | ❌            |\n| **Dimensions** | ✅                       | ✅                | ❌            |\n| **Arguments** | `TProps, int, int`        | `int, int`         | ❌            |\n\nThere are many utility functions for transforming stateless into stateful components or for rendering any component without knowing its type (`reactea.RenderAny`)\n\n## Routes API\n\nRoutes API allows developers for easy development of multi-page apps.\nThey are kind of substitute for window.Location inside Bubbletea\n\n### reactea.CurrentRoute() Route\n\nReturns current route\n\n### reactea.LastRoute() Route\n\nReturns last route\n\n### reactea.WasRouteChanged() bool\n\nreturns `LastRoute() != CurrentRoute()`\n\n## Reactea Routes now support params\n\nParams have been introduced in order to allow routes like: `/teams/123/player/4`\n\nParams have to follow regex `^:.*$`\\\n`^` being beginning of current path level (`/^level/`)\\\n`$`being end of current path level (`/level$/`)\n\nNote that params support wildcards with single `:`, like `/teams/:/player`. `/teams/123/player`, `/teams/456/player` etc will be matched no matter what and param will be ignored in param map.\n\n## Router Component\n\nRouter Component is basic implementation of how routing could look in your application.\nIt doesn't support wildcards yet or relative pathing. All data is provided from within props\n\n### router.Props\n\nrouter.Props is a map of route initializers keyed by routes\n\nWhat is `RouteInitializer`?\n\n`RouteInitializer` is function that initializes the current route component\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flondek%2Freactea","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flondek%2Freactea","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flondek%2Freactea/lists"}