Svelte Crash Course: Building Dynamic User Interfaces
Introduction to Svelte
This chapter provides a comprehensive introduction to Svelte, a modern approach to building web applications. We will explore what Svelte is, how it differs from traditional frameworks like React, and the advantages it offers. We will also set up a development environment and begin building a practical project to solidify our understanding.
What is Svelte?
Svelte is fundamentally a compiler that transforms your code into highly optimized JavaScript.
Compiler: In computer science, a compiler is a special program that translates source code written in a programming language (the source language) into another computer language (the target language, often machine code or bytecode). In the context of Svelte, it takes Svelte code and compiles it into optimized JavaScript.
While technically a compiler, Svelte is often referred to and functions much like a front-end framework. It empowers developers to create reusable user interface (UI) components, manage application logic, and handle styling, similar to frameworks like React, Vue, and Angular. Svelte is well-suited for building Single Page Applications (SPAs), offering a modern and efficient approach to web development.
Single Page Application (SPA): A web application that loads a single HTML page and dynamically updates the content as the user interacts with it, without requiring full page reloads. This provides a more fluid and responsive user experience.
Svelte vs. React: Compiler vs. Virtual DOM
A key distinction between Svelte and frameworks like React lies in their operational mechanisms. Svelte operates at compile time, processing your code and generating optimized, vanilla JavaScript before it even reaches the browser. This contrasts sharply with React’s approach, which relies on a Virtual DOM for runtime interpretation.
Virtual DOM (Virtual Document Object Model): A programming concept where a virtual representation of a UI is kept in memory and synced with the real DOM. React uses the Virtual DOM to efficiently update the actual DOM by comparing the virtual representation with the previous state and applying only the necessary changes.
React’s Virtual DOM, while powerful, introduces runtime overhead. It involves maintaining a virtual representation of the UI, comparing it to the actual DOM, and then updating the DOM as needed. Svelte, by contrast, eliminates this overhead by performing the bulk of the work during compilation. This results in leaner, faster, and more efficient JavaScript code delivered to the browser.
Advantages of Using Svelte
Choosing Svelte for front-end development offers several compelling advantages:
- Optimized JavaScript Code: Svelte’s compiler produces highly optimized JavaScript with minimal overhead. This leads to faster loading times and improved runtime performance.
- No Virtual DOM: By eliminating the Virtual DOM, Svelte avoids the performance bottlenecks associated with runtime UI interpretation and reconciliation.
- Performance Benefits: While performance can vary based on specific application needs and benchmarks, Svelte generally exhibits faster performance compared to frameworks employing a Virtual DOM.
- Built-in Animations and Transitions: Svelte provides intuitive and easy-to-use mechanisms for incorporating animations and transitions directly into your UI components, enhancing user experience.
- Evolving Ecosystem: Svelte boasts a growing ecosystem, including:
- SvelteKit: A framework for building server-side rendered Svelte applications, analogous to Next.js for React.
Server-Side Rendering (SSR): Rendering web application pages on the server instead of in the browser. SSR can improve initial load times and SEO (Search Engine Optimization).
- Svelte Native: A tool for building native mobile applications using Svelte.
- SvelteKit: A framework for building server-side rendered Svelte applications, analogous to Next.js for React.
- Ease of Use and Learning Curve: Svelte is often praised for its simplicity and ease of learning, particularly for developers familiar with HTML, CSS, and JavaScript. It often requires less code to achieve the same functionality compared to some other frameworks.
Prerequisites
Before diving into Svelte development, a solid understanding of the following concepts is essential:
-
JavaScript Fundamentals: A strong grasp of core JavaScript concepts is crucial. This includes:
- Functions
- Loops
- Asynchronous programming (Promises,
async/await
) - Array methods (e.g.,
forEach
,map
,filter
) - Fetch API (for making network requests)
-
Node Package Manager (npm): Familiarity with npm for installing packages, managing project dependencies, and running development scripts is necessary.
Node Package Manager (npm): A package manager for the JavaScript programming language. It is the default package manager for the Node.js JavaScript runtime environment. npm is used to install, share, and manage dependencies for JavaScript projects.
Svelte Components: Building Blocks of UI
Like other modern front-end frameworks, Svelte utilizes a component-based architecture. Components are reusable, self-contained units of UI, encapsulating markup, logic, and styling.
Component Structure
Svelte components are similar in structure to Vue.js components, offering a clear and organized way to build user interfaces. Key characteristics of Svelte components include:
- File Extension: Svelte components are saved with the
.svelte
file extension. - Structure: A typical Svelte component file is structured into three main sections:
<script>
tags (Logic): JavaScript code for component logic, variables, and functions is placed within<script>
tags at the top of the file.- HTML Markup (Output): HTML code defining the component’s output, rendered in the middle section of the file. Dynamic content and expressions are embedded using curly braces
{}
. <style>
tags (Styling): CSS styles specific to the component are enclosed within<style>
tags at the bottom of the file. These styles are scoped to the component by default.
Getting Started with Svelte: Project Setup
Several methods exist to initiate a Svelte project. While SvelteKit offers advanced features like server-side rendering and routing, the simplest approach for a basic Single Page Application is using the degit
scaffolding tool.
Scaffolding Tool: A programming utility that generates basic project structures, files, and configurations, allowing developers to quickly start a new project with a pre-defined setup.
degit
is a tool specifically designed for scaffolding projects from GitHub repositories.
Using degit
:
-
Open your terminal and navigate to the directory where you want to create your project.
-
Run the following command:
npx degit sveltejs/template svelte-feedback-app
npx
allows you to run npm packages without installing them globally.degit sveltejs/template
instructsdegit
to clone the Svelte.js template repository from GitHub.svelte-feedback-app
is the desired name for your project folder.
-
Navigate into the project directory:
cd svelte-feedback-app
-
Install dependencies:
npm install
This process sets up a basic Svelte project with essential configurations, a development server, and a module bundler.
Project Structure Overview
After setting up the project, the directory structure will contain:
-
public
folder: Contains static assets likeindex.html
, global CSS (global.css
), and the bundled JavaScript and CSS output for production.index.html
: The main HTML file for the Single Page Application. Svelte UI will be bound to the<body>
element within this file.bundle.js
: The compiled and bundled JavaScript code for the application, generated after building the project.global.css
: File for global CSS styles that apply across the entire application.bundle.css
: CSS styles extracted from Svelte components, bundled for production.
-
src
folder: Contains the source code of your Svelte application.main.js
: The entry point of the application, responsible for initializing the mainApp
component and mounting it to thedocument.body
.App.svelte
: The root component of your application.
-
scripts
folder: May contain scripts for tasks like setting up TypeScript. -
package.json
: Defines project dependencies, scripts for development and production, and project metadata. -
rollup.config.js
(orvite.config.js
): Configuration file for the module bundler, specifying how modules are bundled for production. Rollup is the default bundler, but Vite is another popular alternative.Module Bundler: A tool that combines and packages JavaScript modules, along with their dependencies, into single or a few files that are optimized for browser loading. Rollup and Vite are popular module bundlers used in modern web development.
Development and Build Scripts (defined in package.json
):
npm run dev
: Starts the development server using Rollup with live reload. This server hosts your application during development, automatically rebuilding and refreshing the browser when code changes are detected.Development Server: A local server used during development to host and serve web application files. It typically provides features like hot reloading and proxying to simplify the development workflow. Live Reload: A development server feature that automatically refreshes the browser whenever changes are made to the project’s source code, providing immediate feedback during development.
npm run build
: Compiles your Svelte code into optimized JavaScript and CSS for production, creating abuild
folder within thepublic
directory.npm run serve
: Serves the production-readypublic
folder, simulating a production environment.
VS Code Extension:
For Visual Studio Code users, installing the “Svelte for VS Code” extension is highly recommended. This extension provides features like:
- Syntax highlighting for
.svelte
files. - Code formatting.
- Auto-completions.
- Emmet support within Svelte components.
Svelte Fundamentals: Core Concepts
Let’s explore some fundamental Svelte concepts through practical examples within the App.svelte
component.
Reactive Declarations ($:
syntax)
Svelte introduces the concept of reactive declarations using the $:
. This syntax allows you to create variables that automatically update whenever their dependencies change.
<script>
let firstName = 'Brad';
let lastName = 'Traversy';
$: name = `${firstName} ${lastName}`; // Reactive declaration
// ... rest of script
</script>
<h1>Hello {name}</h1>
In this example, name
is a reactive variable. Whenever firstName
or lastName
changes, name
will automatically re-evaluate and update the displayed greeting in the <h1>
tag.
Event Handling
Svelte simplifies event handling within components. You can directly attach event listeners to HTML elements using the on:
directive.
<script>
let color = 'blue';
function toggleColor() {
color = color === 'blue' ? 'red' : 'blue';
}
</script>
<h1 style="color: {color}">Hello</h1>
<button on:click={toggleColor}>Click</button>
Here, on:click={toggleColor}
attaches a click event listener to the <button>
. When the button is clicked, the toggleColor
function is executed, changing the color
variable and reactively updating the <h1>
style.
Conditional Rendering ({#if}
)
Svelte provides a straightforward syntax for conditional rendering using {#if}
, {:else}
, and {:else if}
blocks.
<script>
let showText = false;
function toggleText() {
showText = !showText;
}
</script>
<button on:click={toggleText}>Toggle Text</button>
{#if showText}
<p>This text is conditionally rendered.</p>
{:else}
<p>No text to show.</p>
{/if}
The {#if showText}
block conditionally renders the first <p>
element when showText
is true. Otherwise, the {:else}
block renders the second <p>
element.
List Rendering ({#each}
)
Rendering lists of data in Svelte is handled using the {#each}
block.
<script>
let users = [
{ id: 1, name: 'John' },
{ id: 2, name: 'Sarah' },
{ id: 3, name: 'Bob' }
];
function addUser() {
users = [...users, { id: 4, name: 'Gen' }];
}
</script>
<button on:click={addUser}>Add User</button>
{#each users as user (user.id)}
<h3>{user.id}: {user.name}</h3>
{/each}
The {#each users as user (user.id)}
block iterates over the users
array. For each user
, it renders an <h3>
element displaying the user’s id
and name
. The (user.id)
part specifies a unique key for each item, which is important for efficient list updates.
Building a Feedback Application: Practical Project
To solidify our understanding of Svelte, we will build a practical feedback application. This project will demonstrate component creation, state management, event handling, and more.
Project Overview: Feedback App
The feedback application will allow users to:
- Select a rating (1-10).
- Provide text-based feedback.
- Submit feedback.
- View a list of submitted feedback items.
- See statistics: total number of feedback items and average rating.
- Delete individual feedback items.
This project will involve creating several Svelte components and implementing core application logic.
Setting up the Project
If you haven’t already, follow the steps outlined in “Getting Started with Svelte: Project Setup” to create a new Svelte project named svelte-feedback-app
using degit
.
Component Breakdown
Our feedback application will be structured using the following components:
Card.svelte
: A reusable container component to provide a consistent card-like styling with rounded corners and padding.FeedbackItem.svelte
: Displays a single feedback item, including the rating, feedback text, and a delete button.FeedbackList.svelte
: Renders a list ofFeedbackItem
components, handling the display of all feedback items.FeedbackStats.svelte
: Displays statistics about the feedback data, such as the total number of reviews and the average rating.FeedbackForm.svelte
: Provides a form for users to submit feedback, including rating selection and text input.RatingSelect.svelte
: A component specifically for selecting a rating using stylized radio buttons.Button.svelte
: A reusable button component with customizable styles (primary, secondary, disabled).
Implementing Core Features
Let’s walk through the implementation of key features in our feedback application, component by component.
Card Component (Card.svelte
)
The Card
component will be a simple wrapper to apply consistent styling to other components.
<div class="card">
<slot />
</div>
<style>
.card {
background-color: white;
border-radius: 15px;
padding: 40px 50px;
margin: 20px 0;
position: relative; /* For absolute positioning of elements within */
color: #333;
}
</style>
Slot: A placeholder within a component’s template that allows content to be inserted from the parent component. The
<slot>
element inCard.svelte
indicates where the content passed within the<Card>
tags in parent components will be rendered.
The <slot />
element acts as a placeholder where content passed between the <Card>
tags in a parent component will be injected.
Feedback Item Component (FeedbackItem.svelte
)
The FeedbackItem
component will display individual feedback entries.
<script>
import Card from './Card.svelte';
import { createEventDispatcher } from 'svelte';
export let item; // Prop to receive feedback item data
const dispatch = createEventDispatcher();
function handleDelete(id) {
dispatch('delete-feedback', id); // Dispatch custom event
}
</script>
<Card>
<div class="num-display">{item.rating}</div>
<button class="close" on:click={() => handleDelete(item.id)}>X</button>
<div class="text-display">{item.text}</div>
</Card>
<style>
.num-display { /* ... styling for rating number ... */ }
.close { /* ... styling for delete button ... */ }
.text-display { /* ... styling for feedback text ... */ }
</style>
export let item;
: Declaresitem
as a prop, allowingFeedbackList
to pass feedback data toFeedbackItem
.Prop (Property): A mechanism for passing data from a parent component to a child component. In Svelte, props are declared using
export let
in the child component.createEventDispatcher()
: A function from Svelte used to create a custom event dispatcher within the component.Event Dispatcher: A function that allows a component to emit custom events that can be listened to by parent components. This is a mechanism for child components to communicate with their parents.
dispatch('delete-feedback', item.id)
: Dispatches a custom event nameddelete-feedback
when the delete button is clicked, passing theitem.id
as data.- The component uses the
Card
component to wrap its content and includes styling for displaying the rating number, delete button, and feedback text.
Feedback List Component (FeedbackList.svelte
)
The FeedbackList
component will render a list of FeedbackItem
components.
<script>
import FeedbackItem from './FeedbackItem.svelte';
import { fade, scale } from 'svelte/transition'; // Import transitions
export let feedback = []; // Prop to receive feedback data
</script>
{#each feedback as fb (fb.id)}
<div in:scale out:fade={{ duration: 200 }}> {/* Apply transitions */}
<FeedbackItem item={fb} on:delete-feedback /> {/* Render FeedbackItem and forward event */}
</div>
{/each}
{#if feedback.length === 0}
<p>No Feedback Yet</p>
{/if}
export let feedback = [];
: Declaresfeedback
as a prop to receive the array of feedback items from the parent component (App.svelte
initially).{#each feedback as fb (fb.id)}
: Iterates over thefeedback
array to render aFeedbackItem
for each item.<div in:scale out:fade={{ duration: 200 }}
: Applies transitions to the<div>
wrapping theFeedbackItem
.scale
is used for the “in” transition (when added), andfade
is used for the “out” transition (when removed).Transition: Visual effects that animate the appearance or disappearance of elements in the DOM. Svelte provides built-in transitions like
fade
,scale
, andslide
, and allows for customization of duration and easing.on:delete-feedback
: Event forwarding. This line ensures that anydelete-feedback
events dispatched byFeedbackItem
components within the list are automatically forwarded up to the parent component ofFeedbackList
(which isApp.svelte
initially).
Feedback Stats Component (FeedbackStats.svelte
)
The FeedbackStats
component will display feedback statistics.
<script>
export let feedback = []; // Prop to receive feedback data
$: average = feedback.length > 0
? feedback.reduce((total, item) => total + item.rating, 0) / feedback.length
: 0;
$: count = feedback.length;
</script>
<div class="feedback-stats">
<h4>Reviews: {count}</h4>
<h4>Rating Average: {average.toFixed(1)}</h4>
</div>
<style>
.feedback-stats { /* ... styling for stats display ... */ }
</style>
export let feedback = [];
: Declaresfeedback
as a prop to receive the feedback data.$: average = ...
: A reactive declaration that calculates the average rating based on thefeedback
array. It usesreduce
to sum the ratings and divides by the length. It handles the case of an empty array to avoid division by zero.$: count = feedback.length;
: A reactive declaration that calculates the feedback count.- The component displays the
count
andaverage
in<h4>
elements with appropriate labels.
Feedback Form Component (FeedbackForm.svelte
)
The FeedbackForm
component will handle user feedback submission.
<script>
import RatingSelect from './RatingSelect.svelte';
import Button from './Button.svelte';
import Card from './Card.svelte';
import { v4 as uuidv4 } from 'uuid'; // Import UUID library
let text = '';
let rating = 10; // Default rating
let message = null;
let btnDisabled = true;
let min = 10;
function handleInput() {
if (text.trim().length <= min) {
message = `Text must be at least ${min} characters`;
btnDisabled = true;
} else {
message = null;
btnDisabled = false;
}
}
function handleSelect(event) {
rating = +event.detail; // Update rating from RatingSelect event
}
function handleSubmit() {
if (text.trim().length > min) {
const newFeedback = {
id: uuidv4(),
rating: +rating,
text: text.trim()
};
// Dispatch custom event to add feedback (initially)
text = ''; // Clear input field
}
}
</script>
<Card>
<form on:submit|preventDefault={handleSubmit}> {/* Prevent default form submission */}
<header>
<h2>How would you rate your service?</h2>
</header>
<RatingSelect on:rating-select={handleSelect} /> {/* RatingSelect component */}
<div class="input-group">
<input
type="text"
placeholder="Tell us something that keeps you coming back"
on:input={handleInput}
bind:value={text} // Two-way binding for input value
/>
<Button type="submit" disabled={btnDisabled}>Send</Button> {/* Button component */}
</div>
{#if message}
<div class="message">{message}</div>
{/if}
</form>
</Card>
<style>
/* ... styling for form elements ... */
</style>
import { v4 as uuidv4 } from 'uuid';
: Imports theuuidv4
function from theuuid
library to generate unique IDs for feedback items.let text = ''; let rating = 10; ...
: Declares variables to manage form state:text
for input value,rating
for selected rating,message
for validation messages,btnDisabled
to control button state, andmin
for minimum text length.handleInput()
: Function called on input event to perform real-time validation of the text input. It updatesmessage
andbtnDisabled
based on text length.handleSelect(event)
: Function called when therating-select
custom event is dispatched fromRatingSelect
. It updates therating
variable with the selected rating value fromevent.detail
.handleSubmit()
: Function called on form submission (preventing default form submission usingon:submit|preventDefault
). It validates the text length again and, if valid, creates anewFeedback
object with a unique ID, rating, and text. (Initially, this function would dispatch a custom event to add feedback).bind:value={text}
: Two-way binding. This directive creates a two-way binding between thetext
variable in the script and thevalue
property of the<input>
element. Changes in the input field update thetext
variable, and vice versa.Two-Way Binding: A synchronization mechanism that links a variable in the component’s script to an input element’s value. Changes to either the variable or the input field are reflected in the other.
on:submit|preventDefault={handleSubmit}
: Attaches a submit event listener to the<form>
element and callshandleSubmit
when the form is submitted.|preventDefault
is an event modifier that prevents the default form submission behavior (page reload).Event Modifier: Special suffixes that can be added to Svelte event directives (
on:
) to modify event handling behavior.preventDefault
is a modifier that callsevent.preventDefault()
automatically.- The component utilizes
RatingSelect
,Button
, andCard
components and includes styling for form elements and validation messages.
Rating Select Component (RatingSelect.svelte
)
The RatingSelect
component provides stylized radio buttons for rating selection.
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
let selected = 10; // Default selected rating
function onChange(e) {
selected = +e.currentTarget.value; // Update selected rating
dispatch('rating-select', selected); // Dispatch custom event
}
</script>
<ul class="rating">
{#each Array(10) as _, i}
<li>
<input
type="radio"
id={'num' + (i + 1)}
name="rating"
value={i + 1}
checked={selected === i + 1}
on:change={onChange}
/>
<label for={'num' + (i + 1)}>{i + 1}</label>
</li>
{/each}
</ul>
<style>
/* ... styling for rating radio buttons ... */
</style>
createEventDispatcher()
: Used to create a custom event dispatcher.let selected = 10;
: Initializes theselected
rating to 10 by default.onChange(e)
: Function called when a radio button is changed. It updates theselected
rating and dispatches arating-select
custom event with the selected value.{#each Array(10) as _, i}
: Uses{#each}
to loop 10 times, creating radio buttons for ratings 1 to 10.checked={selected === i + 1}
: Dynamically sets thechecked
attribute of the radio button based on theselected
value.on:change={onChange}
: Attaches a change event listener to each radio button, calling theonChange
function.
Button Component (Button.svelte
)
The Button
component is a reusable button with customizable styles.
<script>
export let type = 'primary'; // Prop for button type (primary or secondary)
export let disabled = false; // Prop for disabled state
</script>
<button
class="btn"
class:btn-primary={type === 'primary'}
class:btn-secondary={type === 'secondary'}
{disabled} // Shorthand for disabled={disabled}
on:click
on:submit
>
<slot />
</button>
<style>
/* ... styling for button component ... */
</style>
export let type = 'primary';
: Declares atype
prop with a default value of ‘primary’. This prop determines the button’s style (primary or secondary).export let disabled = false;
: Declares adisabled
prop to control the button’s disabled state.class:btn-primary={type === 'primary'} class:btn-secondary={type === 'secondary'}
: Class directives. These directives conditionally add CSS classes to the button element based on thetype
prop. Iftype
is ‘primary’,btn-primary
class is added; if ‘secondary’,btn-secondary
is added.Class Directive: A Svelte directive (
class:
) that allows conditionally adding or removing CSS classes from an HTML element based on a boolean expression.{disabled}
: Shorthand syntax fordisabled={disabled}
. Ifdisabled
is true, thedisabled
attribute is added to the button.on:click on:submit
: Forwards click and submit events. By including these, anyon:click
oron:submit
event listeners attached to the<Button>
component from its parent will be forwarded to the underlying<button>
element.
Implementing in App.svelte
Finally, let’s integrate these components and implement the application logic in App.svelte
.
Initial App.svelte
(with prop passing and event handling):
<script>
import FeedbackList from './components/FeedbackList.svelte';
import FeedbackStats from './components/FeedbackStats.svelte';
import FeedbackForm from './components/FeedbackForm.svelte';
let feedback = [ // Initial feedback data
{ id: 1, rating: 10, text: 'Great service!' },
{ id: 2, rating: 8, text: 'Good experience overall.' },
{ id: 3, rating: 9, text: 'Will recommend to others.' }
];
function deleteFeedback(event) {
feedback = feedback.filter(item => item.id !== event.detail);
}
function addFeedback(event) {
feedback = [event.detail, ...feedback];
}
</script>
<main class="container">
<FeedbackForm on:add-feedback={addFeedback} />
<FeedbackStats feedback={feedback} />
<FeedbackList feedback={feedback} on:delete-feedback={deleteFeedback} />
</main>
<style>
.container { /* ... styling for container ... */ }
</style>
import
statements: Imports the necessary components.let feedback = [...]
: Initializes thefeedback
array with sample data.deleteFeedback(event)
: Function to handledelete-feedback
custom events fromFeedbackList
. It filters thefeedback
array, removing the item with the specified ID fromevent.detail
.addFeedback(event)
: Function to handleadd-feedback
custom events fromFeedbackForm
. It adds the new feedback item fromevent.detail
to the beginning of thefeedback
array.- Component instantiation: Instantiates
FeedbackForm
,FeedbackStats
, andFeedbackList
components.on:add-feedback={addFeedback}
: Listens for theadd-feedback
custom event fromFeedbackForm
and calls theaddFeedback
function.feedback={feedback}
: Passes thefeedback
array as a prop toFeedbackStats
andFeedbackList
.on:delete-feedback={deleteFeedback}
: Listens for thedelete-feedback
custom event fromFeedbackList
and calls thedeleteFeedback
function.
Deleting Feedback Items
In FeedbackItem.svelte
, we already implemented the dispatch of the delete-feedback
custom event. In App.svelte
(initial version), we implemented the deleteFeedback
function to handle this event and update the feedback
array.
Displaying Feedback Statistics
In FeedbackStats.svelte
, we calculate and display the feedback count and average rating based on the feedback
prop. In App.svelte
(initial version), we pass the feedback
array as a prop to FeedbackStats
.
Building the Feedback Form
In FeedbackForm.svelte
, we created the form structure, input validation, and the handleSubmit
function (initially designed to dispatch an add-feedback
event).
Form Validation
The handleInput
function in FeedbackForm.svelte
implements real-time validation for the feedback text input, displaying an error message and disabling the submit button if the text is too short.
Rating Selection Component
RatingSelect.svelte
provides the UI for rating selection using stylized radio buttons and dispatches a rating-select
custom event with the selected rating value.
Form Submission and Adding Feedback
In FeedbackForm.svelte
, the handleSubmit
function (initial version) was designed to dispatch an add-feedback
custom event when the form is submitted. In App.svelte
(initial version), the addFeedback
function handles this event and updates the feedback
array.
State Management with Stores
To simplify state management and component communication, we can utilize Svelte stores. Stores are objects that hold reactive data and allow components to subscribe to and update that data.
Store: In Svelte, a store is an object that holds reactive data and allows components to subscribe to changes in that data. Stores provide a centralized and efficient way to manage application state and share data between components without prop drilling or complex event handling.
Creating a Store (stores.js
):
Create a new file stores.js
in the src
directory:
import { writable } from 'svelte/store';
export const feedbackStore = writable([ // Initial feedback data in store
{ id: 1, rating: 10, text: 'Great service!' },
{ id: 2, rating: 8, text: 'Good experience overall.' },
{ id: 3, rating: 9, text: 'Will recommend to others.' }
]);
import { writable } from 'svelte/store';
: Imports thewritable
store factory from Svelte’s store module.export const feedbackStore = writable([...]);
: Creates a writable store namedfeedbackStore
and initializes it with the feedback data array.writable
stores allow components to both read and update the store’s value.
Updating Components to Use Stores:
-
App.svelte
(Store-based):<script> import FeedbackList from './components/FeedbackList.svelte'; import FeedbackStats from './components/FeedbackStats.svelte'; import FeedbackForm from './components/FeedbackForm.svelte'; </script> <main class="container"> <FeedbackForm /> {/* No event listener needed */} <FeedbackStats /> {/* No prop needed */} <FeedbackList /> {/* No prop needed */} </main> <style> .container { /* ... styling for container ... */ } </style>
App.svelte
is significantly simplified. It no longer needs to manage thefeedback
array or handle custom events. Components will now interact directly with thefeedbackStore
.
-
FeedbackList.svelte
(Store-based):<script> import FeedbackItem from './FeedbackItem.svelte'; import { fade, scale } from 'svelte/transition'; import { feedbackStore } from '../stores'; // Import the store $: feedbackItems = $feedbackStore; // Subscribe to store using $ prefix </script> {#each feedbackItems as fb (fb.id)} <div in:scale out:fade={{ duration: 200 }}> <FeedbackItem item={fb} /> {/* No event listener needed */} </div> {/each} {#if $feedbackStore.length === 0} {/* Access store value directly */} <p>No Feedback Yet</p> {/if}
import { feedbackStore } from '../stores';
: Imports thefeedbackStore
fromstores.js
.$: feedbackItems = $feedbackStore;
: Subscribes to thefeedbackStore
using the$
prefix. The$
prefix in Svelte automatically subscribes to a store and updates the variable (feedbackItems
in this case) whenever the store’s value changes. When the component is destroyed, Svelte automatically unsubscribes from the store, preventing memory leaks.{#if $feedbackStore.length === 0}
: Accesses the current value of thefeedbackStore
directly using the$
prefix to check the length.
-
FeedbackStats.svelte
(Store-based):<script> import { feedbackStore } from '../stores'; $: feedbackItems = $feedbackStore; // Subscribe to store $: average = feedbackItems.length > 0 ? feedbackItems.reduce((total, item) => total + item.rating, 0) / feedbackItems.length : 0; $: count = feedbackItems.length; </script> <div class="feedback-stats"> <h4>Reviews: {count}</h4> <h4>Rating Average: {average.toFixed(1)}</h4> </div> <style> .feedback-stats { /* ... styling for stats display ... */ } </style>
FeedbackStats
is also updated to use thefeedbackStore
directly, eliminating the need for thefeedback
prop.
-
FeedbackItem.svelte
(Store-based):<script> import Card from './Card.svelte'; import { feedbackStore } from '../stores'; // Import the store export let item; function handleDelete(id) { feedbackStore.update((currentFeedback) => { // Update store directly return currentFeedback.filter(item => item.id !== id); }); } </script> <Card> <div class="num-display">{item.rating}</div> <button class="close" on:click={() => handleDelete(item.id)}>X</button> <div class="text-display">{item.text}</div> </Card> <style> /* ... styling for feedback item ... */ </style>
import { feedbackStore } from '../stores';
: Imports thefeedbackStore
.feedbackStore.update(...)
: Uses theupdate
method of thefeedbackStore
to modify the store’s data. Theupdate
method takes a callback function that receives the current store value (currentFeedback
) and returns the new store value. In this case, it filters thecurrentFeedback
array to remove the item with the specifiedid
.
-
FeedbackForm.svelte
(Store-based):<script> import RatingSelect from './RatingSelect.svelte'; import Button from './Button.svelte'; import Card from './Card.svelte'; import { v4 as uuidv4 } from 'uuid'; import { feedbackStore } from '../stores'; // Import the store // ... (variables and handleInput/handleSelect functions remain the same) function handleSubmit() { if (text.trim().length > min) { const newFeedback = { id: uuidv4(), rating: +rating, text: text.trim() }; feedbackStore.update((currentFeedback) => { // Update store directly return [newFeedback, ...currentFeedback]; }); text = ''; } } // ... (rest of component template remains the same) </script>
import { feedbackStore } from '../stores';
: Imports thefeedbackStore
.feedbackStore.update(...)
: InhandleSubmit
, theupdate
method offeedbackStore
is used to add thenewFeedback
item to the store. The callback function prepends thenewFeedback
item to thecurrentFeedback
array.
By using stores, we have achieved a more streamlined and maintainable application architecture. Components can now access and modify the shared feedback data directly through the feedbackStore
, eliminating prop drilling and complex event propagation.
Adding Transitions
Transitions enhance the user experience by providing visual feedback when elements are added or removed from the DOM. In FeedbackList.svelte
, we already added scale-in and fade-out transitions to the <div>
wrapping each FeedbackItem
.
Deployment
To deploy your Svelte application, you first need to build it for production:
npm run build
This command compiles your Svelte code and generates optimized files in the public/build
directory. You can then deploy the contents of the public
directory to a static hosting service like Vercel, Netlify, or GitHub Pages. Vercel is often recommended for its ease of use and excellent integration with SvelteKit and other modern front-end frameworks.
Conclusion
This chapter has provided a comprehensive introduction to Svelte, covering its core concepts, component structure, and practical application through the development of a feedback application. We explored the advantages of Svelte’s compiler-based approach, learned how to build reusable components, manage state effectively with stores, handle events, and add transitions for enhanced user experience. By building this project, you have gained a solid foundation for further exploration and development with Svelte.