|  | 
| 1 |  | -_This article needs updating for the latest version of React Router_ | 
| 2 |  | - | 
| 3 | 1 | # Using React Router | 
| 4 | 2 | 
 | 
| 5 |  | -React on Rails supports the use of React Router. Client-side code doesn't need any special configuration for the React on Rails gem. Implement React Router how you normally would. Note, you might want to avoid using Turbolinks as both Turbolinks and React Router will be trying to handle the back and forward buttons. If you get this figured out, please do share with the community! Otherwise, you might have to tweak the basic settings for Turbolinks, and this may or may not be worth the effort. | 
|  | 3 | +React on Rails supports React Router for client-side routing. This guide shows how to integrate React Router into your React on Rails application. | 
|  | 4 | + | 
|  | 5 | +**Important:** The React on Rails generator does not install React Router. You'll need to add it to your project manually. | 
|  | 6 | + | 
|  | 7 | +## Compatibility Note | 
|  | 8 | + | 
|  | 9 | +If you're using Turbo (Rails 7+) or Turbolinks (Rails 6 and earlier), be aware that both React Router and Turbo/Turbolinks handle browser navigation and the back button. These two routing systems can conflict. Consider: | 
|  | 10 | + | 
|  | 11 | +- Using one routing approach (either Turbo OR React Router, not both) | 
|  | 12 | +- Disabling Turbo for pages using React Router with `data-turbo="false"` | 
|  | 13 | +- Using code splitting instead of client-side routing for similar performance benefits | 
|  | 14 | + | 
|  | 15 | +If you successfully integrate both, please share your solution with the community! | 
|  | 16 | + | 
|  | 17 | +For more details, see [Turbo/Turbolinks Guide](../rails/turbolinks.md). | 
|  | 18 | + | 
|  | 19 | +## Installation | 
|  | 20 | + | 
|  | 21 | +First, add React Router v6 to your project: | 
|  | 22 | + | 
|  | 23 | +```bash | 
|  | 24 | +npm install react-router-dom@^6.0.0 | 
|  | 25 | +# or | 
|  | 26 | +yarn add react-router-dom@^6.0.0 | 
|  | 27 | +``` | 
|  | 28 | + | 
|  | 29 | +**Why React Router v6?** React Router v7 has merged with Remix and uses a different architecture that may not be fully compatible with React on Rails' server-side rendering approach. We recommend v6 for stable integration. If you need v7 features, please test thoroughly and share your findings with the community. | 
| 6 | 30 | 
 | 
| 7 |  | -If you are working with the HelloWorldApp created by the react_on_rails generator (with `--redux` option), the code below corresponds to your Redux entry point component (typically in `src/HelloWorldApp/ror_components/`). | 
|  | 31 | +React Router v6 offers multiple routing approaches. For React on Rails, we recommend **Declarative Mode** (traditional component-based routing, covered in this guide). | 
| 8 | 32 | 
 | 
| 9 |  | -```js | 
| 10 |  | -import { BrowserRouter, Switch } from 'react-router-dom'; | 
| 11 |  | -import routes from './routes.jsx'; | 
|  | 33 | +**Note on Data Mode:** React Router's Data Mode (with loaders/actions) is designed for SPAs where the client handles data fetching. Since React on Rails uses Rails controllers to load data and pass it as props to React components, Data Mode would create duplicate data loading. Stick with Declarative Mode to leverage React on Rails' server-side data loading pattern. | 
| 12 | 34 | 
 | 
| 13 |  | -const RouterApp = (props, railsContext) => { | 
| 14 |  | -  // create your hydrated store | 
| 15 |  | -  const store = createStore(props); | 
|  | 35 | +## Basic Client-Side Setup with Redux | 
|  | 36 | + | 
|  | 37 | +If you're using Redux (created with `rails generate react_on_rails:install --redux`), you can add React Router by wrapping your app: | 
|  | 38 | + | 
|  | 39 | +**File: `app/javascript/src/HelloWorldApp/ror_components/HelloWorldApp.client.jsx`** | 
|  | 40 | + | 
|  | 41 | +```jsx | 
|  | 42 | +import React from 'react'; | 
|  | 43 | +import { Provider } from 'react-redux'; | 
|  | 44 | +import { BrowserRouter, Routes, Route } from 'react-router-dom'; | 
|  | 45 | + | 
|  | 46 | +import configureStore from '../store/helloWorldStore'; | 
|  | 47 | +import HelloWorldContainer from '../containers/HelloWorldContainer'; | 
|  | 48 | +// Import other components for routing | 
|  | 49 | +// import About from '../components/About'; | 
|  | 50 | +// import Contact from '../components/Contact'; | 
|  | 51 | + | 
|  | 52 | +const HelloWorldApp = (props) => { | 
|  | 53 | +  const store = configureStore(props); | 
| 16 | 54 | 
 | 
| 17 | 55 |   return ( | 
| 18 | 56 |     <Provider store={store}> | 
| 19 | 57 |       <BrowserRouter> | 
| 20 |  | -        <Switch>{routes}</Switch> | 
|  | 58 | +        <Routes> | 
|  | 59 | +          <Route path="/" element={<HelloWorldContainer />} /> | 
|  | 60 | +          {/* Add more routes here */} | 
|  | 61 | +          {/* <Route path="/about" element={<About />} /> */} | 
|  | 62 | +          {/* <Route path="/contact" element={<Contact />} /> */} | 
|  | 63 | +        </Routes> | 
| 21 | 64 |       </BrowserRouter> | 
| 22 | 65 |     </Provider> | 
| 23 | 66 |   ); | 
| 24 | 67 | }; | 
| 25 |  | -``` | 
| 26 | 68 | 
 | 
| 27 |  | -For a fleshed out integration of React on Rails with React Router, check out [React Webpack Rails Tutorial Code](https://github.com/shakacode/react-webpack-rails-tutorial), specifically the routing configuration in: | 
|  | 69 | +export default HelloWorldApp; | 
|  | 70 | +``` | 
| 28 | 71 | 
 | 
| 29 |  | -- [react-webpack-rails-tutorial/client/app/bundles/comments/routes/routes.jsx](https://github.com/shakacode/react-webpack-rails-tutorial/blob/master/client/app/bundles/comments/routes/routes.jsx) | 
|  | 72 | +**Key points:** | 
| 30 | 73 | 
 | 
| 31 |  | -## Server Rendering Using React Router V4 | 
|  | 74 | +- `<Provider>` wraps `<BrowserRouter>` so all routes have Redux access | 
|  | 75 | +- Use `<Routes>` and `<Route>` (not `<Switch>` from React Router v5) | 
|  | 76 | +- Use `element` prop to specify components (not `component` or `render` props from v5) | 
|  | 77 | +- Routes are automatically matched by best fit, not render order | 
| 32 | 78 | 
 | 
| 33 |  | -Your Render-Function may not return an object with the property `renderedHtml`. Thus, you call | 
| 34 |  | -`renderToString()` and return an object with this property. | 
|  | 79 | +## Server-Side Rendering with React Router | 
| 35 | 80 | 
 | 
| 36 |  | -This example **only applies to server-side rendering** and should only be used in the server-side bundle. | 
|  | 81 | +For server rendering, use `StaticRouter` instead of `BrowserRouter`. | 
| 37 | 82 | 
 | 
| 38 |  | -From the [original example in the React Router docs](https://github.com/ReactTraining/react-router/blob/v4.3.1/packages/react-router-dom/docs/guides/server-rendering.md) | 
|  | 83 | +**File: `app/javascript/src/HelloWorldApp/ror_components/HelloWorldApp.server.jsx`** | 
| 39 | 84 | 
 | 
| 40 |  | -```javascript | 
|  | 85 | +```jsx | 
| 41 | 86 | import React from 'react'; | 
| 42 | 87 | import { renderToString } from 'react-dom/server'; | 
| 43 |  | -import { StaticRouter } from 'react-router'; | 
|  | 88 | +import { StaticRouter } from 'react-router-dom/server'; | 
| 44 | 89 | import { Provider } from 'react-redux'; | 
| 45 |  | -import ReactOnRails from 'react-on-rails'; | 
| 46 |  | - | 
| 47 |  | -// App.jsx from src/client/App.jsx | 
| 48 |  | -import App from '../App'; | 
| 49 |  | - | 
| 50 |  | -const ReactServerRenderer = (props, railsContext) => { | 
| 51 |  | -  const context = {}; | 
| 52 |  | - | 
| 53 |  | -  // commentStore from src/server/store/commentStore | 
| 54 |  | -  const store = ReactOnRails.getStore('../store/commentStore'); | 
|  | 90 | +import { Routes, Route } from 'react-router-dom'; | 
| 55 | 91 | 
 | 
| 56 |  | -  // Route Store generated from react-on-rails | 
|  | 92 | +import configureStore from '../store/helloWorldStore'; | 
|  | 93 | +import HelloWorldContainer from '../containers/HelloWorldContainer'; | 
|  | 94 | +// Import other components for routing | 
|  | 95 | +// import About from '../components/About'; | 
|  | 96 | +// import Contact from '../components/Contact'; | 
| 57 | 97 | 
 | 
|  | 98 | +const HelloWorldApp = (props, railsContext) => { | 
|  | 99 | +  const store = configureStore(props); | 
| 58 | 100 |   const { location } = railsContext; | 
| 59 | 101 | 
 | 
| 60 | 102 |   const html = renderToString( | 
| 61 | 103 |     <Provider store={store}> | 
| 62 |  | -      <StaticRouter location={location} context={context} props={props}> | 
| 63 |  | -        <App /> | 
|  | 104 | +      <StaticRouter location={location}> | 
|  | 105 | +        <Routes> | 
|  | 106 | +          <Route path="/" element={<HelloWorldContainer />} /> | 
|  | 107 | +          {/* Add more routes here */} | 
|  | 108 | +          {/* <Route path="/about" element={<About />} /> */} | 
|  | 109 | +          {/* <Route path="/contact" element={<Contact />} /> */} | 
|  | 110 | +        </Routes> | 
| 64 | 111 |       </StaticRouter> | 
| 65 | 112 |     </Provider>, | 
| 66 | 113 |   ); | 
| 67 | 114 | 
 | 
| 68 |  | -  if (context.url) { | 
| 69 |  | -    // Somewhere a `<Redirect>` was rendered | 
| 70 |  | -    redirect(301, context.url); | 
| 71 |  | -  } else { | 
| 72 |  | -    // we're good, send the response | 
| 73 |  | -    return { renderedHtml: html }; | 
| 74 |  | -  } | 
|  | 115 | +  return { renderedHtml: html }; | 
| 75 | 116 | }; | 
|  | 117 | + | 
|  | 118 | +export default HelloWorldApp; | 
|  | 119 | +``` | 
|  | 120 | + | 
|  | 121 | +**Important changes from React Router v5:** | 
|  | 122 | + | 
|  | 123 | +- Import `StaticRouter` from `'react-router-dom/server'` (not `'react-router'`) | 
|  | 124 | +- Use `<Routes>` and `<Route>` with `element` prop | 
|  | 125 | +- `location` prop takes a string path from `railsContext` | 
|  | 126 | +- No need for `match()` or `RouterContext` - simplified API | 
|  | 127 | + | 
|  | 128 | +## Rails Routes Configuration | 
|  | 129 | + | 
|  | 130 | +**Critical Step:** To support direct URL visits, browser refresh, and server-side rendering, you must configure Rails to handle all React Router paths. | 
|  | 131 | + | 
|  | 132 | +Add a wildcard route in your `config/routes.rb`: | 
|  | 133 | + | 
|  | 134 | +```ruby | 
|  | 135 | +# config/routes.rb | 
|  | 136 | +Rails.application.routes.draw do | 
|  | 137 | +  # Your main route | 
|  | 138 | +  get 'your_path', to: 'your_controller#index' | 
|  | 139 | + | 
|  | 140 | +  # Wildcard catch-all for React Router sub-routes | 
|  | 141 | +  get 'your_path/*path', to: 'your_controller#index' | 
|  | 142 | +end | 
|  | 143 | +``` | 
|  | 144 | + | 
|  | 145 | +**Example:** | 
|  | 146 | + | 
|  | 147 | +```ruby | 
|  | 148 | +# For a HelloWorld app with React Router | 
|  | 149 | +get 'hello_world', to: 'hello_world#index' | 
|  | 150 | +get 'hello_world/*path', to: 'hello_world#index' | 
| 76 | 151 | ``` | 
|  | 152 | + | 
|  | 153 | +This configuration ensures: | 
|  | 154 | + | 
|  | 155 | +- `/hello_world` → Renders your React app | 
|  | 156 | +- `/hello_world/about` → Rails serves the same view, React Router handles routing | 
|  | 157 | +- `/hello_world/contact` → Rails serves the same view, React Router handles routing | 
|  | 158 | +- Browser refresh works on any route | 
|  | 159 | +- Direct URL visits work with server-side rendering | 
|  | 160 | + | 
|  | 161 | +**Important:** Your React Router paths should match your Rails route structure. If Rails serves your app at `/hello_world`, your React Router routes should start with `/hello_world`: | 
|  | 162 | + | 
|  | 163 | +```jsx | 
|  | 164 | +<Routes> | 
|  | 165 | +  <Route path="/hello_world" element={<Home />} /> | 
|  | 166 | +  <Route path="/hello_world/about" element={<About />} /> | 
|  | 167 | +  <Route path="/hello_world/contact" element={<Contact />} /> | 
|  | 168 | +</Routes> | 
|  | 169 | +``` | 
|  | 170 | + | 
|  | 171 | +## Example Application | 
|  | 172 | + | 
|  | 173 | +For a complete example of React on Rails with React Router, see the [React Webpack Rails Tutorial](https://github.com/shakacode/react-webpack-rails-tutorial). | 
|  | 174 | + | 
|  | 175 | +For a practical example of route organization, see the [routes configuration file](https://github.com/shakacode/react-webpack-rails-tutorial/blob/master/client/app/bundles/comments/routes/routes.jsx) from the tutorial. | 
|  | 176 | + | 
|  | 177 | +**Note:** This tutorial uses a legacy directory structure (`client/app/bundles`) from earlier React on Rails versions. Modern projects use `app/javascript/src/` structure as shown in this guide. The React Router integration patterns remain applicable. | 
|  | 178 | + | 
|  | 179 | +## Additional Resources | 
|  | 180 | + | 
|  | 181 | +- [React Router Official Documentation](https://reactrouter.com/) | 
|  | 182 | +- [React Router v6 Migration Guide](https://reactrouter.com/en/main/upgrading/v5) - If upgrading from v5 | 
|  | 183 | +- [React on Rails Turbo/Turbolinks Guide](../rails/turbolinks.md) | 
0 commit comments