{"id":21669339,"url":"https://github.com/attack-monkey/react-replay","last_synced_at":"2026-05-09T01:36:00.899Z","repository":{"id":91874070,"uuid":"188303329","full_name":"attack-monkey/react-replay","owner":"attack-monkey","description":"A wrapper around React that keeps the view auto rendered + routes + redux-like state-management","archived":false,"fork":false,"pushed_at":"2019-10-04T21:15:37.000Z","size":222,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-03-20T07:30:39.624Z","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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-05-23T20:40:07.000Z","updated_at":"2019-10-04T21:15:38.000Z","dependencies_parsed_at":null,"dependency_job_id":"81b955f3-61a0-4ac7-8af5-af5fd3f7bc80","html_url":"https://github.com/attack-monkey/react-replay","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/attack-monkey/react-replay","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/attack-monkey%2Freact-replay","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/attack-monkey%2Freact-replay/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/attack-monkey%2Freact-replay/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/attack-monkey%2Freact-replay/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/attack-monkey","download_url":"https://codeload.github.com/attack-monkey/react-replay/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/attack-monkey%2Freact-replay/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32804212,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-08T08:22:46.396Z","status":"ssl_error","status_checked_at":"2026-05-08T08:22:45.650Z","response_time":54,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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:06.984Z","updated_at":"2026-05-09T01:36:00.891Z","avatar_url":"https://github.com/attack-monkey.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# react-replay\n\nreact-replay is a light wrapper around React that provides:\n\n- auto-rerender\n- routing\n- redux-like state management\n\nReplay is an event-loop driven application.  \nWhen an action is dispatched the whole application state is re-calculated.  \nWhen state changes from the previous state, React's diffing algorithm is utilised.  \nOnly the parts of the DOM that change are updated - resulting in an efficient re-render.\n\nAs a result, the entire application can be made from Functional / Stateless Components.\n\n## Install\n\n**The fastest way to get a project going is to install from seed using douglas. douglas installs the project and downloads all the dependencies.**\n\nInstall from seed project:\n\n```\n\nnpx douglas get react-replay-seed\n\n```\n\n\u003e Note that the seed project uses [parcel](https://parceljs.org/) for it's javascript bundling. \n\u003e To install parcel globally - `npm i -g parcel-bundler`\n\u003e Once installed - `npm start` to start the dev server.\n\nInstall from typescript seed project: \n\n```\n\nnpx douglas get react-replay-seed-for-ts\n\n```\n\nJust the package:\n\n```\n\nnpm i react-replay\n\n```\n\n## Basics\n\nreact-replay is a lightweight frontend similar to React + Redux, but with only the functional parts.\n\nThere are 3 main parts to React-Replay:\n\n- State\n- Components\n- Reducers\n\n**Components** are almost always just functional / stateless components, and take in **State** to render a view.  \nEvents such as `onClick` trigger **Actions** to be dispatched to the Root **Reducer**' which update **State**.  \nWhenever the **State** is updated, the view is rerendered.\n\n### Components\n\nThis means much of React can be left behind in favour of simple functional components.\n\neg.\n\n```jsx\n\n// jsx\n\nexport const MyComponent = ({ state }) =\u003e (\n  \u003cdiv\u003e\n    \u003ch1\u003e{ state.greeting }\u003c/h1\u003e\n    \u003cChildComponent state={ state }\u003e\u003c/ChildComponent\u003e\n  \u003c/div\u003e\n) \n\n```\n\nThe above is a stateless / functional component. It's essentially just a function that takes in props;  \nin this case `state`; and returns the component. Notice how we've also got a child component that we're also passing state into.\n\n## Dispatching Actions\n\nUse `dispatch` to dispatch one or more actions.\nAn action is just a simple object that gets passed into your 'masterReducer' function to re-create state.\n\nAfter each dispatch the view is rerendered automatically unless:\n\n- You dispatch an action with the rerender field set to false eg. `{ type: 'MY_ACTION', rerender: false }`\n- Or you dispatch multiple actions in the one dispatch. All actions except the last will have `rerender: false` applied to them automatically, so that only the last action results in a rerender. \n\nFor example in the below snippet, the rerender will wait for ACTION_2 \n\n```javascript\n\ndispatch(\n  { type: 'ACTION_1' },\n  { type: 'ACTION_2' }\n)\n\n```\n\nExample of dispatch applied to an `onClick`\n\n```jsx\n\nimport { dispatch } from '../node_modules/react-replay/src'\n\nexport const MyComponent = ({ state }) =\u003e (\n  \u003cdiv\u003e\n    \u003ch1\u003e{ state.greeting }\u003c/h1\u003e\n    \u003cChildComponent state={ state }\u003e\u003c/ChildComponent\u003e\n    \u003cbutton onClick={ \n      () =\u003e dispatch({ type: 'CHANGE_GREETING', to: 'Hello World'}) \n    }\u003epush me\u003c/button\u003e\n  \u003c/div\u003e\n) \n\n```\n\nBased on the above component expecting `state.greeting` and the action being of type 'CHANGE_GREETING',\nlet's take a look at the corresponding Reducer.\n\n## Reducers\n\nYour master or root reducer is a function that resolves to a state object.\n\nIt accepts an action, along with the current state and produces a new state object\n\nIn it's simplist terms it looks like...\n\n```javascript\n\nexport const reducer = ({ state, action }) =\u003e ({\n  greeting: 'hello world'\n})\n\n```\n\nThis however would always produce a new state of \n\n```javascript\n\n{\n  greeting: 'hello world'\n}\n\n```\n\nTo make it so that it takes in an action to produce a new state, we create smaller reducer functions that do the heavy lifting...\n\n```javascript\n\nimport { greetingReducer } from '...'\n\nexport const reducer = ({ state, action }) =\u003e ({\n  greeting: greetingReducer(action, state.greeting))\n})\n\n```\n\n```javascript\n\n/* greetingReducer\n * ---------------\n * Accepts actions such as { type: 'CHANGE_GREETING', to: '...' }\n *\n */\n\nexport const greetingReducer = (action, greetingState = 'Hello World') =\u003e\n  action.type === 'CHANGE_GREETING'\n    ? action.to\n    : greetingState\n\n```\n\nWhen the `greetingReducer` gets an action that it cares about, it computes a new state, otherwise it just returns the current state.  \nNote that it also has a default state set to 'Hello World'.\n\n### Goto\n\nThe last part to React-Replay is the routing...\n\nTo change routes just use `goto`\n\n```jsx\n\nimport { goto } from '../node_modules/react-replay/src'\n\nexport const MyComponent = ({ state }) =\u003e (\n  \u003cdiv\u003e\n    \u003ch1\u003e{ state.greeting }\u003c/h1\u003e\n    \u003cChildComponent state={ state }\u003e\u003c/ChildComponent\u003e\n    \u003cbutton onClick={ \n      () =\u003e goto('/animals/cats?cat=charlie') \n    }\u003epush me\u003c/button\u003e\n  \u003c/div\u003e\n) \n\n```\n\nClicking on the button will change the route (and url) and also change `state.route` to \n\n```javascript\n\n{\n  segments: [\n    '',\n    'animals',\n    'cats'\n  ],\n  queryString: {\n    cat: 'charlie'\n  }\n}\n\n```\n\n\u003e Note that hashes can also be used in routes and simply form part of the segments (as though the hash didn't exist).\n\n## Putting it altogether\n\nTo bootstrap the application we wrap the First (outer most) Component, along with the root reducer and a mount (The DOM node the app should go into).\n\n```javascript\n\nimport { app } from '../node_modules/react-replay/src'\nimport { FirstComponent } from '...'\nimport { reducer } from '...'\n\nconst mount = document.getElementById('app')\napp(FirstComponent, reducer, mount)\n\n```\n\nOptionally you can also add an initial state object at this app bootstrapping stage...\n\n```javascript\n\nimport { app } from '../node_modules/react-replay/src'\nimport { FirstComponent } from '...'\nimport { reducer } from '...'\n\nconst initState = { greeting: 'Yo Sup' }\nconst mount = document.getElementById('app')\napp(FirstComponent, reducer, mount, initState)\n\n```\n\n## How State Change and Rerender work tldr;\n\nWhenever an action is dispatched, the current application state is retrieved, and both the state and the action are passed through the main reducer (Redux pattern). Even if an asynchronous activity triggered a separate process, once dispatch is called, the **current application state** is retrieved. At the end of the new state recalculation, this new state becomes the  **current application state**. Even if `{ rerender: false }` is used in the action - the current application state is still updated. This keeps the application in sync - even across asynchronous activity.\n\nThe rerender process only kicks in if the new state is different from the old state AND if `{ rerender: false }` was not passed into the action. Only then will the diffing algorithm kick in to update only the DOM nodes that require change.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fattack-monkey%2Freact-replay","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fattack-monkey%2Freact-replay","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fattack-monkey%2Freact-replay/lists"}