Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ Changes since the last non-beta release.

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

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

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

#### Changed
Expand Down
38 changes: 22 additions & 16 deletions bin/lefthook/eslint-lint
Original file line number Diff line number Diff line change
Expand Up @@ -10,50 +10,56 @@ if [ -z "$files" ]; then
exit 0
fi

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

exit_code=0

# Lint root files
if [ -n "$root_files" ]; then
# Lint root files (includes packages/react-on-rails-pro)
root_and_packages_pro_files="$root_files $packages_pro_files"
root_and_packages_pro_files=$(echo "$root_and_packages_pro_files" | xargs) # trim whitespace

if [ -n "$root_and_packages_pro_files" ]; then
if [ "$CONTEXT" = "all-changed" ]; then
echo "🔍 ESLint on root changed files:"
else
echo "🔍 ESLint on root $CONTEXT files:"
fi
printf " %s\n" $root_files
printf " %s\n" $root_and_packages_pro_files

if ! yarn run eslint $root_files --report-unused-disable-directives --fix; then
if ! yarn run eslint $root_and_packages_pro_files --report-unused-disable-directives --fix; then
exit_code=1
fi

# Re-stage files if running on staged or all-changed context
if [ "$CONTEXT" = "staged" ] || [ "$CONTEXT" = "all-changed" ]; then
echo $root_files | xargs -r git add
echo $root_and_packages_pro_files | xargs -r git add
fi
fi

# Lint Pro files (using Pro's ESLint config)
if [ -n "$pro_files" ]; then
# Lint react_on_rails_pro files (using Pro gem's ESLint config)
if [ -n "$react_on_rails_pro_files" ]; then
if [ "$CONTEXT" = "all-changed" ]; then
echo "🔍 ESLint on Pro changed files:"
echo "🔍 ESLint on react_on_rails_pro changed files:"
else
echo "🔍 ESLint on Pro $CONTEXT files:"
echo "🔍 ESLint on react_on_rails_pro $CONTEXT files:"
fi
printf " %s\n" $pro_files
printf " %s\n" $react_on_rails_pro_files

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

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

# Re-stage files if running on staged or all-changed context
if [ "$CONTEXT" = "staged" ] || [ "$CONTEXT" = "all-changed" ]; then
echo $pro_files | xargs -r git add
echo $react_on_rails_pro_files | xargs -r git add
fi
fi

Expand Down
202 changes: 202 additions & 0 deletions docs/guides/improved-error-messages.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
# Improved Error Messages for React on Rails

React on Rails provides enhanced error messages with actionable solutions to help you quickly identify and fix issues.

## Smart Error Messages

React on Rails now provides contextual error messages that:

- Identify the specific problem
- Suggest concrete solutions with code examples
- Offer similar component names when typos occur
- Prioritize auto-bundling as the recommended approach

## Auto-Bundling: The Recommended Approach

React on Rails supports automatic bundling, which eliminates the need for manual component registration.

### Benefits of Auto-Bundling

- **No manual registration**: Components are automatically available
- **Simplified development**: Just create the component file and use it
- **Automatic code splitting**: Each component gets its own bundle
- **Better performance**: Only load what you need

### How to Use Auto-Bundling

1. **Enable in your view:**

```erb
<%= react_component("MyComponent", props: { data: @data }, auto_load_bundle: true) %>
```

2. **Place component in the correct directory:**

```
app/javascript/components/
└── MyComponent/
└── MyComponent.jsx # Must export default
```

3. **Generate bundles:**
Bundles are automatically generated during asset precompilation via the Shakapacker precompile hook. For manual generation during development:
```bash
bundle exec rake react_on_rails:generate_packs
```

That's it! No manual registration needed.

## Error Message Examples

### Component Not Registered

**Before:**

```
Component 'HelloWorld' not found
```

**After:**

````
❌ React on Rails Error

🔍 Problem:
Component 'HelloWorld' was not found in the component registry.

💡 Suggested Solution:

🚀 Recommended: Use Auto-Bundling (No Registration Required!)

1. Enable auto-bundling in your view:
<%= react_component("HelloWorld", props: {}, auto_load_bundle: true) %>

2. Place your component in the components directory:
app/javascript/components/HelloWorld/HelloWorld.jsx

Component structure:
components/
└── HelloWorld/
└── HelloWorld.jsx (must export default)

3. Generate the bundle:
bundle exec rake react_on_rails:generate_packs

✨ That's it! No manual registration needed.

─────────────────────────────────────────────

Alternative: Manual Registration

If you prefer manual registration:
1. Register in your entry file:
ReactOnRails.register({ HelloWorld: HelloWorld });

2. Import the component:
import HelloWorld from './components/HelloWorld';

3. Include the bundle in your layout (e.g., `app/views/layouts/application.html.erb`):
```erb
<%= javascript_pack_tag 'application' %>
<%= stylesheet_pack_tag 'application' %>
````

```

### Enhanced SSR Errors

Server-side rendering errors now include:

- Colored, formatted output for better readability
- Specific error patterns detection (window/document undefined, hydration mismatches)
- Actionable troubleshooting steps
- Props and JavaScript code context
- Console message replay

**Example SSR Error:**

```

❌ React on Rails Server Rendering Error

Component: HelloWorldApp

📋 Error Details:
ReferenceError: window is not defined

💡 Troubleshooting Suggestions:

⚠️ Browser API (window/document) accessed during server render

The component tried to access 'window' which doesn't exist on the server.

Solutions:
• Wrap browser API calls in useEffect:
useEffect(() => { /_ DOM operations here _/ }, [])

• Check if running in browser:
if (typeof window !== 'undefined') { /_ browser code _/ }

• Use dynamic import for browser-only code

````

## Ruby Configuration

### Using SmartError Directly

You can create custom smart errors in your Rails code:

```ruby
raise ReactOnRails::SmartError.new(
error_type: :component_not_registered,
component_name: "MyComponent",
additional_context: {
available_components: ReactOnRails::PackerUtils.registered_components
}
)
````

### Error Types

Available error types:

- `:component_not_registered` - Component not found in registry
- `:missing_auto_loaded_bundle` - Auto-bundle file not found
- `:hydration_mismatch` - Client/server HTML mismatch
- `:server_render_error` - General SSR error
- `:configuration_error` - Invalid configuration

## Best Practices

1. **Prefer auto-bundling** for new components to avoid registration issues
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
3. **Check error messages carefully** - they include specific solutions
4. **Keep components in standard locations** for better error detection

## Troubleshooting

If you encounter issues:

1. **Check component registration:**

```bash
bundle exec rake react_on_rails:doctor
```

2. **Verify auto-bundle generation:**

```bash
bundle exec rake react_on_rails:generate_packs
```

3. **Enable detailed errors** in development:
```bash
FULL_TEXT_ERRORS=true rails server
```

## Related Documentation

- [Auto-Bundling Guide](../core-concepts/auto-bundling-file-system-based-automated-bundle-generation.md)
- [Server Rendering](../core-concepts/react-server-rendering.md)
- [JavaScript API (Component Registration)](../api-reference/javascript-api.md)
24 changes: 16 additions & 8 deletions eslint.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,14 +158,6 @@ const config = tsEslint.config([
'import/extensions': ['error', 'ignorePackages'],
},
},
{
files: ['packages/react-on-rails-pro/**/*'],
rules: {
// Disable import/named for pro package - can't resolve monorepo workspace imports
// TypeScript compiler validates these imports
'import/named': 'off',
},
},
{
files: ['**/*.server.ts', '**/*.server.tsx'],
plugins: {
Expand Down Expand Up @@ -228,6 +220,22 @@ const config = tsEslint.config([
'@typescript-eslint/restrict-template-expressions': 'off',
},
},
{
files: ['packages/react-on-rails-pro/**/*'],
rules: {
// Disable import rules for pro package - can't resolve monorepo workspace imports
// TypeScript compiler validates these imports
'import/named': 'off',
'import/no-unresolved': 'off',
// Disable unsafe type rules - Pro package uses internal APIs with complex types
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-redundant-type-constituents': 'off',
},
},
{
files: ['**/app-react16/**/*'],
rules: {
Expand Down
9 changes: 7 additions & 2 deletions lib/generators/react_on_rails/base_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -412,8 +412,13 @@ def configure_rspack_in_shakapacker
puts Rainbow("🔧 Configuring Shakapacker for Rspack...").yellow

# Parse YAML config properly to avoid fragile regex manipulation
config = YAML.load_file(shakapacker_config_path)

# Support both old and new Psych versions
config = begin
YAML.load_file(shakapacker_config_path, aliases: true)
rescue ArgumentError
# Older Psych versions don't support the aliases parameter
YAML.load_file(shakapacker_config_path)
end
# Update default section
config["default"] ||= {}
config["default"]["assets_bundler"] = "rspack"
Expand Down
14 changes: 6 additions & 8 deletions lib/react_on_rails/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
# 1. The white spacing in this file matters!
# 2. Keep all #{some_var} fully to the left so that all indentation is done evenly in that var
require "react_on_rails/prerender_error"
require "react_on_rails/smart_error"
require "addressable/uri"
require "react_on_rails/utils"
require "react_on_rails/json_output"
Expand Down Expand Up @@ -638,14 +639,11 @@ def in_mailer?
end

def raise_missing_autoloaded_bundle(react_component_name)
msg = <<~MSG
**ERROR** ReactOnRails: Component "#{react_component_name}" is configured as "auto_load_bundle: true"
but the generated component entrypoint, which should have been at #{generated_components_pack_path(react_component_name)},
is missing. You might want to check that this component is in a directory named "#{ReactOnRails.configuration.components_subdirectory}"
& that "bundle exec rake react_on_rails:generate_packs" has been run.
MSG

raise ReactOnRails::Error, msg
raise ReactOnRails::SmartError.new(
error_type: :missing_auto_loaded_bundle,
component_name: react_component_name,
expected_path: generated_components_pack_path(react_component_name)
)
end
end
end
Expand Down
Loading
Loading