{"id":13450496,"url":"https://github.com/inmagik/react-rocketjump","last_synced_at":"2025-07-16T10:37:04.024Z","repository":{"id":34880296,"uuid":"186616186","full_name":"inmagik/react-rocketjump","owner":"inmagik","description":"Rocketjump your react! Manage state and side effects like a breeze","archived":false,"fork":false,"pushed_at":"2023-03-04T03:44:15.000Z","size":10379,"stargazers_count":6,"open_issues_count":51,"forks_count":4,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-01T10:01:43.310Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://inmagik.github.io/react-rocketjump","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/inmagik.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"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}},"created_at":"2019-05-14T12:21:12.000Z","updated_at":"2024-09-15T12:36:19.000Z","dependencies_parsed_at":"2024-04-11T21:48:53.688Z","dependency_job_id":"7db04511-e6c0-4271-afad-4923cb70b988","html_url":"https://github.com/inmagik/react-rocketjump","commit_stats":{"total_commits":464,"total_committers":5,"mean_commits":92.8,"dds":"0.15732758620689657","last_synced_commit":"30420b2c81ffcb01595afef54fb9b655a1c90aa2"},"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inmagik%2Freact-rocketjump","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inmagik%2Freact-rocketjump/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inmagik%2Freact-rocketjump/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inmagik%2Freact-rocketjump/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/inmagik","download_url":"https://codeload.github.com/inmagik/react-rocketjump/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248519545,"owners_count":21117761,"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-07-31T07:00:35.249Z","updated_at":"2025-04-13T15:23:38.297Z","avatar_url":"https://github.com/inmagik.png","language":"TypeScript","funding_links":[],"categories":["Packages"],"sub_categories":[],"readme":"\u003cp align='center'\u003e\n  \u003ca href='https://inmagik.github.io/react-rocketjump/'\u003e\n     \u003cimg alt='rj logo' src='./assets/rj_logo.svg' height='150' /\u003e\n  \u003c/a\u003e\n \n\u003c/p\u003e\n\n# React RocketJump\n\nManage state and side effects like a breeze\n\n[![Build Status](https://travis-ci.com/inmagik/react-rocketjump.svg?branch=master)](https://travis-ci.com/inmagik/react-rocketjump)\n[![npm version](https://badge.fury.io/js/react-rocketjump.svg)](https://badge.fury.io/js/react-rocketjump)\n[![codecov](https://codecov.io/gh/inmagik/react-rocketjump/branch/master/graph/badge.svg)](https://codecov.io/gh/inmagik/react-rocketjump)\n\nReact RocketJump is a flexible, customizable, extensible tool to help developers dealing with side effects and asynchronous code in React Applications\n\nBenefits of using React RocketJump\n\n- asynchronous code is managed locally in your components, without the need of a global state\n- you can start a task and then cancel it before it completes\n- the library detects when components are mounted or unmounted, so that no asynchronous code is run on unmounted components\n- extensible (but already powerful) and composable ecosystem of plugins to manage the most common and challenging tasks\n\n## Quick start\n\n### Install\n\n```shell\nyarn add react-rocketjump\n```\n\n### Your first rocket jump!\n\n```js\n// (1) Import rocketjump (rj for friends)\nimport { rj } from 'react-rocketjump'\n\n// (2) Create a RocketJump Object\nexport const TodosState = rj({\n  // (3) Define your side effect\n  // (...args) =\u003e Promise | Observable\n  effect: () =\u003e fetch(`/api/todos`).then(r =\u003e r.json()),\n})\n\n// (4) And then use it in your component\nimport { useRunRj } from 'react-rocketjump'\nconst TodoList = props =\u003e {\n\n  // Here we use object destructuring operators to rename actions\n  //    this allows to avoid name clashes and to have more auto documented code\n  const [{\n    data: todos, // \u003c-- The result from effect, null at start\n    pending,     // \u003c-- Is effect in pending? false at start\n    error        // \u003c-- The eventually error from side effect, null when side effect starts\n  }] = useRunRj(TodosState) // Run side effects on mount only\n\n  return  (\n    \u003c\u003e\n      {error \u0026\u0026 \u003cdiv\u003eGot some troubles\u003c/div\u003e}\n      {pending \u0026\u0026 \u003cdiv\u003eWait...\u003c/div\u003e}\n      \u003cul\u003e\n        {\n          todos !== null \u0026\u0026\n          todos.map(todo =\u003e (\n          \u003cli key={todo.id}\u003e{todo.title}\u003c/li\u003e\n        ))}\n      \u003c/ul\u003e\n    \u003c/\u003e\n  )\n}\n```\n\n### Trigger side effects on values changes\n\n```js\nimport { rj } from 'react-rocketjump'\nexport const TodosState = rj({\n  effect: (username = 'all') =\u003e fetch(`/api/todos/${username}`).then(r =\u003e r.json()),\n})\n\nimport { useRunRj } from 'react-rocketjump'\nconst TodoList = ({ username }) =\u003e {\n\n  // Every time the username changes the effect re-run\n  // the previouse effect will be canceled if in pending\n  const [\n    { data: todos, pending, error },\n    {\n      // run the sie effect\n      run,\n      // stop the side effect and clear the state\n      clean,\n      // stop the side effect\n      cancel,\n    }\n  ] = useRunRj(TodosState, [username])\n\n  // ...\n}\n```\n\n### Trigger side effects maybe :open_mouth: on values changes\n\n```js\nimport { rj } from 'react-rocketjump'\nexport const TodosState = rj({\n  effect: (username = 'all') =\u003e fetch(`/api/todos/${username}`).then(r =\u003e r.json()),\n})\n\nimport { useRunRj, deps } from 'react-rocketjump'\nconst TodoList = ({ username }) =\u003e {\n\n  // Every time the username changes the effect re-run\n  // the previouse effect will be canceled if in pending\n  const [\n    { data: todos, pending, error },\n    {\n      // run the sie effect\n      run,\n      // stop the side effect and clear the state\n      clean,\n      // stop the side effect\n      cancel,\n    }\n  ] = useRunRj(TodosState, [\n    deps.maybe(username) // if username is falsy deps tell useRj to\n                         // don't run your side effects\n  ])\n\n  // ...\n\n  // there are a lot of cool maybe like monad shortcuts\n\n  // use maybeAll to replace y deps array [] with all maybe values\n  // if username OR group are falsy don't run the side effect\n  useRunRj(deps.allMaybe(username, group))\n\n  // strict check 4 null\n  useRunRj([deps.maybeNull(username)])\n  useRunRj(deps.allMaybeNull(username, group))\n\n  // shortcut 4 lodash style get\n  // if user is falsy doesn't run otherwise runs with get(value, path)\n  useRunRj([deps.maybeGet(user, 'id')])\n\n  // ... you can always use the simple maybe to generated custom run\n  // conditions in a declarative fashion way\n  useRunRj([\n    (username \u0026\u0026 status !== 'banned')\n      ? username  // give the username as dep only if username\n                  // is not falsy and the status is not banned ...\n      : deps.maybe() // otherwise call maybe with nothing\n                     // and nothing is js means undefined so always\n                     // a false maybe\n  ])\n\n}\n```\n\n### Trigger side effects when you want\n\n```js\nimport { rj } from 'react-rocketjump'\nexport const TodosState = rj({\n  effect: (username = 'all') =\u003e fetch(`/api/todos/${username}`).then(r =\u003e r.json()),\n})\n\nimport { useEffect } from 'react'\nimport { useRunRj } from 'react-rocketjump'\nconst TodoList = ({ username }) =\u003e {\n\n  // useRj don't auto trigger side effects\n  // Give you the state and actions generated from the RocketJump Object\n  // is up to you to trigger sie effect\n  // useRunRj is implement with useRj and useEffect to call the run action with your deps\n  const [\n    { data: todos, pending, error },\n    { run }\n  ] = useRj(TodosState, [username])\n\n  useEffect(() =\u003e {\n    if (username) {\n      run(username)\n    }\n  }, [username])\n\n  function onTodosReload() {\n    // or with callbacks\n    run\n      // in callbacks is saftly to run side effects or set react state\n      // because callbacks are automatic unregistred when TodoList unmount\n      .onSuccess((todos) =\u003e {\n        console.log('Reload Y todos!', todos)\n      })\n      .onFailure((error) =\u003e {\n        console.error(\"Can't reload Y todos sorry...\", error)\n      })\n      .run()\n  }\n\n  // ...\n}\n```\n\n### Mutations\n```js\nimport { rj } from 'react-rocketjump'\nexport const TodosState = rj({\n  mutations: {\n    // Give a name to your mutation\n    addTodo: {\n      // Describe the side effect\n      effect: todo =\u003e fetch(`${API_URL}/todos`, {\n        method: 'post',\n        headers: {\n         'Content-Type': 'application/json'\n        },\n        body: JSON.stringify(todo),\n       }).then(r =\u003e r.json()),\n       // Describe how to update the state in respond of effect success\n       // (prevState, effectResult) =\u003e newState\n       updater: (state, todo) =\u003e ({\n        ...state,\n        data: state.data.concat(todo),\n      })\n    }\n  },\n  effect: (username = 'all') =\u003e fetch(`/api/todos/${username}`).then(r =\u003e r.json()),\n})\n\nimport { useEffect } from 'react'\nimport { useRunRj } from 'react-rocketjump'\nconst TodoList = ({ username }) =\u003e {\n  const [\n    { data: todos, pending, error },\n    {\n      run,\n      addTodo, // \u003c-- Match the mutation name\n    }\n  ] = useRj(TodosState, [username])\n\n  // Mutations actions works as run, cancel and clean\n  // trigger the realted side effects and update the state using give updater\n  function handleSubmit(values) {\n    addTodo\n      .onSuccess((newTodo) =\u003e {\n        console.log('Todo added!', newTodo)\n      })\n      .onFailure((error) =\u003e {\n        console.error(\"Can't add todo sorry...\", error)\n      })\n      .run(values)\n  }\n\n  // ...\n}\n```\n\n### Optimistic Mutations\n\nTo make a mutation optimistic add `optimisticResult` to your `mutation` config:\n\n```js\nrj({\n  effect: fetchTodosApi,\n  mutations: {\n    updateTodo: {\n      optimisticResult: (todo) =\u003e todo,\n      updater: (state, updatedTodo) =\u003e ({\n        ...state,\n        data: state.data.map((todo) =\u003e\n          todo.id === updatedTodo.id ? updatedTodo : todo\n        ),\n      }),\n      effect: updateTodoApi,\n    },\n    toggleTodo: {\n      optimisticResult: (todo) =\u003e ({\n        ...todo,\n        done: !todo.done,\n      }),\n      updater: (state, updatedTodo) =\u003e ({\n        ...state,\n        data: state.data.map((todo) =\u003e\n          todo.id === updatedTodo.id ? updatedTodo : todo\n        ),\n      }),\n      effect: (todo) =\u003e\n        updateTodoApi({\n          ...todo,\n          done: !todo.done,\n        }),\n    },\n    incrementTodo: {\n      optimisticResult: (todo) =\u003e todo.id,\n      optmisticUpdater: (state, todoIdToIncrement) =\u003e ({\n        ...state,\n        data: state.data.map((todo) =\u003e\n          todo.id === todoIdToIncrement\n            ? {\n                ...todo,\n                score: todo.score + 1,\n              }\n            : todo\n        ),\n      }),\n      effect: (todo) =\u003e incrementTodoApi(todo.id).then(() =\u003e todo.id),\n    },\n  },\n})\n```\n\nThe `optimisticResult` function will be called with your *params* (as your `effect`)\nand the return value will be passed to the `updater` to update your state.\n\nIf your mutation **SUCCESS** *rocketjump* will commit your state and re-running\nyour `updater` ussing the effect result as a normal mutation.\n\nOtherwise if your mutation **FAILURE** *rocketjump* roll back your state and\nunapply the `optimisticResult`.\n\nSometimes you need to distinguish between an optmisitc update and an update\nfrom `SUCCESS` if you provide the `optimisticUpdater` key in your mutation\nconfig the `optimisticUpdater` is used to perform the optmistic update an\nthe `updater` to perform the update when commit success.\n\nIf your provided **ONLY** `optimisticUpdater` the success commit is skipped\nand used current root state, this is useful for response as `204 No Content`\nstyle where you can ignore the success and skip an-extra React update to your\nstate.\n\nIf you provide only `updater` this is used for **BOTH** optmistic and non-optimistic\nupdates.\n\n## Deep dive\n\nThe full documentation with many examples and detailed information is mantained at\n\n[https://inmagik.github.io/react-rocketjump](https://inmagik.github.io/react-rocketjump)\n\nBe sure to check it out!!\n\n## Built-in logger\nSince v2 rj ships a [redux-logger](https://github.com/LogRocket/redux-logger) inspired logger designed to run only in DEV and helps you debugging rocketjumps.\n\nThis is what it looks like:\n\n![Rj Logger Sample Screen](/assets/logger_rj_in_console.png)\n\nTo enable it, just add this snippet to your `index.js`:\n\n```js\nimport rjLogger from 'react-rocketjump/logger'\n\n// The logger don't log in PRODUCTION\n// (place this before ReactDOM.render)\nrjLogger()\n```\n\nTo add a name to your RocketJump Object in the logger output simply add a `name` key in your rj config:\n\n```js\nconst TomatoesState = rj({\n  effect,\n  // ... rj config ...\n  name: 'Tomatoes'\n})\n```\n\n## Run example\n\nYou can find an example under [example](https://github.com/inmagik/react-rocketjump/tree/master/example), it's a simple REST todo app that uses the great [json-server](https://github.com/typicode/json-server) as fake API server.\n\nTo run it first clone the repo:\n\n```shell\ngit clone git@github.com:inmagik/react-rocketjump.git\n```\n\nThen run:\n\n```shell\nyarn install\nyarn run-example\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finmagik%2Freact-rocketjump","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finmagik%2Freact-rocketjump","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finmagik%2Freact-rocketjump/lists"}