{"id":21669370,"url":"https://github.com/attack-monkey/hypereduce","last_synced_at":"2026-05-08T05:06:11.380Z","repository":{"id":36922177,"uuid":"225216393","full_name":"attack-monkey/hypereduce","owner":"attack-monkey","description":"State management without the boilerplate","archived":false,"fork":false,"pushed_at":"2022-03-25T20:46:39.000Z","size":154,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-02-28T19:12:20.526Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/attack-monkey.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-12-01T19:21:54.000Z","updated_at":"2020-07-26T20:45:43.000Z","dependencies_parsed_at":"2022-08-08T18:16:53.882Z","dependency_job_id":null,"html_url":"https://github.com/attack-monkey/hypereduce","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/attack-monkey%2Fhypereduce","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/attack-monkey%2Fhypereduce/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/attack-monkey%2Fhypereduce/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/attack-monkey%2Fhypereduce/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/attack-monkey","download_url":"https://codeload.github.com/attack-monkey/hypereduce/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244570280,"owners_count":20474024,"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-11-25T12:21:29.475Z","updated_at":"2026-05-08T05:06:11.326Z","avatar_url":"https://github.com/attack-monkey.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# hypeReduce 3.0\n\n**hypeReduce is a simple yet powerful State-Management Library for JS / TS - Browser \u0026 Node**\n\n----\n\n## The Concept\n\nIn hypeReduce everything is managed by dispatching **actions**, \nwhich are just pure data objects that look like\n\n    {\n      type: 'MY_ACTION',\n      payload: 'Do cool stuff'\n    }\n\n**Actions** are responsible for updating application state **using pure functions**.\n\n## Install\n\n```\n\nnpm i hypereduce\n\n```\n\nhypeReduce is broken into 2 main apis; state and route.\n\nThe state.api manages state\n\nThe route api manages url routes and interacts with the state.api\n\n```javascript\n\n// API's\n\nimport { d, dispatch, manageState } from 'hypereduce/fns/state.api'     // For state management\nimport { goto, manageRoutes } from 'hypereduce/fns/route.api'           // For route-handling\n\n...\n\n```\n\n## An intro to State Management\n\nLet's say our application state looks like...\n\n    {\n      counter: 1\n    }\n\nIn order for hypeReduce to manage it, we wrap it in the manageState function...\n\n```javascript\n\nmanageState({\n  counter: 1\n})\n\n```\n\nhypeReduce lets you turn static state into dynamic state by swapping out values for dynamic-nodes. \nWe'll swap the value of 1 for a dynamic-node...\n\n```javascript\n\nmanageState({\n  counter: d()\n})\n\n```\n\nAnd give it an initial value of 1...\n\n```javascript\n\nmanageState({\n  counter: d({\n    init: 1\n  })\n})\n\n```\n\nAnd tell it which actions to respond to in order to produce new state\n\n```javascript\n\n// The following increment function takes in state and action and returns\n// either state + action.payload, or if action.payload isn't provided... state + 1\nconst INC = (state, action) =\u003e state + (action.payload || 1)\n\n// We can register the INC function on a dynamic-node.\n// Now the node is 'listening' for actions of type 'INC'.\nmanageState({\n  counter: d({\n    init: 1,\n    actions: {\n      INC\n    }\n  })\n})\n\n```\n\nTo get the state of the counter out into our app somewhere, we first annotate it with **connects**...\n\n```javascript\n\nconst INC = (state, action) =\u003e state + (action.payload || 1)\n\nmanageState({\n  counter: d({\n    connects: ['counter'],\n    init: 1,\n    actions: {\n      INC\n    }\n  })\n})\n\n```\n\nAnd then register corresponding connects to it from within the app...\n\n```javascript\n\nconst INC = (state, action) =\u003e state + (action.payload || 1)\n\nmanageState({\n  counter: d({\n    connects: ['counter'],\n    init: 1,\n    actions: {\n      INC\n    }\n  })\n})\n\nconnect('counter', _ =\u003e console.log(`Got a new value ${_}`))\n\n```\n\nNow we can dispatch actions and trigger the connection to fire with the updated value...\n\n```javascript\n\nconst INC = (state, action) =\u003e state + (action.payload || 1)\n\nmanageState({\n  counter: d({\n    connects: ['counter'],\n    init: 1,\n    actions: {\n      INC\n    }\n  })\n})\n\nconnect('counter', _ =\u003e display(`Got a new value ${_}`)) // Got a new value 1\ndispatch({ type: 'INC' }) // Got a new value 2\ndispatch({ type: 'INC', payload: 2 }) // Got a new value 4\n\n```\n\nAt the moment though, if we had two nodes listening for `INC` then both nodes would increment.\nTo make actions more targeted, use the node's name as an id that can be targeted...\n\n```javascript\n\nconst INC =\n  (id) =\u003e\n    (state, action) =\u003e\n      id === action.payload.id\n        ? state + (action.payload.by || 1)\n        : state\n\nmanageState({\n  counter: d({\n    connects: ['counter'],\n    init: 1,\n    actions: {\n      INC: INC('counter')\n    }\n  })\n})\n\nconnect('counter', _ =\u003e display(`Got a new value ${_}`)) // Got a new value 1\ndispatch({ type: 'INC', payload: { id: 'counter' } }) // Got a new value 2\ndispatch({ type: 'INC', payload: { id: 'counter', by: 2 } }) // Got a new value 4\n\n```\n\n## $ Wildcards\n\nIf you want a node to respond to ALL actions that it recieves - use the `$` wildcard.\n\n```javascript\n\nconst INC = state =\u003e state + 1\n\nmanageState({\n  counter: d({\n    connections: ['counter'],\n    init: 1,\n    $: INC('counter')\n  })\n})\n\nconnect('counter', _ =\u003e display(`Got a new value ${_}`))\ndispatch({ type: 'ANY_ACTION' }) // Got a new value 2\ndispatch({ type: 'AND_ANOTHER' }) // Got a new value 3\n\n```\n\n## _ Passdowns\n\nLet's say you have a dynamic-node that is an object. \nAnd let's say you want to be able to respond to actions at the full object level, \nbut also at lower-down nodes...\n\nNo problem - use the `_` passdown.\n\n```javascript\n\n\nmanageState({\n  field1: d({\n    connects: ['field1'],\n    init: { text: 'hello', enabled: true },\n    actions: {\n      DISABLE_FIELD1,\n      ENABLE_FIELD1\n    },\n    _: {\n      text: d({\n        actions: {\n          SET_TEXT_FIELD_ONE\n        }\n      }),\n      enabled: d({}) // we can just set this to an empty object to pass back the current state\n    }\n  })\n})\n\n```\n\n## And Remember ... State is composable\n\n```javascript\n\nconst field1 = d({\n  connects: ['field1'],\n  init: { text: 'hello', enabled: true },\n  actions: {\n    DISABLE_FIELD1,\n    ENABLE_FIELD1\n  },\n  _: {\n    text: d({\n      actions: {\n        SET_TEXT_FIELD_ONE\n      }\n    }),\n    enabled: d({})\n  }\n})\n\nconst field2 = ...\n\nmanageState({\n  view1: {\n    field1,\n    field2\n  }\n})\n\n```\n\n## Type-Safety with Typescript\n\nHere we are explicitly stating that field 1 is an object with keys `text` and `enabled`.\n`text` is either a string or a _dynamic-state-node_ of type string.\n`enabled` is either a boolean or a _dynamic-state-node_ of type boolean.\n\nWe can also specify the type that we expect the `connect`ion to get within the app.\n\n```typescript\n\n  manageState({\n    field1: d\u003c{text: (string | DStateNode\u003cstring\u003e), enabled: (boolean | DStateNode\u003cboolean\u003e) }\u003e({\n      connects: ['field1'],\n      init: { text: 'hello', enabled: true },\n      actions: {\n        DISABLE_FIELD1,\n        ENABLE_FIELD1\n      },\n      _: {\n        text: d\u003cstring\u003e({\n          actions: {\n            SET_TEXT_FIELD_ONE\n          }\n        }),\n        enabled: d({}) // we can just set this to an empty object to pass back the current state,\n      }\n    })\n  })\n\n  connect\u003c{text: string, enabled: boolean }\u003e('field1', _ =\u003e display(`GOT VALUE OF ${pretty(_)}`))\n  dispatch({ type: 'SET_FIELD1', payload: 'YO!' })\n  dispatch({ type: 'DISABLE_FIELD1' })\n\n```\n\n## Connecting to React\n\nIn a state directory...\n\n```typescript\n\n\nimport { d, manageState, getConnections, connect, disconnect } from 'hypereduce/lib/fns/state.api'\nimport { useState, useEffect } from 'react'\n\nconst INC =\n  (id: string) =\u003e\n    (state: number, action: { type: string, payload: { id: string, by: number }}) =\u003e\n      id === action.payload.id\n        ? state + (action.payload.by || 10)\n        : state\n\n// useConnect for REACT + HypeReduce\n\nexport const useConnect = (connectKey: string) =\u003e {\n  const [state, setState] = useState(getConnections(connectKey))\n  useEffect(() =\u003e\n    connect(connectKey, newValue =\u003e {\n      setState(newValue)\n      return disconnect(connectKey)\n    })\n  )\n  return state\n}\n\n// HypeReduce State\n\nexport const hypeReduce = () =\u003e manageState({\n  count: d\u003cnumber\u003e({\n    init: 3,\n    connects: [ 'count' ],\n    actions: {\n      INC: INC('count')\n    }\n  })\n})\n\n```\n\nConsider wrapping dispatches into simple messages...\n\n```typescript\n\nimport { dispatch } from \"hypereduce/lib/fns/state.api\";\n\nexport const INC_COUNT = () =\u003e dispatch({ type: 'INC', payload: { id: 'count' } })\n\n```\n\nThen in your components...\n\n```typescript\n\nimport { hypeReduce, useConnect } from '../state'\nimport { INC_COUNT } from '../state/messages'\n\nhypeReduce()\n\nexport default function Home() {\n  const count = useConnect('count') // This syncs to the HypeReduce 'connect' labelled 'count'\n  return \u003cdiv id=\"app\"\u003e\n    \u003ch1\u003eCounter\u003c/h1\u003e\n    \u003ch1\u003e{count}\u003c/h1\u003e\n    \u003cbutton onClick={INC_COUNT}\u003e\n      Click me\n    \u003c/button\u003e\n  \u003c/div\u003e\n}\n\n\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fattack-monkey%2Fhypereduce","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fattack-monkey%2Fhypereduce","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fattack-monkey%2Fhypereduce/lists"}