DEV Community

Cover image for Write less code with Svelte 5's dot notation: self-populating components
Jim Bridger
Jim Bridger

Posted on

Write less code with Svelte 5's dot notation: self-populating components

This is a trick I came up with a few months ago and have been using regularly. It lets you render a component for a specific object without needing to import the component and without needing to pass the object as a prop. Two very common occurrences!

First, here's the component we want to render:

<script lang="ts">
   import { type Instance } from "$lib/classes/Instance.svelte.js"

   let { instance }: { instance: Instance } = $props()
</script>

{instance.name}
Enter fullscreen mode Exit fullscreen mode

Now, here's the effect of what we're going to do, both before and after, with the relevant lines marked:

Before:

<script lang="ts">
   import InstanceComponent from './Instance.svelte' // this line
   import { Instance } from '$lib/classes/Instance.svelte.js'

   const instance = new Instance("Hello there")
</script>

<InstanceComponent {instance} /> <!-- this prop -->
Enter fullscreen mode Exit fullscreen mode

After:

<script lang="ts">
   import { Instance } from '$lib/classes/Instance.svelte.js'

   const instance = new Instance('General Kenobi')
</script>

<instance.component />
Enter fullscreen mode Exit fullscreen mode

To make it work, first save this function somewhere in your project:

import { type Component as ComponentType } from "svelte";
import { type ComponentInternals } from 'svelte'

interface Props {
   [key: string]: any
}

export function withProps<T extends Props>(Component: ComponentType, defaultProps: T) {
   return function ($$anchor: ComponentInternals, $$props: Partial<T>): ReturnType<typeof Component> {
      const mergedProps = { ...defaultProps, ...$$props };
      return Component($$anchor, mergedProps);
   };
}
Enter fullscreen mode Exit fullscreen mode

You don't necessarily need the typescript type imports or interface, but I add it for completeness.

Next, make a class for your object, and add a component getter, like this:

import { withProps } from "$lib/functions/with-props.js"
import InstanceComponent from "$lib/components/Instance.svelte"

export class Instance {
   public name = $state('')

   constructor(name: string) {
      this.name = name
   }

   // has to be a getter for the component to be reactive
   get component() {
      return withProps(InstanceComponent, { instance: this })
   }
}
Enter fullscreen mode Exit fullscreen mode

That's it! Now you can use Svelte 5's dot notation to write less code! :)


Ok, here's how the withProps function works. It's pretty straightforward. Let's look at it without the typescript:

export function withProps(Component, defaultProps) { // this line is called when defining get component()
   return function ($$anchor, $$props) { // this line is called by the dot notation
      const mergedProps = { ...defaultProps, ...$$props }; // anchor is the element to be added to
      return Component($$anchor, mergedProps); // this is what is returned to be rendered
   };
}
Enter fullscreen mode Exit fullscreen mode

All this function does is take the imported component, and the props you want to add by default (which in this case is the class instance.) We're doing this in the component getter. Then withProps returns a function that merges the default props with the props that are passed to the component when Svelte needs to render it, i.e. when you use the dot notation, which in this example is the <instance.component />.

You can of course add more props in the normal way, so say you added a second prop to the component:

let { instance, nonDefaultProp } = $props()
Enter fullscreen mode Exit fullscreen mode
<instance.component nonDefaultProp="How did this happen?" />
Enter fullscreen mode Exit fullscreen mode

Then you would only need to pass that second prop when calling the component.

You can also override the default prop:

<instance.component instance={{ name: "override "}} />
Enter fullscreen mode Exit fullscreen mode

That's everything I know about it. It's been working for me for months and I haven't had any issues with it so far. That said, if anyone knows why it's a bad idea, please let me know! :D

Top comments (0)