Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Any way to read the value of a writable? #2060

Closed
andymans opened this issue Feb 6, 2019 · 24 comments · Fixed by #2145
Closed

Any way to read the value of a writable? #2060

andymans opened this issue Feb 6, 2019 · 24 comments · Fixed by #2145

Comments

@andymans
Copy link

andymans commented Feb 6, 2019

In my store.js, I may have:

const player = writable({name: 'Player A', highScore: 50});
I may need a function in the store file:

const tick = (latestScore) => {
    const highScore = player. highScore;
    if (latestScore > highScore) {
       //set the player's highScore...
    }
}

The idea would be that e.g main.js could invoke 'tick' whenever it needed to and pass in a score, which would conditionally update based on a read.

A delightfully simple task...until the the "reading" of the player's highScore. Obvs, binding this value in a visual component works fine. But, short of using OCR to watch my screen, I have not discovered how to extract data from a writable object in code :-)

@tivac
Copy link
Contributor

tivac commented Feb 6, 2019

AFAIK you'd use player.subscribe() and pass it a callback to get notified when the value changed. You could then save that off in a closure variable somewhere to reference from your tick function.

@andymans
Copy link
Author

andymans commented Feb 6, 2019

Aaah - ok. Thanks. I did briefly look at that possibility, and was immediately reminded of John McEnroe remonstrating with an umpire :-)

@Conduitry
Copy link
Member

Writable stores also have a method update which can be passed a function that takes the current value of the store and returns what the store's value should be updated to. https://github.com/sveltejs/rfcs/blob/master/text/0002-reactive-stores.md#store-api

@andymans
Copy link
Author

andymans commented Feb 6, 2019

Yes, I see how (for UI components) this is exactly how you'd want to go. Most observer implementations work along similar lines; but they usually leave a getter in place for components that are not event-driven to be able to inspect state on an ad-hoc basis. Anyway, I'll see what breaks if I extend the writable and add my own getter.

@Conduitry
Copy link
Member

Something like this is what I meant:

const tick = (latestScore) => {
  player.update(({ name, highScore }) => {
    if (highScore < latestScore) highScore = latestScore;
    return { name, highScore };
  });
}

If you do need to get the current value in a component definition at any moment, and player is in the top level of your component scope, then you can use $player. Even in methods in your script tag, this will be the current value of the store.

@andymans
Copy link
Author

andymans commented Feb 6, 2019

Thanks. Inside script tags I've found no issues it's nice and easy to get the value the value. What goes on inside teh script blocks is strong and makes Svelte v cool. I have several parts of an app where the logic is not in script tags but in separate files. In those cases needing to 'update' objects before we can get their value is an interesting design decision.

@andymans andymans closed this as completed Feb 6, 2019
@btakita
Copy link
Contributor

btakita commented Mar 1, 2019

This would not work with readable or derived. In that case, subscribe work for readable, derived, and writable.

function get__store(store) {
  let $val
  store.subscribe($ => $val = $)()
  return $val
}

@swyxio
Copy link
Contributor

swyxio commented Jan 23, 2020

writing from the future to thank you for get :) it's pretty handy indeed!

@thojanssens
Copy link

thojanssens commented Jun 14, 2020

I store the currently logged user's ID in a readable store. It's easy to imagine that I need to retrieve this user ID repeatedly application-wide.
I code:

let userId;
userIdStore.subscribe(id => (userId = id));
// do something with userID

It's ugly and even my IDE complains about userID not having been initialized.

I was thinking, I do not need to subscribe to that value, it will never change except when user logs out. Do I actually need a store? "Subscribing" doesn't make sense here.

What are the alternatives? localStorage/sessionStorage. But I do not want to persist the ID, I'd rather keep it in memory. If I persist something, I will do it for a reason, I do not see any strong reason here for me. (user stays logged in through cookies)

Then was thinking, I need an additional store, writable and readable don't answer my needs, I need a store that once sets a value and that value will never change again, where I'd have a "get" instead of a "subscribe". I could write such a custom store myself, but the store contract requires a subscribe method. Maybe I should just use the window object? :-/

@Salman2301
Copy link

@thojanssens
If you are using in .svelte file you can just use $ symbol before the store name ($storeName) to get or set the store value

// App.svelte
import { user } from "./store.js"
console.log($user);

// you can also set the store value
$user = {name:"new user"}

If you are using in .js file you need to subscribe to the value to get. you cannot use $storeName
If you want to do it
Use .svelte and set the context="module"

This will load the script only one time
but i am not 100% sure if it support the store test it out and let me know

@thojanssens
Copy link

thojanssens commented Jun 14, 2020

I just need to "subscribe once" to this userID value. The store is not a good fit. I don't know why I was so much focused on using stores, I just coded my own kind of store:

let userId;

function get() { return userId }

async function fetch() {
  const query = 'query { me { id } }';
  const parsed = await request(query);

  userId = parsed.me.id;

  return userId;
}

export { get, fetch }

In the root .svelte file:

  import { fetch as fetchUserId } from './stores/userId.js';


  onMount(async () => {
    await fetchUserId();

Now that the user ID has been fetched from the server, I can use get application-wide.

I have to be careful about the onMount order because the children onMount will be called before the parent's onMount, so I still got to figure out how to deal with that flow.

@halfnelson
Copy link
Contributor

you can import get from svelte/store https://svelte.dev/docs#get

@PierBover
Copy link

PierBover commented Aug 1, 2020

Wouldn't it be nicer if we could do writable.get() instead?

get(writable) is not so great for performance. From the docs:

This works by creating a subscription, reading the value, then unsubscribing. It's therefore not recommended in hot code paths.

I use writable stores a lot on .js files on my Svelte projects. I don't really read values thousands of times per second with get(), per I'm concerned this might not be a valid approach in the long run.

@kevmodrome
Copy link
Contributor

@PierBover That wouldn't work on custom stores. Using the already existing get works on all stores. Why don't you just manually subscribe?

@PierBover
Copy link

PierBover commented Aug 1, 2020

@kevmodrome because I just want to read a value at a particular time and not trigger reactive logic.

Edit:

if (get(writable) === true) {
  // etc
}

@quantuminformation
Copy link
Contributor

this makes me sad

import { get } from 'svelte/store';

Can't understand why you can't just use get on the writable.

@davidhq
Copy link

davidhq commented Dec 8, 2020

this makes me sad

import { get } from 'svelte/store';

Can't understand why you can't just use get on the writable.

I am looking into this and learning. I understand how get(writable) works, shortcomings etc. but yes: why is this needed if writable stores already have get() method? Perhaps because it could happen that we could possibly read an inconsistent state by calling get() directly? There could exist some custom implementations of the store where one could set state in multiple calls without triggering notifications to subscribers... and if in between such calls someone called writable.get() they would read inconsistent state.

Is this the reason for separate get() function that does automatic subscription, reading and cancelling the subscription?

@paganaye
Copy link

paganaye commented Dec 9, 2020

I use my own store function.
store is a writable that has a get method.

import {writable} from "../web_modules/svelte/store.js";
export function store(value) {
  let originalWritable = writable(value);
  function set(newValue) {
    return originalWritable.set(value = newValue);
  }
  function update(fn) {
    originalWritable.update((oldValue) => value = fn(oldValue));
  }
  function get() {
    return value;
  }
  return {set, update, subscribe: originalWritable.subscribe, get};
}

Typescript version

import { Readable, Writable, writable } from 'svelte/store';

export type Store<T> = Writable<T> & { get(): T };

export function store<T>(value: T): Store<T> {
    let originalWritable = writable<T>(value);
    function set(newValue: any) {
        return originalWritable.set(value = newValue);
    }
    function update(fn: (originalValue: T) => T) {
        originalWritable.update((oldValue: T) => (value = fn(oldValue)));
    }
    function get() {
        return value;
    }
    return { set, update, subscribe: originalWritable.subscribe, get }
}

@dpmccabe
Copy link

Thanks, @paganaye, I might end up using this. I'm wondering if anyone can think of some good reasons not to do this, though.

@dpmccabe
Copy link

In case anyone wants to make further modifications to writable or use the start/stop subscription notification feature, it might be easiest just to copy the relevant code from the svelte/stores .ts file into a local file and import from that instead:
https://gist.github.com/dpmccabe/389c4b059bf7050bac550150ac945c96

I briefly looked into adding a get method to derived, but it doesn't really seem possible given the current implementation. In any case, I think derived can always be replaced by writable plus as many upstreamStore1ofN.subscribe() methods as you need to keep that store in sync with its upstream ones. I ended up replacing all of my derived stores in this way. It's not quite as clean, but I was able to combine some of the subscription functions so that they update multiple dependent stores, so the code is actually more efficient from a runtime perspective.

@ryanatkn
Copy link
Contributor

ryanatkn commented May 17, 2022

I have an app with a lot nested Svelte stores being processed in non-reactive event handlers, and did what @dpmccabe suggested and copypasted the store implementations and added a get method. I published the code so it's available for others but I'm not recommending it. It handles derived stores correctly by using the builtin get. (update; there's also svelte-store2 which adds .get() in the same way)

It'd be nice to see something like this in the official stores but I just took the shortest path I saw, and I can understand the hesitation to support it. Maybe it's something to address better in the v4 that's been mentioned by the Svelte team.

@taishi55
Copy link

taishi55 commented May 29, 2022

Just add $ with the variable name

<script>
import { writable } from 'svelte/store';
const isVisible = writable(false)
</script>

{#if $isVisible}
You see this
{:else}
You do not see this
{/if}

@faloon22
Copy link

faloon22 commented Jan 28, 2023

this makes me sad

import { get } from 'svelte/store';

Can't understand why you can't just use get on the writable.

Thank you for this. This makes me very sad too.

It costed me 2 hours of my life trying all other possible variants multiple times. Not sure why $x did not work. But at least x.get() should have. since there is a x.set() as well!

Finally it is working using above import and a get(x);

@ZYinMD
Copy link

ZYinMD commented Dec 11, 2023

It seems this issue will be solved by svelte 5? The $state rune can be consumed in .svelte files and .js files alike?

(I could be wrong though, only briefly looked into runes)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.