@regression-io/claude-config 0.14.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +286 -0
- package/cli.js +260 -0
- package/config-loader.js +1556 -0
- package/package.json +62 -0
- package/scripts/postinstall.js +50 -0
- package/scripts/sync-version.js +65 -0
- package/shared/mcp-registry.json +117 -0
- package/templates/composites/fastapi-react-js/rules/backend-python.md +54 -0
- package/templates/composites/fastapi-react-js/rules/frontend-react.md +69 -0
- package/templates/composites/fastapi-react-js/rules/monorepo.md +77 -0
- package/templates/composites/fastapi-react-js/template.json +7 -0
- package/templates/composites/fastapi-react-ts/rules/backend-python.md +54 -0
- package/templates/composites/fastapi-react-ts/rules/frontend-react.md +64 -0
- package/templates/composites/fastapi-react-ts/rules/monorepo.md +82 -0
- package/templates/composites/fastapi-react-ts/template.json +7 -0
- package/templates/frameworks/fastapi/rules/dependencies.md +89 -0
- package/templates/frameworks/fastapi/rules/endpoints.md +86 -0
- package/templates/frameworks/fastapi/rules/errors.md +101 -0
- package/templates/frameworks/fastapi/rules/structure.md +97 -0
- package/templates/frameworks/fastapi/template.json +6 -0
- package/templates/frameworks/mcp-python/rules/resources.md +93 -0
- package/templates/frameworks/mcp-python/rules/structure.md +74 -0
- package/templates/frameworks/mcp-python/rules/tools.md +80 -0
- package/templates/frameworks/mcp-python/template.json +6 -0
- package/templates/frameworks/python-cli/rules/commands.md +103 -0
- package/templates/frameworks/python-cli/rules/output.md +107 -0
- package/templates/frameworks/python-cli/rules/structure.md +91 -0
- package/templates/frameworks/python-cli/template.json +6 -0
- package/templates/frameworks/react-js/rules/components.md +84 -0
- package/templates/frameworks/react-js/rules/hooks.md +98 -0
- package/templates/frameworks/react-js/template.json +6 -0
- package/templates/frameworks/react-ts/rules/components.md +72 -0
- package/templates/frameworks/react-ts/rules/hooks.md +87 -0
- package/templates/frameworks/react-ts/rules/state.md +93 -0
- package/templates/frameworks/react-ts/template.json +6 -0
- package/templates/languages/javascript/rules/patterns.md +126 -0
- package/templates/languages/javascript/rules/style.md +92 -0
- package/templates/languages/javascript/template.json +6 -0
- package/templates/languages/python/rules/dependencies.md +77 -0
- package/templates/languages/python/rules/patterns.md +95 -0
- package/templates/languages/python/rules/style.md +63 -0
- package/templates/languages/python/template.json +6 -0
- package/templates/languages/typescript/rules/config.md +95 -0
- package/templates/languages/typescript/rules/patterns.md +119 -0
- package/templates/languages/typescript/rules/style.md +82 -0
- package/templates/languages/typescript/template.json +6 -0
- package/templates/universal/commands/commit.md +53 -0
- package/templates/universal/commands/debug.md +53 -0
- package/templates/universal/commands/document.md +54 -0
- package/templates/universal/commands/review.md +45 -0
- package/templates/universal/commands/security-review.md +52 -0
- package/templates/universal/commands/test.md +46 -0
- package/templates/universal/rules/api-design.md +38 -0
- package/templates/universal/rules/code-quality.md +40 -0
- package/templates/universal/rules/documentation.md +38 -0
- package/templates/universal/rules/error-handling.md +37 -0
- package/templates/universal/rules/git-workflow.md +39 -0
- package/templates/universal/rules/security.md +39 -0
- package/templates/universal/rules/testing.md +38 -0
- package/templates/universal/template.json +6 -0
- package/ui/dist/assets/index-C5apzulu.css +32 -0
- package/ui/dist/assets/index-CBNCwCnY.js +489 -0
- package/ui/dist/index.html +14 -0
- package/ui/server.cjs +2237 -0
- package/ui/terminal-server.cjs +160 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# CLI Command Patterns
|
|
2
|
+
|
|
3
|
+
## Command Definition
|
|
4
|
+
```python
|
|
5
|
+
# src/my_cli/commands/init.py
|
|
6
|
+
import typer
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
app = typer.Typer(help="Initialize a new project")
|
|
11
|
+
console = Console()
|
|
12
|
+
|
|
13
|
+
@app.command()
|
|
14
|
+
def project(
|
|
15
|
+
name: str = typer.Argument(..., help="Project name"),
|
|
16
|
+
path: Path = typer.Option(".", "--path", "-p", help="Target directory"),
|
|
17
|
+
force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing"),
|
|
18
|
+
):
|
|
19
|
+
"""Initialize a new project with the given name."""
|
|
20
|
+
target = path / name
|
|
21
|
+
|
|
22
|
+
if target.exists() and not force:
|
|
23
|
+
console.print(f"[red]Error:[/red] {target} already exists. Use --force to overwrite.")
|
|
24
|
+
raise typer.Exit(1)
|
|
25
|
+
|
|
26
|
+
# Create project structure
|
|
27
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
28
|
+
(target / "README.md").write_text(f"# {name}\n")
|
|
29
|
+
|
|
30
|
+
console.print(f"[green]✓[/green] Created project at {target}")
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Arguments and Options
|
|
34
|
+
```python
|
|
35
|
+
@app.command()
|
|
36
|
+
def process(
|
|
37
|
+
# Required argument
|
|
38
|
+
input_file: Path = typer.Argument(..., help="Input file to process"),
|
|
39
|
+
|
|
40
|
+
# Optional argument with default
|
|
41
|
+
output: Path = typer.Argument("output.txt", help="Output file"),
|
|
42
|
+
|
|
43
|
+
# Options
|
|
44
|
+
verbose: bool = typer.Option(False, "--verbose", "-v"),
|
|
45
|
+
count: int = typer.Option(10, "--count", "-n", min=1, max=100),
|
|
46
|
+
format: str = typer.Option("json", "--format", "-f", help="Output format"),
|
|
47
|
+
):
|
|
48
|
+
...
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Choice Options
|
|
52
|
+
```python
|
|
53
|
+
from enum import Enum
|
|
54
|
+
|
|
55
|
+
class OutputFormat(str, Enum):
|
|
56
|
+
json = "json"
|
|
57
|
+
yaml = "yaml"
|
|
58
|
+
text = "text"
|
|
59
|
+
|
|
60
|
+
@app.command()
|
|
61
|
+
def export(
|
|
62
|
+
format: OutputFormat = typer.Option(OutputFormat.json, "--format", "-f"),
|
|
63
|
+
):
|
|
64
|
+
if format == OutputFormat.json:
|
|
65
|
+
...
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Interactive Prompts
|
|
69
|
+
```python
|
|
70
|
+
@app.command()
|
|
71
|
+
def configure():
|
|
72
|
+
name = typer.prompt("Project name")
|
|
73
|
+
description = typer.prompt("Description", default="")
|
|
74
|
+
confirm = typer.confirm("Create project?")
|
|
75
|
+
|
|
76
|
+
if not confirm:
|
|
77
|
+
raise typer.Abort()
|
|
78
|
+
|
|
79
|
+
# Create project...
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Progress and Output
|
|
83
|
+
```python
|
|
84
|
+
from rich.progress import track
|
|
85
|
+
from rich.table import Table
|
|
86
|
+
|
|
87
|
+
@app.command()
|
|
88
|
+
def process_files(files: list[Path] = typer.Argument(...)):
|
|
89
|
+
results = []
|
|
90
|
+
for file in track(files, description="Processing..."):
|
|
91
|
+
result = process_file(file)
|
|
92
|
+
results.append(result)
|
|
93
|
+
|
|
94
|
+
# Display results table
|
|
95
|
+
table = Table(title="Results")
|
|
96
|
+
table.add_column("File")
|
|
97
|
+
table.add_column("Status")
|
|
98
|
+
|
|
99
|
+
for r in results:
|
|
100
|
+
table.add_row(r.file, r.status)
|
|
101
|
+
|
|
102
|
+
console.print(table)
|
|
103
|
+
```
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# CLI Output Best Practices
|
|
2
|
+
|
|
3
|
+
## Rich Console Output
|
|
4
|
+
```python
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
from rich.syntax import Syntax
|
|
8
|
+
|
|
9
|
+
console = Console()
|
|
10
|
+
|
|
11
|
+
# Colors and styles
|
|
12
|
+
console.print("[green]Success![/green] Operation completed.")
|
|
13
|
+
console.print("[red]Error:[/red] Something went wrong.")
|
|
14
|
+
console.print("[yellow]Warning:[/yellow] Proceed with caution.")
|
|
15
|
+
console.print("[bold]Important:[/bold] Read this carefully.")
|
|
16
|
+
|
|
17
|
+
# Panels for grouped info
|
|
18
|
+
console.print(Panel("This is important information", title="Notice"))
|
|
19
|
+
|
|
20
|
+
# Code highlighting
|
|
21
|
+
code = Syntax(source_code, "python", theme="monokai")
|
|
22
|
+
console.print(code)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Structured Output
|
|
26
|
+
```python
|
|
27
|
+
from rich.table import Table
|
|
28
|
+
|
|
29
|
+
def show_results(items: list[Item]):
|
|
30
|
+
table = Table(title="Results", show_header=True)
|
|
31
|
+
table.add_column("ID", style="cyan")
|
|
32
|
+
table.add_column("Name")
|
|
33
|
+
table.add_column("Status", justify="center")
|
|
34
|
+
|
|
35
|
+
for item in items:
|
|
36
|
+
status_color = "green" if item.active else "red"
|
|
37
|
+
table.add_row(
|
|
38
|
+
str(item.id),
|
|
39
|
+
item.name,
|
|
40
|
+
f"[{status_color}]{'Active' if item.active else 'Inactive'}[/]"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
console.print(table)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Progress Indicators
|
|
47
|
+
```python
|
|
48
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
49
|
+
|
|
50
|
+
# Simple progress
|
|
51
|
+
for item in track(items, description="Processing..."):
|
|
52
|
+
process(item)
|
|
53
|
+
|
|
54
|
+
# Custom progress
|
|
55
|
+
with Progress(
|
|
56
|
+
SpinnerColumn(),
|
|
57
|
+
TextColumn("[progress.description]{task.description}"),
|
|
58
|
+
) as progress:
|
|
59
|
+
task = progress.add_task("Loading...", total=None)
|
|
60
|
+
result = long_running_operation()
|
|
61
|
+
progress.update(task, completed=True)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## JSON/Machine Output
|
|
65
|
+
```python
|
|
66
|
+
import json
|
|
67
|
+
|
|
68
|
+
@app.command()
|
|
69
|
+
def list_items(
|
|
70
|
+
json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
|
|
71
|
+
):
|
|
72
|
+
items = get_items()
|
|
73
|
+
|
|
74
|
+
if json_output:
|
|
75
|
+
# Machine-readable output
|
|
76
|
+
print(json.dumps([i.dict() for i in items], indent=2))
|
|
77
|
+
else:
|
|
78
|
+
# Human-readable output
|
|
79
|
+
show_results(items)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Exit Codes
|
|
83
|
+
```python
|
|
84
|
+
# Success
|
|
85
|
+
raise typer.Exit(0)
|
|
86
|
+
|
|
87
|
+
# General error
|
|
88
|
+
raise typer.Exit(1)
|
|
89
|
+
|
|
90
|
+
# User abort
|
|
91
|
+
raise typer.Abort()
|
|
92
|
+
|
|
93
|
+
# Specific exit codes
|
|
94
|
+
EXIT_SUCCESS = 0
|
|
95
|
+
EXIT_ERROR = 1
|
|
96
|
+
EXIT_USAGE = 2
|
|
97
|
+
EXIT_CONFIG = 3
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Error Messages
|
|
101
|
+
```python
|
|
102
|
+
def handle_error(e: Exception):
|
|
103
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
104
|
+
if verbose:
|
|
105
|
+
console.print_exception()
|
|
106
|
+
raise typer.Exit(1)
|
|
107
|
+
```
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Python CLI Project Structure
|
|
2
|
+
|
|
3
|
+
## Directory Layout
|
|
4
|
+
```
|
|
5
|
+
my-cli/
|
|
6
|
+
├── src/
|
|
7
|
+
│ └── my_cli/
|
|
8
|
+
│ ├── __init__.py
|
|
9
|
+
│ ├── __main__.py # Entry point
|
|
10
|
+
│ ├── cli.py # CLI definition
|
|
11
|
+
│ ├── commands/
|
|
12
|
+
│ │ ├── __init__.py
|
|
13
|
+
│ │ ├── init.py
|
|
14
|
+
│ │ └── run.py
|
|
15
|
+
│ ├── config.py
|
|
16
|
+
│ └── utils.py
|
|
17
|
+
├── tests/
|
|
18
|
+
│ └── test_commands.py
|
|
19
|
+
├── pyproject.toml
|
|
20
|
+
└── README.md
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Main Entry Point
|
|
24
|
+
```python
|
|
25
|
+
# src/my_cli/__main__.py
|
|
26
|
+
from .cli import app
|
|
27
|
+
|
|
28
|
+
if __name__ == "__main__":
|
|
29
|
+
app()
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## CLI Definition (Typer)
|
|
33
|
+
```python
|
|
34
|
+
# src/my_cli/cli.py
|
|
35
|
+
import typer
|
|
36
|
+
from typing import Optional
|
|
37
|
+
|
|
38
|
+
from .commands import init, run
|
|
39
|
+
|
|
40
|
+
app = typer.Typer(
|
|
41
|
+
name="my-cli",
|
|
42
|
+
help="My CLI tool description",
|
|
43
|
+
add_completion=False,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Add subcommands
|
|
47
|
+
app.add_typer(init.app, name="init")
|
|
48
|
+
app.add_typer(run.app, name="run")
|
|
49
|
+
|
|
50
|
+
@app.callback()
|
|
51
|
+
def main(
|
|
52
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Verbose output"),
|
|
53
|
+
config: Optional[str] = typer.Option(None, "--config", "-c", help="Config file"),
|
|
54
|
+
):
|
|
55
|
+
"""My CLI tool - does useful things."""
|
|
56
|
+
if verbose:
|
|
57
|
+
# Set up verbose logging
|
|
58
|
+
pass
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## pyproject.toml
|
|
62
|
+
```toml
|
|
63
|
+
[project]
|
|
64
|
+
name = "my-cli"
|
|
65
|
+
version = "0.1.0"
|
|
66
|
+
requires-python = ">=3.10"
|
|
67
|
+
dependencies = [
|
|
68
|
+
"typer>=0.9.0",
|
|
69
|
+
"rich>=13.0.0",
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
[project.scripts]
|
|
73
|
+
my-cli = "my_cli.cli:app"
|
|
74
|
+
|
|
75
|
+
[project.optional-dependencies]
|
|
76
|
+
dev = [
|
|
77
|
+
"pytest>=7.0",
|
|
78
|
+
"pytest-cov>=4.0",
|
|
79
|
+
]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Installation
|
|
83
|
+
```bash
|
|
84
|
+
# Development install
|
|
85
|
+
pip install -e ".[dev]"
|
|
86
|
+
|
|
87
|
+
# Run
|
|
88
|
+
my-cli --help
|
|
89
|
+
my-cli init
|
|
90
|
+
my-cli run --verbose
|
|
91
|
+
```
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# React JavaScript Component Rules
|
|
2
|
+
|
|
3
|
+
## Component Structure
|
|
4
|
+
```javascript
|
|
5
|
+
// Functional components only
|
|
6
|
+
export function UserCard({ user, onEdit, className }) {
|
|
7
|
+
return (
|
|
8
|
+
<div className={className}>
|
|
9
|
+
<h3>{user.name}</h3>
|
|
10
|
+
{onEdit && <button onClick={() => onEdit(user)}>Edit</button>}
|
|
11
|
+
</div>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// PropTypes for runtime validation
|
|
16
|
+
import PropTypes from 'prop-types';
|
|
17
|
+
|
|
18
|
+
UserCard.propTypes = {
|
|
19
|
+
user: PropTypes.shape({
|
|
20
|
+
id: PropTypes.number.isRequired,
|
|
21
|
+
name: PropTypes.string.isRequired,
|
|
22
|
+
}).isRequired,
|
|
23
|
+
onEdit: PropTypes.func,
|
|
24
|
+
className: PropTypes.string,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
UserCard.defaultProps = {
|
|
28
|
+
className: '',
|
|
29
|
+
};
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## File Organization
|
|
33
|
+
```
|
|
34
|
+
src/
|
|
35
|
+
├── components/
|
|
36
|
+
│ ├── ui/ # Generic reusable (Button, Input, Modal)
|
|
37
|
+
│ ├── features/ # Feature-specific (UserCard, OrderList)
|
|
38
|
+
│ └── layout/ # Layout (Header, Sidebar, Footer)
|
|
39
|
+
├── hooks/ # Custom hooks
|
|
40
|
+
├── pages/ # Route pages
|
|
41
|
+
├── services/ # API calls
|
|
42
|
+
└── utils/ # Helper functions
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Naming Conventions
|
|
46
|
+
- Components: `PascalCase` - `UserCard.jsx`
|
|
47
|
+
- Hooks: `useCamelCase` - `useAuth.js`
|
|
48
|
+
- Utils: `camelCase` - `formatDate.js`
|
|
49
|
+
- Files: Match component name
|
|
50
|
+
|
|
51
|
+
## Props Pattern
|
|
52
|
+
```javascript
|
|
53
|
+
// Destructure props with defaults
|
|
54
|
+
export function Button({
|
|
55
|
+
variant = 'primary',
|
|
56
|
+
loading = false,
|
|
57
|
+
children,
|
|
58
|
+
...props
|
|
59
|
+
}) {
|
|
60
|
+
return (
|
|
61
|
+
<button disabled={loading} {...props}>
|
|
62
|
+
{loading ? <Spinner /> : children}
|
|
63
|
+
</button>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
Button.propTypes = {
|
|
68
|
+
variant: PropTypes.oneOf(['primary', 'secondary']),
|
|
69
|
+
loading: PropTypes.bool,
|
|
70
|
+
children: PropTypes.node.isRequired,
|
|
71
|
+
};
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## JSDoc for Type Hints
|
|
75
|
+
```javascript
|
|
76
|
+
/**
|
|
77
|
+
* @param {Object} props
|
|
78
|
+
* @param {import('../types').User} props.user
|
|
79
|
+
* @param {(user: import('../types').User) => void} [props.onEdit]
|
|
80
|
+
*/
|
|
81
|
+
export function UserCard({ user, onEdit }) {
|
|
82
|
+
// IDE gets type hints from JSDoc
|
|
83
|
+
}
|
|
84
|
+
```
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# React Hooks Rules (JavaScript)
|
|
2
|
+
|
|
3
|
+
## Rules of Hooks
|
|
4
|
+
- Only call hooks at the top level
|
|
5
|
+
- Only call hooks from React functions
|
|
6
|
+
- Custom hooks must start with `use`
|
|
7
|
+
|
|
8
|
+
## useState
|
|
9
|
+
```javascript
|
|
10
|
+
// Simple state
|
|
11
|
+
const [count, setCount] = useState(0);
|
|
12
|
+
const [user, setUser] = useState(null);
|
|
13
|
+
|
|
14
|
+
// Lazy initialization for expensive values
|
|
15
|
+
const [data, setData] = useState(() => computeExpensiveValue());
|
|
16
|
+
|
|
17
|
+
// Functional updates
|
|
18
|
+
setCount(prev => prev + 1);
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## useEffect
|
|
22
|
+
```javascript
|
|
23
|
+
// Fetch data
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
let cancelled = false;
|
|
26
|
+
|
|
27
|
+
fetchUser(userId).then(data => {
|
|
28
|
+
if (!cancelled) setUser(data);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return () => { cancelled = true; };
|
|
32
|
+
}, [userId]);
|
|
33
|
+
|
|
34
|
+
// Event listener
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
const handler = (e) => setSize({ width: e.target.innerWidth });
|
|
37
|
+
window.addEventListener('resize', handler);
|
|
38
|
+
return () => window.removeEventListener('resize', handler);
|
|
39
|
+
}, []);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## useMemo & useCallback
|
|
43
|
+
```javascript
|
|
44
|
+
// Expensive calculation
|
|
45
|
+
const sortedList = useMemo(
|
|
46
|
+
() => items.sort((a, b) => a.name.localeCompare(b.name)),
|
|
47
|
+
[items]
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// Stable callback reference
|
|
51
|
+
const handleClick = useCallback(
|
|
52
|
+
() => { onClick(id); },
|
|
53
|
+
[onClick, id]
|
|
54
|
+
);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Custom Hooks
|
|
58
|
+
```javascript
|
|
59
|
+
// Reusable data fetching
|
|
60
|
+
function useFetch(url) {
|
|
61
|
+
const [data, setData] = useState(null);
|
|
62
|
+
const [loading, setLoading] = useState(true);
|
|
63
|
+
const [error, setError] = useState(null);
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
let cancelled = false;
|
|
67
|
+
setLoading(true);
|
|
68
|
+
|
|
69
|
+
fetch(url)
|
|
70
|
+
.then(res => res.json())
|
|
71
|
+
.then(data => {
|
|
72
|
+
if (!cancelled) {
|
|
73
|
+
setData(data);
|
|
74
|
+
setLoading(false);
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
.catch(err => {
|
|
78
|
+
if (!cancelled) {
|
|
79
|
+
setError(err);
|
|
80
|
+
setLoading(false);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return () => { cancelled = true; };
|
|
85
|
+
}, [url]);
|
|
86
|
+
|
|
87
|
+
return { data, loading, error };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Usage
|
|
91
|
+
function UserProfile({ userId }) {
|
|
92
|
+
const { data: user, loading, error } = useFetch(`/api/users/${userId}`);
|
|
93
|
+
|
|
94
|
+
if (loading) return <Spinner />;
|
|
95
|
+
if (error) return <Error message={error.message} />;
|
|
96
|
+
return <UserCard user={user} />;
|
|
97
|
+
}
|
|
98
|
+
```
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# React TypeScript Component Rules
|
|
2
|
+
|
|
3
|
+
## Component Structure
|
|
4
|
+
```typescript
|
|
5
|
+
// Functional components only (no class components)
|
|
6
|
+
interface UserCardProps {
|
|
7
|
+
user: User;
|
|
8
|
+
onEdit?: (user: User) => void;
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function UserCard({ user, onEdit, className }: UserCardProps) {
|
|
13
|
+
return (
|
|
14
|
+
<div className={className}>
|
|
15
|
+
<h3>{user.name}</h3>
|
|
16
|
+
{onEdit && <button onClick={() => onEdit(user)}>Edit</button>}
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## File Organization
|
|
23
|
+
```
|
|
24
|
+
src/
|
|
25
|
+
├── components/
|
|
26
|
+
│ ├── ui/ # Generic reusable (Button, Input, Modal)
|
|
27
|
+
│ ├── features/ # Feature-specific (UserCard, OrderList)
|
|
28
|
+
│ └── layout/ # Layout (Header, Sidebar, Footer)
|
|
29
|
+
├── hooks/ # Custom hooks
|
|
30
|
+
├── pages/ # Route pages
|
|
31
|
+
├── services/ # API calls
|
|
32
|
+
├── types/ # TypeScript types
|
|
33
|
+
└── utils/ # Helper functions
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Naming Conventions
|
|
37
|
+
- Components: `PascalCase` - `UserCard.tsx`
|
|
38
|
+
- Hooks: `useCamelCase` - `useAuth.ts`
|
|
39
|
+
- Utils: `camelCase` - `formatDate.ts`
|
|
40
|
+
- Types: `PascalCase` - `User`, `UserCardProps`
|
|
41
|
+
|
|
42
|
+
## Props Typing
|
|
43
|
+
```typescript
|
|
44
|
+
// Define props interface
|
|
45
|
+
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
46
|
+
variant?: 'primary' | 'secondary';
|
|
47
|
+
loading?: boolean;
|
|
48
|
+
children: React.ReactNode;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Use children explicitly
|
|
52
|
+
export function Button({ variant = 'primary', loading, children, ...props }: ButtonProps) {
|
|
53
|
+
return (
|
|
54
|
+
<button disabled={loading} {...props}>
|
|
55
|
+
{loading ? <Spinner /> : children}
|
|
56
|
+
</button>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Event Handlers
|
|
62
|
+
```typescript
|
|
63
|
+
// Type event handlers properly
|
|
64
|
+
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
|
|
65
|
+
setValue(e.target.value);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
|
69
|
+
e.preventDefault();
|
|
70
|
+
// ...
|
|
71
|
+
}
|
|
72
|
+
```
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# React Hooks Rules
|
|
2
|
+
|
|
3
|
+
## Rules of Hooks
|
|
4
|
+
- Only call hooks at the top level (not in loops, conditions, or nested functions)
|
|
5
|
+
- Only call hooks from React functions (components or custom hooks)
|
|
6
|
+
- Custom hooks must start with `use`
|
|
7
|
+
|
|
8
|
+
## useState
|
|
9
|
+
```typescript
|
|
10
|
+
// Type inference works for primitives
|
|
11
|
+
const [count, setCount] = useState(0);
|
|
12
|
+
|
|
13
|
+
// Explicit typing for complex types
|
|
14
|
+
const [user, setUser] = useState<User | null>(null);
|
|
15
|
+
|
|
16
|
+
// Use functional updates for derived state
|
|
17
|
+
setCount(prev => prev + 1);
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## useEffect
|
|
21
|
+
```typescript
|
|
22
|
+
// Always specify dependencies
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
fetchUser(userId).then(setUser);
|
|
25
|
+
}, [userId]);
|
|
26
|
+
|
|
27
|
+
// Cleanup subscriptions
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
const subscription = api.subscribe(handleUpdate);
|
|
30
|
+
return () => subscription.unsubscribe();
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
// Avoid useEffect for:
|
|
34
|
+
// - Transforming data (use useMemo)
|
|
35
|
+
// - Handling events (use event handlers)
|
|
36
|
+
// - Initializing state (use initializer function)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## useMemo & useCallback
|
|
40
|
+
```typescript
|
|
41
|
+
// useMemo for expensive calculations
|
|
42
|
+
const sortedItems = useMemo(
|
|
43
|
+
() => items.sort((a, b) => a.name.localeCompare(b.name)),
|
|
44
|
+
[items]
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// useCallback for stable function references
|
|
48
|
+
const handleSubmit = useCallback(
|
|
49
|
+
(data: FormData) => {
|
|
50
|
+
submitForm(data);
|
|
51
|
+
},
|
|
52
|
+
[submitForm]
|
|
53
|
+
);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Custom Hooks
|
|
57
|
+
```typescript
|
|
58
|
+
// Extract reusable logic
|
|
59
|
+
function useAsync<T>(asyncFn: () => Promise<T>, deps: unknown[] = []) {
|
|
60
|
+
const [state, setState] = useState<{
|
|
61
|
+
data: T | null;
|
|
62
|
+
loading: boolean;
|
|
63
|
+
error: Error | null;
|
|
64
|
+
}>({ data: null, loading: true, error: null });
|
|
65
|
+
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
setState(s => ({ ...s, loading: true }));
|
|
68
|
+
asyncFn()
|
|
69
|
+
.then(data => setState({ data, loading: false, error: null }))
|
|
70
|
+
.catch(error => setState({ data: null, loading: false, error }));
|
|
71
|
+
}, deps);
|
|
72
|
+
|
|
73
|
+
return state;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Usage
|
|
77
|
+
function UserProfile({ userId }: { userId: string }) {
|
|
78
|
+
const { data: user, loading, error } = useAsync(
|
|
79
|
+
() => fetchUser(userId),
|
|
80
|
+
[userId]
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
if (loading) return <Spinner />;
|
|
84
|
+
if (error) return <Error message={error.message} />;
|
|
85
|
+
return <UserCard user={user!} />;
|
|
86
|
+
}
|
|
87
|
+
```
|