Solid.js 示例

Solid.js 组件示例,包括钩子、状态管理、路由和现代 React 模式

💻 Solid.js 你好世界 typescript

🟢 simple

基础的 Solid.js 组件示例和使用 JSX 的你好世界应用程序

// Solid.js Hello World Examples

// 1. Basic functional component
import { createSignal } from 'solid-js';

function HelloWorld() {
    return <h1>Hello, World!</h1>;
}

// 2. Component with props
interface GreetingProps {
    name: string;
}

function Greeting(props: GreetingProps) {
    return <h1>Hello, {props.name}!</h1>;
}

// 3. Component with signal (reactive state)
function Counter() {
    const [count, setCount] = createSignal(0);

    return (
        <div>
            <h1>Count: {count()}</h1>
            <button onClick={() => setCount(count() + 1)}>
                Increment
            </button>
            <button onClick={() => setCount(count() - 1)}>
                Decrement
            </button>
        </div>
    );
}

// 4. Component with multiple signals
function UserProfile() {
    const [name, setName] = createSignal('Guest');
    const [age, setAge] = createSignal(25);
    const [isLoggedIn, setIsLoggedIn] = createSignal(false);

    return (
        <div>
            <h2>User Profile</h2>
            {isLoggedIn() ? (
                <div>
                    <p>Name: {name()}</p>
                    <p>Age: {age()}</p>
                    <input
                        type="text"
                        value={name()}
                        onInput={(e) => setName(e.currentTarget.value)}
                        placeholder="Enter name"
                    />
                    <input
                        type="number"
                        value={age()}
                        onInput={(e) => setAge(Number(e.currentTarget.value))}
                        placeholder="Enter age"
                    />
                    <button onClick={() => setIsLoggedIn(false)}>Logout</button>
                </div>
            ) : (
                <button onClick={() => setIsLoggedIn(true)}>Login</button>
            )}
        </div>
    );
}

// 5. Component with derived values
function ShoppingCart() {
    const [items, setItems] = createSignal([
        { name: 'Apple', price: 1.5, quantity: 2 },
        { name: 'Banana', price: 0.8, quantity: 5 }
    ]);

    const total = () => items().reduce((sum, item) => sum + (item.price * item.quantity), 0);
    const itemCount = () => items().reduce((sum, item) => sum + item.quantity, 0);

    return (
        <div>
            <h2>Shopping Cart</h2>
            <p>Items: {itemCount()}</p>
            <p>Total: ${total().toFixed(2)}</p>
            <ul>
                {items().map((item, index) => (
                    <li>
                        {item.name} - ${item.price} x {item.quantity}
                        <button
                            onClick={() => {
                                const newItems = [...items()];
                                newItems.splice(index, 1);
                                setItems(newItems);
                            }}
                        >
                            Remove
                        </button>
                    </li>
                ))}
            </ul>
        </div>
    );
}

// Usage examples
function App() {
    return (
        <div>
            <HelloWorld />
            <Greeting name="Solid.js" />
            <Counter />
            <UserProfile />
            <ShoppingCart />
        </div>
    );
}

export { HelloWorld, Greeting, Counter, UserProfile, ShoppingCart, App };

💻 Solid.js 信号和效果 typescript

🟡 intermediate

在 Solid.js 中使用响应式信号、效果和派生值

// Solid.js Signals and Effects

import {
    createSignal,
    createEffect,
    createMemo,
    createComputed,
    onMount,
    onCleanup,
    batch
} from 'solid-js';

// 1. Basic signals and effects
function SignalExample() {
    const [count, setCount] = createSignal(0);
    const [doubled, setDoubled] = createSignal(0);

    // Effect that runs when count changes
    createEffect(() => {
        console.log('Count changed to:', count());
        setDoubled(count() * 2);
    });

    // Effect with cleanup
    createEffect((prevCount) => {
        console.log('Previous count:', prevCount);
        return count();
    });

    return (
        <div>
            <p>Count: {count()}</p>
            <p>Doubled: {doubled()}</p>
            <button onClick={() => setCount(count() + 1)}>Increment</button>
        </div>
    );
}

// 2. Memo (derived values)
function MemoExample() {
    const [firstName, setFirstName] = createSignal('John');
    const [lastName, setLastName] = createSignal('Doe');

    // Memoized computed value
    const fullName = createMemo(() => `${firstName()} ${lastName()}`);
    const greeting = createMemo(() => `Hello, ${fullName()}!`);

    return (
        <div>
            <input
                type="text"
                value={firstName()}
                onInput={(e) => setFirstName(e.currentTarget.value)}
                placeholder="First name"
            />
            <input
                type="text"
                value={lastName()}
                onInput={(e) => setLastName(e.currentTarget.value)}
                placeholder="Last name"
            />
            <p>{greeting()}</p>
        </div>
    );
}

// 3. Lifecycle effects
function LifecycleExample() {
    const [data, setData] = createSignal(null);
    const [loading, setLoading] = createSignal(true);

    onMount(() => {
        console.log('Component mounted');

        // Simulate API call
        setTimeout(() => {
            setData({ message: 'Data loaded successfully!' });
            setLoading(false);
        }, 2000);
    });

    onCleanup(() => {
        console.log('Component will unmount');
    });

    // Effect for data changes
    createEffect(() => {
        if (data()) {
            console.log('Data updated:', data());
        }
    });

    return (
        <div>
            <h3>Lifecycle Example</h3>
            {loading() ? (
                <p>Loading...</p>
            ) : (
                <p>{data()?.message}</p>
            )}
        </div>
    );
}

// 4. Batch updates for performance
function BatchExample() {
    const [count, setCount] = createSignal(0);
    const [name, setName] = createSignal('');
    const [isValid, setIsValid] = createSignal(false);

    // Effect that runs only once when batched
    createEffect(() => {
        console.log('Count:', count(), 'Name:', name(), 'Valid:', isValid());
    });

    const updateMultiple = () => {
        batch(() => {
            setCount(count() + 1);
            setName('Updated');
            setIsValid(true);
        });
    };

    return (
        <div>
            <p>Count: {count()}</p>
            <p>Name: {name()}</p>
            <p>Valid: {isValid() ? 'Yes' : 'No'}</p>
            <button onClick={updateMultiple}>Update Multiple (Batched)</button>
        </div>
    );
}

// 5. Computed signals
function ComputedExample() {
    const [price, setPrice] = createSignal(100);
    const [quantity, setQuantity] = createSignal(2);
    const [taxRate] = createSignal(0.08);

    // Computed signal
    const subtotal = createComputed(() => price() * quantity());
    const tax = createComputed(() => subtotal() * taxRate());
    const total = createComputed(() => subtotal() + tax());

    return (
        <div>
            <h3>Price Calculator</h3>
            <label>
                Price: $
                <input
                    type="number"
                    value={price()}
                    onInput={(e) => setPrice(Number(e.currentTarget.value))}
                />
            </label>
            <label>
                Quantity:
                <input
                    type="number"
                    value={quantity()}
                    onInput={(e) => setQuantity(Number(e.currentTarget.value))}
                />
            </label>
            <p>Subtotal: ${subtotal().toFixed(2)}</p>
            <p>Tax ({taxRate() * 100}%): ${tax().toFixed(2)}</p>
            <p><strong>Total: ${total().toFixed(2)}</strong></p>
        </div>
    );
}

export { SignalExample, MemoExample, LifecycleExample, BatchExample, ComputedExample };

💻 Solid.js 存储 typescript

🟡 intermediate

使用 Solid.js 存储进行全局状态管理

// Solid.js Stores for Global State Management

import { createStore, SetStoreFunction } from 'solid-js/store';

// 1. Simple store
interface User {
    id: number;
    name: string;
    email: string;
    isLoggedIn: boolean;
}

const [user, setUser] = createStore<User>({
    id: 0,
    name: '',
    email: '',
    isLoggedIn: false
});

// Store actions
const userActions = {
    login: (userData: Omit<User, 'isLoggedIn'>) => {
        setUser({
            ...userData,
            isLoggedIn: true
        });
    },

    logout: () => {
        setUser({
            id: 0,
            name: '',
            email: '',
            isLoggedIn: false
        });
    },

    updateProfile: (updates: Partial<Pick<User, 'name' | 'email'>>) => {
        setUser(updates);
    }
};

// 2. Todo store
interface Todo {
    id: number;
    text: string;
    completed: boolean;
    createdAt: Date;
}

interface TodoStore {
    todos: Todo[];
    filter: 'all' | 'active' | 'completed';
}

const [todoStore, setTodoStore] = createStore<TodoStore>({
    todos: [],
    filter: 'all'
});

const todoActions = {
    addTodo: (text: string) => {
        const newTodo: Todo = {
            id: Date.now(),
            text,
            completed: false,
            createdAt: new Date()
        };

        setTodoStore('todos', todos => [...todos, newTodo]);
    },

    toggleTodo: (id: number) => {
        setTodoStore('todos',
            todo => todo.id === id,
            'completed',
            completed => !completed
        );
    },

    deleteTodo: (id: number) => {
        setTodoStore('todos', todos => todos.filter(todo => todo.id !== id));
    },

    clearCompleted: () => {
        setTodoStore('todos', todos => todos.filter(todo => !todo.completed));
    },

    setFilter: (filter: 'all' | 'active' | 'completed') => {
        setTodoStore('filter', filter);
    },

    // Getters
    get activeTodos() {
        return todoStore.todos.filter(todo => !todo.completed);
    },

    get completedTodos() {
        return todoStore.todos.filter(todo => todo.completed);
    },

    get filteredTodos() {
        switch (todoStore.filter) {
            case 'active':
                return todoStore.todos.filter(todo => !todo.completed);
            case 'completed':
                return todoStore.todos.filter(todo => todo.completed);
            default:
                return todoStore.todos;
        }
    },

    get stats() {
        const total = todoStore.todos.length;
        const completed = todoStore.todos.filter(todo => todo.completed).length;
        const active = total - completed;

        return { total, completed, active };
    }
};

// 3. Shopping cart store
interface CartItem {
    id: number;
    name: string;
    price: number;
    quantity: number;
}

interface CartStore {
    items: CartItem[];
    isOpen: boolean;
    couponCode?: string;
    discount: number;
}

const [cartStore, setCartStore] = createStore<CartStore>({
    items: [],
    isOpen: false,
    discount: 0
});

const cartActions = {
    addItem: (item: Omit<CartItem, 'quantity'>) => {
        setCartStore('items', items => {
            const existingItem = items.find(i => i.id === item.id);
            if (existingItem) {
                return items.map(i =>
                    i.id === item.id
                        ? { ...i, quantity: i.quantity + 1 }
                        : i
                );
            }
            return [...items, { ...item, quantity: 1 }];
        });
    },

    removeItem: (id: number) => {
        setCartStore('items', items => items.filter(item => item.id !== id));
    },

    updateQuantity: (id: number, quantity: number) => {
        if (quantity <= 0) {
            cartActions.removeItem(id);
        } else {
            setCartStore('items',
                item => item.id === id,
                'quantity',
                quantity
            );
        }
    },

    clearCart: () => {
        setCartStore('items', []);
    },

    toggleCart: () => {
        setCartStore('isOpen', isOpen => !isOpen);
    },

    applyCoupon: (code: string, discount: number) => {
        setCartStore('couponCode', code);
        setCartStore('discount', discount);
    }
};

// Cart computed values
const cartHelpers = {
    get totalItems() {
        return cartStore.items.reduce((sum, item) => sum + item.quantity, 0);
    },

    get subtotal() {
        return cartStore.items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
    },

    get total() {
        const subtotal = this.subtotal;
        return subtotal - (subtotal * cartStore.discount);
    }
};

// 4. Store with nested state
interface Product {
    id: number;
    name: string;
    price: number;
    category: string;
    inStock: boolean;
}

interface ProductsStore {
    products: Product[];
    categories: string[];
    selectedCategory: string;
    searchTerm: string;
    sortBy: 'name' | 'price';
    sortOrder: 'asc' | 'desc';
}

const [productsStore, setProductsStore] = createStore<ProductsStore>({
    products: [],
    categories: [],
    selectedCategory: '',
    searchTerm: '',
    sortBy: 'name',
    sortOrder: 'asc'
});

const productActions = {
    setProducts: (products: Product[]) => {
        setProductsStore('products', products);

        // Extract categories
        const categories = [...new Set(products.map(p => p.category))];
        setProductsStore('categories', categories);
    },

    setCategory: (category: string) => {
        setProductsStore('selectedCategory', category);
    },

    setSearchTerm: (term: string) => {
        setProductsStore('searchTerm', term);
    },

    setSorting: (sortBy: 'name' | 'price', sortOrder: 'asc' | 'desc') => {
        setProductsStore('sortBy', sortBy);
        setProductsStore('sortOrder', sortOrder);
    }
};

export {
    user, userActions,
    todoStore, todoActions,
    cartStore, cartActions, cartHelpers,
    productsStore, productActions
};

💻 Solid.js 路由 typescript

🟡 intermediate

使用 Solid Router 进行客户端路由

// Solid.js Routing Examples

import {
    createRouter,
    createRoute,
    createMemoryHistory,
    Router,
    Route,
    Link,
    useParams,
    useSearchParams,
    useNavigate,
    A
} from '@solidjs/router';

// 1. Basic router setup
const router = createRouter({
    history: createMemoryHistory(),
    routes: [
        createRoute({
            path: '/',
            component: () => import('./pages/Home')
        }),
        createRoute({
            path: '/about',
            component: () => import('./pages/About')
        }),
        createRoute({
            path: '/users/:id',
            component: () => import('./pages/UserDetail')
        }),
        createRoute({
            path: '/search',
            component: () => import('./pages/Search')
        })
    ]
});

// 2. Navigation components
function Navigation() {
    const navigate = useNavigate();
    const [params] = useSearchParams();

    const handleGoHome = () => navigate('/');
    const handleGoBack = () => navigate(-1);

    return (
        <nav>
            <ul>
                <li><A href="/">Home</A></li>
                <li><A href="/about">About</A></li>
                <li><A href="/users/1">User 1</A></li>
                <li><A href="/search?q=test">Search</A></li>
            </ul>

            <div>
                <button onClick={handleGoHome}>Go Home</button>
                <button onClick={handleGoBack}>Go Back</button>
                <p>Current query: {params.q}</p>
            </div>
        </nav>
    );
}

// 3. Dynamic routes
function UserDetail() {
    const params = useParams();
    const [searchParams] = useSearchParams();

    return (
        <div>
            <h2>User Detail</h2>
            <p>User ID: {params.id}</p>
            <p>Tab: {searchParams.tab || 'profile'}</p>

            <div class="user-tabs">
                <A href={`/users/${params.id}?tab=profile`}>Profile</A>
                <A href={`/users/${params.id}?tab=settings`}>Settings</A>
                <A href={`/users/${params.id}?tab=posts`}>Posts</A>
            </div>

            <div class="tab-content">
                <Show when={searchParams.tab === 'profile'}>
                    <UserProfile id={params.id} />
                </Show>
                <Show when={searchParams.tab === 'settings'}>
                    <UserSettings id={params.id} />
                </Show>
                <Show when={searchParams.tab === 'posts'}>
                    <UserPosts id={params.id} />
                </Show>
            </div>
        </div>
    );
}

// 4. Search functionality
function SearchPage() {
    const [params, setParams] = useSearchParams();
    const navigate = useNavigate();

    const handleSearch = (query: string) => {
        setParams({ q: query });
    };

    const handleAdvancedSearch = (query: string, filters: any) => {
        const searchParams = new URLSearchParams();
        searchParams.set('q', query);
        Object.entries(filters).forEach(([key, value]) => {
            if (value) searchParams.set(key, String(value));
        });
        navigate(`/search?${searchParams.toString()}`);
    };

    return (
        <div>
            <h2>Search</h2>
            <div class="search-form">
                <input
                    type="text"
                    placeholder="Search..."
                    value={params.q || ''}
                    onInput={(e) => handleSearch(e.currentTarget.value)}
                />
            </div>

            <Show when={params.q}>
                <div class="search-results">
                    <p>Searching for: {params.q}</p>
                    <SearchResults query={params.q} />
                </div>
            </Show>
        </div>
    );
}

// 5. Protected routes
interface ProtectedRouteProps {
    children: any;
    redirectTo?: string;
}

function ProtectedRoute(props: ProtectedRouteProps) {
    const isAuthenticated = true; // Replace with actual auth logic
    const navigate = useNavigate();

    createEffect(() => {
        if (!isAuthenticated) {
            navigate(props.redirectTo || '/login');
        }
    });

    return <Show when={isAuthenticated}>{props.children}</Show>;
}

// 6. Route-based code splitting
function LazyPage() {
    const LazyComponent = lazy(() => import('./components/HeavyComponent'));

    return (
        <Suspense fallback={<div>Loading page...</div>}>
            <LazyComponent />
        </Suspense>
    );
}

// 7. Nested routes
function App() {
    return (
        <Router>
            <div class="app">
                <Navigation />
                <main>
                    <Route path="/" component={HomePage} />
                    <Route path="/about" component={AboutPage} />

                    <ProtectedRoute>
                        <Route path="/dashboard/*" component={DashboardLayout}>
                            <Route path="/dashboard" component={DashboardHome} />
                            <Route path="/dashboard/users" component={UserManagement} />
                            <Route path="/dashboard/settings" component={Settings} />
                        </Route>
                    </ProtectedRoute>

                    <Route path="/users/:id" component={UserDetail} />
                    <Route path="/search" component={SearchPage} />

                    <Route path="*" component={NotFoundPage} />
                </main>
            </div>
        </Router>
    );
}

// 8. Page components
function HomePage() {
    return (
        <div>
            <h1>Home Page</h1>
            <p>Welcome to the Solid.js app!</p>
        </div>
    );
}

function AboutPage() {
    return (
        <div>
            <h1>About Page</h1>
            <p>This is an about page with routing.</p>
        </div>
    );
}

function DashboardLayout(props: any) {
    return (
        <div class="dashboard">
            <aside>
                <h3>Dashboard</h3>
                <nav>
                    <A href="/dashboard">Overview</A>
                    <A href="/dashboard/users">Users</A>
                    <A href="/dashboard/settings">Settings</A>
                </nav>
            </aside>
            <div class="dashboard-content">
                {props.children}
            </div>
        </div>
    );
}

function DashboardHome() {
    return <div>Dashboard Overview</div>;
}

function UserManagement() {
    return <div>User Management</div>;
}

function Settings() {
    return <div>Settings</div>;
}

function NotFoundPage() {
    return (
        <div>
            <h1>404 - Page Not Found</h1>
            <p>The page you're looking for doesn't exist.</p>
            <A href="/">Go Home</A>
        </div>
    );
}

// Helper components (simplified implementations)
function UserProfile(props: { id: string }) {
    return <div>User Profile for {props.id}</div>;
}

function UserSettings(props: { id: string }) {
    return <div>User Settings for {props.id}</div>;
}

function UserPosts(props: { id: string }) {
    return <div>User Posts for {props.id}</div>;
}

function SearchResults(props: { query: string }) {
    return <div>Search results for "{props.query}"</div>;
}

export {
    App,
    router,
    Navigation,
    ProtectedRoute,
    HomePage,
    AboutPage,
    DashboardLayout,
    UserDetail,
    SearchPage
};

💻 Solid.js 组件模式 typescript

🔴 complex

Solid.js 中的常见组件模式和最佳实践

// Solid.js Component Patterns

import {
    createSignal,
    For,
    Show,
    Switch,
    Match,
    Index,
    ErrorBoundary,
    Suspense,
    createResource,
    createContext,
    useContext,
    ParentComponent,
    createMemo
} from 'solid-js';

// 1. List rendering patterns
interface User {
    id: number;
    name: string;
    email: string;
    role: 'admin' | 'user' | 'guest';
}

function UserList(props: { users: User[] }) {
    const [selectedUserId, setSelectedUserId] = createSignal<number | null>(null);

    return (
        <div>
            <h3>User List</h3>

            {/* Using For (efficient for dynamic lists) */}
            <For each={props.users}>
                {(user) => (
                    <div
                        class=`user-item ${selectedUserId() === user.id ? 'selected' : ''}`
                        onClick={() => setSelectedUserId(user.id)}
                    >
                        <h4>{user.name}</h4>
                        <p>{user.email}</p>
                        <span class="role">{user.role}</span>
                    </div>
                )}
            </For>

            {/* Using Index (when index matters) */}
            <h4>Indexed List:</h4>
            <Index each={props.users}>
                {(user, index) => (
                    <div>
                        {index + 1}. {user().name} - {user().role}
                    </div>
                )}
            </Index>
        </div>
    );
}

// 2. Conditional rendering patterns
function ConditionalRendering() {
    const [status, setStatus] = createSignal<'loading' | 'success' | 'error'>('loading');
    const [user, setUser] = createSignal<User | null>(null);

    return (
        <div>
            <h3>Conditional Rendering</h3>

            {/* Basic Show/Else */}
            <Show when={user()} fallback={<p>Please log in</p>}>
                {(user) => (
                    <p>Welcome, {user().name}!</p>
                )}
            </Show>

            {/* Switch/Match for multiple conditions */}
            <Switch fallback={<p>Unknown status</p>}>
                <Match when={status() === 'loading'}>
                    <div class="spinner">Loading...</div>
                </Match>
                <Match when={status() === 'success'}>
                    <div class="success">✓ Success!</div>
                </Match>
                <Match when={status() === 'error'}>
                    <div class="error">✗ Error occurred</div>
                </Match>
            </Switch>

            <button onClick={() => setStatus('loading')}>Set Loading</button>
            <button onClick={() => setStatus('success')}>Set Success</button>
            <button onClick={() => setStatus('error')}>Set Error</button>
        </div>
    );
}

// 3. Context pattern
interface ThemeContextType {
    theme: 'light' | 'dark';
    toggleTheme: () => void;
}

const ThemeContext = createContext<ThemeContextType>();

const ThemeProvider: ParentComponent = (props) => {
    const [theme, setTheme] = createSignal<'light' | 'dark'>('light');

    const toggleTheme = () => {
        setTheme(current => current === 'light' ? 'dark' : 'light');
    };

    const context: ThemeContextType = {
        theme: theme(),
        toggleTheme
    };

    return (
        <ThemeContext.Provider value={context}>
            <div class={`app ${theme()}-theme`}>
                {props.children}
            </div>
        </ThemeContext.Provider>
    );
};

const useTheme = () => useContext(ThemeContext);

function ThemedComponent() {
    const { theme, toggleTheme } = useTheme();

    return (
        <div class={`themed-component ${theme}`}>
            <p>Current theme: {theme}</p>
            <button onClick={toggleTheme}>Toggle Theme</button>
        </div>
    );
}

// 4. Error boundaries
function ErrorBoundaryExample() {
    const [shouldError, setShouldError] = createSignal(false);

    const ThrowError = () => {
        if (shouldError()) {
            throw new Error('Something went wrong!');
        }
        return <div>No error</div>;
    };

    return (
        <div>
            <h3>Error Boundary</h3>
            <button onClick={() => setShouldError(true)}>
                Trigger Error
            </button>
            <button onClick={() => setShouldError(false)}>
                Reset
            </button>

            <ErrorBoundary fallback={(err, reset) => (
                <div class="error-boundary">
                    <h4>Error: {err.message}</h4>
                    <button onClick={reset}>Try Again</button>
                </div>
            )}>
                <ThrowError />
            </ErrorBoundary>
        </div>
    );
}

// 5. Data fetching with Suspense
interface Post {
    id: number;
    title: string;
    body: string;
    userId: number;
}

async function fetchPosts(): Promise<Post[]> {
    // Simulate API delay
    await new Promise(resolve => setTimeout(resolve, 2000));
    return [
        { id: 1, title: 'Post 1', body: 'This is post 1', userId: 1 },
        { id: 2, title: 'Post 2', body: 'This is post 2', userId: 2 }
    ];
}

function DataFetching() {
    const [posts] = createResource(fetchPosts);

    return (
        <div>
            <h3>Data Fetching with Suspense</h3>
            <Suspense fallback={<div class="spinner">Loading posts...</div>}>
                <Show when={!posts.error} when={!posts.loading}>
                    <div class="posts">
                        <For each={posts()}>
                            {(post) => (
                                <article class="post">
                                    <h3>{post.title}</h3>
                                    <p>{post.body}</p>
                                </article>
                            )}
                        </For>
                    </div>
                </Show>
            </Suspense>
        </div>
    );
}

// 6. Higher-order component pattern
const withLoading = <P extends object>(Component: (props: P) => any) => {
    return (props: P & { loading?: boolean }) => {
        return (
            <Show when={!props.loading} fallback={<div class="spinner">Loading...</div>}>
                <Component {...props} />
            </Show>
        );
    };
};

const UserProfile = (props: { name: string; email: string }) => (
    <div class="user-profile">
        <h4>{props.name}</h4>
        <p>{props.email}</p>
    </div>
);

const UserProfileWithLoading = withLoading(UserProfile);

// 7. Render props pattern
interface ListProps<T> {
    items: T[];
    renderItem: (item: T, index: () => number) => any;
    emptyMessage?: string;
}

function List<T>(props: ListProps<T>) {
    return (
        <div class="list">
            <Show when={props.items.length > 0} fallback={
                <div class="empty">{props.emptyMessage || 'No items'}</div>
            }>
                <For each={props.items}>
                    {(item, index) => props.renderItem(item, index)}
                </For>
            </Show>
        </div>
    );
}

// 8. Compound component pattern
interface TabsContextType {
    activeTab: string;
    setActiveTab: (tab: string) => void;
}

const TabsContext = createContext<TabsContextType>();

const Tabs: ParentComponent<{ initialTab?: string }> = (props) => {
    const [activeTab, setActiveTab] = createSignal(props.initialTab || '');

    return (
        <TabsContext.Provider value={{ activeTab: activeTab(), setActiveTab }}>
            <div class="tabs">{props.children}</div>
        </TabsContext.Provider>
    );
};

const TabList: ParentComponent = (props) => {
    return <div class="tab-list">{props.children}</div>;
};

const Tab: ParentComponent<{ value: string }> = (props) => {
    const { activeTab, setActiveTab } = useContext(TabsContext)!;
    const isActive = activeTab === props.value;

    return (
        <button
            class={`tab ${isActive ? 'active' : ''}`}
            onClick={() => setActiveTab(props.value)}
        >
            {props.children}
        </button>
    );
};

const TabPanels: ParentComponent = (props) => {
    return <div class="tab-panels">{props.children}</div>;
};

const TabPanel: ParentComponent<{ value: string }> = (props) => {
    const { activeTab } = useContext(TabsContext)!;

    return (
        <Show when={activeTab === props.value}>
            <div class="tab-panel">{props.children}</div>
        </Show>
    );
};

export {
    UserList,
    ConditionalRendering,
    ThemeProvider,
    ThemedComponent,
    ErrorBoundaryExample,
    DataFetching,
    UserProfileWithLoading,
    List,
    Tabs,
    TabList,
    Tab,
    TabPanels,
    TabPanel
};