skip to content

Shared State Between Multiple Frameworks

How can you share state between React.JS, Svelte.JS, Solid.JS, Vue.JS, and more? TanStack Query maintainer Aryan Deora will show us how he does it with Query + Astro.

Full Transcript

Click to toggle the visibility of the transcript

Captions provided by White Coat Captioning (https://whitecoatcaptioning.com/). Communication Access Realtime Translation (CART) is provided in order to facilitate communication accessibility and may not be a totally verbatim record of the proceedings.

JASON: Hello, everyone, and welcome to another episode of Learn With Jason. Today on the show, we've got Aryan Deora. How you doing?

ARYAN: Hi there. Yeah, it's been great. Thanks for having me over, Jason. This is very exciting.

JASON: Yeah, I'm super excited to have you on the show. I've been watching you kind of emerge on to the scene. You're building really cool stuff, which is actually how you ended up here, as I saw this incredible demo that you did where you had a bunch of different frameworks talking to each other, which kind of blew my mind. That's what we're going to be doing today. We're going to be figuring out how to make all these different frameworks talk to each other. But before we talk about that, let's talk a little bit about you. For folks who aren't familiar with you or your work, do you want to give us a bit of a background on who you are and what you do?

ARYAN: Yeah, definitely. So hi, everyone. My name is Aryan. I'm a software engineer living in India working with Global. I'm also very big on open source. I just got involved with it last year, and it's been an amazing journey to talk with very talented folks and just interacting with really amazing people in the ecosystem. I am a maintainer of Solid Toast, which helps you create toast notifications for JS. I'm currently working on Soiled dev tools, and I'm also a maintainer at tan stack query, working specifically on the Solid.js adapter. It needs to catch up with the feature parity.

JASON: Ah, yeah.

ARYAN: So a lot of interesting stuff, a lot of exciting stuff. I've been actually watching the Learn With Jason show since I started learning how to code. And it's been very inspirational, seeing all the folks coming from different backgrounds talk about tech that they're very interested in. And it's great to talk about tech that I'm interested in, which is Astro and TanStack.

JASON: Yeah, I definitely think there's lots of fun stuff happening in the community right now, and you seem to be kind of in the same area that I find really exciting, which is around this -- it almost feels like we're moving to a post-framework mind set about building, which is so exciting to me. It kind of feels like we're treating our frameworks as compilers -- I don't know. Meta compilers, maybe? Let me back up and say that in words that make sense to somebody who's not me. So it seems like with Vite, with Astro, with a lot of the tooling coming out -- with TanStack as well -- there's this move toward let's make tools that work and which framework you choose -- React, solid, Svelte, Vue, whatever -- is incidental. It's no longer the core of what you're building. You're building a thing, and you can feed it a React component or a Vue component, and the compiler doesn't care. Astro doesn't care if you're using no framework or any framework. TanStack is getting increasingly more agnostic. Something that used to be all in on React is now abstracted out to TanStack. I think it works with everything. That, to me, is such an exciting space to be in. It feels like -- I don't know. It sort of feels to me the same way I felt about the web when we saw the shift from, like, no JavaScript frameworks to jQuery. This may be a little before some of y'all's time, but it was a really exciting time where it felt like there was just motion. There was so much innovation happening.. Then we saw the same thing around the beginning of the React explosion. The ecosystem was bubbling. So much was happening. Npm had the same kind of thing. Node just had this huge explosion of people innovating and building stuff and oh, we can build JavaScript in the browser now. Holy crap. What's your read on the situation? If you've been watching my stuff since you started programming, I'm assuming you've been programming for like, what, the last -- how long have you been in the industry?

ARYAN: So I started learning how to code five years ago, I think, approximately. So it's been recent, but I started off learning HTML, CSS, and JavaScript and started my journey with jQuery. But coming back to your point, it's been great to see how these different tools have been working on making the core framework agnostic. And that's what makes it really interesting to be in this space. Coming to you like Astro, right, talking about how it has this island architecture, which frames very nicely with the framework agnostic philosophy that they have, is you can have islands of interactivity on your mostly static page, and that interact activity can be in any framework. So you can have a React component for your search button. Then you can have a feed written in Solid.js. And you can have another widget that's written in Svelte and whatever. They can all be working side by side, and that enables a lot of exciting things that you could do. It also leads into, I believe, newer patterns and, what do you view it -- how we structure our web frameworks. So that's what TanStack is working on. There's a newer talk about the TanStack becoming a framework and building it on top of Astro. So Astro, it actually builds on top of Vite. And Vite offers like an SSR story, but it's like a low-level API. An SSR for each framework you would like to do. But doing it manually for each framework takes a long time. It's going to be a lot of work to get it working with everything. But Astro has figured it all out, right. It has done the SSR, CSR for Svelte, Vue, React, Solid.js components. You have basically all mainstream frameworks working with Astro. So it's an exciting time to build on top of Astro, where you can use Astro's ability to do all of this dehydrating and hydrating all that stuff on the client to create a meta framework on top of it. So a really exciting time for Astro and how this ecosystem will evolve. I'm very excited for it.

JASON: Yeah, I think the -- like specifically, the idea of islands or partial hydration or however you want to frame it, you know, just not shipping JavaScript when you don't need JavaScript is one of those things that once I heard it, I was like, well, of course we should be doing that. Why haven't we been doing that? I know why. It's because it's hard. There are a lot of reasons that make this extremely challenging, and I think that's why people are so excited about Astro right now. You know, Astro has cracked something that I think a lot of us were thinking in the back of our heads. Why don't I only hydrate the component I need? Then, that's too hard. Well, the Astro team decided it was worth pushing through that pain. They built it. They got it running. One of the things that I think is a common piece of pushback on this is, okay, but as soon as you get any really interactive app, you have to hydrate the whole thing anyway. So why bother?

ARYAN: Yeah.

JASON: And that is what you cracked that I'm really excited to see today. Because you've come up with a pattern -- and I don't know that this is -- like, this is a pattern that has been possible, but you've got a really good like poignant example of using it. You can plug together different islands that are independently hydrated, right. So let my clarify. What you built was like four different buttons, each one in a different framework, and you've got the state of like I click one button, and the state of all four updates.

ARYAN: Correct.

JASON: So in and of itself, that's very impressive. But also, if I am understanding correctly, the rest of that page where those four islands are sitting, that's not hydrated. So we have four islands that are the only thing interactive on the page. Everything else is just static HTML and CSS.

ARYAN: Yeah, and we're working towards something where it's going to be much more easy to server side render the queries for the first request. So you'll have like a fully formed HTML when you first view it. Then every time you make a new request, it does it on the client. So you get the best of both tools.

JASON: Oh, so you're saying -- because another pushback you hear about some of the Astro stuff is you don't want full-page refreshes when you click links and stuff. You want that SPA experience. We had an episode on this that I'm going to pull up right now. It was like page transitions. Yeah, this was a very cool episode. I tried to say his name on the show, and I shouldn't have. It has a double R, and I can't roll my Rs. But we did the page transitions API. It was super fun, creates that SPA experience in Astro. So what you're doing, Aryan, is putting together shared stores. So let's talk about the mechanics of that. With the shared transition, shared stores, we're rapidly chipping away at any reason why you would say, well, it's worth adding an extra 2 megabytes of JavaScript so I get app-like experiences. The platform is sort of showing up, and these patterns for sharing state are showing up to really undermine any value in that argument. It's really starting to feel like no, you shouldn't, you shouldn't ship that extra 2 megabytes of JavaScript just to get page transition. You can do that with the platform. No, you shouldn't ship extra 2 megabytes of JavaScript so you can a cart icon and an add to cart icon that are connected because you don't need to hydrate the whole page to make that happen anymore. So at a high level, let's talk a little bit about how this functions. It sounds like magic that you would be able to take state out of React and put that state into Solid. They're different storage mechanisms. They don't really -- they're not compatible. You can't take a React hook and drop it into Solid. So how does this work?

ARYAN: Yeah, so we talk about this in the example as well, but to give you a higher overview of how things work, every framework or every library has -- well, let's go back to the UI component. There's React, there's Svelte. Each of these components have their own life cycles. So you can't just plug in a signal, Solid.js signal in Svelte and expect it to work. Or you cannot put a signal in React and expect it to work out of the box. A signal doesn't know about how to read the React component. So you need reactive wrappers for each library. But that's all you need. All the core logic can still remain in a framework agnostic JavaScript core. And we can just write reactive wrappers for each framework. So that's how TanStack Query works. There's a TanStack Query code, which has all the logic for storing the queries, storing the query caches. Then you write reactive wrappers. So you write one for Solid query, which just subscribes to that, then when it updates, we update in a way that it updates the life cycle of a Solid component. That happens the same way in React. So whenever the query updates somewhere, we actually re-render the component. So that's how -- or that's what the main principle of framework agnostic tooling is.

JASON: So to step back and say even more plainly, the secret sauce here is TanStack Query. Because the way that it functions is that TanStack Query is managing queries based on caching keys. So if we use the same, we can pull in the Solid use query from TanStack query and the React version and the Svelte version. If they're using the same query keys, they don't need to be aware of what frameworks are using it. Only TanStack Query needs to be aware of what's going on.

ARYAN: Uh-huh.

JASON: Very, very cool.

ARYAN: Yeah, and that philosophy extends out to any JavaScript library you want to implement. We can also be talking about this other library called Nano Stores, which implements the exact same. So imagine like an external store, but you write reactive wrappers for each one of these frameworks. Then you can subscribe to an external store.

JASON: Got it. Got it. Okay. Very, very cool. All right. So let's see, I don't think I have any questions that aren't going to be easier for you to show me than tell me. So why don't we switch over into the pair programming view here. And I'm going to start by -- actually, let me do a quick shout to our live captioning. So this episode, like every episode, is being live captioned. We have Rachel here with us from White Coat Captioning today. Thank you, Rachel. That is made possible through the support of our sponsors. Netlify, Nx, New Relic, and Pluralsight are all kicking in to make this show more accessible to more people, which I very much appreciate. If you want to get those live captions, there's a closed caption button right there on the Twitch interface. So just hover over your Twitch player, and you can see them. If you're watching this later on YouTube, captions are also there. And we're talking to Aryan today. Make sure you go and follow on Twitter. We mentioned TanStack. I'm going to drop a link to TanStack real quick. And we mentioned the Nano Stores briefly. I'll drop a link to those. And from here, I'm not sure what to do next. So what should my next move be?

ARYAN: Yeah, so I created an example repository, which I think you've already cloned. So let's try to go into the repository for VS Code.

JASON: I have the code here. I'm going to open it up in VS Code. And here is our repo. So I'm going to open this up bigger. All right.

ARYAN: So if you look at that, it's simple Astro project. If you look at the source directory, you'll see pages, where we have all our pages. So index is where we start off. Then first we talk about Nano Stores. So if you do npm run dev, it should open the project for you.

JASON: All right. So I've got this project running in -- whoa. What was that button? So we have these there. I'm going to take this one, put it over here. And we're going to open up local host 3000. So here's our project.

ARYAN: So the first -- like the index, that actually just contains the links to all the examples we have. So let's take a look at the first link, which is Nanostores. So now we're starting off with the frameworks adding to this. So we have a simple counter right now. So we can go take a look at how this counter component works. It should be inside components and inside the Nanostores. And it should be inside React counter, which will contain the React counter. And Solid counter, which contains the Solid counter. So this is a very typical counter component. You just have you state to keep track of the count. And you have add and subtract functions to increase or decrease it. Similarly on the Solid counter, it's the exact same thing. If you look at that component. So we use create signal and do the exact same thing there. But if you right now increase or decrease the counter, it only increases or decreases the counter on the individual components. We don't have a way to share this state between these two components. How do we share a counter between React and Solid? Can we use hooks outside React components? I don't think so. Similarly, if we put the counter or the signal in React, we can't do the exact same thing. A signal, even though it updates, you can still put it inside a React function. Any time it updates, we'll have to do like a set state inside a create effect or something. So that's, again, not really an ideal position to be at. So this is where what you need to do is extract that store or the state outside the component and keep it in an external object. This is where Nanostores comes in. So there's another file on the Nanostores directory called store.ts. So this is where we're importing an atom. It's like an atomic state you can define for a variable.

JASON: Okay.

ARYAN: This is where you -- when you see the function or the variable on line four, we're creating a countervariable and calling the atom function. So you're creating like an external store or a variable with just a number. You can put in arrays. You can put in objects. You can put in whatever you want over there.

JASON: So the idea here is that with Nanostores, nano implying they're small, when you set up an atom, an atom is kind of -- it would be like one use state is what you would put in an atom. So if a counter is a number, then our atom would be the same thing. It's a number. If we had multiple things we needed to track, like a username, an email, a thing like that, would we be creating an atom that has a big nested object in it, or are you creating multiple atoms for unrelated data?

ARYAN: Yeah, you could do both. I think there's a way to select a certain part of the nested property if you just want to track just one of them. But yeah, it should work similarly to how Redux works, where you can select a piece of the state. They all work on the same principle, but yeah, we'll see why Nanostores works best. So right now, if you go to the React counter, now instead of the count and set count, can we create a new -- can you comment that out? And create a new variable const count. Equals -- you have to do use store.

JASON: And that's here.

ARYAN: Yes. So it comes from the Nanostore/react library. So this is the reactive wrapper I was talking to you about. So we are subscribing to a store, or an atom. So you have to provide an atom inside that function. So you have to put in the counter that we just exported and imported in our counter function.

JASON: That's this one. So this came out of store.

ARYAN: Yes.

JASON: So we're exporting this counter. We're grabbing it here. Then we're telling, in this case, React to use that counter.

ARYAN: Correct.

JASON: Okay.

ARYAN: Yes. Now add and subtract won't work. We need to change that in a certain way to update the counter store or the counter atom now. So instead of set count, we'll do an adder function. And it will be counter.set -- yes. And there you can just put count plus one.

JASON: Okay. Then we can duplicate that.

ARYAN: Subtract as well. And let's save it and see if we're getting it to work. So if you increase the count, now it's working correctly.

JASON: So it's doing the thing.

ARYAN: Now it's subscribed to the counter atom.

JASON: And it's using a Nanostore instead of React state. And this is everything, right? So where did this client come in? Where are we actually using --

ARYAN: We're going to be talking about that in just a little bit.

JASON: Okay. Okay.

ARYAN: So first we're just setting up a counter store. Then we get to the client itself.

JASON: So this counter -- the counter store works without -- like, we don't need a provider. We don't need wrapper components or anything. This is all of the code. This counter, the use count, and then these add, subtract, and what we're exporting here. That's the entirety of putting Nanostores into React.

ARYAN: Yes.

JASON: Very cool. Okay. All right.

ARYAN: Now we can do the same on the Solid counter component. So let's go there.

JASON: Okay. So here is our Solid component. I'm going to bring this back up, actually. I don't know that it's super valuable for us to have all of them together. So I'm going to put this here. Then we want count equals use store counter. All right. Then for these, we can copy/paste, right?

ARYAN: A little bit. So over here, what you're getting is a count signal, not a count --

JASON: Oh!

ARYAN: So that's what I was talking about with reactive wrappers. They handle how each framework handles their internal state. So instead of setting counter.set -- yeah, so that works fine, but you'll have to do count with parentheses there.

JASON: And that is because with a signal, count is a function that returns its value instead of being the value itself. Which if you are not familiar with signals, that's probably a whole other discussion to get into that we're not going to have a ton of time for today. But, okay. So is that it?

ARYAN: Yeah, so let's save that. And now when you --

JASON: Oh, my god. That was too easy.

ARYAN: Yes. It's amazing how easy it is.

JASON: This is absolutely incredible. Because again, this -- the outer part of this app is not hydrated. So this is an island. This is an island. This is React. This is Solid. And these are freaking connected to each other now.

ARYAN: Exactly.

JASON: Holy crap. That is so freaking cool.

ARYAN: Yeah, it just blew my mind when I first tried it. I'm like, wow, this is really cool. Yeah, so this is how you handle client state and share client state between different islands of different frameworks. And it's pretty simple. But when you have like a client-side app, you're also thinking about fetching data, right. You need to show data from an API that you fetch from an end point or do like an RPC. That's the new buzzword going around. So how do we handle fetching state with Nanostores? You'll have to do, like -- you have to create a way to fetch the data when the counter changes and then handle loading states and success states and error states. If only there was another library that was framework agnostic, right.

JASON: If only. This is like very -- like an infomercial. Are you sick of trying figure out how to store all your Tupperware? They open a cupboard and it cascades out at them. Have I got the thing for you.

ARYAN: Exactly. So that's where TanStack Query jumps in. Like we talked about, there's a query client. If you've ever worked with TanStack Query, which has the brains of TanStack Query inside it, which keeps track of the cache, keeps track of all the queries that have been made anywhere around the app and stores a single source of truth. So to get that working, we'll still be on the first --

JASON: Oh, we will. Okay.

ARYAN: And if you go to store.ts --

JASON: Into store.ts.

ARYAN: So that's where we're creating a new query client.

JASON: And this is coming out of TanStack Query core. Got it.

ARYAN: And now we have the query client set up. Let's go back to the React counter. After subtract, you can create a new variable called Pokémon query. We'll be working through the Pokémon API. Have you ever used it?

JASON: I'm familiar with it. I feel like I've made a couple calls. I feel like everybody has built a Pokedex at some point except me.

ARYAN: So we'll just be working through the Pokémon API for all of our examples. So there is a hook called use Pokémon that I just created. Yeah. And if grow there, if you look at the -- it should be below.

JASON: Oh, use Pokémon. Okay.

ARYAN: It's just a use query function, which creates a query key with the count, which we'll pass in, in the Pokémon function. And we'll get Pokémon, which is just the fetcher function.

JASON: Okay. So we do have, if you're interested, a full episode on query. I know we're using v5 today, but I'm relatively certain that there aren't a ton of breaking changes in v5. It's more just new functionality.

ARYAN: Yeah, so right now we're using TanStack Query v5. The alpha version. It's still early. We're not expecting breaking changes, mostly, but yeah, this is how we'll be using React Query from here.

JASON: Got it. At a high level, let's talk through what exactly is happening here, for folks who haven't seen TanStack Query before. And so we pass in an object, like a configuration object. The query key is something unique to the query that will cause it to be invalidated. That's why the count is in here. Because if we just called it Pokémon, it would cache, and when we change the count, nothing would change. It would be like, here's your Pokémon. By adding the count, then Pokémon one is unique. So whenever it's one, that's cached. Pokémon two is unique and cached. So this is how you kind of specify what should and shouldn't be managed by TanStack Query in terms of caching and invalidation across the app and all that. Then query function, this is just telling TanStack Query how to get the data, right.

ARYAN: Yeah, exactly. And now in v5, we are introducing a way -- so if you've used TanStack Query before, what we had to do is provide a query client provider at the top level of the app to provide the query client for the hooks to be subscribed to. So when you use query, you look at the query client provided. But now we have an option to provide a query client right inside use query.

JASON: So this is sneaky because this doesn't look like very much, but this is magic. Because if this removes the need for a provider component, one, it's just nice to not have to use provider components. Like, I don't know why, I've added a million provider components. It's not actually that big of a deal. But I just don't like it. Like I don't know why. I've never liked it. It's like when you're a kid and don't want to put on your coat. I don't want to put on another provider! But this, also by eliminating the provider component, I'm going to make a guess that this is what allows us to share that client between any framework because the client is not React. The client, if we look again, is the core, not the TanStack React query or the Solid query. This is just a generic TanStack query. This is so cool. Okay.

ARYAN: But to preface this, we could still use it with v4. Instead of making a react counter, we'll have to make a React counter parent. There you'll have to provide the query in a query client provider. Then create the React counter inside it. But that eliminates the need to Crete a parent component. So it's easier when you're doing cross-framework stuff.

JASON: Gotcha.

ARYAN: So yeah, now if you go back up, we'll use this query. So use Pokémon. Then put in the count value. Yes. And if you scroll down a little bit, there are two things that are commented out. You can un-comment them. Both of them.

JASON: The dev tools. Great. Then we're going to drop the Pokémon data out.

ARYAN: So we're just showing the Pokémon’s name. Now you see it's showing up.

JASON: Got it.

ARYAN: So let's do the same on the Solid counter as well. So we subscribe to the Solid. We'll also make a Pokémon query again. And you'll just provide a count.

JASON: And looking down here for the use Pokémon, this is identical except for this is -- actually, why are these ones like this?

ARYAN: Yeah, so the reason why we have the options be a function that returns the query options is because we have to keep things reactive. And how Solid.js keeps things reactive is if anything is inside a function scope, whenever stuff changes, we can track those changes when it's a function. So we could extract this outside inside a memo, and you can provide a memo outside it. When you use count, when you do count function, you're calling that function, right. So this is how we create a tracked context.

JASON: Got it. Very cool.

ARYAN: So pretty similar, we'll just un-comment that as well. Now we have the query showing up in both places.

JASON: And notice the first time I get to a number, it has a second to load. Then it's cached. So I've gone up to nine. Nine is cached. Ten won't be. So you get that little bit of a lag.

ARYAN: Yeah. Two things to show here. So if you go on the query dev tools on the left-hand side, you'll see there's like a yellow with two. So there are two query observers. Even though we're using React query dev tools, it knows the queries mounted on the Solid.js component as well.

JASON: Oh, cool.

ARYAN: So this is where the cross-framework is working here in action. You can use different tools. So you can have, like, a react component do stuff for a Solid.js component.

JASON: Okay. So hold on. Does that mean that if I -- if there's not a Solid query dev tool, I could build my whole thing with solid, but then have one React component during dev that was the React query dev tool, and I would still get all the dev tools?

ARYAN: Yep.

JASON: That's incredible. Somebody in the chat earlier said that it's such a game changer for incremental migration stories that you can combine these things together with so little refactoring. Stuff like this, like making the React query dev tools available to any framework through this partial hydration story and Nanostores, that is freaking incredible. Like, it just makes the whole thing so good. It's just a very nice story and something that you can really see actually working. Oh, I could actually move my whole app to Solid without losing all this stuff.

ARYAN: Yep. One other thing that I would like to point out -- sorry, I'm going to make you manage a lot of windows. If you can open the dev tools on the browser. Is it possible to see the network tab?

JASON: Yeah, let's do it.

ARYAN: You can close the React tools. Take a look at the network fetches being made.

JASON: Okay. Oh, it 404ed for me. That makes sense.

ARYAN: Yeah, you'll have to maybe close the console.

JASON: Close the console? Oh, yeah. So there's my 404. And now 12 is the highest I've gone so far. So when I go to 13, there is our actual query. 14, actual query. And never again until we hit a number I haven't hit.

ARYAN: So did you see that even though we're updating the query on both of these components, we're only making one network call. That's because TanStack Query deduced that data between different accesses. So wherever you're accessing the data, we only fetch that data once, for that same query key.

JASON: That is extremely cool. So, there's a lot going on in here. What I think is important to note is for me as a dev, these are all things I want to be true. I always want to dedupe my requests. I always want to cache until day that changes. I always want to make sure that I'm sending the minimum amount of JavaScript. But the challenge for most of my career has always been that doing those things is extremely hard and really finicky. So I would always -- like, maybe I'd get it right, right at the initial build because I'd have the time and energy to really crank through and get it right. Then as requirements change, as things kind of evolve, the app gets more complex. I don't have the time. I don't have the scope. I'm being told to build new features. I don't have the time to re-edit everything I've written before. So my home-rolled cache invalidation starts to age and deteriorate. It just falls apart over time. It's partially because I'm a bad dev, but it's mostly because that's just how real-world dev works. You never get the chance to go back and fix all the code you've ever written when a new requirement comes in. So what I find so exciting about this stuff is that with these new tools, if you use TanStack Query, if you use Astro, these ideas of, like, only shipping the JavaScript you need, of automatically caching and deduping and all this stuff, that's no longer a thing you have to go back and maintain. That's why your company should be donating to the OSS teams that are doing this work for you. They're preventing bugs. Aryan is out here doing all this maintenance for you so you don't have to. That is huge. It's such an enormous boon to the community that I can go out there and write this query once and know that it's going to be deduped and cached properly and super fast and all that stuff. Hot damn. This is really cool, Aryan. I'm very -- I don't know. I'm fired up about this. This is just very cool stuff. It feels like a big step forward for us as a community.

ARYAN: Yeah, whenever I try to do anything with TanStack Query, it's very exciting. I just love developing with these tools. I think you mentioned it, when Dominic was on stream, that when it's really delightful to work with, it's a great heuristic effort. I think I saw Dominic in the chat say why are there no background updates. Usually the fetch data, even when we're showing the cached values, the Pokémon API we're using has a separate cache of itself. So that's why if it's already fetched a Pokémon, it just returns it to us. So we don't see a fetch for that. That's the reason why. So if you click on get Pokémon, we're making a call to PokeAPI. They implement their own cache.

JASON: Oh, I got it. So this library is already kind of just saying, oh, no, you don't need that query. You've already made that query.

ARYAN: Yep.

JASON: Got it.

ARYAN: Okay. So now that we have gotten a hang of stuff, right, of how Nanostores works, how TanStack Query works across different frameworks, let's go back to a more interesting example. So let's go back and open the Pokedex kitchen sink example. So here we have a Pokedex version. We haven't completed it. But we can work through that. So we have already this React and Solid.js component. It should have React pictures. So React will show the picture of the Pokémon. And Solid stats will show the stats of the Pokémon. So it will draw the graph of its hit points and all that stuff. So let's first have an image for the Pokémon itself. Again, it's the same as over here. I've already imported the count add/subtract from Nanostore. Then we have a use query that does the same thing we did. If we go to the Pokémon image component that's being rendered on line 26, that's expecting the whole Pokémon data. We can just put that in the props for our component there.

JASON: Is it Pokémon query?

ARYAN: Data. Now you should see the picture of the Pokémon.

JASON: And nothing wild is happening here. We're getting the data we passed in. Then we just dig into that API response to grab the sprites, which is like an image sprite. Then we get the front default and name is alt because we need things to be accessible. And that is dope. Okay.

ARYAN: So we also have buttons on these. If you click on the next button, yeah.

JASON: See this color changing in the background? What is happening here?

ARYAN: Yeah, that's a separate component. We can get into it if we have more time. It gets the image for the Pokémon then extracts the colors on the image to draw the background with that color.

JASON: That's cool. That's very cool.

ARYAN: If you toggle again to the next Pokémon -- go to a new one. Yeah, can you see the image flash for a little bit? We probably don't want that, right. We want to keep the previous data until we have a new image URL. So there is a function that -- there's an option on use query that we used to use which was keep previous data. But now it's not there anymore. With v5, we are retiring previous data with placeholder data. So placeholder data is a function that can do a lot more stuff. It can do more data transformations on your query, but we can also just return the previous data. So it takes a function, which the arguments will have the previous data. So it will be -- so if you go the -- yes.

JASON: Previous data.

ARYAN: And now you can return the previous data over there.

JASON: So then get out to one we haven't hit yet. See that? Now we don't get that image flash. That's dope. Okay.

ARYAN: So yeah, that's how it works.

JASON: That's really cool. So what this could also do is like if we wanted to, say, I don't know, put a shadow silhouette of a Pokémon loading, if we had a really slow API, so it wasn't like click, three seconds of the previous data, then it flashes in. You could do more here.

ARYAN: Yes.

JASON: And the data we're passing in just has to match whatever the response of get Pokémon is?

ARYAN: Yes.

JASON: Excellent. Okay. Cool. So yeah, that totally makes sense. We can kind of get pretty creative here with how we manage our placeholders. But on a fast API like this, I think keeping the previous data make sense.

ARYAN: Okay. Let's go to the Solid stats component next.

JASON: Okay.

ARYAN: Here again, same thing. If you look at the example at the bottom where we are wanting to draw the stats query -- so if you go to the JSX, there's the header component. Then stats graph. Stats graph is a component that will draw the graphs for us. It's expecting -- so let's look at the props that it's expecting. It's expecting an array of label and value. So label of what the stat is and value of the stat value.

JASON: Got it.

ARYAN: If you go up on the query now, even more up to the --

JASON: Here.

ARYAN: Create query, yes. I actually meant to comment on that value. So another option we're using called select. If you remove that option --

JASON: I'm going to comment so you don't have to watch me type that out later.

ARYAN: Yeah, if you hover over stats query, you'll see when we get Pokémon, it should -- yeah, if you look at the create query results, just look at the type definition of it. So it's going to be a create query result, which will return a Pokémon.

JASON: Got. Okay. So that returns a Pokémon.

ARYAN: And let's look at the shape of the Pokémon data. If you go back to the React query dev tools on the browser and open the Pokémon one and look at the data there. So it gives you a lot of properties on there. It has basic speed and all that stuff, the name of the Pokémon. If you scroll down, you'll see a stats key on there. And that's where we have all the stats for our Pokémon. There's a base stat, which is the value of the stat. Then there's this nested stat function. That's the name of the stat we want to show. So the HP, which is the head points of a Pokémon, is whatever the value is. Defense is whatever the value is.

JASON: Got it.

ARYAN: So that's what we naturally get. We can't just plug in the data right now as it is in the stats graph component. So if you try to do --

JASON: Right. We'd have to write some kind of gnarly -- well, we'd basically have to write something like this.

ARYAN: Yes. So every time the query data changes, we'll have to write a create memo to create a new transform data. But TanStack Query also comes with an option to do data transformations on your query. So if you un-comment that, what you're doing --

JASON: Look at that. The completion automatically happened. We get the type back. That's pretty cool.

ARYAN: Yes, so you're transforming that query only on the observer level. So even though the picture queried for Pokémon one and this is querying for Pokémon one, the data on stats query is going to be the transform value.

JASON: That's so good. Okay. So this doesn't require us to have a separate key. So we're still sharing the cache, but we get a different shape of data. That's so freaking cool.

ARYAN: There's a very, very amazing article by Dominic about React Query data transformation. You should definitely check that out. That's how I learned about it, and I have not stopped using it. So it'll be tkdodo.eu.

JASON: And is it select? Data transformations.

ARYAN: This reasons why you would transform data inside the select function. But yeah, it's super optimized. It maintains the stability. So even if you get the same query data on the transform data, it won't re-render your values.

JASON: That's very, very, very cool. Awesome. All right. This is a heck of a series. That's super cool. Okay. So let's dive back in over here. I'm going to close this up. And now look. Now we've got our stats in here.

ARYAN: So you get fancy animation and all that stuff working.

JASON: And let's get to one we haven't loaded yet.

ARYAN: It's all seamless. It's just going to work as expected. Also now let's go to the Astro file where all of these components are being drawn. So it'll be pokedex.astro. If you scroll down a little bit more, there are two components that are commented out on 38 and 39. So let's un-comment those. And click save. And now we have queries working for -- yeah, the styling is a little off. It worked on my computer. I need to test it out more.

JASON: Worked on my machine. (Laughter). No, it's all good. I just have it a little narrow over here.

ARYAN: So you can know see the evolution change.

JASON: So this is running in Svelte. This is running in Vue. This is running in Solid. This is running in React. God damn it, that's cool. (Laughter)

ARYAN: Data fetching is really hard in one framework. But doing it in four different frameworks with like ten lines of code.

JASON: And, and, and -- let me get in here. Let's look at the thing. Again, four frameworks, all sharing queries.

ARYAN: So this is a little bit unoptimized because we have different functions. It should -- the way I created it, I can optimize it, but it should only make one network call. We have different functions for evolution change. That's why it's doing multiple calls.

JASON: Oh, because the evolution chain, that's a different call. Stuff like that.

ARYAN: Yeah. I just took a shortcut. But we can get it to work with just one query being made.

JASON: That is extremely cool. I mean, it's still -- it's a lot less than I would have been if I had written this myself. That's for dang sure. And I love -- I mean, this is also maybe the most polished demo we've ever had on this show. Like, with the colors changing in the background and stuff like that. This is some pretty incredible stuff.

ARYAN: Thank you.

JASON: Kind of making me rethink my own -- man, I need to get better at building demos. (Laughter). Okay. So this is extremely cool. And we're running four frameworks. They're all sharing a single store. They are managed through React Query and Nanostores. And we've got caching. We've got query deduping. We've got all sorts of fun things going on here.

ARYAN: Yeah.

JASON: Is this one of those like, but wait, there's more kind of things?

ARYAN: No, we can take a look at the Vue and the Svelte components, if you like.

JASON: Yeah, let's do it. So we've got the Svelte details. And let me make sure that stays open. Then I'll open up the Vue. Okay. Then I'm going to -- here's the Svelte stuff. Cool. All right.

ARYAN: So we make a query for get types. So this is why we saw multiple calls. I created like a get types which calls the Pokémon API.

JASON: And it looks like we're not using the client either.

ARYAN: Since Svelte Query is a relatively new addition to the family, there are some bugs, I think, that need to be figured out. So this is the component that needed the query client to provide the client to all these queries. So there's another component called Svelte.Svelte, which does the -- so if you look -- yeah. There's another one. So this is where we're using the query client provider.

JASON: Ah, I got it. Okay.

ARYAN: You would have to do this for React and solid for, like, TanStack Query v4. But you won't have to do that for v5. You won't have to do the same thing for Svelte Query as well. But I think there's a bug we'll get fixed. So that's fine.

JASON: Cool.

ARYAN: But yeah, so you just do the colors query. The colors query just loads the image data and gets the color palette. So that's where you're getting the Pokémon. When the image is loaded, we extract the color on line 190. That does that. Then just the simple function to change that value from RGB. It just helps in creating better gradients.

JASON: Yeah, so cool. All right. I love that. Then we can get into -- so this is the details. This is written in Svelte, but we have the same thing where we're doing the data, and we loop through to create some types. All right. Let's look at Vue. Okay. So Vue, single-file component. So the template is up top. Then the script is down here. So, same kind of thing. We're using the store. We've got add and subtract. We've got a query client in here. Then we're going to use a query to pass in the query itself.

ARYAN: Yep. So over here, it's a little bit different. Vue has their own reactive wrapper. So they have to create a proxy of the query client. So the query client is a little bit different. But it shares the same internals. It's just a wrapper over query client. But it can still have the same guts inside. So what we're doing is whatever the query cache is we get from the main client and provide it. It's a little more involved, but yeah, it'll make sense once you work with Vue Query.

JASON: Got it, got it, got it. So the way this works is once you set the query cache, TanStack Query knows enough to -- like, it updates between the original client and the Vue client. So when we're in here, in the Vue component, when we're changing things, we still get that.

ARYAN: Yeah, so when we provide the query cache, we're getting the cache from the common store that we created. And we're putting it in this new query client. So it's still sharing the same query cache. So even if you make a request on Solid.js component, it will share that same query cache to the Vue component as well.

JASON: Very, very cool. Okay. All right. I'm with you.

ARYAN: Then just the same, you get the data and get the colors. Again, I'm using select where I'm getting the first value but keeping the other two the same. And return that. Then I return all these, which I then use on my template above.

JASON: Very, very cool. Yeah, this is some pretty impressive stuff. I love how, you know, I don't write a lot of Vue. And this doesn't look unfamiliar if I've been using the patterns in React and Solid, which I'm more familiar with, Svelte, which I'm a little more familiar with. I don't feel like I'm completely out of my element. That is great because sometimes I feel like even if a library has been ported between different frameworks, the differences are just subtle and weird enough I can't figure out how to -- like, it works over here, but why don't it work over here? And you have to become an expert in the framework to understand the nuances in how the tools work. I really like that we're starting to bridge those gaps and make it feel more unified. Great update.

ARYAN: So before this demo, I didn't know any Vue at all. But just looking at different libraries, it's just similar how every library implements their own life cycle. So it was pretty easy to get this to work. Then the fact that we're using TanStack Query everywhere. So the syntax for TanStack Query is almost the same in all these frameworks. So that helps. So if you're using utility libraries, which are framework agnostic, you can easily switch between different frameworks, and it should --

JASON: Yeah, because I feel like that's sort of the hurdle for a lot of devs, for me at least, is not how to template. I can go and build a template in Vue or Svelte or Angular or React or Solid or Preact or Lit or whatever really fast. Because the templating part is what's super intuitive with all of these. That's what they're all really, really good at. Where I end up getting tripped up is when you get into their internals. This is how we do reactivity. This is how we do caching. This is how we do all these other pieces that are unique to the architecture of the framework, that are unique to the preferences of the framework authors. Those things just -- it's the way people choose to build things. They don't usually line up one to one. If you like the way that React does re-rendering on every data change, then signals are going to be confusing because they don't do that. If you like signals, React is going to be super annoying because it re-renders every time data changes. Getting your head to think in those mental models takes a while. So being able to port big chunks of logic around through an agnostic tool, what a game changer.

ARYAN: Yeah, yeah. It's amazing. Every time I take a look at any of these TanStack Queries people put out, it just blows my mind. So, I mean, we can talk about other stuff as well a little bit. I think we have time, right?

JASON: Yeah, did you have more that you wanted to show?

ARYAN: Sure. We can talk about SSR with TanStack Query v5. So again, let's open the network tab on the page itself. And let's take a look at the -- so reload the page.

JASON: Okay. I'm going to do a hard refresh.

ARYAN: And instead, filter by all and take a look at the first query that happens in the waterfall, which is the Pokedex. That's the response. So let's look at the response, or the preview of the response. So right now everything is loaded on the client. So we're rendering the queries on the client. We're drawing all the colors on the client. There's no way right now to -- or SSR is not working on these examples. So let's take a look at how we can achieve SSR on TanStack Query. So if you click on the Astro + TanStack Query Pokedex example, it should take you back to the homepage. Then let's look at the last one, which is this one. So this is an example with Solid Query. There are ways to do it with the other adapters as well. It's a little more involved. But we can talk about it, and if anybody has questions about it, we can create a demo for that. But over here you'll see we're already drawing that graph on the server. And then sending it to the client.

JASON: And it's rehydrated.

ARYAN: So again, let's just close the network tab. And let's go to VS Code and open -- so it should be in SSR directory for components.

JASON: Oh, the components directory. Got it. Components, SSR. Okay.

ARYAN: Yeah, so it should be SSR stats. And if you scroll down a little bit more, you'll see that's the query we're making. So we have a query doing the same thing. We are defining a stale time of 60 seconds over here. Now if you reload the page on the left side and take a look at the React Query dev tools. You can see the query has been hydrated, and it also remembers your stale time, which you set on the server.

JASON: Okay. And the stale time -- where do you see that?

ARYAN: You can't see the stale time, but you can see the query is fresh.

JASON: Oh, okay. I get it.

ARYAN: And if you go to the next page, it's rehydrated on the client. So you can fetch the other queries on the client. So that's how it works. Let's see how it works, actually. So if we look at the SSR.Astro file, that's the page. And look at the front matter there. What we're doing is we are pre-loading and making a call to get stats, which is just a get stats function for the Pokémon API. Then we pass this as initial data.

JASON: So this is our initial data, which we pass in here. Then that is grabbed out of here. We have our stats, value label. That's the select we looked at before. And then --

ARYAN: Over here, if you just remove that function from initial data and do props.stat, that should also work. So just remove the whole function there. No, just keep the initial data.

JASON: Oh, oh, oh. I got you. So we're doing props.stats.

ARYAN: Yeah. So it still works, right. And this is how you would use, use query on the client as well. It's not different from how you would do client-side rendering and SSR.

JASON: So the only difference here between -- like, if we didn't have this initial data, then it would refresh on initial page load. Because we're passing in initial data, it's going to check what the initial data is and whether or not it matches this query key, which should be one. If we passed in one but then initialize the count at two, that would be a mismatch, which would cause it to immediately re-query. But because we're passing in the same thing, then it's going to say, oh, I already have one, so let me just put that up here. And we know it's fresh because it's good for 60 seconds. So we don't have to reload. Now look, we've been watching for more than 60 seconds. It just marked itself as stale. It's going to re-query next time we come back to it.

ARYAN: Yeah.

JASON: So cool.

ARYAN: Yes. And I have a small -- sorry, go ahead. Okay, I'll go. We have an example for SSR'ing the name of the Pokémon as well if you want to take a look at that.

JASON: Yeah.

ARYAN: We can do that. So SSR name. It's already commented out. So you can do the same thing here, which is uncomment that whole thing out.

JASON: Okay. So this is a create query. Same query key. We're going with the Pokémon and the count. We load the Pokémon and pass in the initial data.

ARYAN: The Pokémon query itself has a different query key. This has comma count. The other one is stats comma count.

JASON: Yes, so these are two different queries.

ARYAN: Now you can uncomment each one at the bottom. But you still need to pass in initial props on the Astro stats. So you'll have to pre-fetch this Pokémon.

JASON: To do that, I'm going to const Bulbasaur equals await get Pokémon one because that's the ID. Then down here, I need to pass in -- I renamed it. So there we go.

ARYAN: Yep.

JASON: So if I go back to my dev tools -- and again, what we did is because it's in the front matter, this happens on the server side. During a build, it'll load our Bulbasaur.

ARYAN: If you reload the page, you'll see there's no network calls being made on the client. Because we already have the data.

JASON: Here? Yeah. So here we go. No network calls. Very, very cool stuff.

ARYAN: Yeah. Yeah, I think we blasted through all the examples.

JASON: No, this is great. And honestly, we're at a great stopping point here. So man, I got to say, the TanStack OSS team is really raising the bar for demos on this show. When Dominic came on, it was one of the most well-prepared shows. I've linked to it already, but let me link one more time to this show because it was so, so good.

ARYAN: Yeah, I saw his example, and I was like, okay, I need to follow that up. So I need to do something.

JASON: And I think you've risen to the occasion. This is an absolutely stellar example. It went so well. I'm really just very bullish on where framework dev is going right now. Really excited to see this framework agnostic approach. I'm very excited to see this idea of, like, how do we make defaults so good we don't have to think about how I'm going to implement this. I just use whatever framework I like, implement these tools like Nanostore or TanStack Query that kind of remove the need for me to think about how I'm going to share state or to think about how I'm going to do query deduping, caching, et cetera. And now I just go get a build value, right. I'm not figuring out how to structure my code. I'm figuring out how to build the features I want to build. I'm not trying to reinvent wheels that honestly don't need to be reinvented. So that is really, really exciting stuff. So where should people go if they want more -- like if they want to go further with this?

ARYAN: Yeah, I have posted the GitHub repo of all these examples on the Twitch chat, if anybody wants to take a look at these examples, work with them. And yeah, usually all the conversations -- so TanStack is expanding really fast. If you have been following Tanner, he's been working on some amazing things that fly through my brain. So now if you look at the TanStack products on this website, look at the amount of frameworks that are going to be coming out from TanStack. So the Discord channel is the best place to ask questions, reach out to people, share ideas on how to do stuff with TanStack. And yeah, everybody will be very happy to answer your questions if you have any bug reports. Or you can create GitHub issues there as well.

JASON: Yeah, the Discord is wonderful. It's very active. Aryan, this has been great. So why don't we give one more shout out to your Twitter. Make sure you go and give a follow for more incredible demos like this the one we just saw today. And I'm going to do another shout out to our captioning. We've had Rachel from white coat captioning here all day. Thank you so much. That is made possible through the support of our sponsors. Netlify, Nx, New Relic and Pluralsight all kicking in to make this show more accessible to more people. Thank you very much. And we've got so much good stuff coming up on the schedule. Make sure you go and bookmark this page. Subscribe on Twitch. Subscribe on YouTube. Wherever you do your subscribing, get in there. We're going to have some internal mobile dev. We're going to have Jason Bahl on building some WPGraphQL stuff. If you work with clients, this is a really good stack. We have Mark Erikson to do time travel debugging. If you've never seen that before, it's extraordinarily cool. We've got Daniel from the New Relic team coming back. Then Sunil Pai, whose image is apparently broken s coming back to talk about his new project, Partykit, which is real-time collaboration. If you've seen that new demo of TL draw, where everybody can draw at the same time, that's Sunil's work. So we're going to play with that on stream. Very excited to have Sunil back.

ARYAN: I keep going back to Sunil Pai's videos that you have. He's amazing.

JASON: Sunil is one of my absolute favorite people to talk to about, like, the future -- so all of my images are broken. That's fun. I'm going to just drop a link to this, and I'll figure out why everything broke. Anyway, with that being said, Aryan, thank you so much for taking some time. Any final words for the chat before we call this one done?

ARYAN: No, yeah, thank you for having me. It's been a wonderful experience to show off all the stuff that we're doing with TanStack Query. Shout out to the other contributors or maintainers on TanStack Query. There's Damion, who works on Vue Query. I think Lachlan -- maybe I'm butchering the name -- but he works on Svelte Query. Dominic works on React Query. But there are way more contributors now that keep coming up with pull requests on TanStack Query, and we're grateful for all of them. That's it.

JASON: Yeah, awesome. Well, Aryan, this has been so much fun. Chat, we're going to find somebody to raid. We'll see you all next time.

Learn With Jason is made possible by our sponsors: