State Management with Svelte - Context API (Part 2)
Disclaimer - If you are unfamiliar with props in Svelte applications, then please read this blog post before proceeding on. You must understand the limitations of props to better understand the Context API and how it addresses those limitations. For components that need to share data to lower-level descendant components (and slotted content) within the same subtree, the Context API offers two methods for these components to communicate data without prop drilling or dispatching events: getContext and setContext . When a component calls setContext , the component defines a context , which is a value (primitive or object) representing data that can only be accessed by the component's descendants via the getContext method. Defining a context with setContext requires the component to supply a context key and a value . The context key helps to retrieve the context value from the closest ancestor component with a defined context corresponding to this key. For a descendant component to consume context defined within one of its ancestors, call the getContext method, passing to it the context key. Note : The context key does not have to be a string; it can be any value. Using a string as the context key has the downside of conflicting with other contexts with the same context key, especially if these contexts belong to third-party libraries that you have little to no control over. Using an object literal as the context key ensures that its context won't conflict with other contexts since object literals are compared via referential equality. Both setContext and getContext must be called during component initialization at the top-level of <script /> , similar to lifecycle methods. Let's revisit the <Grandparent /> , <Parent /> and <Child /> components' example and refactor the code by substituting the props with context. ( Grandparent.svelte ) ( Parent.svelte ) ( Child.svelte ) This approach is much cleaner compared to the props approach. The intermediate <Parent /> component no longer contains any reference to message , which it previously received from the <Grandparent /> component and forwarded it directly to the <Child /> component. Only the descendant components concerned with message (in this case, the <Child /> component) receive it. At a minimum, a simple scatterplot consists of two labeled axes (an x-axis and y-axis) and some dots. Optionally, a scatterplot may display a legend mapping a dot's color to a specific category it belongs to (for classification purposes). If you break down a <Scatterplot /> component into its constituent components, then you might arrive at this component hierarchy: Note : <svg /> is commonly the root element of interactive data visualizations, especially those built using D3 . When you include props, then you may notice how reliant these components are on the same set of props. As the requirements of the visualization grow and more features are added, passing these props explicitly to each of these components becomes repetitive and unmaintainable. Visit this simple Svelte REPL demo to see how the Context API allows these child components to receive all of this data from a single, common parent component ( <Scatterplot /> ): Context API Demo - D3 Scatterplot The scatterplot shows the relationship between the petal length and petal width of flowers classified as species of Iris (Iris setosa, Iris virginica and Iris versicolor). If you have a background in a data science, statistics or machine learning background, then you are likely to have encountered and explored this multivariate dataset when learning introductory classification algorithms. ( App.svelte ) The <App /> component fetches the Iris dataset from a GitHub Gist, and it establishes the petal length as the independent variable ( x ) and petal width as the dependent variable ( y ) to be plotted in the scatterplot. Each data point is categorized as one of three flower species: setosa, versicolor and virginica. Once the data is fetched, the <Scatterplot /> component is rendered using this data and custom configuration options. Note : The <Scatterplot /> component can accept data from either a remote source (as shown above via d3.csv , fetch , etc.) or locally with this arrangement. The other props passed to the <Scatterplot /> component configure certain aspects of the scatterplot such as its dimensions and axes. ( context-keys.js ) To avoid any conflicts with other contexts, the context key for the scatterplot will be a literal object. This object will be referenced by all of the scatterplot's constituent components when they access the context set by the <Scatterplot /> component. ( Scatterplot.svelte ) The <Scatterplot /> component receives a set of props from a consuming component (in this case, <App /> ), and creates a context via setContext , which contains these values. Notice how no props are passed to the <Dots /> , <XAxis /> , <YAxis /> and <Legend /> components because each of these components will access those values directly from this context via getContext . ( Dots.svelte ) Since both axes are drawn dimensions.margins.left pixels away from the left-side of the visualization, we must automatically translate all of the dots horizontally by this same amount of pixels to ensure that they are drawn within the confines of these axes ( translate(${dimensions.margins.left}, 0) ). Then, each data point is drawn as a dot with a radius of three pixels, colored based on its flower species and positioned based on the values of xScale(item.x) and yScale(item.y) . ( XAxis.svelte ) ( YAxis.svelte ) To generate an axis' tick marks, call the ticks method on the scale function ( xScale or yScale ). The number of tick marks created is based on the number passed to the ticks method. To space the tick marks out evenly, translate each tick mark by a number of pixels determined by scale(tick) ( translate(${xScale(tick)} 0) for each x-axis tick mark and translate(0, ${yScale(tick)}) for each y-axis' tick mark). To position the labels ( <text /> element, last child of the outermost <g /> element), space them slightly away from the halfway point of its corresponding axis line ( <path class="axis-line" /> element). ( Legend.svelte ) The legend is placed 25 pixels to the right of the y-axis (adding x pixels to dimensions.margins.left for the horizontal translation ensures the legend is shifted x pixels from the y-axis) and 25 pixels from the top-side of the canvas. Each category is displayed with its unique color representation and label. If you have built React applications, then up to this point, Svelte's Context API may appear similar to React's Context API : sharing values between components of the same subtree without having to explicitly specify props at every level of the component hierarchy . In Svelte, the setContext creates the context, sets its value and automatically provides access to this context for all of the component's descendants. In React, you must first create the context with the createContext method. Note : According to React's documentation , providing a default value to createContext is useful for testing components in isolation without having to wrap them within a provider component. This value serves as a fallback for when a component has no matching provider (for example, <ScatterplotCtx.Provider /> ) above it. For components to access this context value, create a provider component and have it wrap all of these components: In React, updates to the provider component will cause the useContext hook to trigger a rerender using the latest context value passed to this provider component. However, in Svelte, descendant components cannot receive updates from a context whenever its set to a different value via a subsequent call to setContext . These components are only able to access the values made available to them from their ancestor component (the one setting the context) during component initialization. Since the components are not updated with the new context value and not re-rendered as a result of those changes, we cannot consider the Context API as reactive feature of Svelte. For reactivity, we must use props and/or stores , which allow components to access data and subscribe to updates from global data stores (not restricted to a subtree of components). Proceed to the next blog post to learn more about Svelte stores. If you want to learn more about Svelte, then check out Fullstack Svelte :