SolidJS Reactive Framework

Fine-grained reactivity with SolidJS, signals, stores, and performance optimization

# SolidJS Best Practices

Comprehensive guide for building high-performance web applications with SolidJS, featuring fine-grained reactivity, signals, and optimized rendering.

---

## Core SolidJS Principles

1. **Fine-Grained Reactivity**
   - Understand SolidJS's compile-time optimizations
   - Use signals for reactive state management
   - Leverage automatic dependency tracking
   - Example signal patterns:
     ```tsx
     import { createSignal, createEffect, createMemo } from 'solid-js';

     function Counter() {
       const [count, setCount] = createSignal(0);
       const [multiplier, setMultiplier] = createSignal(2);

       // Derived state with automatic dependency tracking
       const doubledCount = createMemo(() => count() * multiplier());

       // Effects run when dependencies change
       createEffect(() => {
         console.log(`Count is now: ${count()}`);
       });

       // Effect with explicit dependencies
       createEffect(() => {
         if (count() > 10) {
           console.log('Count is getting high!');
         }
       });

       return (
         <div>
           <p>Count: {count()}</p>
           <p>Doubled: {doubledCount()}</p>
           <p>Multiplier: {multiplier()}</p>

           <button onClick={() => setCount(c => c + 1)}>
             Increment
           </button>
           <button onClick={() => setMultiplier(m => m + 1)}>
             Increase Multiplier
           </button>
           <button onClick={() => setCount(0)}>
             Reset
           </button>
         </div>
       );
     }
     ```

2. **Component Architecture**
   - Write functional components with reactive primitives
   - Use props destructuring with proper reactivity
   - Implement proper component composition
   - Example component patterns:
     ```tsx
     import { Component, JSX, splitProps, mergeProps } from 'solid-js';
     import { createSignal, createMemo } from 'solid-js';

     interface UserCardProps {
       user: {
         id: string;
         name: string;
         email: string;
         avatar?: string;
       };
       selected?: boolean;
       onSelect?: (userId: string) => void;
       onEdit?: (userId: string) => void;
       class?: string;
     }

     const UserCard: Component<UserCardProps> = (props) => {
       // Split props to handle reactivity correctly
       const [local, others] = splitProps(props, ['user', 'selected', 'onSelect', 'onEdit']);

       // Merge with default props
       const merged = mergeProps({ selected: false }, props);

       // Derived state
       const displayName = createMemo(() =>
         local.user.name || local.user.email.split('@')[0]
       );

       const avatarUrl = createMemo(() =>
         local.user.avatar || `https://avatar.vercel.sh/${local.user.id}`
       );

       const handleSelect = () => {
         local.onSelect?.(local.user.id);
       };

       const handleEdit = () => {
         local.onEdit?.(local.user.id);
       };

       return (
         <div
           class={`user-card ${merged.selected ? 'selected' : ''} ${others.class || ''}`}
           classList={{
             'user-card': true,
             'selected': merged.selected,
             'has-avatar': !!local.user.avatar
           }}
         >
           <img
             src={avatarUrl()}
             alt={`${displayName()} avatar`}
             class="avatar"
           />

           <div class="user-info">
             <h3 class="name">{displayName()}</h3>
             <p class="email">{local.user.email}</p>
           </div>

           <div class="actions">
             <button
               onClick={handleSelect}
               class="select-btn"
               type="button"
             >
               {merged.selected ? 'Deselect' : 'Select'}
             </button>
             <button
               onClick={handleEdit}
               class="edit-btn"
               type="button"
             >
               Edit
             </button>
           </div>
         </div>
       );
     };

     export default UserCard;
     ```

3. **Control Flow and Conditional Rendering**
   - Use SolidJS control flow components for optimal performance
   - Implement proper conditional rendering patterns
   - Handle lists with For component and proper keying
   - Example control flow patterns:
     ```tsx
     import { Component, createSignal, For, Show, Switch, Match } from 'solid-js';

     interface User {
       id: string;
       name: string;
       status: 'online' | 'offline' | 'away';
       role: 'admin' | 'user' | 'guest';
     }

     const UserList: Component = () => {
       const [users, setUsers] = createSignal<User[]>([]);
       const [loading, setLoading] = createSignal(true);
       const [error, setError] = createSignal<string | null>(null);
       const [filter, setFilter] = createSignal<'all' | 'online' | 'offline'>('all');

       // Filtered users with reactive computation
       const filteredUsers = createMemo(() => {
         const allUsers = users();
         const currentFilter = filter();

         if (currentFilter === 'all') return allUsers;
         return allUsers.filter(user => user.status === currentFilter);
       });

       // Load users effect
       createEffect(async () => {
         try {
           setLoading(true);
           setError(null);
           const response = await fetch('/api/users');
           const userData = await response.json();
           setUsers(userData);
         } catch (err) {
           setError('Failed to load users');
         } finally {
           setLoading(false);
         }
       });

       return (
         <div class="user-list">
           <div class="filters">
             <button
               onClick={() => setFilter('all')}
               classList={{ active: filter() === 'all' }}
             >
               All Users
             </button>
             <button
               onClick={() => setFilter('online')}
               classList={{ active: filter() === 'online' }}
             >
               Online
             </button>
             <button
               onClick={() => setFilter('offline')}
               classList={{ active: filter() === 'offline' }}
             >
               Offline
             </button>
           </div>

           <Show when={!loading()} fallback={<div class="loading">Loading users...</div>}>
             <Show when={!error()} fallback={<div class="error">{error()}</div>}>
               <Show
                 when={filteredUsers().length > 0}
                 fallback={<div class="empty">No users found</div>}
               >
                 <div class="user-grid">
                   <For each={filteredUsers()}>
                     {(user) => (
                       <div class="user-item">
                         <div class="user-avatar">
                           {user.name.charAt(0).toUpperCase()}
                         </div>
                         <div class="user-details">
                           <h4>{user.name}</h4>
                           <Switch>
                             <Match when={user.status === 'online'}>
                               <span class="status online">Online</span>
                             </Match>
                             <Match when={user.status === 'away'}>
                               <span class="status away">Away</span>
                             </Match>
                             <Match when={user.status === 'offline'}>
                               <span class="status offline">Offline</span>
                             </Match>
                           </Switch>
                           <Switch>
                             <Match when={user.role === 'admin'}>
                               <span class="role admin">Administrator</span>
                             </Match>
                             <Match when={user.role === 'user'}>
                               <span class="role user">User</span>
                             </Match>
                             <Match when={user.role === 'guest'}>
                               <span class="role guest">Guest</span>
                             </Match>
                           </Switch>
                         </div>
                       </div>
                     )}
                   </For>
                 </div>
               </Show>
             </Show>
           </Show>
         </div>
       );
     };
     ```

---

## State Management with Stores

4. **Creating and Using Stores**
   - Implement global state with stores
   - Use context for dependency injection
   - Create reactive store patterns
   - Example store implementation:
     ```tsx
     // stores/auth.tsx
     import { createSignal, createContext, useContext, JSX, Component } from 'solid-js';
     import { createStore, produce } from 'solid-js/store';

     interface User {
       id: string;
       name: string;
       email: string;
       role: 'admin' | 'user';
     }

     interface AuthState {
       user: User | null;
       loading: boolean;
       error: string | null;
     }

     interface AuthContextValue {
       state: AuthState;
       login: (email: string, password: string) => Promise<void>;
       logout: () => void;
       register: (userData: RegisterData) => Promise<void>;
       clearError: () => void;
     }

     const AuthContext = createContext<AuthContextValue>();

     export const AuthProvider: Component<{ children: JSX.Element }> = (props) => {
       const [state, setState] = createStore<AuthState>({
         user: null,
         loading: false,
         error: null
       });

       const login = async (email: string, password: string) => {
         setState('loading', true);
         setState('error', null);

         try {
           const response = await fetch('/api/auth/login', {
             method: 'POST',
             headers: { 'Content-Type': 'application/json' },
             body: JSON.stringify({ email, password })
           });

           if (!response.ok) {
             throw new Error('Login failed');
           }

           const { user, token } = await response.json();

           // Store token
           localStorage.setItem('authToken', token);

           setState('user', user);
         } catch (error) {
           setState('error', error.message);
         } finally {
           setState('loading', false);
         }
       };

       const logout = () => {
         localStorage.removeItem('authToken');
         setState(produce(s => {
           s.user = null;
           s.error = null;
         }));
       };

       const register = async (userData: RegisterData) => {
         setState('loading', true);
         setState('error', null);

         try {
           const response = await fetch('/api/auth/register', {
             method: 'POST',
             headers: { 'Content-Type': 'application/json' },
             body: JSON.stringify(userData)
           });

           if (!response.ok) {
             const errorData = await response.json();
             throw new Error(errorData.message);
           }

           const { user, token } = await response.json();

           localStorage.setItem('authToken', token);
           setState('user', user);
         } catch (error) {
           setState('error', error.message);
         } finally {
           setState('loading', false);
         }
       };

       const clearError = () => {
         setState('error', null);
       };

       // Check for existing token on mount
       const token = localStorage.getItem('authToken');
       if (token) {
         // Verify token and get user data
         fetch('/api/auth/me', {
           headers: { Authorization: `Bearer ${token}` }
         })
         .then(response => {
           if (response.ok) {
             return response.json();
           } else {
             localStorage.removeItem('authToken');
           }
         })
         .then(user => {
           if (user) setState('user', user);
         })
         .catch(() => {
           localStorage.removeItem('authToken');
         });
       }

       const contextValue: AuthContextValue = {
         state,
         login,
         logout,
         register,
         clearError
       };

       return (
         <AuthContext.Provider value={contextValue}>
           {props.children}
         </AuthContext.Provider>
       );
     };

     export const useAuth = () => {
       const context = useContext(AuthContext);
       if (!context) {
         throw new Error('useAuth must be used within AuthProvider');
       }
       return context;
     };

     // Usage in components
     const LoginForm: Component = () => {
       const auth = useAuth();
       const [email, setEmail] = createSignal('');
       const [password, setPassword] = createSignal('');

       const handleSubmit = (e: Event) => {
         e.preventDefault();
         auth.login(email(), password());
       };

       return (
         <form onSubmit={handleSubmit}>
           <Show when={auth.state.error}>
             <div class="error">{auth.state.error}</div>
           </Show>

           <input
             type="email"
             placeholder="Email"
             value={email()}
             onInput={(e) => setEmail(e.currentTarget.value)}
             required
           />

           <input
             type="password"
             placeholder="Password"
             value={password()}
             onInput={(e) => setPassword(e.currentTarget.value)}
             required
           />

           <button type="submit" disabled={auth.state.loading}>
             {auth.state.loading ? 'Logging in...' : 'Login'}
           </button>
         </form>
       );
     };
     ```

5. **Resource Pattern for Data Fetching**
   - Use createResource for async data fetching
   - Implement proper loading and error states
   - Handle data refetching and caching
   - Example resource patterns:
     ```tsx
     import { createResource, createSignal, For, Show, Suspense } from 'solid-js';

     interface Post {
       id: string;
       title: string;
       content: string;
       authorId: string;
       createdAt: string;
     }

     interface PostsParams {
       page: number;
       category?: string;
       search?: string;
     }

     const PostList: Component = () => {
       const [params, setParams] = createSignal<PostsParams>({ page: 1 });

       // Resource for fetching posts
       const [posts, { mutate, refetch }] = createResource(
         params,
         async (params): Promise<{ posts: Post[]; totalPages: number }> => {
           const searchParams = new URLSearchParams({
             page: params.page.toString(),
             ...(params.category && { category: params.category }),
             ...(params.search && { search: params.search })
           });

           const response = await fetch(`/api/posts?${searchParams}`);
           if (!response.ok) {
             throw new Error('Failed to fetch posts');
           }

           return response.json();
         }
       );

       // Resource for fetching categories
       const [categories] = createResource(async (): Promise<string[]> => {
         const response = await fetch('/api/categories');
         if (!response.ok) {
           throw new Error('Failed to fetch categories');
         }
         return response.json();
       });

       const handleCategoryChange = (category: string) => {
         setParams(prev => ({ ...prev, category: category || undefined, page: 1 }));
       };

       const handleSearch = (search: string) => {
         setParams(prev => ({ ...prev, search: search || undefined, page: 1 }));
       };

       const handlePageChange = (page: number) => {
         setParams(prev => ({ ...prev, page }));
       };

       const addOptimisticPost = (newPost: Post) => {
         mutate(prev => ({
           posts: [newPost, ...(prev?.posts || [])],
           totalPages: prev?.totalPages || 1
         }));
       };

       return (
         <div class="post-list">
           <div class="filters">
             <Suspense fallback={<div>Loading categories...</div>}>
               <select onChange={(e) => handleCategoryChange(e.currentTarget.value)}>
                 <option value="">All Categories</option>
                 <For each={categories()}>
                   {(category) => <option value={category}>{category}</option>}
                 </For>
               </select>
             </Suspense>

             <input
               type="search"
               placeholder="Search posts..."
               onInput={(e) => handleSearch(e.currentTarget.value)}
             />

             <button onClick={() => refetch()}>
               Refresh
             </button>
           </div>

           <Suspense fallback={<div class="loading">Loading posts...</div>}>
             <Show
               when={posts()}
               fallback={<div class="error">Failed to load posts</div>}
             >
               {(postsData) => (
                 <>
                   <Show
                     when={postsData().posts.length > 0}
                     fallback={<div class="empty">No posts found</div>}
                   >
                     <div class="posts">
                       <For each={postsData().posts}>
                         {(post) => (
                           <article class="post-card">
                             <h2>{post.title}</h2>
                             <p>{post.content.substring(0, 150)}...</p>
                             <time>{new Date(post.createdAt).toLocaleDateString()}</time>
                           </article>
                         )}
                       </For>
                     </div>

                     <Show when={postsData().totalPages > 1}>
                       <div class="pagination">
                         <For each={Array.from({ length: postsData().totalPages }, (_, i) => i + 1)}>
                           {(page) => (
                             <button
                               onClick={() => handlePageChange(page)}
                               classList={{
                                 active: page === params().page
                               }}
                             >
                               {page}
                             </button>
                           )}
                         </For>
                       </div>
                     </Show>
                   </Show>
                 </>
               )}
             </Show>
           </Suspense>
         </div>
       );
     };
     ```

---

## Routing and Navigation

6. **Client-Side Routing with Solid Router**
   - Set up routing with @solidjs/router
   - Implement nested routes and layouts
   - Handle route parameters and query strings
   - Example routing setup:
     ```tsx
     // App.tsx
     import { Router, Routes, Route } from '@solidjs/router';
     import { lazy } from 'solid-js';

     // Lazy load components
     const Home = lazy(() => import('./pages/Home'));
     const About = lazy(() => import('./pages/About'));
     const Blog = lazy(() => import('./pages/Blog'));
     const BlogPost = lazy(() => import('./pages/BlogPost'));
     const Dashboard = lazy(() => import('./pages/Dashboard'));
     const Profile = lazy(() => import('./pages/Profile'));

     function App() {
       return (
         <Router>
           <Routes>
             <Route path="/" component={Home} />
             <Route path="/about" component={About} />
             <Route path="/blog" component={Blog} />
             <Route path="/blog/:slug" component={BlogPost} />
             <Route path="/dashboard" component={Dashboard}>
               <Route path="/profile" component={Profile} />
               <Route path="/settings" component={Settings} />
             </Route>
             <Route path="*" component={NotFound} />
           </Routes>
         </Router>
       );
     }

     // pages/BlogPost.tsx
     import { useParams, useSearchParams, useNavigate } from '@solidjs/router';
     import { createResource, Show, createEffect } from 'solid-js';

     const BlogPost: Component = () => {
       const params = useParams();
       const [searchParams, setSearchParams] = useSearchParams();
       const navigate = useNavigate();

       // Resource that depends on route params
       const [post] = createResource(
         () => params.slug,
         async (slug: string) => {
           const response = await fetch(`/api/posts/${slug}`);
           if (!response.ok) {
             throw new Error('Post not found');
           }
           return response.json();
         }
       );

       // Handle query parameters
       const commentId = () => searchParams.comment;

       createEffect(() => {
         if (commentId()) {
           // Scroll to comment
           const element = document.getElementById(`comment-${commentId()}`);
           if (element) {
             element.scrollIntoView({ behavior: 'smooth' });
           }
         }
       });

       const handleShare = () => {
         const url = window.location.href;
         navigator.clipboard.writeText(url);
       };

       const goBack = () => {
         navigate('/blog');
       };

       return (
         <div class="blog-post">
           <button onClick={goBack} class="back-btn">
             ← Back to Blog
           </button>

           <Show when={post()} fallback={<div>Loading post...</div>}>
             {(postData) => (
               <>
                 <article>
                   <header>
                     <h1>{postData().title}</h1>
                     <div class="meta">
                       <time>{new Date(postData().createdAt).toLocaleDateString()}</time>
                       <button onClick={handleShare}>Share</button>
                     </div>
                   </header>

                   <div class="content">
                     {postData().content}
                   </div>
                 </article>

                 <section class="comments">
                   <h3>Comments</h3>
                   <For each={postData().comments}>
                     {(comment) => (
                       <div
                         id={`comment-${comment.id}`}
                         class="comment"
                         classList={{ highlighted: comment.id === commentId() }}
                       >
                         <div class="comment-author">{comment.author}</div>
                         <div class="comment-content">{comment.content}</div>
                       </div>
                     )}
                   </For>
                 </section>
               </>
             )}
           </Show>
         </div>
       );
     };
     ```

---

## Performance Optimization

7. **Compilation and Bundle Optimization**
   - Leverage SolidJS's compile-time optimizations
   - Use lazy loading for code splitting
   - Implement proper bundling strategies
   - Example optimization techniques:
     ```tsx
     // Lazy loading with Suspense
     import { lazy, Suspense } from 'solid-js';

     const HeavyComponent = lazy(() => import('./HeavyComponent'));
     const Chart = lazy(() => import('./Chart'));

     const Dashboard: Component = () => {
       const [activeTab, setActiveTab] = createSignal<'overview' | 'analytics'>('overview');

       return (
         <div class="dashboard">
           <nav class="tabs">
             <button
               onClick={() => setActiveTab('overview')}
               classList={{ active: activeTab() === 'overview' }}
             >
               Overview
             </button>
             <button
               onClick={() => setActiveTab('analytics')}
               classList={{ active: activeTab() === 'analytics' }}
             >
               Analytics
             </button>
           </nav>

           <div class="tab-content">
             <Show when={activeTab() === 'overview'}>
               <Suspense fallback={<div>Loading overview...</div>}>
                 <HeavyComponent />
               </Suspense>
             </Show>

             <Show when={activeTab() === 'analytics'}>
               <Suspense fallback={<div>Loading analytics...</div>}>
                 <Chart />
               </Suspense>
             </Show>
           </div>
         </div>
       );
     };

     // Memoization for expensive computations
     import { createMemo } from 'solid-js';

     const ExpensiveComponent: Component<{ data: number[] }> = (props) => {
       const processedData = createMemo(() => {
         console.log('Processing data...'); // Only runs when data changes
         return props.data
           .filter(x => x > 0)
           .map(x => x * 2)
           .sort((a, b) => b - a);
       });

       const statistics = createMemo(() => {
         const data = processedData();
         return {
           sum: data.reduce((acc, val) => acc + val, 0),
           average: data.length > 0 ? data.reduce((acc, val) => acc + val, 0) / data.length : 0,
           max: Math.max(...data),
           min: Math.min(...data)
         };
       });

       return (
         <div class="expensive-component">
           <div class="stats">
             <div>Sum: {statistics().sum}</div>
             <div>Average: {statistics().average.toFixed(2)}</div>
             <div>Max: {statistics().max}</div>
             <div>Min: {statistics().min}</div>
           </div>

           <ul class="data-list">
             <For each={processedData()}>
               {(item) => <li>{item}</li>}
             </For>
           </ul>
         </div>
       );
     };
     ```

8. **Memory Management and Cleanup**
   - Properly clean up effects and subscriptions
   - Use onCleanup for resource disposal
   - Handle component unmounting gracefully
   - Example cleanup patterns:
     ```tsx
     import { createSignal, createEffect, onCleanup, onMount } from 'solid-js';

     const TimerComponent: Component = () => {
       const [time, setTime] = createSignal(new Date());
       const [isRunning, setIsRunning] = createSignal(true);

       let intervalId: number;

       onMount(() => {
         // Start timer when component mounts
         intervalId = setInterval(() => {
           if (isRunning()) {
             setTime(new Date());
           }
         }, 1000);
       });

       // Cleanup when component unmounts
       onCleanup(() => {
         if (intervalId) {
           clearInterval(intervalId);
         }
       });

       // Effect with cleanup for event listeners
       createEffect(() => {
         const handleVisibilityChange = () => {
           setIsRunning(!document.hidden);
         };

         document.addEventListener('visibilitychange', handleVisibilityChange);

         onCleanup(() => {
           document.removeEventListener('visibilitychange', handleVisibilityChange);
         });
       });

       return (
         <div class="timer">
           <h2>Current Time</h2>
           <p>{time().toLocaleTimeString()}</p>
           <p>Status: {isRunning() ? 'Running' : 'Paused'}</p>
           <button onClick={() => setIsRunning(!isRunning())}>
             {isRunning() ? 'Pause' : 'Resume'}
           </button>
         </div>
       );
     };

     // WebSocket connection with cleanup
     const useWebSocket = (url: string) => {
       const [socket, setSocket] = createSignal<WebSocket | null>(null);
       const [connected, setConnected] = createSignal(false);
       const [messages, setMessages] = createSignal<string[]>([]);

       createEffect(() => {
         const ws = new WebSocket(url);

         ws.onopen = () => {
           setConnected(true);
           setSocket(ws);
         };

         ws.onclose = () => {
           setConnected(false);
           setSocket(null);
         };

         ws.onmessage = (event) => {
           setMessages(prev => [...prev, event.data]);
         };

         onCleanup(() => {
           ws.close();
         });
       });

       const sendMessage = (message: string) => {
         const ws = socket();
         if (ws && ws.readyState === WebSocket.OPEN) {
           ws.send(message);
         }
       };

       return {
         connected,
         messages,
         sendMessage
       };
     };
     ```

---

## Testing SolidJS Applications

9. **Component Testing Strategies**
   - Test reactive behavior and signal updates
   - Mock external dependencies and resources
   - Test component props and event handling
   - Example testing setup:
     ```tsx
     // __tests__/Counter.test.tsx
     import { render, fireEvent, screen } from 'solid-testing-library';
     import { describe, it, expect } from 'vitest';
     import Counter from '../Counter';

     describe('Counter', () => {
       it('renders initial count', () => {
         render(() => <Counter initialCount={5} />);
         expect(screen.getByText('Count: 5')).toBeInTheDocument();
       });

       it('increments count when button is clicked', async () => {
         render(() => <Counter initialCount={0} />);

         const incrementButton = screen.getByText('Increment');
         await fireEvent.click(incrementButton);

         expect(screen.getByText('Count: 1')).toBeInTheDocument();
       });

       it('calls onCountChange when count updates', async () => {
         const mockCallback = vi.fn();
         render(() => <Counter initialCount={0} onCountChange={mockCallback} />);

         const incrementButton = screen.getByText('Increment');
         await fireEvent.click(incrementButton);

         expect(mockCallback).toHaveBeenCalledWith(1);
       });

       it('disables button when maxCount is reached', async () => {
         render(() => <Counter initialCount={9} maxCount={10} />);

         const incrementButton = screen.getByText('Increment');
         await fireEvent.click(incrementButton);

         expect(incrementButton).toBeDisabled();
         expect(screen.getByText('Count: 10')).toBeInTheDocument();
       });
     });

     // __tests__/store.test.ts
     import { createRoot } from 'solid-js';
     import { describe, it, expect, beforeEach } from 'vitest';
     import { createAuthStore } from '../stores/auth';

     describe('Auth Store', () => {
       it('should initialize with empty state', () => {
         createRoot(() => {
           const [state, actions] = createAuthStore();
           expect(state.user).toBeNull();
           expect(state.loading).toBe(false);
           expect(state.error).toBeNull();
         });
       });

       it('should set loading state during login', () => {
         createRoot(async () => {
           const [state, actions] = createAuthStore();

           // Mock fetch
           global.fetch = vi.fn().mockImplementation(() =>
             new Promise(resolve => setTimeout(() => resolve({
               ok: true,
               json: () => Promise.resolve({ user: { id: '1', name: 'John' }, token: 'abc' })
             }), 100))
           );

           const loginPromise = actions.login('test@example.com', 'password');
           expect(state.loading).toBe(true);

           await loginPromise;
           expect(state.loading).toBe(false);
           expect(state.user).toEqual({ id: '1', name: 'John' });
         });
       });
     });

     // __tests__/resources.test.tsx
     import { render, waitFor } from 'solid-testing-library';
     import { describe, it, expect, vi } from 'vitest';
     import PostList from '../PostList';

     describe('PostList Resource', () => {
       it('should load and display posts', async () => {
         const mockPosts = [
           { id: '1', title: 'Post 1', content: 'Content 1' },
           { id: '2', title: 'Post 2', content: 'Content 2' }
         ];

         global.fetch = vi.fn().mockResolvedValue({
           ok: true,
           json: () => Promise.resolve({ posts: mockPosts, totalPages: 1 })
         });

         render(() => <PostList />);

         await waitFor(() => {
           expect(screen.getByText('Post 1')).toBeInTheDocument();
           expect(screen.getByText('Post 2')).toBeInTheDocument();
         });
       });

       it('should handle loading state', () => {
         global.fetch = vi.fn().mockImplementation(() =>
           new Promise(resolve => setTimeout(resolve, 1000))
         );

         render(() => <PostList />);
         expect(screen.getByText('Loading posts...')).toBeInTheDocument();
       });

       it('should handle error state', async () => {
         global.fetch = vi.fn().mockRejectedValue(new Error('Network error'));

         render(() => <PostList />);

         await waitFor(() => {
           expect(screen.getByText('Failed to load posts')).toBeInTheDocument();
         });
       });
     });
     ```

---

## Summary Checklist

- [ ] Use signals for reactive state management
- [ ] Leverage createMemo for derived computations
- [ ] Implement proper component composition with splitProps
- [ ] Use SolidJS control flow components (Show, For, Switch)
- [ ] Create stores for global state management
- [ ] Use createResource for async data fetching
- [ ] Implement client-side routing with Solid Router
- [ ] Use lazy loading for code splitting and performance
- [ ] Properly clean up effects and subscriptions
- [ ] Write comprehensive tests for components and stores
- [ ] Optimize bundle size with tree shaking
- [ ] Handle error boundaries and loading states
- [ ] Use TypeScript for better development experience
- [ ] Implement proper accessibility patterns

---

Follow these practices to build high-performance, reactive web applications with SolidJS's fine-grained reactivity system.
SolidJS Reactive Framework - Cursor IDE AI Rule