Each component of a client-side application contains internal state that encapsulates its own data, which determines the component's behavior and stores business-related data for rendering. When composing many components into a multi-level hierarchy, components must communicate data and coordinate amongst each other to perform more complex tasks. For example, a
<SearchBar /> component might pass a list of search results to a
<TypeaheadDropdown /> component to display suggestions based on the currently typed query. There are three common approaches to facilitate communication amongst the various components within a Svelte application:
Having full control over state within an application allows developers to know how data flows throughout the application, better reason about the data and optimize accordingly. Modeling data flow with props, contexts and/or stores improves the organization of state, normalizes data to reduce instances of duplicated data and curtails the amount of time spent on debugging (quickly identify the component/s or portions of state responsible for a bug).
Below, and in two additional blog posts, I'm going to show you:
How to manage application/component state with props, context and stores.
The differences between props, contexts and stores.
Several code snippets and demos demonstrating the capabilities and limitations of props, contexts and stores.
How to evaluate the trade-offs between Svelte stores and Redux as a state management solution in large Svelte applications.
The simplest form of communication involves a parent component passing data to its direct child component/s via props. This concept is widely adopted in other popular component-based libraries/frameworks, such as React and Vue. In Svelte, the
export keyword defines a prop that a component can accept from its consumers.
With props, data flows unidirectionally downwards from a higher-level component to a lower-level component in the component hierarchy. This means changes made to a prop in a child component will not propagate back to its parent component and yield any unwanted side effects. Unlike two-way data-binding in AngularJS, which caused performance issues and modal updates to cascade, one-way data binding with props reduces the number of possible points of failure due to the predictable nature of component/application state.
If multiple props need to passed to a component, then consider packing all the props into an object and spreading them onto the component, which serves as a convenient shortcut for passing many props rather than individually specifying them.
If the parent component does not specify the prop on the child component, then its value will be set to
undefined by default. However, instead of setting its value to
undefined, the prop can be set to a default initial value.
function Exports vs.
Specifying props in a component with
export let allows those props to be modified within the component itself.
When clicking the button and incrementing the value of the prop
count, the newly incremented value is logged to the developers tool console, which indicates this prop being modified in the
<Child /> component. However, this change is not propagated to the
<Parent /> component. The
count displayed within its
<p /> tag remains zero no matter how many times you click on the "Increment" button.
Try it out here.
const changes the behavior of
export. Unlike variables declared with
export let, variables declared with
export function or
export class are not props, but instead, are readonly constants that can be imported into other components as named imports.
Try it out here.
To export these variables for consumption in other components via an
import statement, declare the
<script /> block with the attribute
context="module", which will execute the code within the
<script /> block once upon the evaluation of the module, not the instantiation of the component.
Uncommenting out any of the
count++ statements and then clicking on the button bound to the event handler containing this statement will result in an error being logged to the developers tool console and
count not being incremented. Variables declared with
export const cannot be reassigned to a different value. This also applies to variables declared with
export function and
Note: Properties of an object declared with
export const can be modified, but the reference to the object cannot be changed.
Suppose you decide to export a function that changes a value within the component it is exported from.
Clicking the "Increment" button will call the imported
incrementCount function. When
incrementCount is executed, the value of
count is incremented. Because
count is declared within a
<script context="module" /> block,
count is not reactive, and therefore, the
count value displayed within the
<p /> tag remains zero.
A function can be exported from an instance directly and accessed as a property on the component's reference, which can be obtained via the
bind:this directive. These properties are accessible after the component has been mounted to the DOM.
When clicking the "Increment" button, not only is
count incremented, but its new value is displayed in the
Caveat/Limitation of Props#
For grandchild components to receive data from grandparent components, that data must first be passed from the grandparent component to its child component (the grandchild's parent component), and then, that data must be passed from this parent component to its child component (the grandchild component).
This process of explicitly and redundantly passing data from higher-level ancestor components to lower-level descendant components via props is known as prop drilling. As the number of component layers increases, it becomes more difficult to track these props, especially when refactoring components and having to manually rename or remove props. In the above example, if we decide to rename the
message prop to
greeting, then we would have to start renaming the prop at the
<Grandparent /> component and traverse down its subtree, renaming the prop at each subsequent component (the
<Parent /> and
<Child /> components). Also, the intermediate
<Parent /> component does not make use of the
message prop whatsoever. It simply receives this prop and passes it directly to the
<Child /> component. Having these types of props in our components can add unnecessary clutter to them and exacerbate our ability to understand and maintain these components.
Alternatively, to make data available to a component and its descendants without prop drilling, Svelte provides the Context API.
Proceed to the next blog post to learn more about Svelte's Context API.
If you want to learn more about Svelte, then check out Fullstack Svelte: