{"id":15627718,"url":"https://github.com/fabienjuif/myrtille","last_synced_at":"2025-04-28T19:32:51.045Z","repository":{"id":34923804,"uuid":"190903026","full_name":"fabienjuif/myrtille","owner":"fabienjuif","description":"An immutable (but feeling mutable) one-way state manager without reducers","archived":false,"fork":false,"pushed_at":"2023-03-11T15:33:19.000Z","size":860,"stargazers_count":4,"open_issues_count":9,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-25T06:07:34.458Z","etag":null,"topics":["dispatch","event","immutable","not-redux","oneway","reaction","state","state-management","store"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/fabienjuif.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":"2019-06-08T15:19:43.000Z","updated_at":"2022-05-05T09:48:14.000Z","dependencies_parsed_at":"2024-09-18T16:34:30.200Z","dependency_job_id":"2107be07-eb37-448c-bd13-9d909493907a","html_url":"https://github.com/fabienjuif/myrtille","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fabienjuif%2Fmyrtille","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fabienjuif%2Fmyrtille/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fabienjuif%2Fmyrtille/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fabienjuif%2Fmyrtille/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fabienjuif","download_url":"https://codeload.github.com/fabienjuif/myrtille/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251375365,"owners_count":21579433,"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":["dispatch","event","immutable","not-redux","oneway","reaction","state","state-management","store"],"created_at":"2024-10-03T10:19:01.022Z","updated_at":"2025-04-28T19:32:51.009Z","avatar_url":"https://github.com/fabienjuif.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# myrtille\n\u003e An immutable (but feeling mutable) one-way state manager without reducers\n\n[![npm](https://img.shields.io/npm/v/@myrtille/core.svg)](https://www.npmjs.com/package/@myrtille/core) [![npm bundle size](https://img.shields.io/bundlephobia/minzip/@myrtille/core.svg)](https://bundlephobia.com/result?p=@myrtille/core@latest) [![CircleCI](https://img.shields.io/circleci/build/github/fabienjuif/myrtille.svg)](https://app.circleci.com/pipelines/github/fabienjuif/myrtille?branch=master) [![Coveralls github](https://img.shields.io/coveralls/github/fabienjuif/myrtille.svg)](https://coveralls.io/github/fabienjuif/myrtille)\n\n# Features\n- 🔄 One-way state manager: your store is the single source of truth\n- 💎 Immutable but feels mutable\n- ⛏️ Hackable\n- 💡 Based on events (actions)\n- 📖 Compatible with redux-devtools\n\n# Inspirations\n - https://github.com/rakunteam/k-ramel\n - https://github.com/jamiebuilds/bey\n\n# The goal\nThe goal is to have a simple state manager, without being strictely related to single view library (like React or Vue).\nThe Redux pattern, having a one-way state management, is a good pattern but Redux suffers from boilerplate, and once you add middleware you don't have clear separation of concerns.\nMyrtille tries to fix these drawbacks by using [immer](https://github.com/immerjs/immer) under the hood and by merging \"reducers\" and \"reactions\" into one place: listeners.\n\nWe also want to make sure your UI component tree is optimized and only refreshes when needed, that's why you can give a `path` to subscribers, you can make sure your component tree will be refreshed only when this path updates.\n\nOne of the last goals that Myrtille aims at is to let the developper doing whatever he wants with this library, that's why the store is always given and usable inside your callbacks, hack-it!\n\n# Installation\n- `npm install --save @myrtille/core`\n- `yarn add @myrtille/core`\n- `pnpm install --save @myrtille/core`\n\n# Packages\n\n| package name | description | size |\n|--|--|--|\n| `@myrtille/core` | base package, contains the createStore **without** the `mutate` function | ![npm bundle size](https://img.shields.io/bundlephobia/minzip/@myrtille/core.svg) | \n| `@myrtille/mutate` | contains the createStore **WITH** the `mutate` function, powered by immer | ![npm bundle size](https://img.shields.io/bundlephobia/minzip/@myrtille/mutate.svg) | \n| `@myrtille/react` | React.js bindings (works with core or mutate) | ![npm bundle size](https://img.shields.io/bundlephobia/minzip/@myrtille/react.svg) | \n\n# API\n- `createStore(initialState: Object) -\u003e Store`\n  * create a store with the given initial state\n  * eg: `const store = createStore({ todos: [] })`\n- `Store.setState(newState: Object) -\u003e void`\n  * set state to the given one and triggers listeners\n- `Store.getState() -\u003e State`\n  * get the current state\n- `Store.mutate((state: State) -\u003e void): void` (only available with `@myrtille/mutate` version)\n  * register a mutation, the `currentState` given in callback HAVE TO be mutated, it is [myrtille](https://github.com/fabienjuif/myrtille) (via [immer](https://github.com/immerjs/immer)) that makes sure this is done in an immutable way!\n  * eg: `store.mutate(state =\u003e { state.todos.push({ id: 2, label: 'new' }) })`\n- `Store.dispatch(action: String | Object) -\u003e void`\n  * dispatch an action that listeners can register on. If the action is a string, the `action` is created by [myrtille](https://github.com/fabienjuif/myrtille) to follow the standard rule: `{ type: $yourString }`\n  * eg: `store.dispatch('FETCH_TODOS')`\n  * eg: `store.dispatch({ type: 'ADD_TODO', payload: { id: 2, label: 'new' }})`\n- `Store.addListener(action: String | Action | Function, reaction: Function((store: Store, action: Action) -\u003e void) | void) -\u003e Function`\n  * add a listener to the store, a listener **listen** to an action, when this `action` is dispatched, the registered `reaction` (which is a callback) is called.\n  * you can set your `reaction` at first argument, in which it will be called for every actions dispatched.\n  * you can play with the store in the given reaction (dispatch new action, register mutations, etc.)\n  * calling the returned function will remove your reaction\n  * eg: [take a look at listeners examples](#listeners-examples)\n- `Store.subscribe(path: String | Function, callback: Function(store: Store, oldState: State, action: Action) | void) -\u003e Function`\n  * subcribe to state mutations at given `path`. The registered `callback` is called whenever the store was mutated at given `path`.\n  * you can set your `callback` at first argument, in which it will be called for every dispatch (even if there is no mutation)\n  * if you set path to `''` (empty string),  the callback will be called only for root mutations\n  * you can play with the store in the given callback (dispatch new action, register mutations, etc.) but prefer using listeners and dispatching actions.\n  * calling the returned function will unsubscribe your callback\n- `Store.contexts: Object`\n  * used to retrieve some informations from your callbacks given to `addListener` and `subscribe`\n  * eg: `store.contexts.firebase = require('firebase/app')`\n\n# Listeners examples\n**Async and mutation**\n- Listen to an action that looks like `{ type: 'FETCH_TODOS' }`\n- Fetch todos\n- Set todos in store\n```js\nconst store = createStore({ todos: [] })\nstore.subscribe((store) =\u003e { console.log('new state!', store.getState()) })\n\n// the listener to focus on\nstore.addListener('FETCH_TODOS', async (store) =\u003e {\n  const todos = await (await fetch('https://my-api/todos')).json()\n  store.mutate(state =\u003e {\n    state.todos = todos\n  })\n})\n\n// dispatch the listened action\nstore.dispatch('FETCH_TODOS')\n```\n\n**retrieve action informations**\n- Listen to `ADD_TODO` action (that can be dispatched by a click from the UI)\n- Add a new todo with information given in the action's payload\n```js\nconst store = createStore({ todos: [] })\nstore.subscribe((store) =\u003e { console.log('new state!', store.getState()) })\n\n// the listener to focus on\nstore.addListener('ADD_TODO', (store, action) =\u003e {\n  store.mutate(state =\u003e {\n    state.todos.push(action.payload)\n  })\n})\n\n// dispatch the listened action\nstore.dispatch({ type: 'ADD_TODO', payload: { id: 2, label: 'new' } })\n```\n\n**dispatch in a listener**\n- Listen to `@@ui/CLEAR_TODOS` (that can be dispatched by a click from the UI)\n- React by calling `CLEAR_TODOS` if todos are not already empty\n```js\nconst store = createStore({ todos: [{ id: 1, label: 'finish the documentation' }] })\nstore.addListener((store, oldState, action) =\u003e { console.log('new action is dispatched!', action) })\nstore.addListener('CLEAR_TODOS', store =\u003e { store.mutate(state =\u003e { state.todos = [] }) })\n\n// the listener to focus on\nstore.addListener({ type: '@@ui/CLEAR_TODOS' }, store =\u003e {\n  if (store.getState().todos \u0026\u0026 store.getState().todos.length \u003e 0) {\n    store.dispatch('CLEAR_TODOS')\n  }\n})\n\n// dispatch the listened action\nstore.dispatch('@@ui/CLEAR_TODOS')\n```\n\n# Bindings\n## React \u003cimg width=30 src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/640px-React-icon.svg.png\" /\u003e\nPlease look at [this documentation](https://github.com/fabienjuif/myrtille/tree/master/packages/react)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffabienjuif%2Fmyrtille","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffabienjuif%2Fmyrtille","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffabienjuif%2Fmyrtille/lists"}