{"id":21959342,"url":"https://github.com/samuelgja/muya","last_synced_at":"2025-04-23T17:46:37.528Z","repository":{"id":263235390,"uuid":"889756750","full_name":"samuelgja/muya","owner":"samuelgja","description":"🌀 Muya is simple and lightweight react state management library.","archived":false,"fork":false,"pushed_at":"2025-03-17T13:53:46.000Z","size":779,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-30T02:05:12.521Z","etag":null,"topics":["react","react-native","state-management"],"latest_commit_sha":null,"homepage":"https://github.com/samuelgja/muya","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/samuelgja.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":"2024-11-17T06:26:16.000Z","updated_at":"2025-03-17T13:53:50.000Z","dependencies_parsed_at":"2024-11-18T01:54:56.295Z","dependency_job_id":null,"html_url":"https://github.com/samuelgja/muya","commit_stats":null,"previous_names":["samuelgja/muya"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samuelgja%2Fmuya","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samuelgja%2Fmuya/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samuelgja%2Fmuya/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samuelgja%2Fmuya/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/samuelgja","download_url":"https://codeload.github.com/samuelgja/muya/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250484767,"owners_count":21438302,"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":["react","react-native","state-management"],"created_at":"2024-11-29T09:27:47.823Z","updated_at":"2025-04-23T17:46:37.509Z","avatar_url":"https://github.com/samuelgja.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n# **Muya 🌀**\n\nMuya is simple and lightweight react state management library.\n\n---\n\n[![Build](https://github.com/samuelgja/muya/actions/workflows/build.yml/badge.svg)](https://github.com/samuelgja/muya/actions/workflows/build.yml)\n[![Code Quality Check](https://github.com/samuelgja/muya/actions/workflows/code-check.yml/badge.svg)](https://github.com/samuelgja/muya/actions/workflows/code-check.yml)\n[![Build Size](https://img.shields.io/bundlephobia/minzip/muya?label=Bundle%20size)](https://bundlephobia.com/result?p=muya)\n\n\n- **Simplified API**: Only `create` and `select`.\n- **Batch Updates**: Built-in batching ensures efficient `muya` state updates internally.\n- **TypeScript Support**: Type first.\n- **Lightweight**: Minimal bundle size.\n\n---\n\n## 📦 **Installation**\n\nInstall with your favorite package manager:\n```bash\nbun add muya@latest\n```\n```bash\nnpm install muya@latest\n```\n```bash\nyarn add muya@latest\n```\n\n---\n\n## 📝 **Quick Start**\n\n### **Create and Use State**\n\n```typescript\nimport { create } from 'muya';\n\nconst useCounter = create(0);\n\n// Access in a React component\nfunction Counter() {\n  const count = useCounter(); // Call state directly\n  return (\n    \u003cdiv\u003e\n      \u003cbutton onClick={() =\u003e useCounter.set((prev) =\u003e prev + 1)}\u003eIncrement\u003c/button\u003e\n      \u003cp\u003eCount: {count}\u003c/p\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\n---\n\n### **Select and Slice State**\n\nUse `select` to derive a slice of the state:\n\n```typescript\nconst state = create({ count: 0, value: 42 });\n\nconst countSlice = state.select((s) =\u003e s.count);\n\n// Also async is possible, but Muya do not recommend \n// It can lead to spaghetti re-renders code which is hard to maintain and debug\nconst asyncCountSlice = state.select(async (s) =\u003e {\n  const data = await fetchData();\n  return data.count;\n});\n```\n\n---\n\n### **Combine Multiple States**\n\nCombine multiple states into a derived state via `select` method:\n\n```typescript\nimport { create, select } from 'muya'\n\nconst state1 = create(1);\nconst state2 = create(2);\n\nconst sum = select([state1, state2], (s1, s2) =\u003e s1 + s2);\n```\n\n### **Equality Check**\n\nCustomize equality checks to prevent unnecessary updates:\n\n```typescript\nconst state = create({ a: 1, b: 2 }, (prev, next) =\u003e prev.b === next.b);\n\n// Updates only when `b` changes\nstate.set((prev) =\u003e ({ ...prev, a: prev.a + 1 }));\n```\n\nOr in select methods:\n\n```typescript\nconst derived = select([state1, state2], (s1, s2) =\u003e s1 + s2, (prev, next) =\u003e prev === next);\n```\n\n---\n\n\n## 🖥️ **Using State in Components**\n\nAccess state directly or through `useValue` hook:\n\n### **Option 1: Access State Directly**\nEach state can be called as the hook directly\n```typescript\nconst userState = create(0);\n\nfunction App() {\n  const user = userState(); // Directly call state\n  return \u003cp\u003eUser: {user}\u003c/p\u003e;\n}\n```\n\n### **Option 2: Use the Hook**\nOr for convenience, there is `useValue` method\n```typescript\nimport { useValue } from 'muya';\n\nfunction App() {\n  const user = useValue(userState); // Access state via hook\n  return \u003cp\u003eUser: {user}\u003c/p\u003e;\n}\n```\n\n### **Option 3: Slice with Hook**\nFor efficient re-renders, `useValue` provides a slicing method.\n```typescript\nfunction App() {\n  const count = useValue(state, (s) =\u003e s.count); // Use selector in hook\n  return \u003cp\u003eCount: {count}\u003c/p\u003e;\n}\n```\n\n---\n\n## 📖 **API Overview**\n\n### **`create`**\n\nCreate a new state:\n\n```typescript\nconst state = create(initialValue, isEqual?);\n\n// Methods:\nstate.get(); // Get current value\nstate.set(value); // Update value\nstate.listen(listener); // Subscribe to changes\nstate.select(selector, isEqual?); // Create derived state\nstate.destroy(); // Unsubscribe from changes, useful for dynamic state creation in components\nstate.withName(name); // Add a name for debugging, otherwise it will be auto generated number\n```\n\n### **`select`**\n\nCombine or derive new states:\n\n```typescript\nconst derived = select([state1, state2], (s1, s2) =\u003e s1 + s2);\n\n// Methods:\nderived.get(); // Get current value\nderived.listen(listener); // Subscribe to changes\nderived.select(selector, isEqual?); // Create nested derived state\nderived.destroy(); // Unsubscribe from changes, useful for dynamic state creation in components\nderived.withName(name); // Add a name for debugging, otherwise it will be auto generated number\n```\n\n### **`useValue`**\n\nReact hook to access state:\n\n```typescript\nconst value = useValue(state, (s) =\u003e s.slice); // Optional selector\n```\n\n---\n\n## ⚠️ **Notes**\n\n- **Equality Check**: Prevent unnecessary updates by passing a custom equality function to `create` or `select`.\n- **Batch Updates**: Muya batches internal updates for better performance, reducing communication overhead similarly how react do.\n- **Async Selectors / Derives**: Muya has support for async selectors / derives, but do not recommend to use as it can lead to spaghetti re-renders code which is hard to maintain and debug, if you want so, you can or maybe you should consider using other alternatives like `Jotai`.\n\n\n\n`Muya` encourage use async updates withing sync state like this:\n```typescript\nconst state = create({ data: null });\nasync function update() {\n  const data = await fetchData();\n  state.set({ data });\n}\n```\n---\n\nBut of course you can do\n\nNote: Handling async updates for the state (`set`) will cancel the previous pending promise.\n```typescript\nconst state = create(0)\nconst asyncState = state.select(async (s) =\u003e {\n  await longPromise(100)\n  return s + 1\n})\n```\n---\n\n### Lazy resolution\n`Muya` can be used in `immediate` mode or in `lazy` mode. When create a state with just plain data, it will be in immediate mode, but if you create a state with a function, it will be in lazy mode. This is useful when you want to create a state that is executed only when it is accessed for the first time.\n\n\n```typescript\n// immediate mode, so no matter what, this value is already stored in memory\nconst state = create(0)\n\n// lazy mode, value is not stored in memory until it is accessed for the first time via get or component render\nconst state = create(() =\u003e 0)\n```\n\nAnd in async:\n```typescript\n// we can create some initial functions like this\nasync function initialLoad() {\n  return 0\n}\n// immediate mode, so no matter what, this value is already stored in memory\nconst state = create(initialLoad)\n// or\nconst state = create(Promise.resolve(0))\n\n// lazy mode, value is not stored in memory until it is accessed for the first time via get or component render\nconst state = create(() =\u003e Promise.resolve(0))  \n```\n\nAnd when setting state when initial value is promise, set is always sync.\nBut as in react there are two methods how to set a state. Directly `.set(2)` or with a function `.set((prev) =\u003e prev + 1)`.\n\nSo how `set` state will behave with async initial value?\n1. Directly call `.set(2)` will be sync, and will set the value to 2 (it will cancel the initial promise)\n2. Call `.set((prev) =\u003e prev + 1)` will wait until previous promise is resolved, so previous value in set callback is always resolved. \n\n### Async selectors knowledge base\n1. Values in selectors derived from another async selectors will be always sync **(not promise)**\n2. If the selector is async (`state.select(async (s) =\u003e s + 1)`) it will automatically use suspense mode, so on each change it firstly resolved promise (hit suspense) and then update the value.\n3. If the selector is sync, but it's derived from async selector, it will also be in suspense mode - but it depends what the parent is, if the parent is async state, it will hit the suspense only once, on initial load, but if the parent is another async selector, it will hit suspense on each change.\n\n### Debugging\n`Muya` in dev mode automatically connects to the `redux` devtools extension if it is installed in the browser. For now devtool api is simple - state updates.\n\n## 🙏 **Acknowledgments**\n\nThis library is a fun, experimental project and not a replacement for robust state management tools. For more advanced use cases, consider libraries like `Zustand`, `Jotai`, or `Redux`. \nIf you enjoy `Muya`, please give it a ⭐️! :)\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamuelgja%2Fmuya","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsamuelgja%2Fmuya","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamuelgja%2Fmuya/lists"}