Svelte Multiple App Instances with Stores.(without Redux)

Viewed 166

note:- self answered

So by default, there are three main ways in svelte that you can use to pass data around different components.

1. The obvious, by using Props

Pass data from a parent to a child.

  1. One-way data transfer only.
  2. Passed between two parent-child components only. Has to be an immediate child.

2. Setting up and using Context

Can pass data from parent to any deep child component.

  1. One direction data flow only
  2. Cannot pass data from outside the parent-child hierarchy.
  3. Has to be set up when the parent component is initiated.

3. By using svelte Store

Can set,update & use/subscribe the data from/in any file or component.

  1. If multiple instances of the same app are running, the store will save only one value from either of the instances. storeVar.set() sets either of the instance value depending on the page load time. storeVar.update() sets the value from the last instance.

 

Aim

Setup a svelte app to detect multiple custom embed codes from the root index file and run multiple instances of the App. The embed code will have additional data - video size, aspect ratio, etc. So each instance UI will change depending on the embedded code attributes(passed as props to App). On the UI, the user might click on different actions like screen sizes and accordingly, I'll have to update the App values(props passed from index to App) for that instance only and not others.

 

Issue

From the above issues, either I can't pass or update the values later in the components(issue 2.2, 2.3) OR the store doesn't save every instance values(3.1)

1 Answers

 

Solution (tested and working)

Basically, I created an array of objects with the instance id and data. Use update() instead of set(). I filter the new instance object from the array before adding the same instance's new data.

 

Code(typescript)

package.json

"scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "check": "svelte-check --tsconfig ./tsconfig.json"
  },
  "devDependencies": {
    "@sveltejs/vite-plugin-svelte": "^1.0.0-next.30",
    "@tsconfig/svelte": "^2.0.1",
    "autoprefixer": "^10.4.7",
    "cssnano": "^5.1.10",
    "postcss": "^8.4.14",
    "svelte": "^3.44.0",
    "svelte-check": "^2.2.7",
    "svelte-preprocess": "^4.9.8",
    "tailwindcss": "^3.0.24",
    "tslib": "^2.3.1",
    "typescript": "^4.5.4",
    "vite": "^2.9.9"
  },
  "dependencies": {
    "swiper": "^8.2.2"
  }

Index.html

  <body>
    <custom-element
      id="id_custom_element_987654321"
      videoid="987654321"
      size="7"
    ></custom-element>
    <script type="module" src="/src/main.ts"></script>
    <custom-element
      id="id_custom_element_123456789"
      videoid="123456789"
      size="3"
    ></custom-element>
    <script type="module" src="/src/main.ts"></script>
  </body>

main.ts

const embedElements = document.querySelectorAll(
  "custom-element"
);

const app = embedElements.forEach((each) => {
  //  @ts-ignore
  const elementId = each.attributes.id;
  //  @ts-ignore
  const videoid = each.attributes.videoid;

  if (elementId && videoid) {
    return new App({
      target: each,
      props: {
        embedElementId: elementId.value,
        embedVideoid: videoid.value,
      },
    });
  }
});

export default app;

helpers.ts


// Svelte helpers
export type CustomStoreFormatType<K> = {
  videoid: string;
  data: K;
};

export type StoreKeyType<L> = Writable<CustomStoreFormatType<L>[]>;

export const custom_updateStore = <T>(
  storeKey: StoreKeyType<T>,
  updateValue: CustomStoreFormatType<T>
) => {
  storeKey.update((old) => [
    ...old.filter(
      (e) => e.videoid !== updateValue.videoid
    ),
    { ...updateValue },
  ]);
};

export const custom_getStore = <T>(
  storeKey: CustomStoreFormatType<T>[],
  videoid: string
) =>  storeKey.find((e) => e.videoid === videoid);

store.ts

type CommonCustomStoreFormatType<T> = Array<CustomStoreFormatType<T>>;

export const store_videoComments = writable<
  CommonCustomStoreFormatType<Array<VideoCommentsType>>
>([]);

App.svelte

// passed props
  export let embedElementId: string;
  export let embedVideoid: string;
  setContext("contxt_videoid", embedVideoid);

  let videoComments = fetchvideoComments(embedVideoid);
  videoComments.then(({ data }) => {
    custom_updateStore<VideoCommentsType[]>(store_videoComments, {
      videoId: data.id,
      data: data.comments,
    });
  });
</script>

<main>
  {#await videoData}
    <LoaderCircular --color="green" />
  {:then { data }}
    <Video />
  {:catch error}
    <p style="color: red">{error.message}</p>
  {/await}
</main>

any other component (get store instance value)

  import { store_videoComments } from "./store";

  const videoId = getContext("contxt_videoId");
  let { data } = custom_getStore(
    $store_videoComments,
    videoId as string
  );

 

Output - logging $store_videoComments

[
    {
        "videoId": "987654321",
        "data": "[data1,data2,data3]"
    },
    {
        "videoId": "123456789",
        "data": "[data63,data74,data88]"
    }
]

 
As you can see from the log, all app instance's values are stored. Let me know in the comments if you've found any issues with the above method OR know/used a better alternative for all this.

Note:

  • I wanted to keep the build size as low as possible so didn't search/include any 3rd party packages to tackle the issue.
  • I have not used SvelteKit
  • same posted on github-svelte-discussion
Related