Skip to content

Commit 391281a

Browse files
ihabadhamclaude
andcommitted
Update React Router documentation and spec/dummy to v6
This major update modernizes React Router integration from outdated v4/v5 to current v6 API, fixing critical gaps discovered through testing. Documentation Changes (docs/building-features/react-router.md): - Remove 5-year-old "needs updating" warning - Pin React Router to v6 (^6.0.0) with explanation about v7 incompatibility - Add critical "Rails Routes Configuration" section with wildcard routing - Update all code examples to React Router v6 API: * Switch → Routes * component prop → element prop * StaticRouter import from 'react-router-dom/server' - Add path matching guidance (React Router paths must match Rails routes) - Update Turbolinks → Turbo/Turbolinks with Rails version context - Improve clarity: generator doesn't create React Router code - Add installation instructions and compatibility notes spec/dummy Updates (v5→v6 migration): - client/app/routes/routes.jsx: Add Routes wrapper, wildcard path support - client/app/components/RouterLayout.jsx: Switch→Routes, use relative paths - client/app/startup/RouterApp.server.jsx: Update StaticRouter import location - package.json: Upgrade react-router-dom from ^5.2.0 to ^6.0.0 - yarn.lock: Update dependencies (@remix-run/router@1.23.0) Key Discoveries from Testing: 1. React Router v7 merged with Remix, incompatible with our SSR approach 2. Rails wildcard routing is CRITICAL but was never documented 3. Documentation examples were 9+ years misleading about generator output 4. StaticRouter import location changed in v6 (breaks without this update) Testing: - Created fresh test app following documentation step-by-step - Validated client-side routing, SSR, direct URL visits, browser navigation - Confirmed spec/dummy React Router demo works with v6 API 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 20d4328 commit 391281a

File tree

6 files changed

+182
-132
lines changed

6 files changed

+182
-132
lines changed
Lines changed: 148 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,183 @@
1-
_This article needs updating for the latest version of React Router_
2-
31
# Using React Router
42

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.
630

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).
832

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.
1234

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);
1654

1755
return (
1856
<Provider store={store}>
1957
<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>
2164
</BrowserRouter>
2265
</Provider>
2366
);
2467
};
25-
```
2668

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+
```
2871

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:**
3073

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
3278

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
3580

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`.
3782

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`**
3984

40-
```javascript
85+
```jsx
4186
import React from 'react';
4287
import { renderToString } from 'react-dom/server';
43-
import { StaticRouter } from 'react-router';
88+
import { StaticRouter } from 'react-router-dom/server';
4489
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';
5591

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';
5797

98+
const HelloWorldApp = (props, railsContext) => {
99+
const store = configureStore(props);
58100
const { location } = railsContext;
59101

60102
const html = renderToString(
61103
<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>
64111
</StaticRouter>
65112
</Provider>,
66113
);
67114

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 };
75116
};
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'
76151
```
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)

spec/dummy/client/app/components/RouterLayout.jsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { Link, Route, Switch } from 'react-router-dom';
2+
import { Link, Route, Routes } from 'react-router-dom';
33
import RouterFirstPage from './RouterFirstPage';
44
import RouterSecondPage from './RouterSecondPage';
55

@@ -21,10 +21,10 @@ const RouterLayout = () => (
2121
</li>
2222
</ul>
2323
<hr />
24-
<Switch>
25-
<Route path="/react_router/first_page" component={RouterFirstPage} />
26-
<Route path="/react_router/second_page" component={RouterSecondPage} />
27-
</Switch>
24+
<Routes>
25+
<Route path="first_page" element={<RouterFirstPage />} />
26+
<Route path="second_page" element={<RouterSecondPage />} />
27+
</Routes>
2828
</div>
2929
);
3030

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import React from 'react';
2-
import { Route } from 'react-router-dom';
2+
import { Routes, Route } from 'react-router-dom';
33

44
import RouterLayout from '../components/RouterLayout';
55

6-
export default <Route path="/react_router" component={RouterLayout} />;
6+
export default (
7+
<Routes>
8+
<Route path="/react_router/*" element={<RouterLayout />} />
9+
</Routes>
10+
);

spec/dummy/client/app/startup/RouterApp.server.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { StaticRouter } from 'react-router-dom';
2+
import { StaticRouter } from 'react-router-dom/server';
33

44
import routes from '../routes/routes';
55

spec/dummy/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"react-helmet": "^6.1.0",
2424
"react-on-rails": "link:.yalc/react-on-rails",
2525
"react-redux": "^8.0.2",
26-
"react-router-dom": "^5.2.0",
26+
"react-router-dom": "^6.0.0",
2727
"redux": "^4.0.1",
2828
"redux-thunk": "^2.2.0",
2929
"regenerator-runtime": "^0.13.4"

0 commit comments

Comments
 (0)