Can two writable stores in Svelte be set up to subscribe to each other simultaneously?

There is a unique scenario where two objects share data, yet have different structures.

For instance, the 'Team' object has the team ID as its key. The 'Team' object includes 'name' and 'users' objects as its values. The 'users' object utilizes user IDs exclusive to each team.

I need to create a new object that consolidates all users from different teams.

The 'users' object should be subscribable by users, with changes reflecting in the 'Team' object as well. Similarly, the 'Team' object should be subscribable by users, with modifications propagating to the 'users' object.

How can I accomplish this task?

I tried updating both objects using a 'subscribe' function in JavaScript files, but encountered an infinite loop and failure.

Below is the example code along with REPL.

<script>
    import {writable} from "svelte/store";
    
    const teamDict = writable({})
    const userDict = writable({})

    function initTeamDict() {
        teamDict.set({
            1: {
                name: "good team",
                users: {
                    1: "James",
                    2: "Poppy",
                    48: "Hway"
                }
            },
            2: {
                name: "bad team",
                users: {
                    47: "Kelin",
                    35: "Teo",
                    24: "Ferma"
                }
            }
        })
    }

    function initUserDict() {        
        userDict.set(Object.values($teamDict).reduce((acc, team) => ({...acc, ...team[`users`]}), {}))
    }


</script>

<button on:click={initTeamDict}>init team dict</button>
<button on:click={initUserDict}>init user dict</button>

<div> {JSON.stringify($teamDict)}</div>
<div> {JSON.stringify($userDict)}</div>

<button on:click={() => $teamDict[`1`][`users`][`1`] = "top"}>this button should change userDict also </button>
<button on:click={() => $userDict[`1`] = "bottom"}>this button should change teamDict also </button>

REPL

Edit

By following @ghostmodd's solution, I resolved the issue with the provided code below.

Since modifying a copied object does not trigger a subscription, I duplicated the object before making changes.

In order to manage the modified object rendering order, I implemented a separate view using derived.

<script>
    import {derived, writable} from "svelte/store";
    import {Button} from "flowbite-svelte";

    const teamDict = writable({})
    const userDict = writable({})

    teamDict.subscribe(($$teamDict) => {
        const userDictCopy = $userDict
        for (const key in userDictCopy) {
            delete userDictCopy[key]
        }
        Object.assign(userDictCopy, Object.values($$teamDict).reduce((acc, team) => ({...acc, ...team[`users`]}), {}))
    })
    userDict.subscribe(($$userDict) => {
        const teamDictCopy = $teamDict
        for (const team of Object.values(teamDictCopy)) {
            team[`users`] = {}
        }
        for (const [userId, user] of Object.entries($$userDict)) {
            teamDictCopy[user[`team_id`]][`users`][userId] = user
        }
    })

    const storeView = derived(
        [teamDict, userDict],
        ([$teamDict, $userDict], set) => {
            set({teamDict: $teamDict, userDict: $userDict})
        }
    )

    function initTeamDict() {
        teamDict.set({
            1: {
                name: "good team",
                users: {
                    1: {
                        "name": "James",
                        "team_id": 1
                    },
                    2: {
                        "name": "Poppy",
                        "team_id": 1
                    },
                    48: {
                        "name": "Hway",
                        "team_id": 1
                    }
                }
            },
            2: {
                name: "bad team",
                users: {
                    47: {
                        "name": "Kelin",
                        "team_id": 2
                    },
                    35: {
                        "name": "Teo",
                        "team_id": 2
                    },
                    24: {
                        "name": "Ferma",
                        "team_id": 2
                    }
                }
            }
        })
    }


</script>

<Button on:click={initTeamDict}>init team dict</Button>

<div> {JSON.stringify($storeView.teamDict)}</div>
<div> {JSON.stringify($storeView.userDict)}</div>

<Button on:click={() => $teamDict[`1`][`users`][`1`][`name`] = "top"}>this button should change userDict also </Button>
<Button on:click={() => $userDict[`1`][`name`] = "bottom"}>this button should change teamDict also </Button>

REPL

Answer №1

If you're looking to achieve this functionality, you can utilize an intermediary store in Svelte called a "derived store." This concept is akin to React's useEffect hook, as it actively watches specified stores for updates and manages them accordingly.

In addition to your existing "Teams" and "Users" stores, consider creating a new derived store that will effectively capture and store any updates made.

Here's a simplified breakdown of how I approached this task:

const teamStore = writable({})
const userStore = writable({})

Next, I set up the intermediate derived store to track changes in the previously defined stores.

const intermediaryStore = derived(
    [teamStore, userStore],
    ([$teamStore, $userStore], set) => {
        // Your logic goes here
    },
    {
        teamStore: {},
        userStore: {},
    }
)

It's crucial to understand the function arguments - the array of observable stores, the update handler function, and the initial value of the derived store.

The final step involves populating the derived store with the necessary data by implementing a custom handler function.

// Create a copy of the teamStore for easier manipulation
const teamCopy = {
    ...$teamStore,
}

for (let userID in JSON.parse(JSON.stringify($userStore))) {
    const teamID = $teamStore[$userStore[userID].team]

    if ($teamStore[teamID]) {
        teamCopy[teamID].users[userID] = $userStore[userID]
    } else {
        console.log("Error! Team not initialized.")
        return
    }
}

const userCopy = Object.values($teamStore).reduce((acc, team) => ({...acc, ...team[`users`]}), {})

set({
    teamStore: teamCopy,
    userStore: userCopy,
})

Check out the live demo on REPL: Live Demo

P.S. Please excuse any language mistakes - I'm still polishing my English skills! 😊

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Change the class of multiple elements using jQuery with a set time interval

Here is a clever jQuery snippet I've put together to toggle the class of an element at intervals: setInterval(function(){$('.grid-item .slide-image').toggleClass('active')}, 800); The code runs smoothly! However, I now have multi ...

The window.Print() function is currently experiencing a glitch in Microsoft Edge (Version 91.0.864.59) when trying to use it for the first time within an Angular 12

If you encounter the issue, please follow these steps using the latest version 91.0.864.59 of the Edge browser: https://stackblitz.com/edit/angular-ivy-zbvzap?file=src/app/app.component.html Click on the print button. Close the print dialog. Click on the ...

Utilize button element with both href and onClick attributes simultaneously

I'm in the process of preparing a button that includes href and onClick. After testing it on my local environment, everything seems to be working smoothly. Do you know of any specific browsers that may encounter issues with this setup? <Button cl ...

The most convenient method for automatically updating Google Charts embedded on a webpage

I am facing an issue with refreshing a Google Graph that displays data from a MySQL database. The graph is being drawn within a webpage along with other metrics: Data Output from grab_twitter_stats.php: [15, 32], [14, 55], [13, 45], [12, 52], [11, 57], [ ...

How can I retrieve the latest state of the Redux store in getServerSideProps in Next.js?

I am attempting to retrieve the most up-to-date redux state in getServerSideProps like so: export const getServerSideProps = async (ctx) => { const state = store.getState(); console.log(state.name); return { props: { login: false } }; }; H ...

A guide on including a node as a parameter, alongside other data types, in a function using ajax within Umbraco

When attempting to pass a node and two strings as parameters to a JsonResult in Umbraco, I encountered an issue where the node variable had no value when debugging the method. Below is the JavaScript code: function newsSub() { if(validateForm('ne ...

The Highchart formatter function is being called twice on each occasion

My high chart has a formatter function that seems to be running twice. formatter: function() { console.log("starting formatter execution"); return this.value; } Check out the Fiddle for more details! ...

Use the inline IF statement to adjust the icon class depending on the Flask variable

Is it feasible to achieve this using the inline if function? Alternatively, I could use JavaScript. While I've come across some similar posts here, the solutions provided were not exactly what I expected or were implemented in PHP. <i class="f ...

What could be causing the element.style.FontSize to not be effective on classes that have been looped through and stored in an array created with querySelectorAll beforehand?

Greetings Stackoverflow Community, Today, I'm facing an issue related to JavaScript and WordPress. I have a JavaScript script named setDynamicFontHeight.js, as well as PHP documents named header.php and navbar_mobile.php, which simply executes wp_nav_ ...

Ways to conceal submenu when clicking away from the navigation bar

I am looking to create a directive that will generate a navigation bar. Check out my code on JSFiddle here. Here is the code snippet from index.html : <html lang="fr" ng-app="activity" id="ng-app"> <div ng-controller="mainCtrl"> ...

Simplified React conditional rendering made easy

Currently, I am utilizing React 16 with Material-Ui components. In my root component, I have a requirement to load a tab and a view conditionally based on a property. Although I have managed to implement this functionality, the code appears quite messy a ...

Receive a notification for failed login attempts using Spring Boot and JavaScript

Seeking assistance with determining the success of a login using a SpringBoot Controller. Encountering an issue where unsuccessful logins result in a page displaying HTML -> false, indicating that JavaScript may not be running properly (e.g., failure: f ...

A guide to downloading a file linked to Javascript with the help of Java

I have a unique request here. I am looking for a solution using HttpUrlConnection that can interact with JavaScript directly on a webpage, instead of relying on Selenium as a workaround. Can anyone assist me with this? The webpage contains a link (hidden ...

Implementing Shader Effects around Mouse using Three.js

Could someone please share tips on how to add a shader effect around the mouse area using Three.js? I'm inspired by the homepage of this website: I'm eager to explore some leads or examples. Thank you in advance! ...

What is the best way to send the entire image to an API route in Next.js so that I can save it using FS and then upload it to Cloudinary?

I have a form here that utilizes React Hook Form. I'm wondering what I need to pass to the API endpoint via fetch in order to write an image to disk using fs, retrieve its location, and then send that location to Cloudinary. In the body of the fetch ...

The .prepend() method receives the variable returned by ajax and adds it

I'm facing a challenge with adding a dynamic select box to a string within my .prepend() function. The options in the select box are subject to change, so hard coding them is not an option. To tackle this issue, I am using an AJAX call to construct th ...

JavaScript class name modifications are not functioning as expected

Testing a sprite sheet on a small web page I created. The code for each sprite in sprites.css is structured like this... .a320_0 { top: 0px; left: 0px; width: 60px; height: 64px; background: url("images/sprites.png") no-repeat -787 ...

`The server fails to retrieve parameters passed through an angularJS HTTP GET request,

In my controller.js file, I've come across the following block of code: $http({ method: 'GET', url: '/getGuestList', params: {exhibitorID: 1} }) The purpose of this code is to fetch all the guests belonging to a parti ...

I am interested in incorporating a captcha system using ajax

Recently, I implemented email and captcha validation in a form. Now, I am looking to make some modifications. Specifically, I want the form to not reload the page if the captcha is incorrect or left empty. This means that all fields that have already bee ...

Transforming a class component into a functional component using React for a session

While working on a React application, I encountered the need for auto logout functionality for inactive users. After referring to this resource, I attempted to convert my class component code to functional components. However, I faced issues as the functio ...