Ejemplos de Preact

Ejemplos del framework Preact incluyendo componentes, hooks, routing y patrones modernos de Preact

💻 Preact Hello World javascript

🟢 simple

Ejemplos básicos de componentes Preact y aplicación Hello World con JSX

⏱️ 15 min 🏷️ preact, jsx, components, hooks
Prerequisites: JavaScript basics, HTML basics
// Preact Hello World Examples

// 1. Basic functional component
import { h } from 'preact';

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

// 2. Component with props
function Greeting({ name }) {
    return <h1>Hello, {name}!</h1>;
}

// 3. Component with state
import { useState } from 'preact/hooks';

function Counter() {
    const [count, setCount] = useState(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 states
function UserForm() {
    const [name, setName] = useState('');
    const [email, setEmail] = useState('');

    const handleSubmit = (e) => {
        e.preventDefault();
        alert(`Name: ${name}, Email: ${email}`);
    };

    return (
        <form onSubmit={handleSubmit}>
            <div>
                <label>Name: </label>
                <input
                    type="text"
                    value={name}
                    onChange={(e) => setName(e.target.value)}
                />
            </div>
            <div>
                <label>Email: </label>
                <input
                    type="email"
                    value={email}
                    onChange={(e) => setEmail(e.target.value)}
                />
            </div>
            <button type="submit">Submit</button>
        </form>
    );
}

// 5. Conditional rendering
function WelcomeMessage({ isLoggedIn }) {
    return (
        <div>
            {isLoggedIn ? (
                <h1>Welcome back!</h1>
            ) : (
                <h1>Please sign in</h1>
            )}
        </div>
    );
}

// 6. List rendering
function TodoList() {
    const todos = [
        { id: 1, text: 'Learn Preact', completed: false },
        { id: 2, text: 'Build a project', completed: false },
        { id: 3, text: 'Deploy to production', completed: true }
    ];

    return (
        <ul>
            {todos.map(todo => (
                <li key={todo.id} style={{
                    textDecoration: todo.completed ? 'line-through' : 'none'
                }}>
                    {todo.text}
                </li>
            ))}
        </ul>
    );
}

// 7. Event handling
function ButtonExample() {
    const handleClick = () => {
        alert('Button clicked!');
    };

    const handleMouseOver = () => {
        console.log('Mouse over button');
    };

    return (
        <div>
            <button onClick={handleClick}>
                Click me
            </button>
            <button onMouseOver={handleMouseOver}>
                Hover over me
            </button>
        </div>
    );
}

// 8. Component with useEffect
import { useState, useEffect } from 'preact/hooks';

function Timer() {
    const [seconds, setSeconds] = useState(0);

    useEffect(() => {
        const interval = setInterval(() => {
            setSeconds(seconds => seconds + 1);
        }, 1000);

        return () => clearInterval(interval);
    }, []);

    return (
        <div>
            <h1>Timer: {seconds}s</h1>
        </div>
    );
}

// 9. Fetching data with useEffect
function UserProfile({ userId }) {
    const [user, setUser] = useState(null);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
            .then(response => response.json())
            .then(data => {
                setUser(data);
                setLoading(false);
            })
            .catch(error => {
                console.error('Error fetching user:', error);
                setLoading(false);
            });
    }, [userId]);

    if (loading) return <div>Loading...</div>;
    if (!user) return <div>User not found</div>;

    return (
        <div>
            <h2>{user.name}</h2>
            <p>Email: {user.email}</p>
            <p>Phone: {user.phone}</p>
            <p>Website: {user.website}</p>
        </div>
    );
}

// 10. Custom hook example
function useCounter(initialValue = 0) {
    const [count, setCount] = useState(initialValue);

    const increment = () => setCount(count + 1);
    const decrement = () => setCount(count - 1);
    const reset = () => setCount(initialValue);

    return { count, increment, decrement, reset };
}

function CustomCounter() {
    const { count, increment, decrement, reset } = useCounter(10);

    return (
        <div>
            <h1>Count: {count}</h1>
            <button onClick={increment}>+</button>
            <button onClick={decrement}>-</button>
            <button onClick={reset}>Reset</button>
        </div>
    );
}

// 11. Component composition
function Card({ title, children }) {
    return (
        <div style={{
            border: '1px solid #ccc',
            borderRadius: '8px',
            padding: '16px',
            margin: '16px'
        }}>
            <h3>{title}</h3>
            {children}
        </div>
    );
}

function App() {
    return (
        <div>
            <Card title="Welcome">
                <p>This is a card component with children.</p>
                <HelloWorld />
            </Card>
            <Card title="Counter">
                <Counter />
            </Card>
            <Card title="Timer">
                <Timer />
            </Card>
        </div>
    );
}

export {
    HelloWorld,
    Greeting,
    Counter,
    UserForm,
    WelcomeMessage,
    TodoList,
    ButtonExample,
    Timer,
    UserProfile,
    CustomCounter,
    Card,
    App
};

💻 Patrones de Hooks Preact javascript

🟡 intermediate ⭐⭐⭐

Ejemplos avanzados de hooks Preact y patrones de gestión de estado

⏱️ 30 min 🏷️ preact, hooks, state, performance
Prerequisites: Preact basics, JavaScript ES6+, State management concepts
// Preact Hooks Patterns and Advanced Examples

import { h, createContext, useContext } from 'preact';
import { useState, useEffect, useCallback, useMemo, useRef } from 'preact/hooks';

// 1. useContext Example
const ThemeContext = createContext();

function ThemeProvider({ children }) {
    const [theme, setTheme] = useState('light');

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

    return (
        <ThemeContext.Provider value={{ theme, toggleTheme }}>
            {children}
        </ThemeContext.Provider>
    );
}

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

    const styles = {
        backgroundColor: theme === 'light' ? '#fff' : '#333',
        color: theme === 'light' ? '#333' : '#fff',
        padding: '20px',
        borderRadius: '8px'
    };

    return (
        <div style={styles}>
            <h2>Current Theme: {theme}</h2>
            <button onClick={toggleTheme}>Toggle Theme</button>
        </div>
    );
}

// 2. Custom Hook: useLocalState
function useLocalState(key, initialValue) {
    const [state, setState] = useState(() => {
        try {
            const item = localStorage.getItem(key);
            return item ? JSON.parse(item) : initialValue;
        } catch (error) {
            console.error('Error reading localStorage:', error);
            return initialValue;
        }
    });

    const setLocalState = useCallback((value) => {
        try {
            const valueToStore = value instanceof Function ? value(state) : value;
            setState(valueToStore);
            localStorage.setItem(key, JSON.stringify(valueToStore));
        } catch (error) {
            console.error('Error setting localStorage:', error);
        }
    }, [key, state]);

    return [state, setLocalState];
}

function LocalStorageExample() {
    const [name, setName] = useLocalState('userName', '');

    return (
        <div>
            <input
                type="text"
                value={name}
                onChange={(e) => setName(e.target.value)}
                placeholder="Enter your name"
            />
            <p>Your name: {name}</p>
            <p>(Persisted in localStorage)</p>
        </div>
    );
}

// 3. useReducer Pattern
function reducer(state, action) {
    switch (action.type) {
        case 'increment':
            return { count: state.count + 1 };
        case 'decrement':
            return { count: state.count - 1 };
        case 'reset':
            return { count: 0 };
        default:
            throw new Error();
    }
}

function useReducerDemo() {
    const [state, dispatch] = useState({ count: 0 });

    const increment = () => dispatch({ type: 'increment' });
    const decrement = () => dispatch({ type: 'decrement' });
    const reset = () => dispatch({ type: 'reset' });

    return { count: state.count, increment, decrement, reset };
}

function ReducerExample() {
    const { count, increment, decrement, reset } = useReducerDemo();

    return (
        <div>
            <h1>Count: {count}</h1>
            <button onClick={increment}>+</button>
            <button onClick={decrement}>-</button>
            <button onClick={reset}>Reset</button>
        </div>
    );
}

// 4. useMemo Example
function ExpensiveCalculation({ number }) {
    const expensiveResult = useMemo(() => {
        console.log('Running expensive calculation...');
        let result = 0;
        for (let i = 0; i < number * 100000; i++) {
            result += Math.sqrt(i);
        }
        return Math.round(result);
    }, [number]);

    return (
        <div>
            <h3>Expensive Calculation Result: {expensiveResult}</h3>
            <p>Input number: {number}</p>
        </div>
    );
}

// 5. useCallback Example
function ParentComponent() {
    const [count, setCount] = useState(0);

    const handleClick = useCallback(() => {
        console.log('Button clicked! Count:', count);
    }, [count]);

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

// Memoized child component
function ChildButton({ onClick }) {
    console.log('ChildButton rendered');
    return <button onClick={onClick}>Child Button</button>;
}

// 6. useRef Example
function RefExample() {
    const inputRef = useRef(null);
    const countRef = useRef(0);

    const focusInput = () => {
        inputRef.current.focus();
    };

    const incrementRef = () => {
        countRef.current += 1;
        console.log('Ref count:', countRef.current);
    };

    return (
        <div>
            <input ref={inputRef} type="text" placeholder="Focus me" />
            <button onClick={focusInput}>Focus Input</button>
            <button onClick={incrementRef}>Increment Ref</button>
        </div>
    );
}

// 7. Custom Hook: useDebounce
function useDebounce(value, delay) {
    const [debouncedValue, setDebouncedValue] = useState(value);

    useEffect(() => {
        const handler = setTimeout(() => {
            setDebouncedValue(value);
        }, delay);

        return () => {
            clearTimeout(handler);
        };
    }, [value, delay]);

    return debouncedValue;
}

function SearchExample() {
    const [searchTerm, setSearchTerm] = useState('');
    const debouncedSearchTerm = useDebounce(searchTerm, 500);

    useEffect(() => {
        if (debouncedSearchTerm) {
            console.log('Searching for:', debouncedSearchTerm);
            // In real app, this would trigger an API call
        }
    }, [debouncedSearchTerm]);

    return (
        <div>
            <input
                type="text"
                value={searchTerm}
                onChange={(e) => setSearchTerm(e.target.value)}
                placeholder="Search..."
            />
            <p>Debounced search term: {debouncedSearchTerm}</p>
        </div>
    );
}

// 8. useLayoutEffect Example
function LayoutEffectExample() {
    const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
    const divRef = useRef(null);

    useEffect(() => {
        if (divRef.current) {
            const { clientWidth, clientHeight } = divRef.current;
            setDimensions({ width: clientWidth, height: clientHeight });
        }
    }, []);

    return (
        <div>
            <div
                ref={divRef}
                style={{
                    width: '300px',
                    height: '200px',
                    backgroundColor: 'lightblue',
                    padding: '20px'
                }}
            >
                <h3>Component with Layout Effect</h3>
                <p>Width: {dimensions.width}px</p>
                <p>Height: {dimensions.height}px</p>
            </div>
        </div>
    );
}

// 9. Custom Hook: useFetch
function useFetch(url) {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);

    useEffect(() => {
        let isCancelled = false;

        const fetchData = async () => {
            try {
                setLoading(true);
                setError(null);
                const response = await fetch(url);
                const result = await response.json();

                if (!isCancelled) {
                    setData(result);
                }
            } catch (err) {
                if (!isCancelled) {
                    setError(err.message);
                }
            } finally {
                if (!isCancelled) {
                    setLoading(false);
                }
            }
        };

        fetchData();

        return () => {
            isCancelled = true;
        };
    }, [url]);

    return { data, loading, error };
}

function FetchExample() {
    const { data, loading, error } = useFetch('https://jsonplaceholder.typicode.com/posts/1');

    if (loading) return <div>Loading...</div>;
    if (error) return <div>Error: {error}</div>;

    return (
        <div>
            <h2>{data?.title}</h2>
            <p>{data?.body}</p>
        </div>
    );
}

// 10. Performance Optimization Example
function OptimizedList({ items }) {
    const renderItem = useCallback((item) => (
        <li key={item.id}>
            {item.name} - {item.category}
        </li>
    ), []);

    const memoizedItems = useMemo(() => {
        return items.map(renderItem);
    }, [items, renderItem]);

    return <ul>{memoizedItems}</ul>;
}

function PerformanceExample() {
    const [items, setItems] = useState([
        { id: 1, name: 'Item 1', category: 'A' },
        { id: 2, name: 'Item 2', category: 'B' },
        { id: 3, name: 'Item 3', category: 'C' }
    ]);

    return (
        <div>
            <h2>Optimized List</h2>
            <OptimizedList items={items} />
        </div>
    );
}

export {
    ThemeProvider,
    ThemedComponent,
    LocalStorageExample,
    ReducerExample,
    ExpensiveCalculation,
    ParentComponent,
    ChildButton,
    RefExample,
    SearchExample,
    LayoutEffectExample,
    FetchExample,
    PerformanceExample,
    useLocalState,
    useDebounce,
    useFetch
};

💻 Routing Preact javascript

🟡 intermediate ⭐⭐⭐

Routing del lado del cliente en Preact usando preact-router

⏱️ 25 min 🏷️ preact, routing, navigation, spa
Prerequisites: Preact basics, JavaScript ES6+, SPA concepts
// Preact Routing Examples

import { h } from 'preact';
import { Router, route, Link } from 'preact-router';

// 1. Basic Route Components
function Home() {
    return (
        <div>
            <h1>Home Page</h1>
            <p>Welcome to our Preact application!</p>
        </div>
    );
}

function About() {
    return (
        <div>
            <h1>About Us</h1>
            <p>This is a sample Preact application with routing.</p>
        </div>
    );
}

function Contact() {
    return (
        <div>
            <h1>Contact</h1>
            <p>Get in touch with us!</p>
        </div>
    );
}

// 2. Dynamic Routes with Parameters
function UserProfile({ matches }) {
    const userId = matches.user;
    const [user, setUser] = useState(null);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
            .then(response => response.json())
            .then(data => {
                setUser(data);
                setLoading(false);
            })
            .catch(error => {
                console.error('Error fetching user:', error);
                setLoading(false);
            });
    }, [userId]);

    if (loading) return <div>Loading user profile...</div>;
    if (!user) return <div>User not found</div>;

    return (
        <div>
            <h1>User Profile</h1>
            <h2>{user.name}</h2>
            <p><strong>Email:</strong> {user.email}</p>
            <p><strong>Phone:</strong> {user.phone}</p>
            <p><strong>Website:</strong> {user.website}</p>
            <p><strong>Company:</strong> {user.company.name}</p>
            <Link href="/">← Back to Home</Link>
        </div>
    );
}

// 3. Blog Post Routes
function BlogPost({ matches }) {
    const postId = matches.post;
    const [post, setPost] = useState(null);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`)
            .then(response => response.json())
            .then(data => {
                setPost(data);
                setLoading(false);
            })
            .catch(error => {
                console.error('Error fetching post:', error);
                setLoading(false);
            });
    }, [postId]);

    if (loading) return <div>Loading post...</div>;
    if (!post) return <div>Post not found</div>;

    return (
        <div>
            <h1>{post.title}</h1>
            <p>{post.body}</p>
            <Link href="/blog">← Back to Blog</Link>
        </div>
    );
}

function BlogList() {
    const [posts, setPosts] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        fetch('https://jsonplaceholder.typicode.com/posts?_limit=10')
            .then(response => response.json())
            .then(data => {
                setPosts(data);
                setLoading(false);
            })
            .catch(error => {
                console.error('Error fetching posts:', error);
                setLoading(false);
            });
    }, []);

    if (loading) return <div>Loading blog posts...</div>;

    return (
        <div>
            <h1>Blog Posts</h1>
            <ul>
                {posts.map(post => (
                    <li key={post.id}>
                        <Link href={`/blog/${post.id}`}>{post.title}</Link>
                    </li>
                ))}
            </ul>
            <Link href="/">← Back to Home</Link>
        </div>
    );
}

// 4. Navigation Component
function Navigation() {
    const [currentPath, setCurrentPath] = useState('/');

    useEffect(() => {
        // Handle route changes
        const handleRoute = () => {
            setCurrentPath(window.location.pathname);
        };

        // Listen for route changes
        window.addEventListener('popstate', handleRoute);
        handleRoute(); // Initial call

        return () => {
            window.removeEventListener('popstate', handleRoute);
        };
    }, []);

    return (
        <nav style={{
            backgroundColor: '#333',
            padding: '1rem',
            marginBottom: '2rem'
        }}>
            <ul style={{
                listStyle: 'none',
                margin: 0,
                padding: 0,
                display: 'flex',
                gap: '1rem'
            }}>
                <li>
                    <Link
                        href="/"
                        style={{
                            color: currentPath === '/' ? '#fff' : '#ccc',
                            textDecoration: 'none'
                        }}
                    >
                        Home
                    </Link>
                </li>
                <li>
                    <Link
                        href="/about"
                        style={{
                            color: currentPath === '/about' ? '#fff' : '#ccc',
                            textDecoration: 'none'
                        }}
                    >
                        About
                    </Link>
                </li>
                <li>
                    <Link
                        href="/blog"
                        style={{
                            color: currentPath.startsWith('/blog') ? '#fff' : '#ccc',
                            textDecoration: 'none'
                        }}
                    >
                        Blog
                    </Link>
                </li>
                <li>
                    <Link
                        href="/contact"
                        style={{
                            color: currentPath === '/contact' ? '#fff' : '#ccc',
                            textDecoration: 'none'
                        }}
                    >
                        Contact
                    </Link>
                </li>
            </ul>
        </nav>
    );
}

// 5. 404 Not Found Component
function NotFound() {
    return (
        <div>
            <h1>404 - Page Not Found</h1>
            <p>The page you're looking for doesn't exist.</p>
            <Link href="/">← Go back to Home</Link>
        </div>
    );
}

// 6. Protected Route Example
function ProtectedRoute({ component: Component, ...rest }) {
    const [isAuthenticated, setIsAuthenticated] = useState(false);

    useEffect(() => {
        // Check authentication status
        const token = localStorage.getItem('authToken');
        setIsAuthenticated(!!token);
    }, []);

    if (!isAuthenticated) {
        return (
            <div>
                <h1>Authentication Required</h1>
                <p>Please log in to access this page.</p>
                <Link href="/login">Go to Login</Link>
            </div>
        );
    }

    return <Component {...rest} />;
}

function Dashboard() {
    return (
        <div>
            <h1>Dashboard</h1>
            <p>Welcome to your protected dashboard!</p>
            <Link href="/">← Back to Home</Link>
        </div>
    );
}

function Login() {
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');

    const handleLogin = (e) => {
        e.preventDefault();
        // Simulate authentication
        localStorage.setItem('authToken', 'fake-token');
        route('/dashboard');
    };

    return (
        <div>
            <h1>Login</h1>
            <form onSubmit={handleLogin}>
                <div>
                    <label>Email:</label>
                    <input
                        type="email"
                        value={email}
                        onChange={(e) => setEmail(e.target.value)}
                        required
                    />
                </div>
                <div>
                    <label>Password:</label>
                    <input
                        type="password"
                        value={password}
                        onChange={(e) => setPassword(e.target.value)}
                        required
                    />
                </div>
                <button type="submit">Login</button>
            </form>
            <Link href="/">← Back to Home</Link>
        </div>
    );
}

// 7. Main App Component with Router
function App() {
    return (
        <div>
            <Navigation />
            <Router>
                <Home path="/" />
                <About path="/about" />
                <BlogList path="/blog" />
                <BlogPost path="/blog/:post" />
                <UserProfile path="/user/:user" />
                <Contact path="/contact" />
                <Login path="/login" />
                <ProtectedRoute path="/dashboard" component={Dashboard} />
                <NotFound default />
            </Router>
        </div>
    );
}

// 8. Programmatic Navigation Example
function ProgrammaticNavigation() {
    const navigateToProfile = () => {
        route('/user/1');
    };

    const navigateWithDelay = () => {
        setTimeout(() => {
            route('/about');
        }, 2000);
    };

    const goBack = () => {
        window.history.back();
    };

    return (
        <div>
            <h1>Programmatic Navigation</h1>
            <button onClick={navigateToProfile}>Go to User 1</button>
            <button onClick={navigateWithDelay}>Go to About in 2 seconds</button>
            <button onClick={goBack}>Go Back</button>
            <Link href="/">← Back to Home</Link>
        </div>
    );
}

export {
    App,
    Navigation,
    Home,
    About,
    Contact,
    BlogList,
    BlogPost,
    UserProfile,
    NotFound,
    ProtectedRoute,
    Dashboard,
    Login,
    ProgrammaticNavigation
};