Skip to content

Commit 14c7e20

Browse files
justin808claude
andcommitted
Complete Redux TypeScript implementation for generators
- Remove dead configure_babel_for_typescript method that was never called - Add comprehensive TypeScript versions of all Redux files: - helloWorldActionCreators.ts with typed actions and interfaces - helloWorldConstants.ts with const assertions and type exports - helloWorldReducer.ts with proper state interfaces - helloWorldStore.ts with typed store configuration - HelloWorldContainer.ts with ConnectedProps typing - Update Redux generator to use TypeScript files when --typescript flag is enabled - Fix package manager inconsistency by using abstracted dependency installation - Reduce ESLint exclusions to be more targeted for TypeScript template files - Refactor install command building using hash-based approach to reduce complexity 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent e4c2128 commit 14c7e20

File tree

10 files changed

+151
-55
lines changed

10 files changed

+151
-55
lines changed

eslint.config.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@ const config = tsEslint.config([
4444
// fixtures
4545
'**/fixtures/',
4646
'**/.yalc/**/*',
47-
// generator templates
48-
'**/templates/**/*',
47+
// generator templates - exclude TypeScript templates that need tsconfig.json
48+
'**/templates/**/*.tsx',
49+
'**/templates/**/*.ts',
4950
]),
5051
{
5152
files: ['**/*.[jt]s', '**/*.[jt]sx', '**/*.[cm][jt]s'],

lib/generators/react_on_rails/install_generator.rb

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -397,40 +397,6 @@ def create_typescript_config
397397
puts Rainbow("✅ Created tsconfig.json").green
398398
end
399399

400-
def configure_babel_for_typescript
401-
# Install Babel TypeScript preset
402-
package_manager = detect_package_manager
403-
return unless package_manager
404-
405-
babel_typescript_package = "@babel/preset-typescript"
406-
407-
install_command = case package_manager
408-
when "npm"
409-
"npm install --save-dev #{babel_typescript_package}"
410-
when "yarn"
411-
"yarn add --dev #{babel_typescript_package}"
412-
when "pnpm"
413-
"pnpm add --save-dev #{babel_typescript_package}"
414-
when "bun"
415-
"bun add --dev #{babel_typescript_package}"
416-
end
417-
418-
puts Rainbow("📝 Installing Babel TypeScript preset...").yellow
419-
success = system(install_command)
420-
return if success
421-
422-
warning = <<~MSG.strip
423-
⚠️ Failed to install Babel TypeScript preset automatically.
424-
425-
Please run manually:
426-
#{install_command}
427-
428-
TypeScript compilation may not work without this preset.
429-
MSG
430-
GeneratorMessages.add_warning(warning)
431-
nil
432-
end
433-
434400
# Removed: Shakapacker auto-installation logic (now explicit dependency)
435401

436402
# Removed: Shakapacker 8+ is now required as explicit dependency

lib/generators/react_on_rails/react_with_redux_generator.rb

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,16 @@ def copy_base_files
4444
def copy_base_redux_files
4545
base_hello_world_path = "redux/base/app/javascript/bundles/HelloWorld"
4646
component_extension = options.typescript? ? "tsx" : "jsx"
47+
redux_extension = options.typescript? ? "ts" : "js"
4748

48-
# Copy non-component files (keep as .js for now)
49-
%w[actions/helloWorldActionCreators.js
50-
containers/HelloWorldContainer.js
51-
constants/helloWorldConstants.js
52-
reducers/helloWorldReducer.js
53-
store/helloWorldStore.js].each do |file|
54-
copy_file("#{base_hello_world_path}/#{file}",
55-
"app/javascript/src/HelloWorldApp/#{file}")
49+
# Copy Redux infrastructure files with appropriate extension
50+
%w[actions/helloWorldActionCreators
51+
containers/HelloWorldContainer
52+
constants/helloWorldConstants
53+
reducers/helloWorldReducer
54+
store/helloWorldStore].each do |file|
55+
copy_file("#{base_hello_world_path}/#{file}.#{redux_extension}",
56+
"app/javascript/src/HelloWorldApp/#{file}.#{redux_extension}")
5657
end
5758

5859
# Copy component file with appropriate extension
@@ -72,11 +73,59 @@ def create_appropriate_templates
7273
end
7374

7475
def add_redux_npm_dependencies
76+
# Add Redux dependencies as regular dependencies
77+
regular_packages = %w[redux react-redux]
78+
79+
# Try using GeneratorHelper first (package manager agnostic)
80+
success = add_npm_dependencies(regular_packages)
81+
82+
# Add TypeScript types as dev dependency if TypeScript is enabled
7583
if options.typescript?
76-
run "npm install redux react-redux @types/react-redux"
77-
else
78-
run "npm install redux react-redux"
84+
types_success = add_npm_dependencies(%w[@types/react-redux], dev: true)
85+
success &&= types_success
7986
end
87+
88+
# Fallback to package manager detection if GeneratorHelper fails
89+
return if success
90+
91+
package_manager = detect_package_manager
92+
return unless package_manager
93+
94+
install_packages_with_fallback(regular_packages, dev: false, package_manager: package_manager)
95+
96+
return unless options.typescript?
97+
98+
install_packages_with_fallback(%w[@types/react-redux], dev: true, package_manager: package_manager)
99+
end
100+
101+
private
102+
103+
def install_packages_with_fallback(packages, dev:, package_manager:)
104+
packages_str = packages.join(" ")
105+
install_command = build_install_command(package_manager, dev, packages_str)
106+
107+
success = system(install_command)
108+
return if success
109+
110+
warning = <<~MSG.strip
111+
⚠️ Failed to install Redux dependencies automatically.
112+
113+
Please run manually:
114+
#{install_command}
115+
MSG
116+
GeneratorMessages.add_warning(warning)
117+
end
118+
119+
def build_install_command(package_manager, dev, packages_str)
120+
commands = {
121+
"npm" => { dev: "npm install --save-dev", prod: "npm install" },
122+
"yarn" => { dev: "yarn add --dev", prod: "yarn add" },
123+
"pnpm" => { dev: "pnpm add --save-dev", prod: "pnpm add" },
124+
"bun" => { dev: "bun add --dev", prod: "bun add" }
125+
}
126+
127+
command_type = dev ? :dev : :prod
128+
"#{commands[package_manager][command_type]} #{packages_str}"
80129
end
81130

82131
def add_redux_specific_messages
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/* eslint-disable import/prefer-default-export */
2+
3+
import { HELLO_WORLD_NAME_UPDATE } from '../constants/helloWorldConstants';
4+
5+
// Action interface
6+
export interface UpdateNameAction {
7+
type: typeof HELLO_WORLD_NAME_UPDATE;
8+
text: string;
9+
}
10+
11+
// Union type for all actions
12+
export type HelloWorldAction = UpdateNameAction;
13+
14+
// Action creator with proper TypeScript typing
15+
export const updateName = (text: string): UpdateNameAction => ({
16+
type: HELLO_WORLD_NAME_UPDATE,
17+
text,
18+
});

lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import React from 'react';
22
import * as style from './HelloWorld.module.css';
3+
import { PropsFromRedux } from '../containers/HelloWorldContainer';
34

4-
interface HelloWorldProps {
5-
name: string;
6-
updateName: (name: string) => void;
7-
}
5+
// Component props are inferred from Redux container
6+
type HelloWorldProps = PropsFromRedux;
87

98
const HelloWorld: React.FC<HelloWorldProps> = ({ name, updateName }) => (
109
<div>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/* eslint-disable import/prefer-default-export */
2+
3+
export const HELLO_WORLD_NAME_UPDATE = 'HELLO_WORLD_NAME_UPDATE' as const;
4+
5+
// Action type for TypeScript
6+
export type HelloWorldActionType = typeof HELLO_WORLD_NAME_UPDATE;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Simple example of a React "smart" component
2+
3+
import { connect, ConnectedProps } from 'react-redux';
4+
import HelloWorld from '../components/HelloWorld';
5+
import * as actions from '../actions/helloWorldActionCreators';
6+
import { HelloWorldState } from '../reducers/helloWorldReducer';
7+
8+
// Which part of the Redux global state does our component want to receive as props?
9+
const mapStateToProps = (state: HelloWorldState) => ({ name: state.name });
10+
11+
// Create the connector
12+
const connector = connect(mapStateToProps, actions);
13+
14+
// Infer the props from Redux state and actions
15+
export type PropsFromRedux = ConnectedProps<typeof connector>;
16+
17+
// Don't forget to actually use connect!
18+
// Note that we don't export HelloWorld, but the redux "connected" version of it.
19+
// See https://github.com/reactjs/react-redux/blob/master/docs/api.md#examples
20+
export default connector(HelloWorld);
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { combineReducers } from 'redux';
2+
import { HELLO_WORLD_NAME_UPDATE } from '../constants/helloWorldConstants';
3+
import { HelloWorldAction } from '../actions/helloWorldActionCreators';
4+
5+
// State interface
6+
export interface HelloWorldState {
7+
name: string;
8+
}
9+
10+
// Individual reducer with TypeScript types
11+
const name = (state: string = '', action: HelloWorldAction): string => {
12+
switch (action.type) {
13+
case HELLO_WORLD_NAME_UPDATE:
14+
return action.text;
15+
default:
16+
return state;
17+
}
18+
};
19+
20+
const helloWorldReducer = combineReducers<HelloWorldState>({ name });
21+
22+
export default helloWorldReducer;

lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.client.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import React from 'react';
22
import { Provider } from 'react-redux';
33

4-
import configureStore from '../store/helloWorldStore';
4+
import configureStore, { RailsProps } from '../store/helloWorldStore';
55
import HelloWorldContainer from '../containers/HelloWorldContainer';
66

7-
interface HelloWorldAppProps {
8-
name: string;
9-
}
7+
// Props interface matches what Rails will pass from the controller
8+
type HelloWorldAppProps = RailsProps;
109

1110
// See documentation for https://github.com/reactjs/react-redux.
1211
// This is how you get props from the Rails view into the redux store.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { createStore, Store } from 'redux';
2+
import helloWorldReducer, { HelloWorldState } from '../reducers/helloWorldReducer';
3+
4+
// Rails props interface - customize based on your Rails controller
5+
export interface RailsProps {
6+
name: string;
7+
[key: string]: any; // Allow additional props from Rails
8+
}
9+
10+
// Store type
11+
export type HelloWorldStore = Store<HelloWorldState>;
12+
13+
const configureStore = (railsProps: RailsProps): HelloWorldStore =>
14+
createStore(helloWorldReducer, railsProps);
15+
16+
export default configureStore;

0 commit comments

Comments
 (0)