{"id":20849831,"url":"https://github.com/garbles/concave","last_synced_at":"2025-05-12T04:30:56.929Z","repository":{"id":45094373,"uuid":"432919625","full_name":"garbles/concave","owner":"garbles","description":"🧐 Lens-like state management (for React).","archived":false,"fork":false,"pushed_at":"2022-01-19T03:38:35.000Z","size":754,"stargazers_count":13,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-29T10:49:07.208Z","etag":null,"topics":["lenses","optics","react","react-hooks","state","state-management"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/garbles.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}},"created_at":"2021-11-29T05:44:38.000Z","updated_at":"2023-06-14T00:16:11.000Z","dependencies_parsed_at":"2022-09-26T17:30:34.029Z","dependency_job_id":null,"html_url":"https://github.com/garbles/concave","commit_stats":null,"previous_names":[],"tags_count":36,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/garbles%2Fconcave","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/garbles%2Fconcave/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/garbles%2Fconcave/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/garbles%2Fconcave/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/garbles","download_url":"https://codeload.github.com/garbles/concave/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253391502,"owners_count":21900952,"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":["lenses","optics","react","react-hooks","state","state-management"],"created_at":"2024-11-18T03:07:01.850Z","updated_at":"2025-05-12T04:30:56.601Z","avatar_url":"https://github.com/garbles.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./image.png\" /\u003e\n\u003c/p\u003e\n\nLens-like state management (for React).\n\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n\n- [Overview](#overview)\n  - [Why use Concave?](#why-use-concave)\n  - [Create your lens](#create-your-lens)\n  - [Build your application](#build-your-application)\n- [Thinking in lenses (for React developers)](#thinking-in-lenses-for-react-developers)\n  - [From Selectors](#from-selectors)\n  - [Now kiss!](#now-kiss)\n  - [Looking recursively](#looking-recursively)\n- [Installation](#installation)\n- [API](#api)\n  - [createLens](#createlens)\n  - [Lens](#lens)\n  - [Lens.getStore(): Direct access to the store](#lensgetstore-direct-access-to-the-store)\n  - [Lens.use(): Hook into a React component](#lensuse-hook-into-a-react-component)\n  - [Should use() re-render?](#should-use-re-render)\n  - [Lens.$key: A unique key for the `Lens\u003cA\u003e`](#lenskey-a-unique-key-for-the-lensa)\n  - [Store](#store)\n  - [connection](#connection)\n  - [Connection](#connection)\n  - [useCreateLens](#usecreatelens)\n- [Use without TypeScript](#use-without-typescript)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n## Overview\n\nConcave is not a general purpose state management library. It is intended for highly interactive UIs where the shape of the state is recursive and/or closely reflects the shape of the UI. Specifically, Concave is an strong candidate for page/form/diagram builder-type applications (written in React).\n\n### Why use Concave?\n\n1. Excellent for handling recursive application states.\n2. Use it where you need it. Not an all or nothing solution.\n3. Minimalistic and intuitive API.\n\n### Create your lens\n\n```ts\n// lens.ts\n\nimport { createLens } from \"concave\";\n\nexport type Todo = {\n  description: string;\n  completed: boolean;\n};\n\nexport type State = {\n  todos: Todo[];\n};\n\nconst initialAppState: State = {\n  todos: [],\n};\n\nexport const lens = createLens\u003cState\u003e(initialAppState);\n```\n\n### Build your application\n\n```tsx\n// index.tsx\n\nimport ReactDOM from \"react\";\nimport { lens } from \"./lens\";\nimport { App } from \"./components/App\";\n\n/**\n * Retreive the underlying store.\n */\nconst store = lens.getStore();\n\n/**\n * Subscribe to state updates.\n */\nstore.subscribe(() =\u003e {\n  const currentState = store.getSnapshot();\n\n  /**\n   * Do something with the `currentState`.\n   */\n});\n\nconst root = ReactDOM.createRoot(document.querySelector(\"#root\"));\nroot.render(\u003cApp state={lens} /\u003e);\n```\n\n```tsx\n// components/App.tsx\n\nimport { Lens } from \"concave\";\nimport { State } from \"../lens\";\nimport { NewTodoForm } from \"./NewTodoForm\";\nimport { Todo } from \"./Todo\";\n\ntype Props = {\n  state: Lens\u003cState\u003e;\n};\n\n/**\n * Fully memoize the component because `Lens\u003cState\u003e` is static and will never change.\n */\nexport const App = React.memo((props: Props) =\u003e {\n  /**\n   * `lens.use()` is a React hook that integrates the underlying\n   * store into the component life cycle.\n   *\n   * It takes a \"should update?\" argument that decides whether the hook should\n   * trigger a re-render. In this case, we render when the length of todos changes\n   * or any todo.completed is toggled.\n   */\n  const [todos, updateTodos] = props.state.todos.use({ completed: true });\n\n  const incomplete = todos.filter((todo) =\u003e !todo.completed);\n  const complete = todos.filter((todo) =\u003e todo.completed);\n\n  return (\n    \u003c\u003e\n      {/* When creating a new TODO, append it to the list of existing todos. */}\n      \u003cNewTodoForm onCreate={(todo) =\u003e updateTodos((prev) =\u003e [...prev, todo])} /\u003e\n      {incomplete.map((todo) =\u003e {\n        /**\n         * Tranform data back into `Lens\u003cTodo\u003e`.\n         */\n        const lens = todo.toLens();\n\n        /**\n         * Render using the unique `lens.$key` as the key.\n         */\n        return \u003cTodo state={lens} key={lens.$key} /\u003e;\n      })}\n      {complete.map((todo) =\u003e {\n        const lens = todo.toLens();\n        return \u003cTodo state={lens} key={lens.$key} /\u003e;\n      })}\n    \u003c/\u003e\n  );\n});\n```\n\n```tsx\n// components/Todo.tsx\n\nimport { Lens } from \"concave\";\nimport type { Todo } from \"../lens\";\n\ntype Props = {\n  state: Lens\u003cTodo\u003e;\n};\n\n/**\n * Fully memoize the component because `Lens\u003cTodo\u003e` is static and will never change.\n */\nexport const Todo = React.memo((props: Props) =\u003e {\n  const [todo, setTodo] = props.state.use();\n\n  /**\n   * Render the Todo.\n   */\n});\n```\n\n## Thinking in lenses (for React developers)\n\n### From Selectors\n\nIf you have built React applications with Redux then you are probably familiar with [selectors](https://redux.js.org/usage/deriving-data-selectors). A Redux selector is a \"getter\" from the monolithic application state meant to obfuscate the shape of that state from the rest of the application. Used correctly, they are a good application of the [Law of Demeter](https://en.wikipedia.org/wiki/Law_of_Demeter).\n\n```ts\nimport { State, User } from \"./state\";\n\n/**\n * Get `User` off of the global `State`\n */\nexport const getUser = (state: State): User =\u003e state.user;\n\n/**\n * Get `name` off the `User`\n */\nexport const getUserName = (state: State) =\u003e getUser(state).name;\n```\n\nThe second \"getter\", `getUserName`, is a \"refinement\" on `getUser`. It gives us a way to write `getUserName` in terms of the _entire_ application state without revealing it. That is, `getUserName` only needs to know the shape of `User`, while `getUser` can get it from the parent. And so on...\n\nIn Redux, state updates occur through dispatching actions. Lets consider how could look with explicit \"setters\" (... \"setlectors\"? :trollface:).\n\n```ts\n/**\n * Set `user` on the global `State`.\n */\nexport const setUser = (state: State, user: User) =\u003e {\n  return {\n    ...state,\n    user,\n  };\n};\n\n/**\n * Set `name` on `user` which in turn will set `user` on the global `State`.\n */\nexport const setUserName = (state: State, name: string) =\u003e {\n  const user = getUser(state);\n\n  return setUser(state, {\n    ...user,\n    name,\n  });\n};\n```\n\nAgain, notice how the second \"setter\" relies on the first: `setUserName` is a \"refinement\" of `setUser`. Once more, `setUserName` can rely on `getUser` and `setUser` in order to get and set the user on the global state without revealing it.\n\n### Now kiss!\n\nIn the most basic sense, a lens is just a getter and setter pair where their refinements are explicitly coupled to each other. When we define a way to get the user's name, lets also define the way to set it. Starting from the global state, each refinement _focuses_ in on a smaller piece of data—which is why they are called lenses.\n\nLets start by writing a basic lens for the entire state.\n\n```ts\nconst stateLens: BasicLens\u003cState, State\u003e = {\n  get(state: State): State {\n    return state;\n  },\n\n  set(prev: State, next: State): State {\n    return next;\n  },\n};\n```\n\nThis is the identity equivalent for a lens and not interesting, but now lets refine the lens for the user.\n\n```ts\nconst userLens: BasicLens\u003cState, User\u003e = {\n  get(state: State): User {\n    return stateLens.get(state).user;\n  },\n\n  set(state: State, next: User): State {\n    const prev = stateLens.get(state);\n\n    return stateLens.set(state, {\n      ...prev,\n      user,\n    });\n  },\n};\n```\n\nAnd finally for the user name.\n\n```ts\nconst userNameLens: BasicLens\u003cState, string\u003e = {\n  get(state: State) {\n    return userLens.get(state).name;\n  },\n\n  set(state: State, name: string): State {\n    const user = userLens.get(state);\n\n    return userLens.set(state, {\n      ...user,\n      name,\n    });\n  },\n};\n```\n\nThese look nearly identical to the getter/setter examples at the beginning of this section except they are defacto paired together. Again, each refinement focuses more and more on a smaller piece of data. Despite that, they are always rooted in terms of the global `State`.\n\n```ts\nconst globalState: State = {\n  /* ... */\n};\n\n/**\n * Retrieve the user name using the global `State`.\n */\nconst userName = userNameLens.get(globalState);\n\n// ...\n\n/**\n * Set a new user name in terms of the global `State`.\n */\nconst nextGlobalState = userNameLens.set(globalState, \"Gabey Baby\");\n```\n\nYou may have noticed that it is probably common to make `keyof` refinements and so we can just write a helper function to do this.\n\n```ts\ndeclare function prop\u003cState, Refinement, Key extends keyof Refinement\u003e(\n  lens: BasicLens\u003cState, Refinement\u003e,\n  key: Key\n): BasicLens\u003cState, Refinement[Key]\u003e;\n```\n\nAnd so instead, you might say,\n\n```ts\nconst userLens = prop(stateLens, \"user\");\nconst userNameLens = prop(userLens, \"name\");\n```\n\n### Looking recursively\n\nLenses start to become particularly useful in situations where both the UI and application state are recursive. Builder-type applications\noften have sections inside of sections inside of sections with arbitrary contents and their data is represented as such. Using Redux to maintain this kind of state will often devolve into coming up with some kind of weird scheme where we keep track of the key path and pass it as an argument to the action so that the reducer can walk the state and find the piece of data that you actually meant to update. By pairing the data getter with a corresponding setter, these kinds of updates become trivial.\n\n## Installation\n\nThis library uses `useSyncExternalStore` (introduced in React 18). If you want to use Concave with a version of React older than 18, you must also [install a shim](https://github.com/reactwg/react-18/discussions/86).\n\n```bash\nnpm install concave use-sync-external-store\n```\n\n## API\n\n### createLens\n\n`createLens\u003cS\u003e(initialState: S): Lens\u003cS\u003e`\n\nCreates a store with state `S` and wraps it in a `Lens\u003cS\u003e` which is returned. To create a `Lens\u003cS\u003e` inside of a React component, use `useCreateLens` (see below).\n\n```ts\nimport { createLens } from \"concave\";\nimport { State, initialState } from \"./state\";\n\nexport const lens = createLens\u003cState\u003e(initialState);\n```\n\n### Lens\n\n```ts\ntype Lens\u003cA\u003e = {\n  getStore(): Store\u003cA\u003e;\n  use(shouldUpdate?: ShouldUpdate): [ProxyValue\u003cA\u003e, Update\u003cA\u003e];\n  $key: string;\n};\n```\n\nA stateless [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) around some data `A`. Inherits all\n_own keys_ that the underlying object/array would have.\n\nFor example,\n\n```ts\ntype Account = {\n  name: string;\n  email: string;\n};\n\ntype User = {\n  id: string;\n  account: Account;\n};\n\ntype State = {\n  user: User;\n};\n\nlet lens: Lens\u003cState\u003e;\n\n// ...\n\nconst userLens: Lens\u003cUser\u003e = lens.user;\nconst accountLens: Lens\u003cAccount\u003e = userLens.account;\nconst emailLens: Lens\u003cstring\u003e = accountLens.email;\n```\n\nLenses are cached and static from the time they are first accessed. `lens.user.account` will always _be_ the same `Lens\u003cAccount\u003e`.\n\n:warning: If a React component only accepts a `Lens\u003cAccount\u003e` as props then it can be fully memoized with `React.memo`.\n\n### Lens.getStore(): Direct access to the store\n\n```ts\ndeclare function getStore(): Store\u003cA\u003e;\n```\n\nEvery `Lens\u003cA\u003e` exposes a `getStore()` method that returns the underlying `Store\u003cA\u003e` (see below). With this you can access the current state of the store for `A`, as well as subscribe to and push updates.\n\n```ts\nlet accountLens: Lens\u003cAccount\u003e;\n\nconst accountStore: Store\u003cAccount\u003e = accountLens.getStore();\n\n/**\n * Subscribe to all updates that may be relevant to `Lens\u003cAccount\u003e`.\n */\nconst unsubscribe = accountStore.subscribe(() =\u003e {\n  const currentAccount = accountStore.getSnapshot();\n\n  /**\n   * Do something with `currentAccount`.\n   */\n});\n\n// ...\n\nlet email: string;\n\n/**\n * Update email.\n */\naccountStore.setSnapshot({\n  ...accountStore.getSnapshot(),\n  email,\n});\n```\n\n### Lens.use(): Hook into a React component\n\n```ts\ndeclare function use(shouldUpdate?: ShouldUpdate): [ProxyValue\u003cA\u003e, Update\u003cA\u003e]`\n```\n\nA React hook that wraps `getStore()` into the component lifecycle and returns a tuple similar to `React.useState`.\n\nThe first value, `ProxyValue\u003cA\u003e`, is a Proxy around some state `A`.\n\n```ts\ntype ProxyValue\u003cA\u003e = { [K in keyof A]: ProxyValue\u003cA[K]\u003e } \u0026 { toLens(): Lens\u003cA\u003e };\n```\n\nIt applies recursively, so accessing properties of a `ProxyValue\u003cA\u003e` will return another `ProxyValue\u003cA[keyof A\u003e` **unless it is a primitive value** :warning:. That is,\n\n```ts\nlet lens: Lens\u003cState\u003e;\n\nconst App = () =\u003e {\n  const [state, updateState] = lens.use();\n\n  /**\n   * `state.user.account` is a `ProxyValue\u003cAccount\u003e`.\n   */\n  const accountLens = state.user.account.toLens();\n\n  /**\n   * Error! `.toLens()` is not defined on primitive values.\n   */\n  state.user.account.email.toLens();\n\n  // ...\n};\n```\n\nCalling `toLens()` will return the same `Lens\u003cA\u003e` as if you had just traversed the lens.\n\n```ts\nlet lens: Lens\u003cState\u003e;\n\nconst App = () =\u003e {\n  const [state, updateState] = lens.use();\n\n  /**\n   * These are the same. `Object.is(accountLens1, accountLens2) === true`.\n   */\n  const accountLens1 = state.user.account.toLens();\n  const accountLens2 = lens.user.account;\n\n  // ...\n};\n```\n\nThe second value in the `use()` tuple, `Update\u003cA\u003e`, is a function that takes a callback where the current store value is passed as an argument and expects to return the next value.\n\n```ts\nlet lens: Lens\u003cState\u003e;\n\nconst App = () =\u003e {\n  const [account, updateAccount] = lens.user.account.use();\n\n  // ...\n\n  updateAccount((currentAccount) =\u003e {\n    return {\n      ...currentAccount,\n      email: \"neato@example.com\",\n    };\n  });\n\n  // ...\n};\n```\n\n### Should use() re-render?\n\nWhether it's iterating an array or switching on a [discriminated union](https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#union-exhaustiveness-checking), you will need to call `Lens.use()` in order to access the underlying data and decide _what_ to render. The \"should update\" argument is an optional way to decide whether `Lens.use()` _should_ re-render. Specifically, it provides a convenient way to define the data dependencies for the component—not unlike the dependency array for `useEffect`, `useCallback`, etc.\n\nThe default behavior of `Lens.use()` is to render when the next value is no longer _strictly equal_ to the previous one. However, this is not sufficient for a deeply recursive component tree. And the closer the `Lens.use()` call is to the root state, the more likely an update will fail the strictly equal check because changes cascade up the lens' inner traversal.\n\nFor example, a component relying on `lens.element.use()` that does not define a \"should update\" argument would trigger a re-render when another component updates from `lens.element.data.children[1].data.placeholder.use()`. Therefore, if the component only actually relied on the `status` property of the `element`, it could be rewritten as `lens.element.use({ status: true })` and all other changes would be ignored.\n\nThe following is a list of ways to define \"should update\",\n\n1. `true`: Noop. Will inherit the default behavior.\n2. `false`: Will never re-render.\n3. `(prev: A, next: A) =\u003e boolean`: Similar to React's `shouldComponentUpdate`.\n4. `(keyof A)[]`: Will only render when any of the listed keys change.\n5. `{ [K in keyof A]: ShouldUpdate\u003cA[K]\u003e }`: Will recursively apply these rules to values and ignore any keys that are not provided.\n\nHere are some examples of how you can define \"should update\",\n\n```ts\n/**\n * Render when the account object changes.\n */\nlens.user.account.use(true);\n\n/**\n * Never re-render.\n */\nlens.user.account.use(false);\n\n/**\n * Render _only_ when the account.email changes.\n */\nlens.user.account.use({ email: true });\n\n/**\n * Render _only_ when the next email value is longer than the one\n * that was previously rendered.\n */\nlens.user.account.use({ email: (prev, next) =\u003e next.length \u003e prev.length });\n\n/**\n * Functionally equivalent to `false`. Never re-render.\n */\nlens.user.account.use({});\n\n/**\n * Render _only_ when the account.name changes.\n */\nlens.user.account.use([\"name\"]);\n\n/**\n * Render _only_ when the account.name or account.email changes.\n */\nlens.user.account.use([\"name\", \"email\"]);\n\n/**\n * Render _only_ when the user.account.name changes. Note this is different than\n * the above as it is the lens for the entire User and not just the Account.\n */\nlens.user.use({ account: [\"name\"] });\n```\n\n:warning: **For arrays, when defining \"should update\" as an array of keys or an object (4 or 5 above), the library assumes that you also mean to trigger a render when the length of the array changes. Additionally, when specifying properties on an array, it is assumed that you mean to target all of the members of the array. You therefore do not need to traverse the keys of the array (the indices) and instead you define keys of the individual members.** :warning:\n\nFor example,\n\n```ts\ntype State = {\n  todos: Array\u003c{\n    completed: boolean;\n    description: string;\n    // ...\n  }\u003e;\n};\n\nlet lens: Lens\u003cState\u003e;\n\n/**\n * Render _only_ when the length of `todos` has changed and/or _any_ of\n * the todos' `completed` is toggled.\n */\nlens.todos.use({ completed: true });\n\n/**\n * Render _only_ when the length has changed _or_ any of the todos' `description` has changed.\n */\nlens.todos.use([\"description\"]);\n\n/**\n * Render _only_ when the length has changed.\n */\nlens.todos.use([]);\nlens.todos.use({});\nlens.todos.use((prev, next) =\u003e prev.length !== next.length);\n```\n\n### Lens.$key: A unique key for the `Lens\u003cA\u003e`\n\nA unique key for the `Lens\u003cA\u003e` (Just matches the traversal path.) `lens.user.account.email.$key === \"root.user.account.email\"`. Meant to be used when React requires a key.\n\n```tsx\nexport const TodoList = () =\u003e {\n  const [todos] = todoLens.use();\n\n  return \u003c\u003e\n    {todos.map((todo) =\u003e {\n      const lens = todo.toLens();\n\n      return \u003cTodo state={lens} key={lens.$key} /\u003e\n    })}\n  \u003c\u003e\n}\n```\n\n### Store\n\n```ts\ntype Store\u003cA\u003e = {\n  getSnapshot(): A;\n  setSnapshot(next: A): void;\n  subscribe(listener: Listener): Unsubscribe;\n};\n\ntype Listener = () =\u003e void;\ntype Unsubscribe = () =\u003e void;\n```\n\nReturned by `lens.getStore()`. Used to make imperative operations easy to do. Get and set the data directly as well as subscribe to updates.\n\nStores are used by [`connection`](#connection) to allow for complex async behaviors directly in the lens. As such, calling `getSnapshot()` on a store belonging to a connection before it has received at least one value will throw a Promise.\n\n### connection\n\n```ts\ndeclare function connection\u003cA, I\u003e(create: (store: Store\u003cA\u003e, input: I) =\u003e Unsubscribe | void): Connection\u003cA, I\u003e;\n```\n\nA connection takes a `create` callback that receives a [`Store\u003cA\u003e`](#store) and some input `I`. Connections can be embedded inside a monolithic `Lens\u003cS\u003e` and follow the protocol for [React Suspense](https://reactjs.org/docs/concurrent-mode-suspense.html)—they throw a Promise if data is not yet present—so that async data fetching can be written as if it is synchronous.\n\n```ts\nconst timer = connection\u003cnumber, { startTime: number; interval: number }\u003e((store, input) =\u003e {\n  /**\n   * This store is identical to any other store.\n   */\n  store.setSnapshot(input.startTime);\n\n  const intervalId = setInterval(() =\u003e {\n    const prev = store.getSnapshot();\n    store.setSnapshot(prev + 1);\n  }, input.interval);\n\n  return () =\u003e {\n    clearInterval(intervalId);\n  };\n});\n\nconst state = {\n  // ...\n  count: timer,\n};\n\nexport const lens = createLens(state);\n```\n\nAnd then in a component, the connection can be collapsed into a lens given some input.\n\n```tsx\ntype Props = {\n  state: Lens\u003c{ count: Connection\u003cnumber, number\u003e }\u003e;\n};\n\nconst App = (props: Props) =\u003e {\n  /**\n   * As part of the lens, `count` is a function that takes the input for the connection\n   * and returns a new lens.\n   */\n  const [count, updateCount] = props.state.count({ startTime: 10, interval: 1000 }).use();\n\n  // ...\n};\n```\n\n`count` and `updateCount` work exactly as they would for any other lens, meaning that you could, for example, subtract 20 seconds off of the timer by calling `updateCount(prev =\u003e prev - 20)`.\n\nConnections only automatically share state _if_ they exist at the same key path _and_ have the same input. The input is serialized as a key with `JSON.stringify`, so changing the order of keys or including extraneous values will create a new cached value.\n\nFurthermore, `connection` can store any value. Walking that value, even if there is no data yet, happens with the `Lens` as if the data is already present.\n\n```tsx\ntype Props = {\n  /**\n   * Imagine we have a lens with the following\n   */\n  state: Lens\u003c{\n    me: {\n      /**\n       * This `Connection` does not have a second type variable because it doesn't take any input\n       */\n      profile: Connection\u003c{\n        account: {\n          emailPreferences: {\n            subscribed: boolean;\n          };\n        };\n      }\u003e;\n    };\n  }\u003e;\n};\n\nexport const EmailPreferencesApp = (props: Props) =\u003e {\n  const [subscribed, setSubscribed] = props.state.me.profile().account.emailPreferences.subscribed.use();\n\n  // ...\n};\n```\n\nThis component `EmailPreferencesApp` will be suspended until the profile connection has resolved a value. The connection itself might look something like this.\n\n```ts\ntype Profile = {\n  account: {\n    emailPreferences: {\n      subscribed: boolean;\n    };\n  };\n};\n\nconst profile = connection\u003cProfile, void\u003e((store) =\u003e {\n  fetch(\"/profile\")\n    .then((resp) =\u003e resp.json())\n    .then((data) =\u003e {\n      store.setSnapshot(data);\n    });\n});\n```\n\n:warning: Calling `.use()` on a connection—for example, `props.state.me.profile.use()` will return the raw `Connection` which you should not attempt to replace or write to. It is a special object with magical powers, so just don't. :warning:\n\n### Connection\n\n```ts\ntype Connection\u003cA, I = void\u003e = {};\n```\n\nThe type returned by `connection`. It is useless outside of the library internals, but necessary for typing your state/lens.\n\n- `A`: The data kept inside of the store.\n- `I`: The input data provided to the store.\n\n### useCreateLens\n\n```ts\ndeclare function useCreateLens\u003cA\u003e(initialState: S | (() =\u003e S)): Lens\u003cS\u003e;\n```\n\nA convenience wrapper that memoizes a call to `createLens`. If passed a function, it will call it once when creating the `Lens\u003cS\u003e`.\n\n## Use without TypeScript\n\nThis library relies heavily on the meta-programming capabilities afforded by TypeScript + Proxy. I really do not recommend using this without TypeScript. It's 2021. Why aren't you writing TypeScript, bud?\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgarbles%2Fconcave","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgarbles%2Fconcave","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgarbles%2Fconcave/lists"}