Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When should I use derived in Svelte custom stores?

Tags:

svelte

I've followed along with the great Svelte tutorial but I'm having trouble understanding when I should use derived in my custom stores. In this example, I'm creating a game with 3 states:

  • PRE_GAME
  • IN_GAME
  • POST_GAME

I want to return a boolean check for when I'm in one of those states, and I think it would be best on the custom store itself.

This is my current code:

import { writable, derived } from 'svelte/store';

export const gamestate = (() {
  const { set, subscribe } = writable('PRE_GAME');

  return {
    subscribe,
    set
  };
})();

export const preGame = derived(gamestate, ($gamestate) => $gamestate === 'PRE_GAME');
export const inGame = derived(gamestate, ($gamestate) => $gamestate === 'IN_GAME');
export const postGame = derived(gamestate, ($gamestate) => $gamestate === 'POST_GAME');

Is it possible to move the derived methods onto the gamestate store like gamestate.preGame()? Does that make sense to do in Svelte? Then I can call $gameState and get whichever value, but also return a boolean check when I need an explicit value.

I expected to be able to do check the internal value without needing to derive its value. Maybe something like below, but it always returns false because gamestate is a writable object.

export const createStore = (() {
  const { set, subscribe } = writable('PRE_GAME');

  return {
    subscribe,
    set,
    preGame: () => gamestate === 'PRE_GAME',
    inGame: () => gamestate === 'IN_GAME',
    postGame: () => gamestate === 'POST_GAME',
  };
})();

What am I misunderstanding here?

like image 474
bholtbholt Avatar asked Dec 22 '22 17:12

bholtbholt


2 Answers

What you do is return a subscription to a derived store in your custom store:

import { derived, writable } from 'svelte/store'

export const gameState = (() => {
  const store = writable('PRE_GAME')
  const store2 = derived(store, $store => ({
    preGame: $store === 'PRE_GAME',
    inGame: $store === 'IN_GAME',
    postGame: $store === 'POST_GAME'        
  }))
    
  return {
    set: store.set,
    subscribe: store2.subscribe
  }
})()

Now when you do set on the store it will set the value on store, then store2 will derive new values and the subscribers will react to those changes:

<script>
  import { gameState } from "./store.js"
</script>

<button on:click="{() => $gameState='PRE_GAME'}">go PRE_GAME</button>
<button on:click="{() => $gameState='IN_GAME'}">go IN_GAME</button>
<button on:click="{() => $gameState='POST_GAME'}">go POST_GAME</button>

<br />

{#if $gameState.preGame}
    <span>PRE_GAME</span>
{/if}

{#if $gameState.inGame}
    <span>IN_GAME</span>
{/if}

{#if $gameState.postGame}
    <span>POST_GAME</span>
{/if}

Alternative

You can also define different getters for each specific state:

import { derived, writable } from 'svelte/store'

export const gameState = (() => {
  const store = writable('PRE_GAME')
    const { set, subscribe } = store

  return {
    set,
    subscribe,
        get preGame() { return derived(store, $store => $store === 'PRE_GAME') },
        get inGame() { return derived(store, $store => $store === 'IN_GAME') },
        get postGame() { return derived(store, $store=> $store === 'POST_GAME') },
  }
})()

Now the only problem is that doing $gameState.preGame does not exists because that tries to get the preGame prop from the content of $gameState, and doing gameState.$preGame or similar is invalid syntax. To get around that you can destructure the props out of the store where you need them:

<script>
  import { gameState } from './store.js'
  const { preGame, inGame, postGame } = gameState
</script>

<span>Current State: {$gameState}</span>
{#if preGame}<span>PRE_GAME</span>{/if}
{#if inGame}<span>IN_GAME</span>{/if}
{#if postGame}<span>POST_GAME</span>{/if}
like image 174
Stephane Vanraes Avatar answered Mar 21 '23 07:03

Stephane Vanraes


Custom methods on the stores are for how you mutate the state (set and update), but stores generally are only have one getter: the state. And I don't think you want it to be otherwise.

From here on, I see three solutions : 

  • Make your state an object { gameSate: 'PRE_GAME', isPreGame: true, isInGame: false, isPostGame: false } and then use a custom update function.
  • Use derived stores as you did (I see no point in importing derived in the main store or using get()).
  • Make that calculation in the components, possibly making a dedicated component. Your components would still purely declarative and the only thing mutating still be in the store.

I would decide between just by pure DRYness: imports vs. repeating the clause.

like image 44
Etienne Avatar answered Mar 21 '23 07:03

Etienne