Skip to content

Commit cf17e36

Browse files
justin808claude
andcommitted
Fix component registration race condition by using defer for all scripts
Changes the default loading strategy from :async to :defer to prevent race conditions where React hydration starts before component registrations complete. Root cause: With async script loading, both client-bundle and generated component packs can execute in any order. When client-bundle executes first, React attempts to hydrate components that haven't been registered yet, causing "Could not find component registered with name X" errors. Solution: - Default generated_component_packs_loading_strategy to :defer (was :async) - Use defer: true for client-bundle in dummy app layout - Update comments to reflect new strategy With defer, scripts execute in DOM order after parsing, ensuring: 1. Generated component packs load first 2. Components register before main bundle executes 3. React hydration finds all registered components Fixes CI failures in: - spec/system/integration_spec.rb[1:1:6:1:2] - spec/system/integration_spec.rb[1:1:6:2:2] 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent c06fe70 commit cf17e36

File tree

5 files changed

+317
-21
lines changed

5 files changed

+317
-21
lines changed

CLAUDE.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,25 @@ Pre-commit hooks automatically run:
5151

5252
**CRITICAL: NEVER wait for CI to verify fixes. Always replicate failures locally first.**
5353

54+
#### Switch Between CI Configurations (🔥 NEW!)
55+
56+
The project tests against two configurations:
57+
- **Latest**: Ruby 3.4, Node 22, Shakapacker 9.3.0, React 19 (runs on all PRs)
58+
- **Minimum**: Ruby 3.2, Node 20, Shakapacker 8.2.0, React 18 (runs only on master)
59+
60+
```bash
61+
# Check your current configuration
62+
bin/ci-switch-config status
63+
64+
# Switch to minimum dependencies (for debugging minimum CI failures)
65+
bin/ci-switch-config minimum
66+
67+
# Switch back to latest dependencies
68+
bin/ci-switch-config latest
69+
```
70+
71+
**See `SWITCHING_CI_CONFIGS.md` for detailed usage and troubleshooting.**
72+
5473
#### Re-run Failed CI Jobs (🔥 NEW - Most Efficient!)
5574

5675
```bash

SWITCHING_CI_CONFIGS.md

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
# Switching Between CI Configurations Locally
2+
3+
This guide explains how to switch between different CI test configurations locally to replicate CI failures.
4+
5+
## Quick Start
6+
7+
```bash
8+
# Check your current configuration
9+
bin/ci-switch-config status
10+
11+
# Switch to minimum dependencies (Ruby 3.2, Node 20)
12+
bin/ci-switch-config minimum
13+
14+
# Switch back to latest dependencies (Ruby 3.4, Node 22)
15+
bin/ci-switch-config latest
16+
```
17+
18+
## CI Configurations
19+
20+
The project runs tests against two configurations:
21+
22+
### Latest (Default Development)
23+
24+
- **Ruby**: 3.4
25+
- **Node**: 22
26+
- **Shakapacker**: 9.3.0
27+
- **React**: 19.0.0
28+
- **Dependencies**: Latest versions with `--frozen-lockfile`
29+
- **When it runs**: Always on PRs and master
30+
31+
### Minimum (Compatibility Testing)
32+
33+
- **Ruby**: 3.2
34+
- **Node**: 20
35+
- **Shakapacker**: 8.2.0
36+
- **React**: 18.0.0
37+
- **Dependencies**: Minimum supported versions
38+
- **When it runs**: Only on master branch
39+
40+
## When to Switch Configurations
41+
42+
**Switch to minimum when:**
43+
44+
- CI fails on `dummy-app-integration-tests (3.2, 20, minimum)` but passes on latest
45+
- You're debugging compatibility with older dependencies
46+
- You want to verify minimum version support before releasing
47+
48+
**Switch to latest when:**
49+
50+
- You're done testing minimum configuration
51+
- You want to return to normal development
52+
- CI failures are on latest configuration
53+
54+
## Prerequisites
55+
56+
You must have [asdf](https://asdf-vm.com/) installed to manage Ruby and Node versions.
57+
58+
```bash
59+
# Install asdf if needed (macOS with Homebrew)
60+
brew install asdf
61+
echo -e "\n. $(brew --prefix asdf)/libexec/asdf.sh" >> ~/.zshrc
62+
source ~/.zshrc
63+
64+
# Install plugins
65+
asdf plugin add ruby
66+
asdf plugin add nodejs
67+
```
68+
69+
## Detailed Usage
70+
71+
### 1. Check Current Configuration
72+
73+
```bash
74+
bin/ci-switch-config status
75+
```
76+
77+
This shows:
78+
79+
- Current Ruby and Node versions
80+
- Dependency versions (Shakapacker, React)
81+
- Which configuration you're currently on
82+
83+
### 2. Switch to Minimum Configuration
84+
85+
```bash
86+
bin/ci-switch-config minimum
87+
```
88+
89+
This will:
90+
91+
1. Create `.tool-versions` with Ruby 3.2.8 and Node 20.18.1
92+
2. Run `script/convert` to downgrade dependencies:
93+
- Shakapacker 9.3.0 → 8.2.0
94+
- React 19.0.0 → 18.0.0
95+
- Remove ESLint and other packages incompatible with Node 20
96+
3. Clean `node_modules` and `yarn.lock`
97+
4. Reinstall dependencies without `--frozen-lockfile`
98+
5. Clean and reinstall spec/dummy dependencies
99+
100+
**After switching, run:**
101+
102+
```bash
103+
# Reload your shell to pick up new Ruby/Node versions
104+
cd /Users/justin/conductor/react_on_rails/.conductor/kuwait-v1
105+
asdf current
106+
107+
# Build and test
108+
rake node_package
109+
cd spec/dummy
110+
bin/shakapacker-precompile-hook
111+
RAILS_ENV=test bin/shakapacker
112+
cd ../..
113+
bundle exec rake run_rspec:all_dummy
114+
```
115+
116+
### 3. Switch Back to Latest Configuration
117+
118+
```bash
119+
bin/ci-switch-config latest
120+
```
121+
122+
This will:
123+
124+
1. Create `.tool-versions` with Ruby 3.4.3 and Node 22.12.0
125+
2. Restore files from git (reverting changes made by `script/convert`)
126+
3. Clean `node_modules` and `yarn.lock`
127+
4. Reinstall dependencies with `--frozen-lockfile`
128+
5. Clean and reinstall spec/dummy dependencies
129+
130+
**After switching, run:**
131+
132+
```bash
133+
# Reload your shell to pick up new Ruby/Node versions
134+
cd /Users/justin/conductor/react_on_rails/.conductor/kuwait-v1
135+
asdf current
136+
137+
# Build and test
138+
rake node_package
139+
cd spec/dummy
140+
bin/shakapacker-precompile-hook
141+
RAILS_ENV=test bin/shakapacker
142+
cd ../..
143+
bundle exec rake run_rspec:all_dummy
144+
```
145+
146+
## What Gets Modified
147+
148+
When switching to **minimum**, these files are modified:
149+
150+
- `.tool-versions` - Ruby/Node versions
151+
- `Gemfile.development_dependencies` - Shakapacker gem version
152+
- `package.json` - React versions, dev dependencies removed
153+
- `spec/dummy/package.json` - React and Shakapacker versions
154+
- `packages/react-on-rails-pro/package.json` - Test scripts modified
155+
- `node_modules/`, `yarn.lock` - Cleaned and regenerated
156+
- `spec/dummy/node_modules/`, `spec/dummy/yarn.lock` - Cleaned and regenerated
157+
158+
When switching to **latest**, these files are restored from git.
159+
160+
## Common Workflows
161+
162+
### Debugging a Minimum Config CI Failure
163+
164+
```bash
165+
# 1. Check current config
166+
bin/ci-switch-config status
167+
168+
# 2. Switch to minimum
169+
bin/ci-switch-config minimum
170+
171+
# 3. Reload shell
172+
cd /Users/justin/conductor/react_on_rails/.conductor/kuwait-v1
173+
174+
# 4. Verify versions changed
175+
ruby --version # Should show 3.2.x
176+
node --version # Should show v20.x
177+
178+
# 5. Build and test
179+
rake node_package
180+
cd spec/dummy
181+
bin/shakapacker-precompile-hook
182+
RAILS_ENV=test bin/shakapacker
183+
cd ../..
184+
185+
# 6. Run the failing tests
186+
bundle exec rake run_rspec:all_dummy
187+
188+
# 7. Fix the issue
189+
190+
# 8. Switch back when done
191+
bin/ci-switch-config latest
192+
```
193+
194+
### Quick Test in Both Configurations
195+
196+
```bash
197+
# Test in latest (current default)
198+
bin/ci-switch-config status
199+
bundle exec rake run_rspec:all_dummy
200+
201+
# Switch and test in minimum
202+
bin/ci-switch-config minimum
203+
rake node_package
204+
cd spec/dummy && bin/shakapacker-precompile-hook && RAILS_ENV=test bin/shakapacker && cd ../..
205+
bundle exec rake run_rspec:all_dummy
206+
207+
# Switch back
208+
bin/ci-switch-config latest
209+
```
210+
211+
## Troubleshooting
212+
213+
### "asdf: No version is set for ruby"
214+
215+
After switching, you need to reload your shell:
216+
217+
```bash
218+
cd /Users/justin/conductor/react_on_rails/.conductor/kuwait-v1
219+
# The cd command will trigger asdf to load the new versions
220+
ruby --version # Verify it changed
221+
```
222+
223+
### Ruby/Node version didn't change
224+
225+
If asdf doesn't automatically switch:
226+
227+
```bash
228+
asdf install # Install missing versions from .tool-versions
229+
asdf reshim ruby
230+
asdf reshim nodejs
231+
```
232+
233+
### Yarn install fails
234+
235+
If you get package resolution errors:
236+
237+
```bash
238+
# Clean everything and try again
239+
rm -rf node_modules yarn.lock spec/dummy/node_modules spec/dummy/yarn.lock
240+
yarn install
241+
cd spec/dummy && yarn install
242+
```
243+
244+
### Git complains about modified files
245+
246+
The script will warn you if you have uncommitted changes. You can:
247+
248+
- Commit or stash your changes first, OR
249+
- Proceed (script will ask for confirmation)
250+
251+
### Switching back doesn't restore everything
252+
253+
If `git restore` doesn't work:
254+
255+
```bash
256+
# Manually restore from git
257+
git restore Gemfile.development_dependencies package.json spec/dummy/package.json packages/react-on-rails-pro/package.json
258+
259+
# Then run latest again
260+
bin/ci-switch-config latest
261+
```
262+
263+
## Integration with CI Debugging Tools
264+
265+
This script works well with the other CI debugging tools:
266+
267+
```bash
268+
# 1. Check what failed in CI
269+
bin/ci-rerun-failures
270+
271+
# 2. If it's a minimum config failure, switch
272+
bin/ci-switch-config minimum
273+
274+
# 3. Run the specific failing tests
275+
pbpaste | bin/ci-run-failed-specs
276+
277+
# 4. Switch back when done
278+
bin/ci-switch-config latest
279+
```
280+
281+
## See Also
282+
283+
- `CLAUDE.md` - Main development guide with CI debugging info
284+
- `bin/ci-rerun-failures` - Re-run only failed CI jobs locally
285+
- `bin/ci-run-failed-specs` - Run specific failing RSpec examples
286+
- `bin/ci-local` - Smart test detection based on changes

lib/react_on_rails/configuration.rb

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,9 @@ def check_component_registry_timeout
154154
raise ReactOnRails::Error, "component_registry_timeout must be a positive integer"
155155
end
156156

157-
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
157+
# rubocop:disable Metrics/CyclomaticComplexity
158158
def validate_generated_component_packs_loading_strategy
159-
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
159+
# rubocop:enable Metrics/CyclomaticComplexity
160160

161161
if defer_generated_component_packs
162162
if %i[async sync].include?(generated_component_packs_loading_strategy)
@@ -176,13 +176,11 @@ def validate_generated_component_packs_loading_strategy
176176
1. Use :defer or :sync loading strategy instead of :async
177177
2. Upgrade to Shakapacker v8.2.0 or above to enable async script loading
178178
MSG
179-
if PackerUtils.supports_async_loading?
180-
self.generated_component_packs_loading_strategy ||= :async
181-
elsif generated_component_packs_loading_strategy.nil?
182-
# Use defer as the default for older Shakapacker versions to ensure
183-
# generated component packs load and register components before main bundle executes
179+
if generated_component_packs_loading_strategy.nil?
180+
# Use defer as the default to ensure generated component packs load and register
181+
# components before main bundle executes, avoiding race conditions with async loading
184182
self.generated_component_packs_loading_strategy = :defer
185-
elsif generated_component_packs_loading_strategy == :async
183+
elsif generated_component_packs_loading_strategy == :async && !PackerUtils.supports_async_loading?
186184
raise ReactOnRails::Error, "**ERROR** #{msg}"
187185
end
188186

spec/dummy/app/views/layouts/application.html.erb

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,10 @@
99

1010
<%= yield :head %>
1111

12-
<%# Conditionally use defer: true for pages with Redux shared stores (inline registration).
13-
Modern apps should use async: true for optimal performance. See docs for details:
14-
docs/building-features/streaming-server-rendering.md %>
15-
<% if uses_redux_shared_store? %>
16-
<%# defer: true required for Redux shared stores with inline component registration %>
17-
<%= javascript_pack_tag('client-bundle', 'data-turbo-track': 'reload', defer: true) %>
18-
<% else %>
19-
<%# async: true is the recommended approach for modern apps (Shakapacker >= 8.2.0) %>
20-
<%= javascript_pack_tag('client-bundle', 'data-turbo-track': 'reload', async: true) %>
21-
<% end %>
12+
<%# Use defer: true to ensure proper script execution order.
13+
When using generated component packs (auto_load_bundle), defer ensures
14+
component registrations complete before React hydration begins. %>
15+
<%= javascript_pack_tag('client-bundle', 'data-turbo-track': 'reload', defer: true) %>
2216

2317
<%= csrf_meta_tags %>
2418
</head>

spec/dummy/config/initializers/react_on_rails.rb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ def self.adjust_props_for_client_side_hydration(component_name, props)
4242
config.components_subdirectory = "startup"
4343
config.auto_load_bundle = true
4444
config.immediate_hydration = false
45-
# Don't explicitly set generated_component_packs_loading_strategy - let it default based on Shakapacker version
46-
# - Shakapacker >= 8.2.0: defaults to :async (optimal performance)
47-
# - Shakapacker < 8.2.0: defaults to :defer (ensures proper component registration order)
45+
# Don't explicitly set generated_component_packs_loading_strategy - let it default to :defer
46+
# which ensures generated component packs load and register components before main bundle executes
4847
end

0 commit comments

Comments
 (0)