{"id":28284718,"url":"https://github.com/danieljharvey/purescript-radox","last_synced_at":"2026-01-29T21:34:34.922Z","repository":{"id":58225476,"uuid":"188874729","full_name":"danieljharvey/purescript-radox","owner":"danieljharvey","description":"Like Redux, For Purescript, But With Less Features","archived":false,"fork":false,"pushed_at":"2019-09-18T11:47:34.000Z","size":25,"stargazers_count":2,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-05-21T17:14:45.576Z","etag":null,"topics":["purescript","purescript-react","state-management","variants"],"latest_commit_sha":null,"homepage":null,"language":"PureScript","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/danieljharvey.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-05-27T16:13:42.000Z","updated_at":"2019-09-18T11:46:43.000Z","dependencies_parsed_at":"2022-08-31T05:01:59.975Z","dependency_job_id":null,"html_url":"https://github.com/danieljharvey/purescript-radox","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danieljharvey%2Fpurescript-radox","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danieljharvey%2Fpurescript-radox/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danieljharvey%2Fpurescript-radox/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danieljharvey%2Fpurescript-radox/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/danieljharvey","download_url":"https://codeload.github.com/danieljharvey/purescript-radox/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danieljharvey%2Fpurescript-radox/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259176114,"owners_count":22817120,"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":["purescript","purescript-react","state-management","variants"],"created_at":"2025-05-21T17:14:43.677Z","updated_at":"2026-01-29T21:34:34.888Z","avatar_url":"https://github.com/danieljharvey.png","language":"PureScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Purescript Radox\n\n## It's Like Redux, But Even Cleaner!\n\nPurescript Radox is a small state management library designed to plug into things like Purescript React. It uses [variants](https://github.com/natefaubion/purescript-variant) to help us keep all our different reducers simple to work with whilst maintaining that sweet type safety.\n\nDocumentation available on [Pursuit](https://pursuit.purescript.org/packages/purescript-radox).\n\n#### OK. That's painfully vague, could I perhaps have an example?\n\nLet's look at a simple implementation with two reducers, to show how it works.\n\n1. Here is our important application state. It tells us about dogs we may or may not have found, and contains a number.\n\n```haskell\nimport Prelude\nimport Radox\n\ndata DogState\n  = NotTried\n  | LookingForADog\n  | FoundADog String\n  | HeavenKnowsI'mMiserableNow\n\ntype State\n  = { value     :: Int\n    , dog       :: DogState\n    , waiting   :: Boolean\n    }\n\ndefaultState :: State\ndefaultState\n  = { value     : 0\n    , dog       : NotTried\n    , waiting   : false\n    }\n```\n\n2. We'd like to be able to change this data in a nice clean way. Here is how we'd express the arbitrary counting that we'd like to do. Hopefully if you've used Elm or Redux this should seem pretty familiar to you.\n\nA `Reducer` function has the type `action -\u003e state -\u003e state`.\n\n```haskell\ndata CountingAction\n  = Up\n  | Down\n\ninstance hasLabelCountingAction :: HasLabel CountingAction \"counting\"\n\ncountingReducer \n  :: Reducer CountingAction State\ncountingReducer action state\n  = case action of\n      Up \n        -\u003e state { value = state.value + 1 }\n      Down \n        -\u003e state { value = state.value - 1 }\n```\n\n(As is hopefully clear, `CountingAction` is a sum type of the different counting-related events that may happen, and `countingReducer` is a function that actions them onto the `State`.)\n\n3. We'd like to be able to change the dog portions of this data. But wait! We also need some effectful things to happen in the Real World when this is happening. Here is how we'd express the dog-based logic changes.\n\n```haskell\ndata Dogs\n  = LoadNewDog\n  | ApologiesThisDogIsTakingSoLong\n  | GotNewDog String\n  | DogError String\n\ninstance hasLabelDogs :: HasLabel Dogs \"dogs\"\n\ndogReducer \n  :: EffectfulReducer Dogs State LiftedAction \ndogReducer { dispatch } action state\n  = case action of\n      LoadNewDog \n        -\u003e UpdateStateAndRunEffect \n                       (state { dog = LookingForADog\n                              , waiting = false \n                              }) \n                       (warnAfterTimeout dispatch)\n\n      ApologiesThisDogIsTakingSoLong\n        -\u003e case state.dog of\n              LookingForADog -\u003e UpdateState $ state { waiting = true }\n              _              -\u003e NoOp\n\n      GotNewDog url\n        -\u003e UpdateState $ state { dog = (FoundADog url) }\n\n      DogError _\n        -\u003e UpdateState $ state { dog = HeavenKnowsI'mMiserableNow } \n\nwarnAfterTimeout\n  :: (LiftedAction -\u003e Effect Unit)\n  -\u003e Aff Unit\nwarnAfterTimeout dispatch = \n  liftEffect $ do\n     let action = dispatch (lift ApologiesThisDogIsTakingSoLong)\n     _ \u003c- setTimeout 200 action\n     pure unit\n```\n\nAn `EffectfulReducer` is similar to `Reducer`, but has the type signature\n`(RadoxEffects state action) -\u003e action -\u003e state -\u003e ReducerReturn`.\n\n`RadoxEffects` is a record containing useful functions to use in reducers, and\nhas the following type:\n\n```haskell\ntype RadoxEffects state action\n  = { dispatch :: (action -\u003e Effect Unit)\n    , getState :: Effect state\n    , state    :: state\n    }\n```\n\nThis means that our effectful reducer is able to inspect the current state at\nany point, and dispatch further actions.\n\nWhat the hell is `ReducerReturn` though? If you've used Purescript Thermite or\nReasonReact this might be familiar to you - it lets us either return some new\nstate, some sort of effect (inside an `Aff`) or a combination of the two.\n\n```haskell\n-- | Type of return value from reducer\ndata ReducerReturn stateType\n  = NoOp\n  | UpdateState stateType\n  | UpdateStateAndRunEffect stateType (Aff Unit)\n  | RunEffect (Aff Unit)\n```\n\nTherefore, we use `NoOp` to do nothing, `UpdateState` to return a new `state`,\n`UpdateStateAndRunEffect` to change the state and do something, or `RunEffect`\nto just do something and leave the state alone. \n\n4. We can now make a combined reducer that works on all of these at once. First we create a type that contains all our action types:\n\n```haskell\ntype AnyAction \n  = Variant ( counting :: Counting\n            , dogs :: Dogs\n            )\n```\n\n(`Variant` comes from the [variant](https://github.com/natefaubion/purescript-variant) package btw)\n\n5. Now we use `match` from `variant` to pipe the actions to the right place:\n\n```haskell\nrootReducer \n  :: CombinedReducer LiftedAction State \nrootReducer dispatch state action' =\n  match\n    { counting: \\action -\u003e \n                    UpdateState $ countReducer action state\n    , dogs:     \\action -\u003e \n                    dogReducer dispatch action state\n    } action'\n```\n\n(Our `CombinedReducer` type must return an `ReducerReturn` type from above, therefore we wrap the output of the regular `Reducer` of `counting` with `UpdateState`, but does not need to for the already wrapped `EffectfulReducer` of `dogs`.)\n\n6. That's all quite nice - but let's actually save the outcome as well. Let's create a Radox store and use it.\n\n```haskell\nimport Effect\nimport Effect.Console (logShow, log)\n\nmakeStore :: Effect Unit\nmakeStore = do\n  radox \u003c- createStore defaultState [log] rootReducer\n  radox.dispatch (lift LoadNewDog)\n  radox.dispatch (lift Up)\n  radox.dispatch (lift (GotNewDog \"dog\"))\n  state \u003c- radox.getState\n  pure unit\n```\n\n#### What should happen then?\n\nWe've created a Radox store, passed it out `defaultState` and `rootReducer` we defined earlier, and it has given us two things:\n\n`radox.dispatch :: action -\u003e Effect Unit` - this takes a lifted action type and applies it to the reducers.\n\n`radox.getState :: Effect state` - this retrieves the state at any time.\n\nBecause we have passed it `log`, each time the state changes it will be logged into the browser console or terminal.\n\n#### What's this `lift` business?\n\nFor us to use our individual action sum types (like `DogAction` or `CountingAction`) with the shared reducer, we have to run `lift` on them (provided by the library) to turn them from normal values into `Variant` values.\n\n#### How could I use this with React or whatever?\n\nI wanted to avoid tying the store itself into a particular library, so a separate library that provides tools to connect this with Context in Purescript React live here as [purescript-react-radox](https://github.com/danieljharvey/purescript-react-radox).\n\n#### How is this cleaner, exactly?\n\nI mean basically it has less features.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanieljharvey%2Fpurescript-radox","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdanieljharvey%2Fpurescript-radox","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanieljharvey%2Fpurescript-radox/lists"}