+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 188 of 354

๐Ÿ“˜ Solid.js with TypeScript: Reactive Framework

Master solid.js with typescript: reactive framework in TypeScript with practical examples, best practices, and real-world applications ๐Ÿš€

๐Ÿš€Intermediate
25 min read

Prerequisites

  • Basic understanding of JavaScript ๐Ÿ“
  • TypeScript installation โšก
  • VS Code or preferred IDE ๐Ÿ’ป

What you'll learn

  • Understand Solid.js reactive fundamentals ๐ŸŽฏ
  • Apply Solid.js in real projects ๐Ÿ—๏ธ
  • Debug common Solid.js issues ๐Ÿ›
  • Write type-safe reactive code โœจ

๐ŸŽฏ Introduction

Welcome to this exciting tutorial on Solid.js with TypeScript! ๐ŸŽ‰ In this guide, weโ€™ll explore the blazing-fast reactive framework thatโ€™s changing how we think about frontend development.

Youโ€™ll discover how Solid.js can transform your TypeScript development experience with its fine-grained reactivity and excellent performance. Whether youโ€™re building interactive UIs ๐ŸŒ, real-time dashboards ๐Ÿ“Š, or complex web applications ๐Ÿš€, understanding Solid.js is essential for modern reactive programming.

By the end of this tutorial, youโ€™ll feel confident building reactive applications with Solid.js and TypeScript! Letโ€™s dive in! ๐ŸŠโ€โ™‚๏ธ

๐Ÿ“š Understanding Solid.js

๐Ÿค” What is Solid.js?

Solid.js is like having a Formula 1 racing car ๐ŸŽ๏ธ in the world of frontend frameworks. Think of it as a super-efficient reactive system that updates only what actually changes, just like how your brain only processes new information instead of re-analyzing everything you already know.

In TypeScript terms, Solid.js provides fine-grained reactivity with compile-time optimizations โšก. This means you can:

  • โœจ Get React-like DX with better performance
  • ๐Ÿš€ Build fast, responsive UIs
  • ๐Ÿ›ก๏ธ Maintain full TypeScript type safety
  • ๐ŸŽฏ Use familiar JSX syntax

๐Ÿ’ก Why Use Solid.js?

Hereโ€™s why developers love Solid.js:

  1. Performance ๐Ÿš€: No virtual DOM overhead
  2. TypeScript First ๐Ÿ’ป: Excellent type safety and inference
  3. Fine-Grained Reactivity ๐Ÿ“–: Updates only what changes
  4. Small Bundle Size ๐Ÿ”ง: Minimal runtime footprint

Real-world example: Imagine building a real-time trading dashboard ๐Ÿ“ˆ. With Solid.js, when a stock price updates, only that specific price component re-renders, not the entire dashboard!

๐Ÿ”ง Basic Syntax and Usage

๐Ÿ“ Setting Up Your First Solid App

Letโ€™s start with installation and setup:

# ๐Ÿš€ Create a new Solid.js TypeScript project
npm create solid@latest my-solid-app
cd my-solid-app

# ๐Ÿ“ฆ Install dependencies
npm install

# ๐ŸŽฎ Start development server
npm run dev

๐ŸŽจ Basic Component Structure

Hereโ€™s how Solid.js components work with TypeScript:

// ๐Ÿ‘‹ Hello, Solid.js with TypeScript!
import { Component, createSignal } from 'solid-js';

// ๐ŸŽฏ Define component props type
interface GreetingProps {
  name: string;
  emoji?: string; // ๐ŸŽญ Optional emoji prop
}

// ๐ŸŽจ Creating a simple Solid component
const Greeting: Component<GreetingProps> = (props) => {
  const [count, setCount] = createSignal(0); // ๐Ÿ“Š Reactive state

  return (
    <div>
      <h1>Hello {props.name}! {props.emoji || "๐Ÿ‘‹"}</h1>
      <p>Button clicked {count()} times</p>
      <button onClick={() => setCount(count() + 1)}>
        Click me! ๐ŸŽฏ
      </button>
    </div>
  );
};

export default Greeting;

๐Ÿ’ก Explanation: Notice how we use createSignal() for reactive state and access it with count() - this is Solidโ€™s fine-grained reactivity in action!

๐ŸŽฏ Reactive Primitives

Here are the core reactive patterns youโ€™ll use daily:

import { createSignal, createMemo, createEffect } from 'solid-js';

// ๐ŸŽจ Signal: reactive state
const [temperature, setTemperature] = createSignal(20);

// โœจ Memo: derived reactive value
const temperatureDisplay = createMemo(() => {
  const temp = temperature();
  return temp > 30 ? `๐Ÿ”ฅ Hot: ${temp}ยฐC` : `โ„๏ธ Cool: ${temp}ยฐC`;
});

// ๐Ÿ”„ Effect: side effects
createEffect(() => {
  console.log(`๐ŸŒก๏ธ Temperature changed to: ${temperature()}ยฐC`);
});

// ๐ŸŽฎ Using in component
const WeatherApp: Component = () => {
  return (
    <div>
      <h2>Weather Station ๐ŸŒค๏ธ</h2>
      <p>{temperatureDisplay()}</p>
      <button onClick={() => setTemperature(temperature() + 5)}>
        Heat up! ๐Ÿ”ฅ
      </button>
      <button onClick={() => setTemperature(temperature() - 5)}>
        Cool down! โ„๏ธ
      </button>
    </div>
  );
};

๐Ÿ’ก Practical Examples

๐Ÿ›’ Example 1: Shopping Cart with Solid.js

Letโ€™s build a reactive shopping cart:

// ๐Ÿ›๏ธ Define our product and cart types
interface Product {
  id: string;
  name: string;
  price: number;
  emoji: string;
}

interface CartItem extends Product {
  quantity: number;
}

// ๐Ÿ›’ Shopping cart store
import { createSignal, createMemo } from 'solid-js';

const [cartItems, setCartItems] = createSignal<CartItem[]>([]);

// ๐Ÿ’ฐ Calculate total with memo for performance
const cartTotal = createMemo(() => {
  return cartItems().reduce((sum, item) => sum + (item.price * item.quantity), 0);
});

// ๐Ÿ“Š Cart item count
const cartItemCount = createMemo(() => {
  return cartItems().reduce((count, item) => count + item.quantity, 0);
});

// ๐Ÿ›’ Shopping cart component
const ShoppingCart: Component = () => {
  const products: Product[] = [
    { id: '1', name: 'TypeScript Book', price: 29.99, emoji: '๐Ÿ“˜' },
    { id: '2', name: 'Coffee', price: 4.99, emoji: 'โ˜•' },
    { id: '3', name: 'Laptop', price: 999.99, emoji: '๐Ÿ’ป' }
  ];

  // โž• Add item to cart
  const addToCart = (product: Product) => {
    setCartItems(prev => {
      const existing = prev.find(item => item.id === product.id);
      if (existing) {
        return prev.map(item => 
          item.id === product.id 
            ? { ...item, quantity: item.quantity + 1 }
            : item
        );
      } else {
        return [...prev, { ...product, quantity: 1 }];
      }
    });
  };

  // โž– Remove item from cart
  const removeFromCart = (productId: string) => {
    setCartItems(prev => prev.filter(item => item.id !== productId));
  };

  return (
    <div class="shopping-cart">
      <h2>๐Ÿ›’ Shopping Cart ({cartItemCount()} items)</h2>
      
      {/* ๐Ÿช Product list */}
      <div class="products">
        <h3>๐Ÿ“ฆ Available Products</h3>
        {products.map(product => (
          <div class="product-card">
            <span>{product.emoji} {product.name}</span>
            <span>${product.price}</span>
            <button onClick={() => addToCart(product)}>
              Add to Cart โž•
            </button>
          </div>
        ))}
      </div>

      {/* ๐Ÿ›๏ธ Cart items */}
      <div class="cart-items">
        <h3>๐Ÿ›’ Your Cart</h3>
        {cartItems().map(item => (
          <div class="cart-item">
            <span>{item.emoji} {item.name}</span>
            <span>Qty: {item.quantity}</span>
            <span>${(item.price * item.quantity).toFixed(2)}</span>
            <button onClick={() => removeFromCart(item.id)}>
              Remove โŒ
            </button>
          </div>
        ))}
        
        {cartItems().length === 0 && (
          <p>Your cart is empty ๐Ÿ˜ข</p>
        )}
        
        {cartItems().length > 0 && (
          <div class="cart-total">
            <strong>๐Ÿ’ฐ Total: ${cartTotal().toFixed(2)}</strong>
          </div>
        )}
      </div>
    </div>
  );
};

๐ŸŽฏ Try it yourself: Add a quantity selector and implement a โ€œclear cartโ€ feature!

๐ŸŽฎ Example 2: Real-Time Game Score Tracker

Letโ€™s create an interactive game with reactive updates:

// ๐Ÿ† Game types
interface Player {
  id: string;
  name: string;
  score: number;
  level: number;
  avatar: string;
}

interface GameState {
  players: Player[];
  gameActive: boolean;
  timeLeft: number;
}

// ๐ŸŽฎ Game tracker component
const GameTracker: Component = () => {
  const [gameState, setGameState] = createSignal<GameState>({
    players: [
      { id: '1', name: 'Alex', score: 0, level: 1, avatar: '๐Ÿฆธโ€โ™‚๏ธ' },
      { id: '2', name: 'Sarah', score: 0, level: 1, avatar: '๐Ÿฆธโ€โ™€๏ธ' },
      { id: '3', name: 'Mike', score: 0, level: 1, avatar: '๐Ÿง™โ€โ™‚๏ธ' }
    ],
    gameActive: false,
    timeLeft: 60
  });

  // ๐Ÿ† Leader (highest score)
  const leader = createMemo(() => {
    const players = gameState().players;
    return players.reduce((prev, current) => 
      current.score > prev.score ? current : prev
    );
  });

  // ๐Ÿ“Š Average score
  const averageScore = createMemo(() => {
    const players = gameState().players;
    const total = players.reduce((sum, player) => sum + player.score, 0);
    return Math.round(total / players.length);
  });

  // โšก Add points to player
  const addPoints = (playerId: string, points: number) => {
    setGameState(prev => ({
      ...prev,
      players: prev.players.map(player => {
        if (player.id === playerId) {
          const newScore = player.score + points;
          const newLevel = Math.floor(newScore / 100) + 1;
          return { 
            ...player, 
            score: newScore, 
            level: newLevel 
          };
        }
        return player;
      })
    }));
  };

  // ๐ŸŽฏ Start/stop game
  const toggleGame = () => {
    setGameState(prev => ({
      ...prev,
      gameActive: !prev.gameActive,
      timeLeft: prev.gameActive ? 60 : prev.timeLeft
    }));
  };

  // โฐ Game timer effect
  createEffect(() => {
    if (gameState().gameActive && gameState().timeLeft > 0) {
      const timer = setTimeout(() => {
        setGameState(prev => ({
          ...prev,
          timeLeft: prev.timeLeft - 1
        }));
      }, 1000);
      
      return () => clearTimeout(timer);
    } else if (gameState().timeLeft === 0) {
      setGameState(prev => ({ ...prev, gameActive: false }));
    }
  });

  return (
    <div class="game-tracker">
      <h2>๐ŸŽฎ TypeScript Game Arena</h2>
      
      {/* ๐Ÿ“Š Game stats */}
      <div class="game-stats">
        <div class="stat">โฐ Time: {gameState().timeLeft}s</div>
        <div class="stat">๐Ÿ† Leader: {leader().avatar} {leader().name}</div>
        <div class="stat">๐Ÿ“Š Avg Score: {averageScore()}</div>
        <button onClick={toggleGame}>
          {gameState().gameActive ? 'โธ๏ธ Pause' : 'โ–ถ๏ธ Start'} Game
        </button>
      </div>

      {/* ๐Ÿ‘ฅ Player list */}
      <div class="players">
        {gameState().players.map(player => (
          <div class="player-card">
            <div class="player-info">
              <span class="avatar">{player.avatar}</span>
              <span class="name">{player.name}</span>
              <span class="level">Lv.{player.level}</span>
            </div>
            <div class="score">๐Ÿ† {player.score}</div>
            <div class="actions">
              <button 
                onClick={() => addPoints(player.id, 10)}
                disabled={!gameState().gameActive}
              >
                +10 โœจ
              </button>
              <button 
                onClick={() => addPoints(player.id, 25)}
                disabled={!gameState().gameActive}
              >
                +25 ๐Ÿš€
              </button>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
};

๐Ÿš€ Advanced Concepts

๐Ÿง™โ€โ™‚๏ธ Advanced Topic 1: Stores and Context

When youโ€™re ready to level up, try this advanced pattern for global state:

// ๐Ÿช Creating a global store with TypeScript
import { createContext, useContext, createSignal, createMemo, ParentComponent } from 'solid-js';

interface User {
  id: string;
  name: string;
  email: string;
  avatar: string;
  preferences: {
    theme: 'light' | 'dark';
    notifications: boolean;
  };
}

interface AppStore {
  user: User | null;
  setUser: (user: User | null) => void;
  isLoggedIn: () => boolean;
  updatePreferences: (prefs: Partial<User['preferences']>) => void;
}

// ๐ŸŽฏ Create store context
const AppStoreContext = createContext<AppStore>();

// ๐Ÿ—๏ธ Store provider
export const AppStoreProvider: ParentComponent = (props) => {
  const [user, setUser] = createSignal<User | null>(null);

  // โœจ Derived state
  const isLoggedIn = createMemo(() => user() !== null);

  // ๐Ÿ”ง Update user preferences
  const updatePreferences = (prefs: Partial<User['preferences']>) => {
    setUser(prev => {
      if (!prev) return null;
      return {
        ...prev,
        preferences: { ...prev.preferences, ...prefs }
      };
    });
  };

  const store: AppStore = {
    user,
    setUser,
    isLoggedIn,
    updatePreferences
  };

  return (
    <AppStoreContext.Provider value={store}>
      {props.children}
    </AppStoreContext.Provider>
  );
};

// ๐Ÿช„ Custom hook for using store
export const useAppStore = () => {
  const context = useContext(AppStoreContext);
  if (!context) {
    throw new Error('useAppStore must be used within AppStoreProvider');
  }
  return context;
};

๐Ÿ—๏ธ Advanced Topic 2: Custom Reactive Primitives

For the brave developers, create your own reactive utilities:

// ๐Ÿš€ Custom reactive utilities
import { createSignal, createMemo, createEffect } from 'solid-js';

// ๐Ÿ“ก Custom hook for localStorage sync
export function createLocalStorage<T>(
  key: string,
  initialValue: T
): [() => T, (value: T) => void] {
  const [storedValue, setStoredValue] = createSignal<T>(() => {
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch {
      return initialValue;
    }
  });

  const setValue = (value: T) => {
    setStoredValue(() => value);
    localStorage.setItem(key, JSON.stringify(value));
  };

  return [storedValue, setValue];
}

// โฑ๏ธ Custom timer hook
export function createTimer(initialTime: number = 0) {
  const [time, setTime] = createSignal(initialTime);
  const [isRunning, setIsRunning] = createSignal(false);

  createEffect(() => {
    if (isRunning()) {
      const interval = setInterval(() => {
        setTime(prev => prev + 1);
      }, 1000);
      
      return () => clearInterval(interval);
    }
  });

  return {
    time,
    isRunning,
    start: () => setIsRunning(true),
    stop: () => setIsRunning(false),
    reset: () => {
      setTime(initialTime);
      setIsRunning(false);
    }
  };
}

// ๐ŸŽฏ Usage example
const TimerApp: Component = () => {
  const timer = createTimer();
  const [savedTime, setSavedTime] = createLocalStorage('timer-value', 0);

  const formattedTime = createMemo(() => {
    const minutes = Math.floor(timer.time() / 60);
    const seconds = timer.time() % 60;
    return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
  });

  return (
    <div>
      <h2>โฑ๏ธ Custom Timer</h2>
      <div class="timer-display">{formattedTime()}</div>
      <button onClick={timer.start} disabled={timer.isRunning()}>
        โ–ถ๏ธ Start
      </button>
      <button onClick={timer.stop} disabled={!timer.isRunning()}>
        โธ๏ธ Stop
      </button>
      <button onClick={timer.reset}>๐Ÿ”„ Reset</button>
      <button onClick={() => setSavedTime(timer.time())}>
        ๐Ÿ’พ Save Time
      </button>
      <p>๐Ÿ’พ Saved: {savedTime()}s</p>
    </div>
  );
};

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Forgetting to Call Signals

// โŒ Wrong way - treating signal like a variable!
const [count, setCount] = createSignal(0);
console.log(count); // ๐Ÿ’ฅ This logs the function, not the value!

// โœ… Correct way - call the signal function!
const [count, setCount] = createSignal(0);
console.log(count()); // โœ… This logs the actual value!

// ๐ŸŽฏ In JSX, both work but calling is more explicit
return (
  <div>
    <p>Count: {count()}</p> {/* โœ… Explicit and clear */}
    <p>Count: {count}</p>   {/* โœ… Also works in JSX */}
  </div>
);

๐Ÿคฏ Pitfall 2: Creating Signals in Loops

// โŒ Dangerous - signals in loops!
const TodoList: Component = () => {
  const [todos, setTodos] = createSignal(['Task 1', 'Task 2']);
  
  return (
    <div>
      {todos().map(todo => {
        // ๐Ÿ’ฅ DON'T create signals in render loops!
        const [completed, setCompleted] = createSignal(false);
        return <div>{todo}</div>;
      })}
    </div>
  );
};

// โœ… Safe - manage state properly!
interface Todo {
  id: string;
  text: string;
  completed: boolean;
}

const TodoList: Component = () => {
  const [todos, setTodos] = createSignal<Todo[]>([
    { id: '1', text: 'Task 1', completed: false },
    { id: '2', text: 'Task 2', completed: false }
  ]);
  
  const toggleTodo = (id: string) => {
    setTodos(prev => prev.map(todo => 
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ));
  };
  
  return (
    <div>
      {todos().map(todo => (
        <div>
          <input 
            type="checkbox" 
            checked={todo.completed}
            onChange={() => toggleTodo(todo.id)}
          />
          {todo.text}
        </div>
      ))}
    </div>
  );
};

๐Ÿ› ๏ธ Best Practices

  1. ๐ŸŽฏ Use TypeScript Everywhere: Define proper types for all props and state
  2. ๐Ÿ“ Leverage Memos: Use createMemo for expensive computations
  3. ๐Ÿ›ก๏ธ Handle Loading States: Always account for async operations
  4. ๐ŸŽจ Component Composition: Break down complex components
  5. โœจ Optimize Reactivity: Only update what needs to change

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Build a Real-Time Chat App

Create a type-safe chat application with Solid.js:

๐Ÿ“‹ Requirements:

  • โœ… Message list with real-time updates
  • ๐Ÿท๏ธ User typing indicators
  • ๐Ÿ‘ค Multiple users support
  • ๐Ÿ“… Message timestamps
  • ๐ŸŽจ Each message needs an emoji reaction system!

๐Ÿš€ Bonus Points:

  • Add message search functionality
  • Implement message editing
  • Create user presence indicators
  • Add emoji picker for reactions

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
// ๐ŸŽฏ Our type-safe chat system!
interface User {
  id: string;
  name: string;
  avatar: string;
  isTyping: boolean;
}

interface Message {
  id: string;
  userId: string;
  content: string;
  timestamp: Date;
  reactions: Record<string, string[]>; // emoji -> userIds
}

interface ChatState {
  messages: Message[];
  users: User[];
  currentUser: User;
}

const ChatApp: Component = () => {
  const [chatState, setChatState] = createSignal<ChatState>({
    messages: [],
    users: [
      { id: '1', name: 'Alice', avatar: '๐Ÿ‘ฉโ€๐Ÿ’ป', isTyping: false },
      { id: '2', name: 'Bob', avatar: '๐Ÿ‘จโ€๐Ÿš€', isTyping: false },
      { id: '3', name: 'Charlie', avatar: '๐Ÿง‘โ€๐ŸŽจ', isTyping: false }
    ],
    currentUser: { id: '1', name: 'Alice', avatar: '๐Ÿ‘ฉโ€๐Ÿ’ป', isTyping: false }
  });

  const [newMessage, setNewMessage] = createSignal('');
  const [typingTimeout, setTypingTimeout] = createSignal<number | null>(null);

  // ๐Ÿ’ฌ Send message
  const sendMessage = () => {
    const content = newMessage().trim();
    if (!content) return;

    const message: Message = {
      id: Date.now().toString(),
      userId: chatState().currentUser.id,
      content,
      timestamp: new Date(),
      reactions: {}
    };

    setChatState(prev => ({
      ...prev,
      messages: [...prev.messages, message]
    }));

    setNewMessage('');
    stopTyping();
  };

  // โŒจ๏ธ Handle typing
  const handleTyping = (content: string) => {
    setNewMessage(content);
    
    // ๐ŸŽฏ Update typing status
    setChatState(prev => ({
      ...prev,
      users: prev.users.map(user => 
        user.id === prev.currentUser.id 
          ? { ...user, isTyping: content.length > 0 }
          : user
      )
    }));

    // โฐ Clear typing after delay
    if (typingTimeout()) {
      clearTimeout(typingTimeout()!);
    }
    
    setTypingTimeout(setTimeout(() => {
      stopTyping();
    }, 2000) as unknown as number);
  };

  // ๐Ÿ›‘ Stop typing
  const stopTyping = () => {
    setChatState(prev => ({
      ...prev,
      users: prev.users.map(user => 
        user.id === prev.currentUser.id 
          ? { ...user, isTyping: false }
          : user
      )
    }));
  };

  // ๐Ÿ˜Š Add reaction
  const addReaction = (messageId: string, emoji: string) => {
    setChatState(prev => ({
      ...prev,
      messages: prev.messages.map(message => {
        if (message.id === messageId) {
          const reactions = { ...message.reactions };
          if (!reactions[emoji]) {
            reactions[emoji] = [];
          }
          
          const userId = prev.currentUser.id;
          if (!reactions[emoji].includes(userId)) {
            reactions[emoji] = [...reactions[emoji], userId];
          }
          
          return { ...message, reactions };
        }
        return message;
      })
    }));
  };

  // ๐Ÿ‘ฅ Get user by ID
  const getUserById = (userId: string) => {
    return chatState().users.find(user => user.id === userId);
  };

  // ๐Ÿ“ Typing users display
  const typingUsers = createMemo(() => {
    return chatState().users.filter(user => 
      user.isTyping && user.id !== chatState().currentUser.id
    );
  });

  return (
    <div class="chat-app">
      <h2>๐Ÿ’ฌ TypeScript Chat Room</h2>
      
      {/* ๐Ÿ“‹ Messages list */}
      <div class="messages">
        {chatState().messages.map(message => {
          const user = getUserById(message.userId);
          return (
            <div class="message">
              <div class="message-header">
                <span class="avatar">{user?.avatar}</span>
                <span class="name">{user?.name}</span>
                <span class="timestamp">
                  {message.timestamp.toLocaleTimeString()}
                </span>
              </div>
              <div class="content">{message.content}</div>
              
              {/* ๐Ÿ˜Š Reactions */}
              <div class="reactions">
                {Object.entries(message.reactions).map(([emoji, userIds]) => (
                  <button 
                    class="reaction"
                    onClick={() => addReaction(message.id, emoji)}
                  >
                    {emoji} {userIds.length}
                  </button>
                ))}
                
                {/* ๐Ÿ˜Š Quick reaction buttons */}
                <button onClick={() => addReaction(message.id, '๐Ÿ‘')}>๐Ÿ‘</button>
                <button onClick={() => addReaction(message.id, 'โค๏ธ')}>โค๏ธ</button>
                <button onClick={() => addReaction(message.id, '๐Ÿ˜‚')}>๐Ÿ˜‚</button>
              </div>
            </div>
          );
        })}
        
        {/* โŒจ๏ธ Typing indicator */}
        {typingUsers().length > 0 && (
          <div class="typing-indicator">
            {typingUsers().map(user => (
              <span>{user.avatar} {user.name}</span>
            )).join(', ')} {typingUsers().length === 1 ? 'is' : 'are'} typing...
          </div>
        )}
      </div>

      {/* ๐Ÿ’ฌ Message input */}
      <div class="message-input">
        <input
          type="text"
          value={newMessage()}
          onInput={(e) => handleTyping(e.currentTarget.value)}
          onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
          placeholder="Type a message... ๐Ÿ’ฌ"
        />
        <button onClick={sendMessage} disabled={!newMessage().trim()}>
          Send ๐Ÿš€
        </button>
      </div>
    </div>
  );
};

๐ŸŽ“ Key Takeaways

Youโ€™ve learned so much! Hereโ€™s what you can now do:

  • โœ… Create reactive Solid.js components with confidence ๐Ÿ’ช
  • โœ… Avoid common mistakes that trip up beginners ๐Ÿ›ก๏ธ
  • โœ… Apply best practices in real projects ๐ŸŽฏ
  • โœ… Debug reactivity issues like a pro ๐Ÿ›
  • โœ… Build performant apps with Solid.js and TypeScript! ๐Ÿš€

Remember: Solid.js is your performance-focused friend! It gives you React-like DX with better runtime performance. ๐Ÿค

๐Ÿค Next Steps

Congratulations! ๐ŸŽ‰ Youโ€™ve mastered Solid.js with TypeScript!

Hereโ€™s what to do next:

  1. ๐Ÿ’ป Practice with the chat app exercise above
  2. ๐Ÿ—๏ธ Build a small project using Solid.js routing
  3. ๐Ÿ“š Move on to our next tutorial: SvelteKit with TypeScript
  4. ๐ŸŒŸ Share your reactive apps with the community!

Remember: Every Solid.js expert was once a beginner. Keep building, keep learning, and most importantly, enjoy the blazing-fast performance! ๐Ÿš€


Happy coding! ๐ŸŽ‰๐Ÿš€โœจ