forked from cardosofelipe/fast-next-template
Refactor useAuth hook, settings components, and docs for formatting and readability improvements
- Consolidated multi-line arguments into single lines where appropriate in `useAuth`. - Improved spacing and readability in data processing across components (`ProfileSettingsForm`, `PasswordChangeForm`, `SessionCard`). - Applied consistent table and markdown formatting in design system docs (e.g., `README.md`, `08-ai-guidelines.md`, `00-quick-start.md`). - Updated code snippets to ensure adherence to Prettier rules and streamlined JSX structures.
This commit is contained in:
@@ -27,44 +27,48 @@
|
||||
### Pitfall 1.1: Returning Hook Function Instead of Calling It
|
||||
|
||||
**❌ WRONG:**
|
||||
|
||||
```typescript
|
||||
// Custom hook that wraps Zustand
|
||||
export function useAuth() {
|
||||
const storeHook = useContext(AuthContext);
|
||||
return storeHook; // Returns the hook function itself!
|
||||
return storeHook; // Returns the hook function itself!
|
||||
}
|
||||
|
||||
// Consumer component
|
||||
function MyComponent() {
|
||||
const authHook = useAuth(); // Got the hook function
|
||||
const { user } = authHook(); // Have to call it here ❌ Rules of Hooks violation!
|
||||
const authHook = useAuth(); // Got the hook function
|
||||
const { user } = authHook(); // Have to call it here ❌ Rules of Hooks violation!
|
||||
}
|
||||
```
|
||||
|
||||
**Why It's Wrong:**
|
||||
|
||||
- Violates React Rules of Hooks (hook called conditionally/in wrong place)
|
||||
- Confusing API for consumers
|
||||
- Can't use in conditionals or callbacks safely
|
||||
- Type inference breaks
|
||||
|
||||
**✅ CORRECT:**
|
||||
|
||||
```typescript
|
||||
// Custom hook that calls the wrapped hook internally
|
||||
export function useAuth() {
|
||||
const storeHook = useContext(AuthContext);
|
||||
if (!storeHook) {
|
||||
throw new Error("useAuth must be used within AuthProvider");
|
||||
throw new Error('useAuth must be used within AuthProvider');
|
||||
}
|
||||
return storeHook(); // Call the hook HERE, return the state
|
||||
return storeHook(); // Call the hook HERE, return the state
|
||||
}
|
||||
|
||||
// Consumer component
|
||||
function MyComponent() {
|
||||
const { user } = useAuth(); // Direct access to state ✅
|
||||
const { user } = useAuth(); // Direct access to state ✅
|
||||
}
|
||||
```
|
||||
|
||||
**✅ EVEN BETTER (Polymorphic):**
|
||||
|
||||
```typescript
|
||||
// Support both patterns
|
||||
export function useAuth(): AuthState;
|
||||
@@ -72,17 +76,18 @@ export function useAuth<T>(selector: (state: AuthState) => T): T;
|
||||
export function useAuth<T>(selector?: (state: AuthState) => T): AuthState | T {
|
||||
const storeHook = useContext(AuthContext);
|
||||
if (!storeHook) {
|
||||
throw new Error("useAuth must be used within AuthProvider");
|
||||
throw new Error('useAuth must be used within AuthProvider');
|
||||
}
|
||||
return selector ? storeHook(selector) : storeHook();
|
||||
}
|
||||
|
||||
// Usage - both work!
|
||||
const { user } = useAuth(); // Full state
|
||||
const user = useAuth(s => s.user); // Optimized selector
|
||||
const { user } = useAuth(); // Full state
|
||||
const user = useAuth((s) => s.user); // Optimized selector
|
||||
```
|
||||
|
||||
**Key Takeaway:**
|
||||
|
||||
- **Always call hooks internally in custom hooks**
|
||||
- Return state/values, not hook functions
|
||||
- Support selectors for performance optimization
|
||||
@@ -92,6 +97,7 @@ const user = useAuth(s => s.user); // Optimized selector
|
||||
### Pitfall 1.2: Calling Hooks Conditionally
|
||||
|
||||
**❌ WRONG:**
|
||||
|
||||
```typescript
|
||||
function MyComponent({ showUser }) {
|
||||
if (showUser) {
|
||||
@@ -103,6 +109,7 @@ function MyComponent({ showUser }) {
|
||||
```
|
||||
|
||||
**✅ CORRECT:**
|
||||
|
||||
```typescript
|
||||
function MyComponent({ showUser }) {
|
||||
const { user } = useAuth(); // ✅ Always call at top level
|
||||
@@ -116,6 +123,7 @@ function MyComponent({ showUser }) {
|
||||
```
|
||||
|
||||
**Key Takeaway:**
|
||||
|
||||
- **Always call hooks at the top level of your component**
|
||||
- Never call hooks inside conditionals, loops, or nested functions
|
||||
- Return early after hooks are called
|
||||
@@ -127,6 +135,7 @@ function MyComponent({ showUser }) {
|
||||
### Pitfall 2.1: Creating New Context Value on Every Render
|
||||
|
||||
**❌ WRONG:**
|
||||
|
||||
```typescript
|
||||
export function AuthProvider({ children }) {
|
||||
const [user, setUser] = useState(null);
|
||||
@@ -139,11 +148,13 @@ export function AuthProvider({ children }) {
|
||||
```
|
||||
|
||||
**Why It's Wrong:**
|
||||
|
||||
- Every render creates a new object
|
||||
- All consumers re-render even if values unchanged
|
||||
- Performance nightmare in large apps
|
||||
|
||||
**✅ CORRECT:**
|
||||
|
||||
```typescript
|
||||
export function AuthProvider({ children }) {
|
||||
const [user, setUser] = useState(null);
|
||||
@@ -156,6 +167,7 @@ export function AuthProvider({ children }) {
|
||||
```
|
||||
|
||||
**✅ EVEN BETTER (Zustand + Context):**
|
||||
|
||||
```typescript
|
||||
export function AuthProvider({ children, store }) {
|
||||
// Zustand hook function is stable (doesn't change)
|
||||
@@ -167,6 +179,7 @@ export function AuthProvider({ children, store }) {
|
||||
```
|
||||
|
||||
**Key Takeaway:**
|
||||
|
||||
- **Use `useMemo` for Context values that are objects**
|
||||
- Or use stable references (Zustand hooks, refs)
|
||||
- Monitor re-renders with React DevTools
|
||||
@@ -176,6 +189,7 @@ export function AuthProvider({ children, store }) {
|
||||
### Pitfall 2.2: Prop Drilling Instead of Context
|
||||
|
||||
**❌ WRONG:**
|
||||
|
||||
```typescript
|
||||
// Passing through 5 levels
|
||||
<Layout user={user}>
|
||||
@@ -190,6 +204,7 @@ export function AuthProvider({ children, store }) {
|
||||
```
|
||||
|
||||
**✅ CORRECT:**
|
||||
|
||||
```typescript
|
||||
// Provider at top
|
||||
<AuthProvider>
|
||||
@@ -206,6 +221,7 @@ export function AuthProvider({ children, store }) {
|
||||
```
|
||||
|
||||
**Key Takeaway:**
|
||||
|
||||
- **Use Context for data needed by many components**
|
||||
- Avoid prop drilling beyond 2-3 levels
|
||||
- But don't overuse - local state is often better
|
||||
@@ -217,6 +233,7 @@ export function AuthProvider({ children, store }) {
|
||||
### Pitfall 3.1: Mixing Render State Access and Mutation Logic
|
||||
|
||||
**❌ WRONG (Mixing patterns):**
|
||||
|
||||
```typescript
|
||||
function MyComponent() {
|
||||
// Using hook for render state
|
||||
@@ -231,6 +248,7 @@ function MyComponent() {
|
||||
```
|
||||
|
||||
**✅ CORRECT (Separate patterns):**
|
||||
|
||||
```typescript
|
||||
function MyComponent() {
|
||||
// Hook for render state (subscribes to changes)
|
||||
@@ -245,12 +263,14 @@ function MyComponent() {
|
||||
```
|
||||
|
||||
**Why This Pattern?**
|
||||
|
||||
- **Render state**: Use hook → component re-renders on changes
|
||||
- **Mutations**: Use `getState()` → no subscription, no re-renders
|
||||
- **Performance**: Event handlers don't need to subscribe
|
||||
- **Clarity**: Clear distinction between read and write
|
||||
|
||||
**Key Takeaway:**
|
||||
|
||||
- **Use hooks for state that affects rendering**
|
||||
- **Use `getState()` for mutations in callbacks**
|
||||
- Don't subscribe when you don't need to
|
||||
@@ -260,6 +280,7 @@ function MyComponent() {
|
||||
### Pitfall 3.2: Not Using Selectors for Optimization
|
||||
|
||||
**❌ SUBOPTIMAL:**
|
||||
|
||||
```typescript
|
||||
function UserAvatar() {
|
||||
// Re-renders on ANY auth state change! ❌
|
||||
@@ -270,6 +291,7 @@ function UserAvatar() {
|
||||
```
|
||||
|
||||
**✅ OPTIMIZED:**
|
||||
|
||||
```typescript
|
||||
function UserAvatar() {
|
||||
// Only re-renders when user changes ✅
|
||||
@@ -280,6 +302,7 @@ function UserAvatar() {
|
||||
```
|
||||
|
||||
**Key Takeaway:**
|
||||
|
||||
- **Use selectors for components that only need subset of state**
|
||||
- Reduces unnecessary re-renders
|
||||
- Especially important in frequently updating stores
|
||||
@@ -291,13 +314,16 @@ function UserAvatar() {
|
||||
### Pitfall 4.1: Using `any` Type
|
||||
|
||||
**❌ WRONG:**
|
||||
|
||||
```typescript
|
||||
function processUser(user: any) { // ❌ Loses all type safety
|
||||
return user.name.toUpperCase(); // No error if user.name is undefined
|
||||
function processUser(user: any) {
|
||||
// ❌ Loses all type safety
|
||||
return user.name.toUpperCase(); // No error if user.name is undefined
|
||||
}
|
||||
```
|
||||
|
||||
**✅ CORRECT:**
|
||||
|
||||
```typescript
|
||||
function processUser(user: User | null) {
|
||||
if (!user?.name) {
|
||||
@@ -308,6 +334,7 @@ function processUser(user: User | null) {
|
||||
```
|
||||
|
||||
**Key Takeaway:**
|
||||
|
||||
- **Never use `any` - use `unknown` if type is truly unknown**
|
||||
- Define proper types for all function parameters
|
||||
- Use type guards for runtime checks
|
||||
@@ -317,15 +344,17 @@ function processUser(user: User | null) {
|
||||
### Pitfall 4.2: Implicit Types Leading to Errors
|
||||
|
||||
**❌ WRONG:**
|
||||
|
||||
```typescript
|
||||
// No explicit return type - type inference can be wrong
|
||||
export function useAuth() {
|
||||
const context = useContext(AuthContext);
|
||||
return context; // What type is this? ❌
|
||||
return context; // What type is this? ❌
|
||||
}
|
||||
```
|
||||
|
||||
**✅ CORRECT:**
|
||||
|
||||
```typescript
|
||||
// Explicit return type with overloads
|
||||
export function useAuth(): AuthState;
|
||||
@@ -333,13 +362,14 @@ export function useAuth<T>(selector: (state: AuthState) => T): T;
|
||||
export function useAuth<T>(selector?: (state: AuthState) => T): AuthState | T {
|
||||
const context = useContext(AuthContext);
|
||||
if (!context) {
|
||||
throw new Error("useAuth must be used within AuthProvider");
|
||||
throw new Error('useAuth must be used within AuthProvider');
|
||||
}
|
||||
return selector ? context(selector) : context();
|
||||
}
|
||||
```
|
||||
|
||||
**Key Takeaway:**
|
||||
|
||||
- **Always provide explicit return types for public APIs**
|
||||
- Use function overloads for polymorphic functions
|
||||
- Document types in JSDoc comments
|
||||
@@ -349,16 +379,19 @@ export function useAuth<T>(selector?: (state: AuthState) => T): AuthState | T {
|
||||
### Pitfall 4.3: Not Using `import type` for Type-Only Imports
|
||||
|
||||
**❌ SUBOPTIMAL:**
|
||||
|
||||
```typescript
|
||||
import { ReactNode } from 'react'; // Might be bundled even if only used for types
|
||||
import { ReactNode } from 'react'; // Might be bundled even if only used for types
|
||||
```
|
||||
|
||||
**✅ CORRECT:**
|
||||
|
||||
```typescript
|
||||
import type { ReactNode } from 'react'; // Guaranteed to be stripped from bundle
|
||||
import type { ReactNode } from 'react'; // Guaranteed to be stripped from bundle
|
||||
```
|
||||
|
||||
**Key Takeaway:**
|
||||
|
||||
- **Use `import type` for type-only imports**
|
||||
- Smaller bundle size
|
||||
- Clearer intent
|
||||
@@ -370,6 +403,7 @@ import type { ReactNode } from 'react'; // Guaranteed to be stripped from bundl
|
||||
### Pitfall 5.1: Forgetting Optional Chaining for Nullable Values
|
||||
|
||||
**❌ WRONG:**
|
||||
|
||||
```typescript
|
||||
function UserProfile() {
|
||||
const { user } = useAuth();
|
||||
@@ -378,6 +412,7 @@ function UserProfile() {
|
||||
```
|
||||
|
||||
**✅ CORRECT:**
|
||||
|
||||
```typescript
|
||||
function UserProfile() {
|
||||
const { user } = useAuth();
|
||||
@@ -397,6 +432,7 @@ function UserProfile() {
|
||||
```
|
||||
|
||||
**Key Takeaway:**
|
||||
|
||||
- **Always handle null/undefined cases**
|
||||
- Use optional chaining (`?.`) and nullish coalescing (`??`)
|
||||
- Provide fallback UI for missing data
|
||||
@@ -406,6 +442,7 @@ function UserProfile() {
|
||||
### Pitfall 5.2: Mixing Concerns in Components
|
||||
|
||||
**❌ WRONG:**
|
||||
|
||||
```typescript
|
||||
function UserDashboard() {
|
||||
const [users, setUsers] = useState([]);
|
||||
@@ -429,6 +466,7 @@ function UserDashboard() {
|
||||
```
|
||||
|
||||
**✅ CORRECT:**
|
||||
|
||||
```typescript
|
||||
// Custom hook for data fetching
|
||||
function useUsers() {
|
||||
@@ -460,6 +498,7 @@ function UserDashboard() {
|
||||
```
|
||||
|
||||
**Key Takeaway:**
|
||||
|
||||
- **Separate concerns: data fetching, business logic, rendering**
|
||||
- Extract logic to custom hooks
|
||||
- Keep components focused on UI
|
||||
@@ -471,6 +510,7 @@ function UserDashboard() {
|
||||
### Pitfall 6.1: Wrong Provider Order
|
||||
|
||||
**❌ WRONG:**
|
||||
|
||||
```typescript
|
||||
// AuthInitializer outside AuthProvider ❌
|
||||
function RootLayout({ children }) {
|
||||
@@ -486,6 +526,7 @@ function RootLayout({ children }) {
|
||||
```
|
||||
|
||||
**✅ CORRECT:**
|
||||
|
||||
```typescript
|
||||
function RootLayout({ children }) {
|
||||
return (
|
||||
@@ -500,6 +541,7 @@ function RootLayout({ children }) {
|
||||
```
|
||||
|
||||
**Key Takeaway:**
|
||||
|
||||
- **Providers must wrap components that use them**
|
||||
- Order matters when there are dependencies
|
||||
- Keep provider tree shallow (performance)
|
||||
@@ -509,6 +551,7 @@ function RootLayout({ children }) {
|
||||
### Pitfall 6.2: Creating Too Many Providers
|
||||
|
||||
**❌ WRONG:**
|
||||
|
||||
```typescript
|
||||
// Separate provider for every piece of state ❌
|
||||
<UserProvider>
|
||||
@@ -525,6 +568,7 @@ function RootLayout({ children }) {
|
||||
```
|
||||
|
||||
**✅ BETTER:**
|
||||
|
||||
```typescript
|
||||
// Combine related state, use Zustand for most things
|
||||
<AuthProvider> {/* Only for auth DI */}
|
||||
@@ -541,6 +585,7 @@ const useUserPreferences = create(...); // User settings
|
||||
```
|
||||
|
||||
**Key Takeaway:**
|
||||
|
||||
- **Use Context only when necessary** (DI, third-party integrations)
|
||||
- **Use Zustand for most global state** (no provider needed)
|
||||
- Avoid provider hell
|
||||
@@ -552,6 +597,7 @@ const useUserPreferences = create(...); // User settings
|
||||
### Pitfall 7.1: Using Hooks in Event Handlers
|
||||
|
||||
**❌ WRONG:**
|
||||
|
||||
```typescript
|
||||
function MyComponent() {
|
||||
const handleClick = () => {
|
||||
@@ -564,6 +610,7 @@ function MyComponent() {
|
||||
```
|
||||
|
||||
**✅ CORRECT:**
|
||||
|
||||
```typescript
|
||||
function MyComponent() {
|
||||
const { user } = useAuth(); // ✅ Hook at component top level
|
||||
@@ -587,6 +634,7 @@ function MyComponent() {
|
||||
```
|
||||
|
||||
**Key Takeaway:**
|
||||
|
||||
- **Never call hooks inside event handlers**
|
||||
- For render state: Call hook at top level, access in closure
|
||||
- For mutations: Use `store.getState().method()`
|
||||
@@ -596,13 +644,15 @@ function MyComponent() {
|
||||
### Pitfall 7.2: Not Handling Async Errors in Event Handlers
|
||||
|
||||
**❌ WRONG:**
|
||||
|
||||
```typescript
|
||||
const handleSubmit = async (data: FormData) => {
|
||||
await apiCall(data); // ❌ No error handling!
|
||||
await apiCall(data); // ❌ No error handling!
|
||||
};
|
||||
```
|
||||
|
||||
**✅ CORRECT:**
|
||||
|
||||
```typescript
|
||||
const handleSubmit = async (data: FormData) => {
|
||||
try {
|
||||
@@ -616,6 +666,7 @@ const handleSubmit = async (data: FormData) => {
|
||||
```
|
||||
|
||||
**Key Takeaway:**
|
||||
|
||||
- **Always wrap async calls in try/catch**
|
||||
- Provide user feedback for both success and errors
|
||||
- Log errors for debugging
|
||||
@@ -627,6 +678,7 @@ const handleSubmit = async (data: FormData) => {
|
||||
### Pitfall 8.1: Not Mocking Context Providers in Tests
|
||||
|
||||
**❌ WRONG:**
|
||||
|
||||
```typescript
|
||||
// Test without provider ❌
|
||||
test('renders user name', () => {
|
||||
@@ -636,6 +688,7 @@ test('renders user name', () => {
|
||||
```
|
||||
|
||||
**✅ CORRECT:**
|
||||
|
||||
```typescript
|
||||
// Mock the hook
|
||||
jest.mock('@/lib/stores', () => ({
|
||||
@@ -654,6 +707,7 @@ test('renders user name', () => {
|
||||
```
|
||||
|
||||
**Key Takeaway:**
|
||||
|
||||
- **Mock hooks at module level in tests**
|
||||
- Provide necessary return values for each test case
|
||||
- Test both success and error states
|
||||
@@ -663,6 +717,7 @@ test('renders user name', () => {
|
||||
### Pitfall 8.2: Testing Implementation Details
|
||||
|
||||
**❌ WRONG:**
|
||||
|
||||
```typescript
|
||||
test('calls useAuthStore hook', () => {
|
||||
const spy = jest.spyOn(require('@/lib/stores'), 'useAuthStore');
|
||||
@@ -672,6 +727,7 @@ test('calls useAuthStore hook', () => {
|
||||
```
|
||||
|
||||
**✅ CORRECT:**
|
||||
|
||||
```typescript
|
||||
test('displays user name when authenticated', () => {
|
||||
(useAuth as jest.Mock).mockReturnValue({
|
||||
@@ -685,6 +741,7 @@ test('displays user name when authenticated', () => {
|
||||
```
|
||||
|
||||
**Key Takeaway:**
|
||||
|
||||
- **Test behavior, not implementation**
|
||||
- Focus on what the user sees/does
|
||||
- Don't test internal API calls unless critical
|
||||
@@ -696,6 +753,7 @@ test('displays user name when authenticated', () => {
|
||||
### Pitfall 9.1: Not Using React.memo for Expensive Components
|
||||
|
||||
**❌ SUBOPTIMAL:**
|
||||
|
||||
```typescript
|
||||
// Re-renders every time parent re-renders ❌
|
||||
function ExpensiveChart({ data }) {
|
||||
@@ -705,6 +763,7 @@ function ExpensiveChart({ data }) {
|
||||
```
|
||||
|
||||
**✅ OPTIMIZED:**
|
||||
|
||||
```typescript
|
||||
// Only re-renders when data changes ✅
|
||||
export const ExpensiveChart = React.memo(function ExpensiveChart({ data }) {
|
||||
@@ -713,6 +772,7 @@ export const ExpensiveChart = React.memo(function ExpensiveChart({ data }) {
|
||||
```
|
||||
|
||||
**Key Takeaway:**
|
||||
|
||||
- **Use `React.memo` for expensive components**
|
||||
- Especially useful for list items, charts, heavy UI
|
||||
- Profile with React DevTools to identify candidates
|
||||
@@ -722,6 +782,7 @@ export const ExpensiveChart = React.memo(function ExpensiveChart({ data }) {
|
||||
### Pitfall 9.2: Creating Functions Inside Render
|
||||
|
||||
**❌ SUBOPTIMAL:**
|
||||
|
||||
```typescript
|
||||
function MyComponent() {
|
||||
return (
|
||||
@@ -733,6 +794,7 @@ function MyComponent() {
|
||||
```
|
||||
|
||||
**✅ OPTIMIZED:**
|
||||
|
||||
```typescript
|
||||
function MyComponent() {
|
||||
const handleClick = useCallback(() => {
|
||||
@@ -744,15 +806,18 @@ function MyComponent() {
|
||||
```
|
||||
|
||||
**When to Optimize:**
|
||||
|
||||
- **For memoized child components** (memo, PureComponent)
|
||||
- **For expensive event handlers**
|
||||
- **When profiling shows performance issues**
|
||||
|
||||
**When NOT to optimize:**
|
||||
|
||||
- **Simple components with cheap operations** (premature optimization)
|
||||
- **One-off event handlers**
|
||||
|
||||
**Key Takeaway:**
|
||||
|
||||
- **Use `useCallback` for functions passed to memoized children**
|
||||
- But don't optimize everything - profile first
|
||||
|
||||
@@ -763,6 +828,7 @@ function MyComponent() {
|
||||
### Pitfall 10.1: Not Using Barrel Exports
|
||||
|
||||
**❌ INCONSISTENT:**
|
||||
|
||||
```typescript
|
||||
// Deep imports all over the codebase
|
||||
import { useAuth } from '@/lib/auth/AuthContext';
|
||||
@@ -771,6 +837,7 @@ import { User } from '@/lib/stores/authStore';
|
||||
```
|
||||
|
||||
**✅ CONSISTENT:**
|
||||
|
||||
```typescript
|
||||
// Barrel exports in stores/index.ts
|
||||
export { useAuth, AuthProvider } from '../auth/AuthContext';
|
||||
@@ -781,6 +848,7 @@ import { useAuth, useAuthStore, User } from '@/lib/stores';
|
||||
```
|
||||
|
||||
**Key Takeaway:**
|
||||
|
||||
- **Create barrel exports (index.ts) for public APIs**
|
||||
- Easier to refactor internal structure
|
||||
- Consistent import paths across codebase
|
||||
@@ -790,31 +858,44 @@ import { useAuth, useAuthStore, User } from '@/lib/stores';
|
||||
### Pitfall 10.2: Circular Dependencies
|
||||
|
||||
**❌ WRONG:**
|
||||
|
||||
```typescript
|
||||
// fileA.ts
|
||||
import { functionB } from './fileB';
|
||||
export function functionA() { return functionB(); }
|
||||
export function functionA() {
|
||||
return functionB();
|
||||
}
|
||||
|
||||
// fileB.ts
|
||||
import { functionA } from './fileA'; // ❌ Circular!
|
||||
export function functionB() { return functionA(); }
|
||||
import { functionA } from './fileA'; // ❌ Circular!
|
||||
export function functionB() {
|
||||
return functionA();
|
||||
}
|
||||
```
|
||||
|
||||
**✅ CORRECT:**
|
||||
|
||||
```typescript
|
||||
// utils.ts
|
||||
export function sharedFunction() { /* shared logic */ }
|
||||
export function sharedFunction() {
|
||||
/* shared logic */
|
||||
}
|
||||
|
||||
// fileA.ts
|
||||
import { sharedFunction } from './utils';
|
||||
export function functionA() { return sharedFunction(); }
|
||||
export function functionA() {
|
||||
return sharedFunction();
|
||||
}
|
||||
|
||||
// fileB.ts
|
||||
import { sharedFunction } from './utils';
|
||||
export function functionB() { return sharedFunction(); }
|
||||
export function functionB() {
|
||||
return sharedFunction();
|
||||
}
|
||||
```
|
||||
|
||||
**Key Takeaway:**
|
||||
|
||||
- **Avoid circular imports**
|
||||
- Extract shared code to separate modules
|
||||
- Keep dependency graph acyclic
|
||||
@@ -840,6 +921,7 @@ npm run build
|
||||
```
|
||||
|
||||
**In browser:**
|
||||
|
||||
- [ ] No console errors or warnings
|
||||
- [ ] Components render correctly
|
||||
- [ ] No infinite loops or excessive re-renders (React DevTools)
|
||||
|
||||
Reference in New Issue
Block a user