{"id":15368111,"url":"https://github.com/iyobo/react-spoon","last_synced_at":"2025-04-15T12:53:27.889Z","repository":{"id":46913003,"uuid":"86656080","full_name":"iyobo/react-spoon","owner":"iyobo","description":"A sane React Routing library for the frontend","archived":false,"fork":false,"pushed_at":"2023-01-24T01:07:00.000Z","size":1076,"stargazers_count":23,"open_issues_count":12,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-28T20:24:06.539Z","etag":null,"topics":["react","reactjs","router"],"latest_commit_sha":null,"homepage":"http://iyobo.co","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/iyobo.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":"2017-03-30T03:44:27.000Z","updated_at":"2023-11-09T05:02:32.000Z","dependencies_parsed_at":"2023-02-02T14:47:35.137Z","dependency_job_id":null,"html_url":"https://github.com/iyobo/react-spoon","commit_stats":null,"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iyobo%2Freact-spoon","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iyobo%2Freact-spoon/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iyobo%2Freact-spoon/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iyobo%2Freact-spoon/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/iyobo","download_url":"https://codeload.github.com/iyobo/react-spoon/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248894940,"owners_count":21179153,"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":["react","reactjs","router"],"created_at":"2024-10-01T13:28:22.914Z","updated_at":"2025-04-15T12:53:27.868Z","avatar_url":"https://github.com/iyobo.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# React-Spoon\n\nA sane front-end Routing library for React\nGithub: https://github.com/iyobo/react-spoon\n\n\n\n## Features\n* Making the router is as easy as instantiating a class.\n* Simple JSON-based configuration.\n* Nested routing\n* Component-level Navigation Hooks (static + reliable triggering)\n* Navigation Links (w/ Hierarchial active state)\n* Store and retrieve state from URL as Key:Object or key:Value pairs\n\n\n## Caveat Emptor\n* Only hash-based routing is supported at this time.\n\n\n## How to use\n\n```typescript\nnpm install --save react-spoon\n```\n\n\nin html\n```typescript\n\u003cdiv id='app'\u003e\u003c/div\u003e\n```\n\nIn main file:\n```typescript\n\nimport React from 'react';\nimport {ReactSpoon} from 'react-spoon';\n\nconst SomeProvider = {...} //Redux, MobX or any provider\nconst store = {...} //any props the provider needs\n\nnew ReactSpoon([\n    {\n        name: '',\n        path: '*',\n        handler: AppLayout,\n        children: [\n            { path: '', redirectTo: 'dashboard' },\n            { path: 'dashboard', name: 'dashboard', handler: DashboardPage },\n            { path: 'about', name: 'about', handler: () =\u003e \u003ch1\u003eAbout\u003c/h1\u003e },\n            {\n             path: 'models/:modelName*', name: 'models', handler: ModelLayout, children: [\n                { path: 'models/:modelName', name: 'models.list', handler: ModelListPage },\n                { path: 'models/:modelName/create', name: 'models.create', handler: ModelEditPage },\n                { path: 'models/:modelName/:id', name: 'models.edit', handler: ModelEditPage }\n              ]\n            }\n        ]\n    }\n\n], \n{ \n    domId: 'app', \n    providers: [ \n        {component: SomeProvider, props: { store }}, \n        {component: AnotherProvider} \n    ]\n});\n```\n\nYou are also able to have multiple ReactSpoon instances on a page with multiple anchor points/domIds.\n\n### The ReactSpoon Class\nIts signature: new ReactSpoon(routeTree, opts)\n\n* routeTree: a JSON object to define hierarchial routes. See above.\n* opts: a JSON object with config options\n* opts.domId: The DOM id of the object to mount your routed app on.\n* opts.providers: An array of components and their props which you intend to wrap around your routed components.\n* opts.providers.*.component: The provider component to instantiate\n* opts.providers.*.props: The props to attach to this provider component when instantiating it.\nThe order of defining providers is important. In the above example, this is the JSX equivalent\n\n\u003cSomeProvider {...store}\u003e\n    \u003cAnotherProvider\u003e\n        // ... your routed components\n    \u003c/AnotherProvider\u003e\n\u003c/SomeProvider\u003e\n\nThese have been replaced by opts.providers and are now **decommisioned**. Please upgrade by using the new opts.providers syntax:\n~~* opts.provider: The component class declaration or equivalent of a provider to wrap your app with (In the future, this will alternatively be an array for nesting multiple provdiders)~~\n~~* opts.providerProps: A prop map of attributes/properties to attach to the equivalent provider. (In the future, this will alternatively be an array of prop maps)~~\n\n\n## True Nested Routing\nA layout rendering nested pages is really just rendering children.\n```typescript\nimport {Component} from 'react';\n\nclass AppLayout extends Component {\n    \n    render(){\n        ...\n        \u003cdiv\u003e\n           {this.props.children}\n        \u003c/div\u003e\n        ...\n    }\n}\n```\n\n\n## Nav Links (Named Routes)\n\n```typescript\nimport {Link} from 'react-spoon';\n\n...\n\n\n\n//This will have class=\"active\" when we are on the route named \"dashboard\".\n//\n    \u003cLink toName=\"dashboard\"\u003e\n        \u003cp\u003eDashboard\u003c/p\u003e\n    \u003c/Link\u003e\n\n\n//Passing params to named Routes\n\n    \u003cLink toName=\"models.list\" params={{modelName: 'User'}} \u003e\n        \u003cp\u003eList All Users\u003c/p\u003e\n    \u003c/Link\u003e\n\n    \u003cLink toName=\"models.edit\" params={{modelName: 'Role', id: 'abc123'}} \u003e\n        \u003cp\u003eEdit Role\u003c/p\u003e\n    \u003c/Link\u003e\n\n\n```\n\n## Programmatic navigation\n\nEvery instance of React-Spoon makes use of the react context API for making itself visible in any component. \nYou grab a reference to this instance by statically defining contextTypes in your component:\n\n```typescript\nclass MyComponent extends React.Component{\n\n    ... \n    \n    static contextTypes = {\n        router: PropTypes.any\n    }\n    \n    ...\n    \n    someFunction = () =\u003e {\n    \n        //navigates to destination\n        this.context.router.go('app.myRouteName', {routeParams} )\n        \n        //or if you just want the url without actually navigating to it\n        const path = this.context.router.buildLink('app.myRouteName', {routeParams} )\n    }\n\n```\n\n\n\nIt is highly recommended to navigate this way as opposed to just trying to change window.location.href... (even though that should still work).\n\nAlso, only **named routes** can be programmatically navigated to at this time. (It's a better pattern/structure to navigate with named routes anyway!)\n\n\n## On-Enter Hook\n\nSpoon will look for a static OnEnter(props) function declaration in your React Component and call it whenever it is navigating to that component.\nThis respects nested routing too, with the topmost component's onEnter triggered first and the next in sequence. (First to Last)\nIn the future, this function might be made to handle (returned) promises.\n\n```typescript\nclass DashboardPage extends React.Component{\n\n...\n\n  static onEnter(props){\n    console.log('Entering Dashboard Page');\n  }\n...\n\n}\n```\n\n~~If you'd like an onLeave(...) hook, create an issue on github. I am actively using RSpoon for open-source dev so chances are I'll add that before you create it. Race ya :P~~\n\n## On-Leave hook (Added since v1.4)\n\nBeat you to it! :D\n\nYou can now create a static onLeave(props) function that RSpoon will call whenever it is navigating away from a route's component.\nThis, like onEnter, respects nested routing. Only difference is that it reverses the order of activation, i.e. Last to First.\n\n```typescript\nclass DashboardPage extends React.Component{\n\n...\n\n  static onLeave(props){\n    console.log('Leaving Dashboard Page');\n  }\n...\n\n}\n```\n\n## URL State (Added since v1.3)\n\nUse these to store and retrieve values or objects from the URL.\nIt should not be used for persistence, but more so as a way for your users to be able to return to a very particular/predictable state of your app by using the exact same URL.\ne.g See what Google Maps does to it's URL while you navigate. Then try copy that URL in some other tab (or send it to a friend ) and see how it takes you back to that exact map center/zoom state.\n\n### Storing State\n\n```typescript\nimport {storeState} from 'react-spoon';\n\n\nvar myState = {\n  paging: {\n          page: 1,\n          limit: 10\n      },\n      sort: { dateCreated: -1 },\n      filters: []\n  }\n}\n\nstoreState('modelFilter', myState);\n\n```\n\nSimple!\nYou'll notice your URL changes after you call this function:\ne.g #/models/Foo**@{\"modelFilter\":{\"paging\":{\"page\":1,\"limit\":10},\"sort\":{\"dateCreated\":-1},\"filters\":[]}}**\n\n\nThe First '@' is the delimeter that seperates your path from your data.\nThe data is stored as a JSON string.\n\nYou'll want to do this, for example, when the state you are trying to persist in your url changes\n\n### Retrieving State\nRetrieving state is just as easy\n```typescript\nimport {getState} from 'react-spoon';\n\n...\n\nmyState = getState('modelFilter');\n\n```\nAnd now myState is chuck full of all that state that is currently in your URL.\n\nClearly, You probably want to do this when loading/mounting/onEntering something that needs this state.\n\n\n## Q\u0026A\n\n### Is that JSON? Ewwww Why?\n\nJSON is more flexible to configure than ~~XML~~ JSX :D.\nFor one, It's simply the best vehicle for conditional or even distributed route configuration.\nYou can have different modules/functions/whatever define what they want for a route and it can all then be converged into one JS object that RSpoon consumes.\n\n\n### Why another React Router?\n\nSo while building [JollofJS](http://github.com/iyobo/jollofjs), I found that the routing libraries in the React ecosystem **just didn't seem to get** what routing libraries do IMO.\nThey either had too much ceremony around actually using them, or they kept stripping themselves of useful features with every major release (fascinating right?).\nI also remember the devs of one claiming that \"Named routes are an anti-pattern\". :P\n\nAnyway! It became clear that if I was ever going to continue onwards without having to keep revisiting this routing issues, I'd just have to grab the bull by the horns and create one that worked \"for me\".\nI'm sharing it in hopes that it works for someone out there too.\n\n### Okay but what's with 'Spoon'?\n\nI created React-Spoon while creating [JollofJS](http://github.com/iyobo/jollofjs) (Think Django for NodeJS - Still in Development). \nI created its built-in admin tool with React.\nJollofJS is named after Jollof Rice, a delicious Nigerian rice dish.\nYou'd usually use a spoon when serving rice... which made React-Spoon an appropriate router name.\n\n...Yes, I'm a Foodie :P\n\n\n### Why use a hook? React has life-cycle functions!\n\nBecause React's life-cycle functions can be unpredictable, especially when doing nested routing.\nSay you wanted to print the name of each page the router was rendering in the EXACT NESTED ORDER each time you route there i.e AppLayout \u003e ModelLayout \u003e ModelListPage ,\n\nYou may find that they don't show up in this order or sometimes don't show up at all. Sometimes, one route will show up multiple times while others never do.\n\nDefining/using the onEnter hook just gives you one solid way to reliably control your react app's page-nesting sequencing with improved precision.\nAnd it is WAY more cleaner and elegant to define this static hook at the class/component level, and not in your main file or wherever you are defining the route tree.\n\nBesides wanting an elegant, full-featured **frontend** routing library...needing one with Reliable, Object-Level route-event handling was one of the key reasons why I created React-Spoon\n\n\n\n\n## Development\n* `npm i`\n\n* Make sure babel is installed globally\n\n* Build with `npm run build`;\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fiyobo%2Freact-spoon","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fiyobo%2Freact-spoon","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fiyobo%2Freact-spoon/lists"}