Skip to content

Commit 32c1352

Browse files
justin808claude
andauthored
feat: Add Ruby-side smart error messages (#1934)
Adds contextual, actionable error messages for React on Rails server-side errors. ## Key Features - **SmartError class** with pattern detection and fuzzy component name matching - **Enhanced PrerenderError** with troubleshooting tips for common SSR issues (window/document undefined, hydration mismatches) - **Auto-bundling suggestions** as the primary recommended approach - **Colored, formatted error output** for better readability ## Benefits - ⚡ Faster debugging with specific, actionable guidance - 🎯 Reduced time spent on common mistakes (typos, missing registration) - 📝 Clear examples showing how to fix each error type - 🚀 Prioritizes simpler auto-bundling over manual registration ## Testing - ✅ All RSpec tests passing - ✅ RuboCop clean (with appropriate disable directives for complexity) ## Documentation Ruby-focused error message guide included in `docs/guides/improved-error-messages.md` ## Part of Improvement Series This is PR #1 of 3 focused PRs split from #1834: - **PR #1 (this):** Ruby-side error improvements ✨ - PR #2: JavaScript debug logging (coming next) - PR #3: Documentation and roadmap (optional) Closes part of #1834 <!-- Reviewable:start --> - - - This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/shakacode/react_on_rails/1934) <!-- Reviewable:end --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Smart, context-aware error messages with actionable solutions and fuzzy component-name suggestions. * Optional flags to enable debugMode and logComponentRegistration for richer registration visibility. * **Documentation** * New guide documenting improved error messages, examples, troubleshooting steps, and best practices. * **Bug Fixes** * Improved server-side error formatting with structured sections and clearer troubleshooting guidance. * **Tests** * Added comprehensive tests covering smart error messages and solution generation. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude <[email protected]>
1 parent 301a432 commit 32c1352

File tree

15 files changed

+893
-58
lines changed

15 files changed

+893
-58
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ Changes since the last non-beta release.
3131

3232
- **Improved Error Messages**: Error messages for version mismatches and package configuration issues now include package-manager-specific installation commands (npm, yarn, pnpm, bun). [PR #1881](https://github.com/shakacode/react_on_rails/pull/1881) by [AbanoubGhadban](https://github.com/AbanoubGhadban).
3333

34+
- **Smart Error Messages with Actionable Solutions**: Added intelligent Ruby-side error handling with context-aware, actionable solutions for common issues. Features include fuzzy matching for component name typos, environment-specific debugging suggestions, color-coded error formatting, and detailed troubleshooting guides for component registration, auto-bundling, hydration mismatches, server rendering errors, and Redux store issues. See the [Improved Error Messages guide](docs/guides/improved-error-messages.md) for details. [PR 1934](https://github.com/shakacode/react_on_rails/pull/1934) by [justin808](https://github.com/justin808).
35+
3436
- **Improved RSC Payload Error Handling**: Errors that happen during generation of RSC payload are transferred properly to rails side and logs the error message and stack. [PR #1888](https://github.com/shakacode/react_on_rails/pull/1888) by [AbanoubGhadban](https://github.com/AbanoubGhadban).
3537

3638
#### Changed

bin/lefthook/eslint-lint

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,50 +10,56 @@ if [ -z "$files" ]; then
1010
exit 0
1111
fi
1212

13-
# Separate files into root and Pro directories
14-
root_files=$(echo "$files" | grep -v '^react_on_rails_pro/' || true)
15-
pro_files=$(echo "$files" | grep '^react_on_rails_pro/' || true)
13+
# Separate files into different directories
14+
# react_on_rails_pro/ has its own ESLint config
15+
# packages/react-on-rails-pro/ uses root ESLint config
16+
react_on_rails_pro_files=$(echo "$files" | grep '^react_on_rails_pro/' || true)
17+
packages_pro_files=$(echo "$files" | grep '^packages/react-on-rails-pro/' || true)
18+
root_files=$(echo "$files" | grep -v '^react_on_rails_pro/' | grep -v '^packages/react-on-rails-pro/' || true)
1619

1720
exit_code=0
1821

19-
# Lint root files
20-
if [ -n "$root_files" ]; then
22+
# Lint root files (includes packages/react-on-rails-pro)
23+
root_and_packages_pro_files="$root_files $packages_pro_files"
24+
root_and_packages_pro_files=$(echo "$root_and_packages_pro_files" | xargs) # trim whitespace
25+
26+
if [ -n "$root_and_packages_pro_files" ]; then
2127
if [ "$CONTEXT" = "all-changed" ]; then
2228
echo "🔍 ESLint on root changed files:"
2329
else
2430
echo "🔍 ESLint on root $CONTEXT files:"
2531
fi
26-
printf " %s\n" $root_files
32+
printf " %s\n" $root_and_packages_pro_files
2733

28-
if ! yarn run eslint $root_files --report-unused-disable-directives --fix; then
34+
if ! yarn run eslint $root_and_packages_pro_files --report-unused-disable-directives --fix; then
2935
exit_code=1
3036
fi
3137

3238
# Re-stage files if running on staged or all-changed context
3339
if [ "$CONTEXT" = "staged" ] || [ "$CONTEXT" = "all-changed" ]; then
34-
echo $root_files | xargs -r git add
40+
echo $root_and_packages_pro_files | xargs -r git add
3541
fi
3642
fi
3743

38-
# Lint Pro files (using Pro's ESLint config)
39-
if [ -n "$pro_files" ]; then
44+
# Lint react_on_rails_pro files (using Pro gem's ESLint config)
45+
if [ -n "$react_on_rails_pro_files" ]; then
4046
if [ "$CONTEXT" = "all-changed" ]; then
41-
echo "🔍 ESLint on Pro changed files:"
47+
echo "🔍 ESLint on react_on_rails_pro changed files:"
4248
else
43-
echo "🔍 ESLint on Pro $CONTEXT files:"
49+
echo "🔍 ESLint on react_on_rails_pro $CONTEXT files:"
4450
fi
45-
printf " %s\n" $pro_files
51+
printf " %s\n" $react_on_rails_pro_files
4652

4753
# Strip react_on_rails_pro/ prefix for running in Pro directory
48-
pro_files_relative=$(echo "$pro_files" | sed 's|^react_on_rails_pro/||')
54+
react_on_rails_pro_files_relative=$(echo "$react_on_rails_pro_files" | sed 's|^react_on_rails_pro/||')
4955

50-
if ! (cd react_on_rails_pro && yarn run eslint $pro_files_relative --report-unused-disable-directives --fix); then
56+
if ! (cd react_on_rails_pro && yarn run eslint $react_on_rails_pro_files_relative --report-unused-disable-directives --fix); then
5157
exit_code=1
5258
fi
5359

5460
# Re-stage files if running on staged or all-changed context
5561
if [ "$CONTEXT" = "staged" ] || [ "$CONTEXT" = "all-changed" ]; then
56-
echo $pro_files | xargs -r git add
62+
echo $react_on_rails_pro_files | xargs -r git add
5763
fi
5864
fi
5965

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
# Improved Error Messages for React on Rails
2+
3+
React on Rails provides enhanced error messages with actionable solutions to help you quickly identify and fix issues.
4+
5+
## Smart Error Messages
6+
7+
React on Rails now provides contextual error messages that:
8+
9+
- Identify the specific problem
10+
- Suggest concrete solutions with code examples
11+
- Offer similar component names when typos occur
12+
- Prioritize auto-bundling as the recommended approach
13+
14+
## Auto-Bundling: The Recommended Approach
15+
16+
React on Rails supports automatic bundling, which eliminates the need for manual component registration.
17+
18+
### Benefits of Auto-Bundling
19+
20+
- **No manual registration**: Components are automatically available
21+
- **Simplified development**: Just create the component file and use it
22+
- **Automatic code splitting**: Each component gets its own bundle
23+
- **Better performance**: Only load what you need
24+
25+
### How to Use Auto-Bundling
26+
27+
1. **Enable in your view:**
28+
29+
```erb
30+
<%= react_component("MyComponent", props: { data: @data }, auto_load_bundle: true) %>
31+
```
32+
33+
2. **Place component in the correct directory:**
34+
35+
```
36+
app/javascript/components/
37+
└── MyComponent/
38+
└── MyComponent.jsx # Must export default
39+
```
40+
41+
3. **Generate bundles:**
42+
Bundles are automatically generated during asset precompilation via the Shakapacker precompile hook. For manual generation during development:
43+
```bash
44+
bundle exec rake react_on_rails:generate_packs
45+
```
46+
47+
That's it! No manual registration needed.
48+
49+
## Error Message Examples
50+
51+
### Component Not Registered
52+
53+
**Before:**
54+
55+
```
56+
Component 'HelloWorld' not found
57+
```
58+
59+
**After:**
60+
61+
````
62+
❌ React on Rails Error
63+
64+
🔍 Problem:
65+
Component 'HelloWorld' was not found in the component registry.
66+
67+
💡 Suggested Solution:
68+
69+
🚀 Recommended: Use Auto-Bundling (No Registration Required!)
70+
71+
1. Enable auto-bundling in your view:
72+
<%= react_component("HelloWorld", props: {}, auto_load_bundle: true) %>
73+
74+
2. Place your component in the components directory:
75+
app/javascript/components/HelloWorld/HelloWorld.jsx
76+
77+
Component structure:
78+
components/
79+
└── HelloWorld/
80+
└── HelloWorld.jsx (must export default)
81+
82+
3. Generate the bundle:
83+
bundle exec rake react_on_rails:generate_packs
84+
85+
✨ That's it! No manual registration needed.
86+
87+
─────────────────────────────────────────────
88+
89+
Alternative: Manual Registration
90+
91+
If you prefer manual registration:
92+
1. Register in your entry file:
93+
ReactOnRails.register({ HelloWorld: HelloWorld });
94+
95+
2. Import the component:
96+
import HelloWorld from './components/HelloWorld';
97+
98+
3. Include the bundle in your layout (e.g., `app/views/layouts/application.html.erb`):
99+
```erb
100+
<%= javascript_pack_tag 'application' %>
101+
<%= stylesheet_pack_tag 'application' %>
102+
````
103+
104+
```
105+
106+
### Enhanced SSR Errors
107+
108+
Server-side rendering errors now include:
109+
110+
- Colored, formatted output for better readability
111+
- Specific error patterns detection (window/document undefined, hydration mismatches)
112+
- Actionable troubleshooting steps
113+
- Props and JavaScript code context
114+
- Console message replay
115+
116+
**Example SSR Error:**
117+
118+
```
119+
120+
❌ React on Rails Server Rendering Error
121+
122+
Component: HelloWorldApp
123+
124+
📋 Error Details:
125+
ReferenceError: window is not defined
126+
127+
💡 Troubleshooting Suggestions:
128+
129+
⚠️ Browser API (window/document) accessed during server render
130+
131+
The component tried to access 'window' which doesn't exist on the server.
132+
133+
Solutions:
134+
• Wrap browser API calls in useEffect:
135+
useEffect(() => { /_ DOM operations here _/ }, [])
136+
137+
• Check if running in browser:
138+
if (typeof window !== 'undefined') { /_ browser code _/ }
139+
140+
• Use dynamic import for browser-only code
141+
142+
````
143+
144+
## Ruby Configuration
145+
146+
### Using SmartError Directly
147+
148+
You can create custom smart errors in your Rails code:
149+
150+
```ruby
151+
raise ReactOnRails::SmartError.new(
152+
error_type: :component_not_registered,
153+
component_name: "MyComponent",
154+
additional_context: {
155+
available_components: ReactOnRails::PackerUtils.registered_components
156+
}
157+
)
158+
````
159+
160+
### Error Types
161+
162+
Available error types:
163+
164+
- `:component_not_registered` - Component not found in registry
165+
- `:missing_auto_loaded_bundle` - Auto-bundle file not found
166+
- `:hydration_mismatch` - Client/server HTML mismatch
167+
- `:server_render_error` - General SSR error
168+
- `:configuration_error` - Invalid configuration
169+
170+
## Best Practices
171+
172+
1. **Prefer auto-bundling** for new components to avoid registration issues
173+
2. **Use server-side rendering** to catch React component errors, hydration mismatches, and SSR-specific issues (like accessing browser APIs) during development before they reach production
174+
3. **Check error messages carefully** - they include specific solutions
175+
4. **Keep components in standard locations** for better error detection
176+
177+
## Troubleshooting
178+
179+
If you encounter issues:
180+
181+
1. **Check component registration:**
182+
183+
```bash
184+
bundle exec rake react_on_rails:doctor
185+
```
186+
187+
2. **Verify auto-bundle generation:**
188+
189+
```bash
190+
bundle exec rake react_on_rails:generate_packs
191+
```
192+
193+
3. **Enable detailed errors** in development:
194+
```bash
195+
FULL_TEXT_ERRORS=true rails server
196+
```
197+
198+
## Related Documentation
199+
200+
- [Auto-Bundling Guide](../core-concepts/auto-bundling-file-system-based-automated-bundle-generation.md)
201+
- [Server Rendering](../core-concepts/react-server-rendering.md)
202+
- [JavaScript API (Component Registration)](../api-reference/javascript-api.md)

eslint.config.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -158,14 +158,6 @@ const config = tsEslint.config([
158158
'import/extensions': ['error', 'ignorePackages'],
159159
},
160160
},
161-
{
162-
files: ['packages/react-on-rails-pro/**/*'],
163-
rules: {
164-
// Disable import/named for pro package - can't resolve monorepo workspace imports
165-
// TypeScript compiler validates these imports
166-
'import/named': 'off',
167-
},
168-
},
169161
{
170162
files: ['**/*.server.ts', '**/*.server.tsx'],
171163
plugins: {
@@ -228,6 +220,22 @@ const config = tsEslint.config([
228220
'@typescript-eslint/restrict-template-expressions': 'off',
229221
},
230222
},
223+
{
224+
files: ['packages/react-on-rails-pro/**/*'],
225+
rules: {
226+
// Disable import rules for pro package - can't resolve monorepo workspace imports
227+
// TypeScript compiler validates these imports
228+
'import/named': 'off',
229+
'import/no-unresolved': 'off',
230+
// Disable unsafe type rules - Pro package uses internal APIs with complex types
231+
'@typescript-eslint/no-unsafe-assignment': 'off',
232+
'@typescript-eslint/no-unsafe-call': 'off',
233+
'@typescript-eslint/no-unsafe-member-access': 'off',
234+
'@typescript-eslint/no-unsafe-return': 'off',
235+
'@typescript-eslint/no-unsafe-argument': 'off',
236+
'@typescript-eslint/no-redundant-type-constituents': 'off',
237+
},
238+
},
231239
{
232240
files: ['**/app-react16/**/*'],
233241
rules: {

lib/generators/react_on_rails/base_generator.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -412,8 +412,13 @@ def configure_rspack_in_shakapacker
412412
puts Rainbow("🔧 Configuring Shakapacker for Rspack...").yellow
413413

414414
# Parse YAML config properly to avoid fragile regex manipulation
415-
config = YAML.load_file(shakapacker_config_path)
416-
415+
# Support both old and new Psych versions
416+
config = begin
417+
YAML.load_file(shakapacker_config_path, aliases: true)
418+
rescue ArgumentError
419+
# Older Psych versions don't support the aliases parameter
420+
YAML.load_file(shakapacker_config_path)
421+
end
417422
# Update default section
418423
config["default"] ||= {}
419424
config["default"]["assets_bundler"] = "rspack"

lib/react_on_rails/helper.rb

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
# 1. The white spacing in this file matters!
88
# 2. Keep all #{some_var} fully to the left so that all indentation is done evenly in that var
99
require "react_on_rails/prerender_error"
10+
require "react_on_rails/smart_error"
1011
require "addressable/uri"
1112
require "react_on_rails/utils"
1213
require "react_on_rails/json_output"
@@ -638,14 +639,11 @@ def in_mailer?
638639
end
639640

640641
def raise_missing_autoloaded_bundle(react_component_name)
641-
msg = <<~MSG
642-
**ERROR** ReactOnRails: Component "#{react_component_name}" is configured as "auto_load_bundle: true"
643-
but the generated component entrypoint, which should have been at #{generated_components_pack_path(react_component_name)},
644-
is missing. You might want to check that this component is in a directory named "#{ReactOnRails.configuration.components_subdirectory}"
645-
& that "bundle exec rake react_on_rails:generate_packs" has been run.
646-
MSG
647-
648-
raise ReactOnRails::Error, msg
642+
raise ReactOnRails::SmartError.new(
643+
error_type: :missing_auto_loaded_bundle,
644+
component_name: react_component_name,
645+
expected_path: generated_components_pack_path(react_component_name)
646+
)
649647
end
650648
end
651649
end

0 commit comments

Comments
 (0)