Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
4 changes: 2 additions & 2 deletions react_on_rails/spec/dummy/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ GEM
rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0)
semantic_range (3.1.0)
shakapacker (9.3.0)
shakapacker (9.4.0)
activesupport (>= 5.2)
package_json
rack-proxy (>= 0.6.1)
Expand Down Expand Up @@ -461,7 +461,7 @@ DEPENDENCIES
sass-rails (~> 6.0)
sdoc
selenium-webdriver (= 4.9.0)
shakapacker (= 9.3.0)
shakapacker (= 9.4.0)
spring (~> 4.0)
sprockets (~> 4.0)
sqlite3 (~> 1.6)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import React from 'react';
import { combineReducers, applyMiddleware, createStore } from 'redux';
import { Provider } from 'react-redux';
import thunkMiddleware from 'redux-thunk';
import { thunk } from 'redux-thunk';
import ReactDOM from 'react-dom';

import reducers from '../../app/reducers/reducersIndex';
Expand All @@ -29,7 +29,7 @@ export default (props, railsContext, domNodeId) => {

// This is where we'll put in the middleware for the async function. Placeholder.
// store will have helloWorldData as a top level property
const store = createStore(combinedReducer, combinedProps, applyMiddleware(thunkMiddleware));
const store = createStore(combinedReducer, combinedProps, applyMiddleware(thunk));

// renderApp is a function required for hot reloading. see
// https://github.com/retroalgic/react-on-rails-hot-minimal/blob/master/client/src/entry.js
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import React from 'react';
import { Helmet } from 'react-helmet';
import { Helmet } from '@dr.pogodin/react-helmet';
import HelloWorld from '../startup/HelloWorld';

// Note: This component expects to be wrapped in a HelmetProvider by its parent.
// For client-side rendering, wrap in HelmetProvider at the app root.
// For server-side rendering, the server entry point provides the HelmetProvider.
const ReactHelmet = (props) => (
<div>
<Helmet>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
// Top level component for simple client side only rendering
import React from 'react';
import { HelmetProvider } from '@dr.pogodin/react-helmet';
import ReactHelmet from '../components/ReactHelmet';

// This works fine, React functional component:
// export default (props) => <ReactHelmet {...props} />;

export default (props) => <ReactHelmet {...props} />;
// HelmetProvider is required by @dr.pogodin/react-helmet for both client and server rendering
export default (props) => (
<HelmetProvider>
<ReactHelmet {...props} />
</HelmetProvider>
);

// Note, the server side has to be a Render-Function

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Top level component for simple client side only rendering
import React from 'react';
import { renderToString } from 'react-dom/server';
import { Helmet } from 'react-helmet';
import ReactHelmet from '../components/ReactHelmet';
import { Helmet, HelmetProvider } from '@dr.pogodin/react-helmet';
import HelloWorld from './HelloWorld';

/*
* Export a function that takes the props and returns an object with { renderedHtml }
Expand All @@ -16,12 +16,27 @@ import ReactHelmet from '../components/ReactHelmet';
* the function could get the property of `.renderFunction = true` added to it.
*/
export default (props, _railsContext) => {
const componentHtml = renderToString(<ReactHelmet {...props} />);
const helmet = Helmet.renderStatic();
// For server-side rendering with @dr.pogodin/react-helmet, we pass a context object
// to HelmetProvider to capture the helmet data per-request (thread-safe)
const helmetContext = {};

const componentHtml = renderToString(
<HelmetProvider context={helmetContext}>
<div>
<Helmet>
<title>Custom page title</title>
</Helmet>
Props: {JSON.stringify(props)}
<HelloWorld {...props} />
</div>
</HelmetProvider>,
);

const { helmet } = helmetContext;

const renderedHtml = {
componentHtml,
title: helmet.title.toString(),
title: helmet ? helmet.title.toString() : '',
};

// Note that this function returns an Object for server rendering.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
// Top level component for simple client side only rendering
import React from 'react';
import { HelmetProvider } from '@dr.pogodin/react-helmet';
import ReactHelmet from '../components/ReactHelmet';

// This works fine, React functional component:
// export default (props) => <ReactHelmet {...props} />;

export default (props) => <ReactHelmet {...props} />;
// HelmetProvider is required by @dr.pogodin/react-helmet for both client and server rendering
export default (props) => (
<HelmetProvider>
<ReactHelmet {...props} />
</HelmetProvider>
);

// Note, the server side has to be a Render-Function

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// function. The point of this is to provide a good error.
import React from 'react';
import { renderToString } from 'react-dom/server';
import { Helmet } from 'react-helmet';
import { HelmetProvider } from '@dr.pogodin/react-helmet';
import ReactHelmet from '../components/ReactHelmet';

/*
Expand All @@ -18,12 +18,17 @@ import ReactHelmet from '../components/ReactHelmet';
* Alternately, the function could get the property of `.renderFunction = true` added to it.
*/
export default (props) => {
const componentHtml = renderToString(<ReactHelmet {...props} />);
const helmet = Helmet.renderStatic();
const helmetContext = {};
const componentHtml = renderToString(
<HelmetProvider context={helmetContext}>
<ReactHelmet {...props} />
</HelmetProvider>,
);
const { helmet } = helmetContext;

const renderedHtml = {
componentHtml,
title: helmet.title.toString(),
title: helmet ? helmet.title.toString() : '',
};
return { renderedHtml };
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import React from 'react';
import { combineReducers, applyMiddleware, createStore } from 'redux';
import { Provider } from 'react-redux';
import thunkMiddleware from 'redux-thunk';
import { thunk } from 'redux-thunk';
import ReactDOMClient from 'react-dom/client';

import reducers from '../reducers/reducersIndex';
Expand Down Expand Up @@ -34,7 +34,7 @@ export default (props, railsContext, domNodeId) => {

// This is where we'll put in the middleware for the async function. Placeholder.
// store will have helloWorldData as a top level property
const store = createStore(combinedReducer, combinedProps, applyMiddleware(thunkMiddleware));
const store = createStore(combinedReducer, combinedProps, applyMiddleware(thunk));

// renderApp is a function required for hot reloading. see
// https://github.com/retroalgic/react-on-rails-hot-minimal/blob/master/client/src/entry.js
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import React from 'react';
import { combineReducers, applyMiddleware, createStore } from 'redux';
import { Provider } from 'react-redux';
import middleware from 'redux-thunk';
import { thunk } from 'redux-thunk';

// Uses the index
import reducers from '../reducers/reducersIndex';
Expand All @@ -28,7 +28,7 @@ export default (props, railsContext) => {

// This is where we'll put in the middleware for the async function. Placeholder.
// store will have helloWorldData as a top level property
const store = applyMiddleware(middleware)(createStore)(combinedReducer, combinedProps);
const store = applyMiddleware(thunk)(createStore)(combinedReducer, combinedProps);

// Provider uses the this.props.children, so we're not typical React syntax.
// This allows redux to add additional props to the HelloWorldContainer.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { combineReducers, applyMiddleware, createStore } from 'redux';
import middleware from 'redux-thunk';
import { thunk } from 'redux-thunk';

import reducers from '../reducers/reducersIndex';

Expand All @@ -12,5 +12,5 @@ export default (props, railsContext) => {
delete props.prerender;
const combinedReducer = combineReducers(reducers);
const newProps = { ...props, railsContext };
return applyMiddleware(middleware)(createStore)(combinedReducer, newProps);
return applyMiddleware(thunk)(createStore)(combinedReducer, newProps);
};
13 changes: 6 additions & 7 deletions react_on_rails/spec/dummy/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@
"node-libs-browser": "^2.2.1",
"null-loader": "^4.0.0",
"prop-types": "^15.7.2",
"react": "18.0.0",
"react-dom": "18.0.0",
"react-helmet": "^6.1.0",
"@dr.pogodin/react-helmet": "^3.0.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-on-rails": "link:.yalc/react-on-rails",
"react-redux": "^8.0.2",
"react-redux": "^9.2.0",
"react-router-dom": "^6.0.0",
"redux": "^4.0.1",
"redux-thunk": "^2.2.0",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
Comment on lines 21 to 28
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Script executed:

#!/bin/bash
# Find all package.json files in the repository
fd package.json -t f

Repository: shakacode/react_on_rails


<shell_result>
Repository: shakacode/react_on_rails
Exit code: 0

stdout:

package.json
packages/react-on-rails/package.json
packages/react-on-rails-pro/package.json
packages/react-on-rails-pro-node-renderer/package.json
react_on_rails/spec/dummy/package.json
react_on_rails/spec/react_on_rails/fixtures/absolute_path_package.json
react_on_rails/spec/react_on_rails/fixtures/beta_package.json
react_on_rails/spec/react_on_rails/fixtures/git_package.json
react_on_rails/spec/react_on_rails/fixtures/normal_package.json
react_on_rails/spec/react_on_rails/fixtures/pro_package.json
react_on_rails/spec/react_on_rails/fixtures/pro_semver_caret_package.json
react_on_rails/spec/react_on_rails/fixtures/relative_path_package.json
react_on_rails/spec/react_on_rails/fixtures/semver_caret_package.json
react_on_rails/spec/react_on_rails/fixtures/semver_exact_package.json
react_on_rails/spec/react_on_rails/fixtures/semver_range_package.json
react_on_rails/spec/react_on_rails/fixtures/semver_tilde_package.json
react_on_rails/spec/react_on_rails/fixtures/similar_packages_package.json
react_on_rails/spec/react_on_rails/fixtures/yalc_package.json
react_on_rails_pro/package.json
react_on_rails_pro/spec/dummy/package.json
react_on_rails_pro/spec/execjs-compatible-dummy/package.json

</shell_result>


Script executed:

#!/bin/bash
# Extract React/Redux/Helmet versions from key package.json files
echo "=== Root package.json ==="
cat package.json | jq '.dependencies // empty | {react: .react, "react-dom": .["react-dom"], "react-redux": .["react-redux"], redux: .redux, "redux-thunk": .["redux-thunk"], "@dr.pogodin/react-helmet": .["@dr.pogodin/react-helmet"]} | del(.[] | select(. == null))' 2>/dev/null || echo "No dependencies section"

echo -e "\n=== packages/react-on-rails/package.json ==="
cat packages/react-on-rails/package.json | jq '.peerDependencies // empty | {react: .react, "react-dom": .["react-dom"], "react-redux": .["react-redux"], redux: .redux, "redux-thunk": .["redux-thunk"], "@dr.pogodin/react-helmet": .["@dr.pogodin/react-helmet"]} | del(.[] | select(. == null))' 2>/dev/null || echo "No peerDependencies section"

echo -e "\n=== react_on_rails/spec/dummy/package.json ==="
cat react_on_rails/spec/dummy/package.json | jq '.dependencies // empty | {react: .react, "react-dom": .["react-dom"], "react-redux": .["react-redux"], redux: .redux, "redux-thunk": .["redux-thunk"], "@dr.pogodin/react-helmet": .["@dr.pogodin/react-helmet"]} | del(.[] | select(. == null))' 2>/dev/null || echo "No dependencies section"

echo -e "\n=== react_on_rails_pro/package.json ==="
cat react_on_rails_pro/package.json | jq '.dependencies // empty | {react: .react, "react-dom": .["react-dom"], "react-redux": .["react-redux"], redux: .redux, "redux-thunk": .["redux-thunk"], "@dr.pogodin/react-helmet": .["@dr.pogodin/react-helmet"]} | del(.[] | select(. == null))' 2>/dev/null || echo "No dependencies section"

echo -e "\n=== react_on_rails_pro/spec/dummy/package.json ==="
cat react_on_rails_pro/spec/dummy/package.json | jq '.dependencies // empty | {react: .react, "react-dom": .["react-dom"], "react-redux": .["react-redux"], redux: .redux, "redux-thunk": .["redux-thunk"], "@dr.pogodin/react-helmet": .["@dr.pogodin/react-helmet"]} | del(.[] | select(. == null))' 2>/dev/null || echo "No dependencies section"

Repository: shakacode/react_on_rails


<shell_result>
Repository: shakacode/react_on_rails
Exit code: 0

stdout:

=== Root package.json ===

=== packages/react-on-rails/package.json ===
{
  "react": ">= 16",
  "react-dom": ">= 16"
}

=== react_on_rails/spec/dummy/package.json ===
{
  "react": "^19.0.0",
  "react-dom": "^19.0.0",
  "react-redux": "^9.2.0",
  "redux": "^5.0.1",
  "redux-thunk": "^3.1.0",
  "@dr.pogodin/react-helmet": "^3.0.4"
}

=== react_on_rails_pro/package.json ===

=== react_on_rails_pro/spec/dummy/package.json ===
{
  "react": "19.0.0",
  "react-dom": "19.0.0",
  "react-redux": "^9.2.0",
  "redux": "^5.0.1",
  "redux-thunk": "^3.1.0"
}

</shell_result>


Script executed:

#!/bin/bash
# Check if CHANGELOG.md was updated
echo "=== Recent CHANGELOG.md entries ==="
head -50 CHANGELOG.md

echo -e "\n=== Check git diff for CHANGELOG.md ==="
git diff HEAD~1 CHANGELOG.md 2>/dev/null | head -30 || echo "Cannot get git diff"

Repository: shakacode/react_on_rails


<shell_result>
Repository: shakacode/react_on_rails
Exit code: 0

stdout:

=== Recent CHANGELOG.md entries ===
# Change Log

All notable changes to this project's source code will be documented in this file. Items under `Unreleased` is upcoming features that will be out in the next version.

Migration instructions for the major updates can be found [here](https://www.shakacode.com/react-on-rails/docs/guides/upgrading-react-on-rails#upgrading-to-version-9.md). Some smaller migration information can be found here.

## Want to Save Time Updating?

If you need help upgrading `react_on_rails`, `webpacker` to `shakapacker`, or JS packages, contact [email protected]. We can upgrade your project and improve your development and customer experiences, allowing you to focus on building new features or fixing bugs instead.

For an overview of working with us, see our [Client Engagement Model](https://www.shakacode.com/blog/client-engagement-model/) article and [how we bill for time](https://www.shakacode.com/blog/shortcut-jira-trello-github-toggl-time-and-task-tracking/).

If you think ShakaCode can help your project, [click here](https://meetings.hubspot.com/justingordon/30-minute-consultation) to book a call with [Justin Gordon](mailto:[email protected]), the creator of React on Rails and Shakapacker.

## Contributors

Please follow the recommendations outlined at [keepachangelog.com](http://keepachangelog.com/). Please use the existing headings and styling as a guide.
After a release, please make sure to run `bundle exec rake update_changelog`. This will add a heading for the latest version and update the links at the end of the file.

## Versions

### [Unreleased]

Changes since the last non-beta release.

#### Added

- **Service Dependency Checking for bin/dev**: Added optional `.dev-services.yml` configuration to validate required external services (Redis, PostgreSQL, Elasticsearch, etc.) are running before `bin/dev` starts the development server. Provides clear error messages with start commands and install hints when services are missing. Zero impact if not configured - backwards compatible with all existing installations. [PR 2098](https://github.com/shakacode/react_on_rails/pull/2098) by [justin808](https://github.com/justin808).

#### Changed

- **Monorepo Structure Reorganization**: Restructured the monorepo to use two top-level product directories (`react_on_rails/` and `react_on_rails_pro/`) instead of mixing source files at the root level. This improves organization and clarity for contributors working on either the open-source or Pro versions. **Important for contributors**: If you have an existing clone of the repository, you may need to update your IDE exclusion patterns and paths. See the updated `CLAUDE.md` for current project structure. [PR 2114](https://github.com/shakacode/react_on_rails/pull/2114) by [justin808](https://github.com/justin808).

- **Package Manager Migration to pnpm (Contributors Only)**: Migrated the monorepo from Yarn Classic to pnpm for improved dependency management and faster installs. **This only affects contributors** - end users can continue using any package manager (npm, yarn, pnpm) with their applications. Contributors should reinstall dependencies with `pnpm install` after pulling this change. [PR 2121](https://github.com/shakacode/react_on_rails/pull/2121) by [justin808](https://github.com/justin808).

#### Improved

- **Automatic Precompile Hook Coordination in bin/dev**: The `bin/dev` command now automatically runs Shakapacker's `precompile_hook` once before starting development processes and sets `SHAKAPACKER_SKIP_PRECOMPILE_HOOK=true` to prevent duplicate execution in spawned webpack processes.
  - Eliminates the need for manual coordination, sleep hacks, and duplicate task calls in Procfile.dev
  - Users can configure expensive build tasks (like locale generation or ReScript compilation) once in `config/shakapacker.yml` and `bin/dev` handles coordination automatically
  - Includes warning for Shakapacker versions below 9.4.0 (the `SHAKAPACKER_SKIP_PRECOMPILE_HOOK` environment variable is only supported in 9.4.0+)
  - The `SHAKAPACKER_SKIP_PRECOMPILE_HOOK` environment variable is set for all spawned processes, making it available for custom scripts that need to detect when `bin/dev` is managing the precompile hook
  - Addresses [2091](https://github.com/shakacode/react_on_rails/issues/2091) by [justin808](https://github.com/justin808)

- **Idempotent Locale Generation**: The `react_on_rails:locale` rake task is now idempotent, automatically skipping generation when locale files are already up-to-date. This makes it safe to call multiple times (e.g., in Shakapacker's `precompile_hook`) without duplicate work. Added `force=true` option to override timestamp checking. [PR 2090](https://github.com/shakacode/react_on_rails/pull/2090) by [justin808](https://github.com/justin808).

### [v16.2.0.beta.12] - 2025-11-20

#### Added


=== Check git diff for CHANGELOG.md ===

</shell_result>


Script executed:

#!/bin/bash
# Check lines 35-45 in spec/dummy/package.json to see the devDependencies
sed -n '35,45p' react_on_rails/spec/dummy/package.json

Repository: shakacode/react_on_rails


<shell_result>
Repository: shakacode/react_on_rails
Exit code: 0

stdout:

    "@babel/preset-react": "^7.10.4",
    "@playwright/test": "^1.55.1",
    "@pmmmwh/react-refresh-webpack-plugin": "^0.5.1",
    "@rescript/react": "^0.13.0",
    "@types/react": "^19.0.0",
    "@types/react-dom": "^19.0.0",
    "babel-loader": "8.2.4",
    "babel-plugin-transform-react-remove-prop-types": "^0.4.24",
    "compression-webpack-plugin": "9",
    "css-loader": "^6.5.1",
    "expose-loader": "^1.0.3",

</shell_result>


Based on my verification, I've identified real cross-package inconsistencies and a missing CHANGELOG entry. Here's the rewritten review comment:


Version inconsistencies exist across spec/dummy packages; React 19 upgrade missing from CHANGELOG

Cross-package comparison reveals:

  • react_on_rails/spec/dummy/package.json: React ^19.0.0, React-Redux ^9.2.0, Redux ^5.0.1, redux-thunk ^3.1.0, @dr.pogodin/react-helmet ^3.0.4
  • react_on_rails_pro/spec/dummy/package.json: React 19.0.0 (exact version, not caret), React-Redux ^9.2.0, Redux ^5.0.1, redux-thunk ^3.1.0, @dr.pogodin/react-helmet missing

The semver mismatch (^19.0.0 vs 19.0.0) and missing Helmet dependency between the two spec/dummy packages should be reconciled. Additionally, per project guidelines, the React 19 upgrade and dependency bumps should be documented in CHANGELOG.md under the Unreleased section.

🤖 Prompt for AI Agents
In react_on_rails/spec/dummy/package.json around lines 21-28 there is a
semver/dep mismatch vs react_on_rails_pro/spec/dummy/package.json (this file
uses "react": "^19.0.0" and includes "@dr.pogodin/react-helmet": "^3.0.4", while
the pro package uses "react": "19.0.0" and omits Helmet); make the versions
consistent across both spec/dummy package.json files (choose careted or exact
for react and apply the same style to react-dom), add the missing
@dr.pogodin/react-helmet dependency to
react_on_rails_pro/spec/dummy/package.json with the same version, and add a
brief entry under the Unreleased section of CHANGELOG.md documenting the React
19 upgrade and dependency bumps.

"regenerator-runtime": "^0.13.4"
},
"devDependencies": {
Expand All @@ -38,7 +38,6 @@
"@rescript/react": "^0.13.0",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@types/react-helmet": "^6.1.5",
"babel-loader": "8.2.4",
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
"compression-webpack-plugin": "9",
Expand Down
Loading