{"id":17975588,"url":"https://github.com/rafaelrinaldi/transitionable-routes","last_synced_at":"2025-03-25T15:30:38.208Z","repository":{"id":57379111,"uuid":"125682046","full_name":"rafaelrinaldi/transitionable-routes","owner":"rafaelrinaldi","description":"Perform transitions when changing routes with React Router","archived":false,"fork":false,"pushed_at":"2018-07-25T20:26:18.000Z","size":260,"stargazers_count":26,"open_issues_count":0,"forks_count":3,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-18T15:04:04.329Z","etag":null,"topics":["animations","motion","react","react-router","transitions"],"latest_commit_sha":null,"homepage":"","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/rafaelrinaldi.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":"2018-03-18T01:07:40.000Z","updated_at":"2023-07-01T10:30:02.000Z","dependencies_parsed_at":"2022-09-02T20:41:24.452Z","dependency_job_id":null,"html_url":"https://github.com/rafaelrinaldi/transitionable-routes","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafaelrinaldi%2Ftransitionable-routes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafaelrinaldi%2Ftransitionable-routes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafaelrinaldi%2Ftransitionable-routes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafaelrinaldi%2Ftransitionable-routes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rafaelrinaldi","download_url":"https://codeload.github.com/rafaelrinaldi/transitionable-routes/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245489754,"owners_count":20623789,"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":["animations","motion","react","react-router","transitions"],"created_at":"2024-10-29T17:20:13.081Z","updated_at":"2025-03-25T15:30:37.852Z","avatar_url":"https://github.com/rafaelrinaldi.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[animate]: http://animate.mhaagens.me\n[events]: https://github.com/Gozala/events\n[potato]: https://github.com/codify-to/Potato\n[react-router-v4-transition]: https://github.com/aboeglin/react-router-v4-transition\n[react-router]: https://reacttraining.com/react-router\n[react-transition-group]: https://github.com/reactjs/react-transition-group\n[react]: https://reactjs.org\n[redux]: https://redux.js.org\n[robotlegs]: http://www.robotlegs.org\n[url]: https://rinaldi.io\n\n# transitionable-routes ![Experimental](https://img.shields.io/badge/stability-experimental-orange.svg)\n\n\u003e Perform transitions when changing routes with React Router\n\n# Install\n\n```sh\nnpm i transitionable-routes\n```\n\n## The Problem\n\nThe ability to add animations whenever a route changes seems like such a trivial feature but weirdly enough I haven't found a nice way of doing it with [React][react] and [React Router][react-router].\n\nAll the examples I've seen out there – including the ones in the official docs – usually assume you are performing generic transitions such as fading in and out the routes entering and leaving. If you need more control over that, good luck.\n\nAt some point in time this was achievable via [react-transition-group][react-transition-group] but at some point React Router had a major bump that broke integration with it and as a workaround the react-transition-group team changed the API in a way that for me was a downgrade since they've made much harder to make things customizable (they've added a `in` property that makes no sense to me and instead of controlling animations via callbacks you had to pass down how long transitions would take, which is an idea that I dislike very much).\n\nComing from a Flash background I remember this used to be a feature we took for granted. Either you rolled your own at the beginning of the project or you would have it available via frameworks (shout out to [Potato \u003csmall\u003e(aka Patota)\u003c/small\u003e][potato] and [Robotlegs][robotlegs]).\n\n## The Solution\n\n\u003eKeep in mind that this was put together in a few hours and is still experimental. If you have ideas on how to improve it, do chime in.\n\nOut of desperation (I couldn't imagine this would take longer than a few minutes to ship) I decided to hack my way into a solution. I have started by writing the API that I wanted then started putting something together off of some good ideas that I've seen in the wild.\n\n### `TransitionableSwitch`\n\nThis is a simple stateful React component that acts as a replacement for React Router's `Switch`. It knows how to render route components based on the active route, but it also knows how to coordinate rendering of routes that are transitioning (either entering or leaving).\n\nThis component injects hooks to every route component that is transitioning so inside of it you have access to transition states.\n\nThe coordination of transition states is done via [event emitters][events]. These are responsible for communicating state from the switcher down to the component.\nI have tried using React `ref` for this but had a bad experience, specially when you're trying to wrap a [Redux][redux]-connected component, so I went for good ol' event emitters (which still sounds kinda crazy but did the job well).\n\n### `TransitionableComponent`\n\nThis is a simple React component that automatically handles syncing the component to the event emitter and exposes all the hooks available for transitions:\n\n* `willAppear`\n* `willEnter`\n* `willLeave`\n\nAll hooks receive a callback function as an argument so you can do whatever you want and call them once you're done.\n\n## Usage\n\n```jsx\nimport React from 'react'\nimport { render } from 'react-dom'\nimport { BrowserRouter, Route } from 'react-router-dom'\nimport { TransitionableSwitch, TransitionableComponent } from 'transitionable-routes'\n\nconst Home = () =\u003e \u003ch1\u003eHome\u003c/h1\u003e\n\nclass About extends TransitionableComponent {\n  constructor (props) {\n    super(props)\n    this.state = { transition: null }\n  }\n\n  willEnter (done) {\n    this.setState({ transition: 'enter' }, done)\n  }\n\n  willLeave (done) {\n    this.setState({ transition: 'leave' }, () =\u003e setTimeout(done, 500))\n  }\n\n  render () {\n    const { transition } = this.state\n    const style = {\n      opacity: transition === 'enter' ? 1 : 0,\n      transition: 'opacity 0.5s ease-out'\n    }\n    return \u003ch1 style={style}\u003eAbout\u003c/h1\u003e\n  }\n}\n\nclass Contact extends TransitionableComponent {\n  constructor (props) {\n    super(props)\n    this.state = { transition: null }\n  }\n\n  willEnter (done) {\n    this.setState({ transition: 'enter' }, done)\n  }\n\n  willLeave (done) {\n    this.setState({ transition: 'leave' }, () =\u003e setTimeout(done, 1000))\n  }\n\n  render () {\n    const { transition } = this.state\n    const style = {\n      transform: `translateY(${transition === 'enter' ? 0 : '20px'})`,\n      opacity: transition === 'enter' ? 1 : 0,\n      transition: 'all 1s ease-out'\n    }\n    return \u003ch1 style={style}\u003eContact\u003c/h1\u003e\n  }\n}\n\nconst App = () =\u003e (\n  \u003cBrowserRouter\u003e\n    \u003cTransitionableSwitch\u003e\n      \u003cRoute exact path='/' render={() =\u003e \u003cHome /\u003e} /\u003e\n      \u003cRoute path='/about' component={About} /\u003e\n      \u003cRoute path='/contact' children={\u003cContact /\u003e} /\u003e\n    \u003c/TransitionableSwitch\u003e\n  \u003c/BrowserRouter\u003e\n)\n\nrender(\u003cApp /\u003e, document.querySelector('[data-app]'))\n```\n\nCheckout the [examples folder](./examples).\n\n```sh\nnpm start\n```\n\n### Conditional rendering\n\nIt's very common to want to conditionally render a route. Say you want to redirect logged out users to a sign in page instead of giving guest access to private routes:\n\n```js\nimport React from 'react'\nimport { Redirect, Route } from 'react-router-dom'\nimport SuperSecretRoute from './components/SuperSecretRoute'\n\nconst withAuth = (props, Component) =\u003e {\n  // If user is logged in, we're good to go, just return the component itself\n  if (isUserLoggedIn) return \u003cComponent {...props} /\u003e\n\n  // It's very handy to have \"referrer\" when you're redirecting, so we use local state for that\n  const referrer = props.path || props.location.pathname\n\n  // Return a `Redirect` component pointing to a sign in page\n  return (\n    \u003cRedirect\n      to={{\n        pathname: '/sign-in',\n        state: { referrer },\n      }}\n    /\u003e\n  );\n};\n\nconst App = () =\u003e (\n  \u003cTransitionableSwitch\u003e\n    \u003cRoute path=\"/super-secret-route\" render={props =\u003e withAuth(props, SuperSecretRoute)} /\u003e\n  \u003c/TransitionableSwitch\u003e\n);\n```\n\n## Project Pipeline\n\n![Help Wanted](http://messages.hellobits.com/warning.svg?message=Help%20Wanted)\n\n### API\n\nEven though we have `TransitionableComponent` it still feels like we could improve the API since there's quite a lot of boilerplate involved as it is right now. To create a route component that is able to perform transitions:\n\n1. Create a new React component that extends `TransitionableComponent`\n2. Override transition lifecycle methods and fire `done()`\n3. If your component needs to do anything on either `componentWillMount()` or `componentWillUnmount()` you have to remember to invoke `super()` otherwise things will break\n4. Whenever implementing a transition you must make your component stateful and manually change transition steps so you can do your thing on `render()`, this can become annoying if you have a lot of custom transitions\n\nAs mentioned on #1 this could perhaps be improved with HOCs instead of using a class like `TransitionableComponent` but I'm not sure yet on what's the best thing to do there.\n\n### Examples\n\nCurrent examples are super simple and limited. It would be nice to have better ones that properly showcase this project's capabilities.\n\n## Related\n\n* [animate][animate]\n* [react-router-v4-transition][react-router-v4-transition]\n\n## License\n\nMIT © [Rafael Rinaldi][url]\n\n---\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://buymeacoff.ee/rinaldi\" title=\"Buy me a coffee\"\u003eBuy me a ☕\u003c/a\u003e\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frafaelrinaldi%2Ftransitionable-routes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frafaelrinaldi%2Ftransitionable-routes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frafaelrinaldi%2Ftransitionable-routes/lists"}