From b84252f1a185b3cb437ad956e55ec3d29a8142eb Mon Sep 17 00:00:00 2001 From: obsofficer-ctrl Date: Tue, 17 Mar 2026 14:30:51 +0400 Subject: [PATCH 1/7] Add playground\src\components\generic-list-connected.tsx --- ...c\\components\\generic-list-connected.tsx" | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 "playground\\src\\components\\generic-list-connected.tsx" diff --git "a/playground\\src\\components\\generic-list-connected.tsx" "b/playground\\src\\components\\generic-list-connected.tsx" new file mode 100644 index 0000000..d8ba9bc --- /dev/null +++ "b/playground\\src\\components\\generic-list-connected.tsx" @@ -0,0 +1,46 @@ +import * as React from 'react'; +import { connect } from 'react-redux'; + +import { RootState } from '../store/types'; + +// Generic List Component +export type GenericListProps = { + items: T[]; + itemRenderer: (item: T) => JSX.Element; +}; + +export class GenericList extends React.Component, {}> { + render() { + const { items, itemRenderer } = this.props; + return ( +
+ {items.map(item => itemRenderer(item))} +
+ ); + } +} + +// Usage with a concrete type +export type Todo = { + id: number; + title: string; + completed: boolean; +}; + +// Concrete connected component using GenericList +type OwnProps = {}; + +const mapStateToProps = (state: RootState, _ownProps: OwnProps): GenericListProps => ({ + items: state.todos.items as Todo[], + itemRenderer: (item: Todo) => ( +
+ {item.title} - {item.completed ? 'Done' : 'Pending'} +
+ ), +}); + +// Since TypeScript doesn't support generic JSX syntax like `connect<...>()(Component)`, +// we create a concrete class that extends the generic component. +class TodoList extends GenericList {} + +export const ConnectedTodoList = connect(mapStateToProps)(TodoList); From 6ce17b162117a564d6fdbfdd2f0930da41d551e2 Mon Sep 17 00:00:00 2001 From: obsofficer-ctrl Date: Tue, 17 Mar 2026 14:30:52 +0400 Subject: [PATCH 2/7] Update playground\src\components\generic-list-connected.tsx --- ...c\\components\\generic-list-connected.tsx" | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git "a/playground\\src\\components\\generic-list-connected.tsx" "b/playground\\src\\components\\generic-list-connected.tsx" index d8ba9bc..dcc6e8f 100644 --- "a/playground\\src\\components\\generic-list-connected.tsx" +++ "b/playground\\src\\components\\generic-list-connected.tsx" @@ -3,44 +3,48 @@ import { connect } from 'react-redux'; import { RootState } from '../store/types'; -// Generic List Component +// Generic List Component (reusable) export type GenericListProps = { - items: T[]; - itemRenderer: (item: T) => JSX.Element; + readonly items: T[]; + readonly itemRenderer: (item: T) => JSX.Element; }; -export class GenericList extends React.Component, {}> { +export class GenericList extends React.Component> { render() { const { items, itemRenderer } = this.props; return ( -
- {items.map(item => itemRenderer(item))} -
+
    + {items.map((item, i) => ( +
  • {itemRenderer(item)}
  • + ))} +
); } } -// Usage with a concrete type +// Concrete type for the connected example export type Todo = { - id: number; - title: string; - completed: boolean; + readonly id: number; + readonly title: string; + readonly completed: boolean; }; -// Concrete connected component using GenericList -type OwnProps = {}; +// State shape expected (you would normally import from store types) +type OwnProps = { + readonly title: string; +}; + +// Since TypeScript does not support parameterized generic JSX in connect(), +// the workaround is to create a concrete subclass and connect that instead. +class TodoList extends GenericList {} -const mapStateToProps = (state: RootState, _ownProps: OwnProps): GenericListProps => ({ +const mapStateToProps = (state: RootState, ownProps: OwnProps): GenericListProps => ({ items: state.todos.items as Todo[], itemRenderer: (item: Todo) => (
- {item.title} - {item.completed ? 'Done' : 'Pending'} + [{item.completed ? 'x' : ' '}] {item.title}
), }); -// Since TypeScript doesn't support generic JSX syntax like `connect<...>()(Component)`, -// we create a concrete class that extends the generic component. -class TodoList extends GenericList {} - export const ConnectedTodoList = connect(mapStateToProps)(TodoList); From 083aeff7241318a043d432cdda4fa9b9fa66034c Mon Sep 17 00:00:00 2001 From: obsofficer-ctrl Date: Tue, 17 Mar 2026 14:30:54 +0400 Subject: [PATCH 3/7] Update playground\src\components\generic-list-connected.tsx --- ...c\\components\\generic-list-connected.tsx" | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git "a/playground\\src\\components\\generic-list-connected.tsx" "b/playground\\src\\components\\generic-list-connected.tsx" index dcc6e8f..3097a88 100644 --- "a/playground\\src\\components\\generic-list-connected.tsx" +++ "b/playground\\src\\components\\generic-list-connected.tsx" @@ -1,9 +1,7 @@ import * as React from 'react'; import { connect } from 'react-redux'; -import { RootState } from '../store/types'; - -// Generic List Component (reusable) +// Generic List Component export type GenericListProps = { readonly items: T[]; readonly itemRenderer: (item: T) => JSX.Element; @@ -22,24 +20,38 @@ export class GenericList extends React.Component> { } } -// Concrete type for the connected example +// Concrete type to be used in the connected example export type Todo = { readonly id: number; readonly title: string; readonly completed: boolean; }; -// State shape expected (you would normally import from store types) +// Connected Component +// TypeScript does not support parameterized generic arguments in JSX, +// e.g.: connect(...)(GenericList) is not valid syntax. +// Workaround: Create a concrete subclass that fixes the generic parameter, +// then connect the concrete subclass. + +type ConnectedState = { + todos: { + items: Todo[]; + }; +}; + type OwnProps = { - readonly title: string; + readonly title?: string; }; -// Since TypeScript does not support parameterized generic JSX in connect(), -// the workaround is to create a concrete subclass and connect that instead. +// Step 1: Create a concrete (non-generic) subclass class TodoList extends GenericList {} -const mapStateToProps = (state: RootState, ownProps: OwnProps): GenericListProps => ({ - items: state.todos.items as Todo[], +// Step 2: Map state to the props of the concrete subclass +const mapStateToProps = ( + state: ConnectedState, + _ownProps: OwnProps +): GenericListProps => ({ + items: state.todos.items, itemRenderer: (item: Todo) => (
[{item.completed ? 'x' : ' '}] {item.title} @@ -47,4 +59,5 @@ const mapStateToProps = (state: RootState, ownProps: OwnProps): GenericListProps ), }); +// Step 3: Connect the concrete subclass export const ConnectedTodoList = connect(mapStateToProps)(TodoList); From 27fb45a84dc2e2bc4ac6cf98ec1909d6125dbf73 Mon Sep 17 00:00:00 2001 From: obsofficer-ctrl Date: Tue, 17 Mar 2026 14:30:56 +0400 Subject: [PATCH 4/7] Update playground\src\components\generic-list-connected.tsx --- ...c\\components\\generic-list-connected.tsx" | 52 +++++++++---------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git "a/playground\\src\\components\\generic-list-connected.tsx" "b/playground\\src\\components\\generic-list-connected.tsx" index 3097a88..8f8cfd1 100644 --- "a/playground\\src\\components\\generic-list-connected.tsx" +++ "b/playground\\src\\components\\generic-list-connected.tsx" @@ -1,7 +1,7 @@ import * as React from 'react'; import { connect } from 'react-redux'; -// Generic List Component +/** Generic List Component */ export type GenericListProps = { readonly items: T[]; readonly itemRenderer: (item: T) => JSX.Element; @@ -12,52 +12,48 @@ export class GenericList extends React.Component> { const { items, itemRenderer } = this.props; return (
    - {items.map((item, i) => ( -
  • {itemRenderer(item)}
  • + {items.map((item, idx) => ( +
  • {itemRenderer(item)}
  • ))}
); } } -// Concrete type to be used in the connected example +/** + * Connected Generic List + * + * TypeScript does not allow parameterized generic JSX type arguments, so we + * cannot write: connect(...)(GenericList) + * + * The recommended workaround is: + * 1. Define the concrete item type (e.g. Todo) + * 2. Create a concrete subclass of GenericList that fixes the type parameter + * 3. Connect the concrete subclass + */ + export type Todo = { readonly id: number; readonly title: string; readonly completed: boolean; }; -// Connected Component -// TypeScript does not support parameterized generic arguments in JSX, -// e.g.: connect(...)(GenericList) is not valid syntax. -// Workaround: Create a concrete subclass that fixes the generic parameter, -// then connect the concrete subclass. - -type ConnectedState = { - todos: { - items: Todo[]; - }; +type StoreState = { + readonly todos: Todo[]; }; -type OwnProps = { - readonly title?: string; -}; +type OwnProps = {}; -// Step 1: Create a concrete (non-generic) subclass +// Step 1 – Concrete subclass (fixes the generic parameter to `Todo`) class TodoList extends GenericList {} -// Step 2: Map state to the props of the concrete subclass +// Step 2 – Map Redux state to the props of the concrete list const mapStateToProps = ( - state: ConnectedState, + state: StoreState, _ownProps: OwnProps -): GenericListProps => ({ - items: state.todos.items, - itemRenderer: (item: Todo) => ( -
- [{item.completed ? 'x' : ' '}] {item.title} -
- ), +): Pick, 'items'> => ({ + items: state.todos, }); -// Step 3: Connect the concrete subclass +// Step 3 – Connect the concrete subclass export const ConnectedTodoList = connect(mapStateToProps)(TodoList); From e4c9ed379f31022f7d2986e2e9ed118be6619a61 Mon Sep 17 00:00:00 2001 From: obsofficer-ctrl Date: Tue, 17 Mar 2026 14:30:58 +0400 Subject: [PATCH 5/7] Add playground\src\components\generic-list-connected.usage.tsx --- ...ponents\\generic-list-connected.usage.tsx" | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 "playground\\src\\components\\generic-list-connected.usage.tsx" diff --git "a/playground\\src\\components\\generic-list-connected.usage.tsx" "b/playground\\src\\components\\generic-list-connected.usage.tsx" new file mode 100644 index 0000000..a9184db --- /dev/null +++ "b/playground\\src\\components\\generic-list-connected.usage.tsx" @@ -0,0 +1,33 @@ +import * as React from 'react'; +import { createStore } from 'redux'; +import { Provider } from 'react-redux'; + +import { ConnectedTodoList, Todo } from './generic-list-connected'; + +type StoreState = { + readonly todos: Todo[]; +}; + +const initialState: StoreState = { + todos: [ + { id: 1, title: 'Learn TypeScript', completed: true }, + { id: 2, title: 'Read react-redux-typescript-guide', completed: false }, + { id: 3, title: 'Build something great', completed: false }, + ], +}; + +const reducer = (state: StoreState = initialState): StoreState => state; + +const store = createStore(reducer); + +const itemRenderer = (item: Todo): JSX.Element => ( + + [{item.completed ? 'x' : ' '}] {item.title} + +); + +export const App = () => ( + + + +); From 59ba1db3bd8015bcc3e0d750ea87bdea1d94f753 Mon Sep 17 00:00:00 2001 From: obsofficer-ctrl Date: Tue, 17 Mar 2026 14:31:00 +0400 Subject: [PATCH 6/7] Update playground\src\components\generic-list-connected.tsx --- ...src\\components\\generic-list-connected.tsx" | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git "a/playground\\src\\components\\generic-list-connected.tsx" "b/playground\\src\\components\\generic-list-connected.tsx" index 8f8cfd1..3a222d1 100644 --- "a/playground\\src\\components\\generic-list-connected.tsx" +++ "b/playground\\src\\components\\generic-list-connected.tsx" @@ -23,13 +23,12 @@ export class GenericList extends React.Component> { /** * Connected Generic List * - * TypeScript does not allow parameterized generic JSX type arguments, so we - * cannot write: connect(...)(GenericList) + * TypeScript does not allow parameterized generic type arguments in JSX, so + * `connect(...)(GenericList)` is not valid syntax. * - * The recommended workaround is: - * 1. Define the concrete item type (e.g. Todo) - * 2. Create a concrete subclass of GenericList that fixes the type parameter - * 3. Connect the concrete subclass + * Workaround: + * 1. Fix the generic type parameter via a concrete subclass + * 2. Connect the concrete subclass */ export type Todo = { @@ -44,10 +43,10 @@ type StoreState = { type OwnProps = {}; -// Step 1 – Concrete subclass (fixes the generic parameter to `Todo`) +// Step 1 – Concrete subclass fixes the type parameter to `Todo` class TodoList extends GenericList {} -// Step 2 – Map Redux state to the props of the concrete list +// Step 2 – mapStateToProps typed against the concrete props const mapStateToProps = ( state: StoreState, _ownProps: OwnProps @@ -55,5 +54,5 @@ const mapStateToProps = ( items: state.todos, }); -// Step 3 – Connect the concrete subclass +// Step 3 – connect the concrete subclass export const ConnectedTodoList = connect(mapStateToProps)(TodoList); From 07c3160b6ded8bddfc4cd6caa016ef088883f8ec Mon Sep 17 00:00:00 2001 From: obsofficer-ctrl Date: Tue, 17 Mar 2026 14:31:02 +0400 Subject: [PATCH 7/7] Update playground\src\components\generic-list-connected.usage.tsx --- ...\\components\\generic-list-connected.usage.tsx" | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git "a/playground\\src\\components\\generic-list-connected.usage.tsx" "b/playground\\src\\components\\generic-list-connected.usage.tsx" index a9184db..19c619a 100644 --- "a/playground\\src\\components\\generic-list-connected.usage.tsx" +++ "b/playground\\src\\components\\generic-list-connected.usage.tsx" @@ -4,6 +4,7 @@ import { Provider } from 'react-redux'; import { ConnectedTodoList, Todo } from './generic-list-connected'; +/** Store Setup */ type StoreState = { readonly todos: Todo[]; }; @@ -16,18 +17,23 @@ const initialState: StoreState = { ], }; -const reducer = (state: StoreState = initialState): StoreState => state; +function reducer(state: StoreState = initialState): StoreState { + return state; +} const store = createStore(reducer); -const itemRenderer = (item: Todo): JSX.Element => ( +/** Item Renderer */ +const todoItemRenderer = (item: Todo): JSX.Element => ( [{item.completed ? 'x' : ' '}] {item.title} ); -export const App = () => ( +/** Usage */ +export const App: React.FC = () => ( - + {/* `items` is injected by connect; only `itemRenderer` needs to be supplied */} + );