{"id":13511355,"url":"https://github.com/franciscop/statux","last_synced_at":"2025-04-07T10:23:36.851Z","repository":{"id":57370334,"uuid":"190879437","full_name":"franciscop/statux","owner":"franciscop","description":"⚛️ The easy state management library for React with Hooks","archived":false,"fork":false,"pushed_at":"2024-11-10T07:32:09.000Z","size":1994,"stargazers_count":71,"open_issues_count":1,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-31T05:08:31.538Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://statux.dev","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/franciscop.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","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},"funding":{"custom":"https://www.paypal.me/franciscopresencia/19"}},"created_at":"2019-06-08T11:22:27.000Z","updated_at":"2024-11-10T07:32:13.000Z","dependencies_parsed_at":"2024-08-27T23:25:28.589Z","dependency_job_id":"52152467-bf8f-4b63-891b-dd7a03f02639","html_url":"https://github.com/franciscop/statux","commit_stats":{"total_commits":215,"total_committers":2,"mean_commits":107.5,"dds":"0.16744186046511633","last_synced_commit":"a5b0ba7f6b76a595ffe9fc9f953f2cd28771e719"},"previous_names":[],"tags_count":47,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/franciscop%2Fstatux","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/franciscop%2Fstatux/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/franciscop%2Fstatux/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/franciscop%2Fstatux/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/franciscop","download_url":"https://codeload.github.com/franciscop/statux/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247632719,"owners_count":20970207,"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":[],"created_at":"2024-08-01T03:00:48.166Z","updated_at":"2025-04-07T10:23:36.793Z","avatar_url":"https://github.com/franciscop.png","language":"JavaScript","funding_links":["https://www.paypal.me/franciscopresencia/19"],"categories":["react","List"],"sub_categories":[],"readme":"# Statux [![npm install statux](https://img.shields.io/badge/npm%20install-statux-blue.svg \"install badge\")](https://www.npmjs.com/package/statux) [![test badge](https://github.com/franciscop/statux/workflows/tests/badge.svg \"test badge\")](https://github.com/franciscop/statux/blob/master/.github/workflows/tests.yml) [![gzip size](https://badgen.net/bundlephobia/minzip/statux?label=gzip\u0026color=green \"gzip badge\")](https://github.com/franciscop/statux/blob/master/index.min.js)\n\nThe easy state management library with [React Hooks](#react-hooks) and [immutable state](#truly-immutable):\n\n\u003ca href=\"https://codesandbox.io/s/upbeat-swirles-q972b\"\u003e\u003cimg class=\"full-mobile\" width=\"46%\" align=\"right\" src=\"./assets/profile.webp\" alt=\"User Profile example screenshot\" /\u003e\u003cimg class=\"full-mobile\" width=\"46%\" float=\"left\" src=\"./assets/app.webp\" alt=\"App example screenshot\" /\u003e\u003c/a\u003e\n\nIt allows you to share state across different components of your WebApp with a simple and clean syntax. This [reduces a lot of boilerplate](#direct-manipulation) so you can focus on the actual app that you are building.\n\nJump to docs for [`\u003cStore\u003e`](#store), [`useStore()`](#usestore), [`useSelector()`](#useselector), [`useActions()`](#useactions), [_examples_](#examples).\n\n## Getting started\n\nFirst create a React project (try [Create-React-App](https://github.com/facebook/create-react-app)) and install `statux`:\n\n```\nnpm install statux\n```\n\nThen initialize the store at the App.js level with a couple of initial values:\n\n```js\n// src/App.js\nimport Store from \"statux\"; // This library\nimport Website from \"./Website\"; // Your code\n\n// Initial state is { user: null, books: [] }\nexport default () =\u003e (\n  \u003cStore user={null} books={[]}\u003e\n    \u003cWebsite /\u003e\n  \u003c/Store\u003e\n);\n```\n\nFinally, use and update these values wherever you want:\n\n```js\n// src/User.js\nimport { useStore } from \"statux\";\n\nexport default () =\u003e {\n  const [user, setUser] = useStore(\"user\");\n  const login = () =\u003e setUser({ name: \"Maria\" });\n  return (\n    \u003cdiv\u003eHello {user ? user.name : \u003cbutton onClick={login}\u003eLogin\u003c/button\u003e}\u003c/div\u003e\n  );\n};\n```\n\n## API\n\nThere are four pieces exported from the library:\n\n- [**`\u003cStore\u003e`**](#store): the default export that should wrap your whole App. Its props define the store structure.\n- [**`useStore(selector)`**](#usestore): extracts a part of the store for data retrieval and manipulation. Accepts a parameter to specify what subtree of the state to use.\n- [**`useSelector(selector)`**](#useselector): retrieve a specific part of the store state based on the selector or the whole state if none was given.\n- [**`useActions(selector)`**](#useactions): generate actions to modify the state while avoiding mutations. Includes default actions and can be extended.\n\n### \\\u003cStore\u003e\n\nThis should wrap your whole project, ideally in `src/App.js` or similar. You define the structure of all of your state within the `\u003cStore\u003e`:\n\n```js\n// src/App.js\nimport Store from \"statux\";\nimport Navigation from \"./Navigation\";\n\n// state = { id: null, friends: [] }\nexport default () =\u003e (\n  \u003cStore id={null} friends={[]}\u003e\n    \u003cNavigation /\u003e\n  \u003c/Store\u003e\n);\n```\n\nWhen your state starts to grow - but not before - it is recommended to split it into a separated variable for clarity:\n\n```js\n// src/App.js\nimport Store from \"statux\";\nimport Navigation from \"./Navigation\";\n\nconst initialState = {\n  id: null,\n  friends: [],\n  // ...\n};\n\nexport default () =\u003e (\n  \u003cStore {...initialState}\u003e\n    \u003cNavigation /\u003e\n  \u003c/Store\u003e\n);\n```\n\nThat's all you need to know for creating your state. When your app starts to grow, best-practices of redux like normalizing your state are recommended.\n\n### useStore()\n\nThis is a [React hook](https://reactjs.org/docs/hooks-overview.html) to handle a state subtree. It accepts **a string selector** and returns an array similar to [React's `useState()`](https://reactjs.org/docs/hooks-state.html):\n\n```js\nimport { useStore } from \"statux\";\n\nexport default () =\u003e {\n  const [user, setUser] = useStore(\"user\");\n  return (\n    \u003cdiv onClick={(e) =\u003e setUser({ name: \"Maria\" })}\u003e\n      {user ? user.name : \"Anonymous\"}\n    \u003c/div\u003e\n  );\n};\n```\n\nYou can access deeper items and properties within your state through the selector:\n\n```js\nimport { useStore } from \"statux\";\n\nexport default () =\u003e {\n  // If `user` is null, this will throw an error\n  const [name = \"Anonymous\", setName] = useStore(\"user.name\");\n  return \u003cdiv onClick={(e) =\u003e setName(\"John\")}\u003e{name}\u003c/div\u003e;\n};\n```\n\nIt accepts a _string_ selector that will find the corresponding state subtree, and also return a modifier for that subtree. `useStore()` behaves as the string selector for `useSelector()` and `useActions()` together:\n\n```js\nconst [user, setUser] = useStore(\"user\");\n// Same as\nconst user = useSelector(\"user\");\nconst setUser = useActions(\"user\");\n```\n\n\u003e Note: useStore() **only** accepts either a string selector or no selector at all; it **does not** accept ~~functions~~ or ~~objects~~ as parameters.\n\nThe first returned parameter is the frozen selected state subtree, and the second parameter is the setter. This setter is quite flexible:\n\n```js\n// Plain object to update it\nsetUser({ ...user, name: \"Francisco\" });\n\n// Function that accepts the current user\nsetUser((user) =\u003e ({ ...user, name: \"Francisco\" }));\n\n// Modify only the specified props\nsetUser.assign({ name: \"Francisco\" });\n```\n\nSee the details and list of helpers on [the `useActions()` section](#useactions).\n\n### useSelector()\n\nThis React hook retrieves a frozen (read-only) fragment of the state:\n\n```js\nimport { useSelector } from \"statux\";\n\nexport default () =\u003e {\n  const user = useSelector(\"user\");\n  return \u003cdiv\u003e{user ? user.name : \"Anonymous\"}\u003c/div\u003e;\n};\n```\n\nYou can access deeper objects with the dot selector, which works both on objects and array indexes:\n\n```js\nimport { useStore } from \"statux\";\n\nexport default () =\u003e {\n  const title = useSelector(\"books.0.title\");\n  const name = useSelector(\"user.name\");\n  return (\n    \u003cdiv\u003e\n      {title} - by {name}\n    \u003c/div\u003e\n  );\n};\n```\n\nIt accepts both a _string selector_ and a _function selector_ to find the state that we want:\n\n```js\nconst user = useSelector(\"user\");\nconst user = useSelector(({ user }) =\u003e user);\nconst user = useSelector((state) =\u003e state.user);\n```\n\nYou can dig for nested state, but if any of the intermediate trees is missing then it will fail:\n\n```js\n// Requires `user` to be an object\nconst name = useSelector(\"user.name\");\n\n// Can accept no user at all:\nconst user = useSelector(({ user }) =\u003e (user ? user.name : \"Anonymous\"));\n\n// This will dig the array friends -\u003e 0\nconst bestFriend = useSelector(\"friends.0\");\n```\n\n### useActions()\n\nThis React hook is used to modify the state in some way. Pass a selector to specify what state fragment to modify:\n\n```js\nconst setState = useActions();\nconst setUser = useActions('user');\nconst setName = useActions('user.name');\n\n// Update in multiple ways\nsetName('Francisco');\nsetName(name =\u003e 'San ' + name);\nsetName((name, key, state) =\u003e { ... });\n```\n\nThese actions must be executed within the appropriate callback:\n\n```js\nimport { useActions } from \"statux\";\nimport Form from \"your-form-library\";\n\nconst ChangeName = () =\u003e {\n  const setName = useActions(\"user.name\");\n  const onSubmit = ({ name }) =\u003e setName(name);\n  return \u003cForm onSubmit={onSubmit}\u003e...\u003c/Form\u003e;\n};\n```\n\nThere are several helper methods. These are based on/inspired by the array and object prototype linked in their names:\n\n- [`fill()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill) (_array_): replace all items by the specified one.\n- [`pop()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/pop) (_array_): remove the last item.\n- [`push()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push) (_array_): append an item to the end.\n- [`reverse()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse) (_array_): invert the order of the items.\n- [`shift()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift) (_array_): remove the first item.\n- [`sort()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) (_array_): change the item order according to the passed function.\n- [`splice()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice) (_array_): modify the items in varied ways.\n- [`unshift()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift) (_array_): prepend an item to the beginning.\n- [`append()`]() (_array_): add an item to the end (alias of `push()`).\n- [`prepend()`]() (_array_): add an item to the beginning (alias of `unshift()`).\n- [`remove()`]() (_array_): remove an item by its index.\n- [`assign()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) (_object_): add new properties as specified in the argument.\n- `remove()` (_object_): remove the specified property.\n- `extend()` (_object_): add new properties as specified in the passed object (alias of `assign()`).\n\nSee them in action:\n\n```js\n// For the state of: books = ['a', 'b', 'c']\nconst { fill, pop, push, ...setBooks } = useActions(\"books\");\n\nfill(1); // [1, 1, 1]\npop(); // ['a', 'b']\npush(\"d\"); // ['a', 'b', 'c', 'd']\nsetBooks.reverse(); // ['c', 'b', 'a']\nsetBooks.shift(); // ['b', 'c']\nsetBooks.sort(); // ['a', 'b', 'c']\nsetBooks.splice(1, 1, \"x\"); // ['a', 'x', 'c']\nsetBooks.unshift(\"x\"); // ['x', 'a', 'b', 'c']\n\n// Aliases\nsetBooks.append(\"x\"); // ['a', 'b', 'c', 'x']\nsetBooks.prepend(\"x\"); // ['x', 'a', 'b', 'c']\nsetBooks.remove(1); // ['a', 'c']\n\n// These are immutable, but this still helps:\nsetBooks.concat(\"d\", \"e\"); // ['a', 'b', 'c', 'd', 'e']\nsetBooks.slice(1, 1); // ['b']\nsetBooks.filter((item) =\u003e /^(a|b)$/.test(item)); // ['a', 'b']\nsetBooks.map((book) =\u003e book + \"!\"); // ['a!', 'b!', 'c!']\nsetBooks.reduce((all, book) =\u003e [...all, book + \"x\"], []); // ['ax', 'bx', 'cx']\nsetBooks.reduceRight((all, book) =\u003e [...all, book], []); // ['c', 'b', 'a']\n\n// For the state of: user = { id: 1, name: 'John' }\nconst setUser = useActions(\"user\");\nsetUser((user) =\u003e ({ ...user, name: \"Sarah\" })); // { id: 1, name: 'Sarah' }\n\nsetUser.assign({ name: \"Sarah\" }); // { id: 1, name: 'Sarah' }\nsetUser.extend({ name: \"Sarah\" }); // { id: 1, name: 'Sarah' }\nsetUser.remove(\"name\"); // { id: 1 }\n```\n\nThese methods can be extracted right in the actions or used as a method:\n\n```js\nconst BookForm = () =\u003e {\n  const setBooks = useActions(\"books\");\n  const onSubmit = (book) =\u003e setBooks.append(book);\n  // OR\n  const { append } = useActions(\"books\");\n  const onSubmit = (book) =\u003e append(book);\n\n  return \u003cForm onSubmit={onSubmit}\u003e...\u003c/Form\u003e;\n};\n```\n\n## Examples\n\nSome examples to show how _statux_ works. Feel free to [suggest new ones](https://github.com/franciscop/statux/issues/new?template=suggest-example.md).\n\n### Todo list\n\nA TODO list in 30 lines ([**see codesandbox**](https://codesandbox.io/s/elegant-tdd-c8jlq)):\n\n![TODO List](./assets/todo.jpg \"todo example screenshot\")\n\n```js\n// App.js\nexport default () =\u003e (\n  \u003cStore todo={[]}\u003e\n    \u003ch1\u003eTODO List:\u003c/h1\u003e\n    \u003cTodoList /\u003e\n  \u003c/Store\u003e\n);\n```\n\n```js\n// TodoList.js\nimport { useStore } from \"statux\";\nimport Form from \"form-mate\";\n\nfunction TodoItem({ index }) {\n  const [item, setItem] = useStore(`todo.${index}`);\n  return (\n    \u003cli onClick={() =\u003e setItem.assign({ done: !item.done })}\u003e\n      {item.done ? \u003cstrike\u003e{item.text}\u003c/strike\u003e : item.text}\n    \u003c/li\u003e\n  );\n}\n\nexport default function TodoList() {\n  const [todo, { append }] = useStore(\"todo\");\n  return (\n    \u003cul\u003e\n      {todo.map((item, i) =\u003e (\n        \u003cTodoItem key={item.text} index={i} /\u003e\n      ))}\n      \u003cli\u003e\n        \u003cForm onSubmit={append} autoReset\u003e\n          \u003cinput name=\"text\" placeholder=\"Add item\" /\u003e\n          \u003cbutton\u003eAdd\u003c/button\u003e\n        \u003c/Form\u003e\n      \u003c/li\u003e\n    \u003c/ul\u003e\n  );\n}\n```\n\n### Initial data loading\n\nLoad a pokemon list with graphics from an API ([**see codesandbox**](https://codesandbox.io/s/elastic-glitter-crofz)):\n\n![Pokemon List](./assets/pokemon.jpg \"pokemon list example screenshot\")\n\n```js\n// src/App.js\nimport Store from \"statux\";\nimport PokemonList from \"./PokemonList\";\n\nexport default () =\u003e (\n  \u003cStore pokemon={[]}\u003e\n    \u003ch1\u003eThe Best 151:\u003c/h1\u003e\n    \u003cPokemonList /\u003e\n  \u003c/Store\u003e\n);\n```\n\n```js\n// src/PokemonList.js\nimport { useStore } from \"statux\";\nimport { useEffect } from \"react\";\nimport styled from \"styled-components\";\n\nconst url = \"https://pokeapi.co/api/v2/pokemon/?limit=151\";\nconst catchAll = () =\u003e\n  fetch(url)\n    .then((r) =\u003e r.json())\n    .then((r) =\u003e r.results);\n\nconst Pokemon = styled.div`...`;\nconst Label = styled.div`...`;\n\nexport default () =\u003e {\n  const [pokemon, setPokemon] = useStore(\"pokemon\");\n  useEffect(() =\u003e {\n    catchAll().then(setPokemon);\n  }, [setPokemon]);\n  if (!pokemon.length) return \"Loading...\";\n  return pokemon.map((poke, i) =\u003e (\n    \u003cPokemon key={i} id={i + 1}\u003e\n      \u003cLabel\u003e{poke.name}\u003c/Label\u003e\n    \u003c/Pokemon\u003e\n  ));\n};\n```\n\n### API calls\n\nYou already saw how to make initial calls on load [in the previous example]().\n\nNow let's see how to make API calls to respond to a user action, in this case when the user submits the Login form:\n\n```js\n// LoginForm.js\nimport { useActions } from \"statux\";\nimport axios from \"axios\";\nimport Form from \"form-mate\";\n\nexport default () =\u003e {\n  const setUser = useActions(\"user\");\n  const onSubmit = async (data) =\u003e {\n    const { data } = await axios.post(\"/login\", data);\n    setUser(data);\n  };\n  return (\n    \u003cForm onSubmit={onSubmit}\u003e\n      \u003cinput type=\"text\" name=\"email\" placeholder=\"Email\" /\u003e\n      \u003cinput type=\"password\" name=\"password\" placeholder=\"Password\" /\u003e\n      \u003cbutton\u003eLogin\u003c/button\u003e\n    \u003c/Form\u003e\n  );\n};\n```\n\n\u003e The libraries [`axios`](https://www.npmjs.com/package/axios) and [`form-mate`](https://www.npmjs.com/package/form-mate) that we are using here are not needed, but they do make our lifes easier.\n\n### With localStorage\n\nLet's say we want to keep all of our small WebApp state in localStorage, we can do that as well:\n\n```js\nimport Store, { useSelector } from \"statux\";\n\n// Define the initial state as an object:\nconst todo = JSON.parse(localStorage.todo || \"[]\");\n\n// Listen for changes on the state and save it in localStorage:\nconst LocalStorage = () =\u003e {\n  const todo = useSelector(\"todo\");\n  localStorage.todo = JSON.stringify(todo);\n  return null;\n};\n\nexport default () =\u003e (\n  \u003cStore todo={todo}\u003e\n    \u003cLocalStorage /\u003e\n    ...\n  \u003c/Store\u003e\n);\n```\n\nThis can be applied to Dark Mode as well, since localStorage is sync we can read it before running any React to avoid flashing a white screen first:\n\n```js\nimport Store, { useSelector } from \"statux\";\n\n// Define the initial state as an object:\nconst dark = localStorage.dark === \"true\";\n\n// Save this state fragment when it changes:\nconst LocalStorage = () =\u003e {\n  localStorage.dark = useSelector(\"dark\");\n  return null;\n};\n\nexport default () =\u003e (\n  \u003cStore dark={dark}\u003e\n    \u003cLocalStorage /\u003e\n    ...\n  \u003c/Store\u003e\n);\n```\n\n### Reset initial state\n\nTo reset the initial state we should first keep it separated, and then trigger a reset from the root state ([**see codesandbox**](https://codesandbox.io/s/elastic-haslett-njqjr)):\n\n```js\nimport Store, { useActions, useStore } from \"statux\";\n\n// Define the initial state as an object\nconst init = { user: null, todo: [] };\n\n// We then trigger a useActions without any selector\nconst ResetState = () =\u003e {\n  const setState = useActions();\n  const reset = () =\u003e setState(init);\n  return \u003cbutton onClick={reset}\u003eClear\u003c/button\u003e;\n};\n\nconst Login = () =\u003e {\n  const [user, setUser] = useStore(\"user\");\n  const login = () =\u003e setUser(\"Mike\");\n  if (user) return \u003cp\u003eHi {user}\u003c/p\u003e;\n  return (\n    \u003cp\u003e\n      \u003cbutton onClick={login}\u003eLogin\u003c/button\u003e\n    \u003c/p\u003e\n  );\n};\n\nexport default () =\u003e (\n  \u003cStore {...init}\u003e\n    \u003cResetState /\u003e\n    \u003cLogin /\u003e\n  \u003c/Store\u003e\n);\n```\n\n## Motivation\n\nWhy did I create Statux instead of using useState+useContext() or Redux? I built a library that sits between the simple but local React Hooks and the solid but complex full Flux architecture. There are few reasons that you might care about:\n\n### React Hooks\n\nWhen there's a major shift on a technology it's a good chance to reevaluate our choices. And React Hooks is no different, our components are now cleaner and the code is easier to reuse than ever.\n\nSo I wanted a _minimal_ library that follows React Hooks' pattern of accessing and writing state, but on an app-level instead of a component-level. I tried with Context for a while, but found that you have to create many contexts to avoid some issues ([by design](https://github.com/facebook/react/issues/15156#issuecomment-474590693)) and found that too cumbersome. I just wanted `useState`, but globally.\n\nSo here it is, now you can use `useStore()` as a global `useState()`. I've followed Hooks' syntax where possible, with differences only when needed e.g. not initial state on a component-level since that's global:\n\n```js\nconst [user, setUser] = useState(null); // React Hooks\nconst [user, setUser] = useStore(\"user\"); // Statux\n```\n\n### Direct manipulation\n\nWith Statux you directly define the state you want on your actions. You remove [a full layer of indirection](https://twitter.com/dan_abramov/status/802564042648944642) by **not** following the [Flux architecture](https://www.youtube.com/watch?v=nYkdrAPrdcw).\n\nThis removes a lot of boilerplate commonly seen on apps that use Redux. Where many would define the reducers, actions, action creators, thunk action creators, etc. with Statux you change your state directly:\n\n```js\nexport default function UserProfile() {\n  const [user, setUser] = useStore(\"user\");\n  if (!user) {\n    const login = () =\u003e setUser(\"Mike\");\n    return \u003cbutton onClick={login}\u003eLogin\u003c/button\u003e;\n  }\n  return user;\n}\n```\n\nThis has a disadvantage for very large and complex apps (100+ components) where the coupling of state and actions make changes in the state structure around twice as hard. But if [you are following this Redux antipattern](https://rangle.slides.com/yazanalaboudi/deck) you might not really need Redux, so give Statux a try and it _will_ simplify your code.\n\n### Truly immutable\n\nThe whole state is [frozen with `Object.freeze()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) so no accidental mutation can drive subtle bugs and stale state. Try mutating the state of your app for testing ([**see demo**](https://codesandbox.io/s/gallant-firefly-59684)):\n\n```js\nconst App = () =\u003e {\n  const [user] = useStore(\"user\");\n  // TypeError - can't define property \"name\"; Object is not extensible\n  user.name = \"John\";\n  return \u003cdiv\u003e{user.name}\u003c/div\u003e;\n};\n```\n\nThis will avoid whole categories of bugs for newbies working on your team and experienced devs as well:\n\n- `arr.sort((a, b) =\u003e {...}).map()` is also mutating the original array.\n- `setValue(value++)` will mutate the original value.\n\nWhen you try to mutate the state directly it will throw a TypeError. Instead, try defining a new variable if you indeed want to read it with a default:\n\n```js\nconst App = () =\u003e {\n  const [user] = useStore(\"user\");\n  const name = user.name || \"John\";\n  return \u003cdiv\u003e{name}\u003c/div\u003e;\n};\n```\n\nOr directly access the name with the correct selector and a default if you know `user` is defined:\n\n```js\nconst App = () =\u003e {\n  const [name = \"John\"] = useStore(\"user.name\");\n  return \u003cdiv\u003e{name}\u003c/div\u003e;\n};\n```\n\nStatux also provides some helpers for modifying the state easily:\n\n```js\n// Set the name of the user\nconst onClick = (name) =\u003e setUser({ ...user, name });\nconst onClick = (name) =\u003e setUser((user) =\u003e ({ ...user, name }));\nconst onClick = (name) =\u003e setUser.assign({ name });\n\n// Add a book to the list\nconst onSubmit = (book) =\u003e setBooks([...books, book]);\nconst onSubmit = (book) =\u003e setBooks((books) =\u003e [...books, book]);\nconst onSubmit = (book) =\u003e setBooks.append(book);\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffranciscop%2Fstatux","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffranciscop%2Fstatux","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffranciscop%2Fstatux/lists"}