skip to content
Podcast cover art for React Query with Tanner Linsley
Podcast

React Query with Tanner Linsley

Tanner Linsley walks through the TanStack libraries—React Query, React Table, React Charts, and more—and how his startup Nozzle drives his open source work.

Open .md

Episode Description

Tanner Linsley walks through the TanStack libraries—React Query, React Table, React Charts, and more—and how his startup Nozzle drives his open source work.

Episode Summary

In this episode, Tanner Linsley joins the FSJam podcast to discuss the TanStack ecosystem and how each library fits into modern React development. The conversation opens with a detailed comparison of React Query and Apollo, covering their differing approaches to caching, normalization, and data fetching defaults like stale time and cache time. Tanner explains why React Query's aggressive defaults—such as background refetching and window focus refetching—are both its best feature and its biggest source of confusion for newcomers. The discussion moves into Remix and server components, where Tanner acknowledges these paradigms may reduce the need for client-side data fetching libraries in some cases but argues that SPAs and real-time synchronization use cases will keep React Query relevant. The hosts then explore React Table's evolution from a fully rendered component with over 120 props to a headless hook in v7, and Tanner teases a TypeScript rewrite for v8 along with a commercial React Table Pro offering. React Charts, React Virtual, React Ranger, React Form, and the lesser-known Swimmer library each get their moment. Throughout, Tanner connects everything back to Nozzle, his SEO rank-tracking startup, explaining how real product needs drive his open source output and how his co-founder status lets him own these libraries personally—a model the hosts describe as "open core inside out."

Chapters

00:00:00 - Introductions and React Query vs Apollo

The episode kicks off with playful banter about Tanner Linsley's name before diving into the TanStack's three main libraries. When asked to pick a favorite, Tanner chooses React Query for its simplicity and value, which leads into a direct comparison with Apollo.

The hosts and Tanner break down the key differences between the two: Apollo is GraphQL-specific with built-in cache normalization, while React Query is protocol-agnostic and works with any promise-based data source. Christopher shares his experience replacing Apollo with React Query at Everfund, highlighting instant benefits like background refreshing, window focus refetching, and prefetching paginated data. Tanner emphasizes that React Query's "Important Defaults" section exists precisely because these powerful behaviors surprise first-time users.

00:07:07 - Cache Time, Stale Time, and Caching Philosophy

Tanner unpacks the two most commonly confused concepts in React Query: cache time and stale time. He explains that cache time controls how long unused data stays in memory before garbage collection—defaulting to five minutes—while most other state management tools hold data indefinitely, leading to unbounded memory growth.

Stale time, set to zero by default, determines how long fetched data is considered fresh before React Query will attempt a background refetch. Tanner uses the Pokemon example to illustrate when you might set stale time to infinity versus a shared document scenario where five-second staleness makes sense. Christopher adds a practical example from Everfund's pricing page, where a day-long stale time is perfectly acceptable, and Tanner shares that he sets some column definitions to infinity since they rarely change.

00:09:21 - Remix, Server Components, and the Future of React Query

Anthony raises the topic of Remix and its server-centric caching model. Tanner explains that Remix represents a return to server-first thinking, where routing and data fetching happen on the server and the browser becomes much lighter. He candidly admits that Remix—and eventually React server components—will reduce the need for libraries like React Query for straightforward fetch-and-display use cases.

However, Tanner draws an important line: applications requiring real-time data synchronization while users are on a page will still benefit from React Query. He describes the existing SSR integration pattern with Next.js, where data is fetched server-side and rehydrated into React Query's client cache, and predicts a similar pattern for Remix. He reassures listeners that SPAs aren't going anywhere and that traditional React applications with React Query remain the easiest data-fetching experience available today.

00:14:42 - SSR Without Next, State Management, and GraphQL with React Query

Christopher asks about React Query's SSR documentation for non-Next frameworks, and Tanner explains the raw Express-style example is intentionally low-level to show the portable APIs. The conversation circles back to stale time before pivoting to whether a global state manager is still necessary alongside React Query. Tanner shares that after adopting React Query, his Redux store shrank to just theme toggles and modal states, which he moved to a lightweight atom-based library.

The discussion then turns to using GraphQL with React Query. Tanner notes that a simple HTTP POST with a template string query is often all you need, or you can step up to Prisma's GraphQL Request library for a lightweight wrapper. He acknowledges that users heavily relying on subscriptions or normalization might miss a full GraphQL client, but for the majority of cases, React Query with a minimal GraphQL setup covers ninety percent of needs.

00:25:35 - React Table: From 120 Props to a Headless Hook

Christopher introduces React Table, which Everfund uses for row selection and export functionality. Tanner recounts the library's history: versions five and six were full component-based solutions that rendered markup and styles, eventually ballooning to around 120 configurable props. The fundamental problem was that every user had different CSS strategies, component libraries, and customization needs.

Tanner describes the dramatic v6-to-v7 rewrite that stripped out all UI rendering and replaced it with the useTable hook. Users now call the hook with their data and column definitions, receive a table instance object with prop-getter functions, and build their own HTML table using whatever markup and styling they prefer. Anthony connects this to a job interview experience building a vanilla React table, and the group discusses how the headless approach lets you plug in Material UI, Bootstrap, or custom styles without fighting the library's opinions.

00:38:19 - React Table v8, TypeScript, and React Charts

Tanner announces that React Table v8 is being rewritten entirely in TypeScript with a typed plugin system, calling library-level TypeScript "100 times harder" than application-level TypeScript. He also teases React Table Pro, a commercial offering that will include full UI rendering on top of the headless core, available through GitHub Sponsors at the bronze tier and above.

The conversation shifts to React Charts, which Tanner explains is the opposite of headless—a single opinionated component that accepts options and handles everything. Built on D3 internals with React rendering, it was inspired by his earlier work building Chart.js 2.0. Tanner shares his strong opinions on data visualization, arguing that tables, line charts, and bar charts cover ninety percent of use cases, and deliberately excludes pie charts from the library because they mislead users about their data.

00:49:31 - React Virtual, React Form, React Ranger, and Swimmer

Tanner highlights React Virtual, a headless hook for DOM windowing and scroll virtualization that gives developers full control over rendered markup, contrasting it with component-based alternatives like React Window. He then candidly discusses React Form, his experimental form library focused on asynchronous debounced validation—a niche feature involving synchronous checks followed by server-side username availability checks with debouncing to prevent excessive network requests.

Christopher brings up React Ranger, a headless hook for building range sliders with single or multiple handles, useful for things like quantity-based pricing controls. The tour concludes with Swimmer, a small utility for pooling and throttling asynchronous tasks with configurable concurrency limits. Anthony likens it to a front-end load balancer, and Christopher notes the three-year-old TypeScript port request still sitting in the issues, which Tanner acknowledges with good humor.

01:03:17 - Nozzle, Open Source Sustainability, and the Tech Stack

Tanner introduces Nozzle, his six-year-old startup that reverse-engineers Google search results for SEO rank tracking. The platform harvests search engine result pages on configurable schedules, measures everything down to pixel-level positioning, stores terabytes of data in Google BigQuery, and provides dashboards to clients ranging from music streaming companies to major home improvement retailers.

He explains how Nozzle's needs directly drive every TanStack library and describes his unique position as a co-founder who personally owns the open source he creates. Anthony characterizes this as "open core inside out," where a product drives open source rather than the reverse. Tanner reflects on how this model removes the pressure of relying on open source income to feed his family, freeing him to experiment and take risks with GitHub Sponsors and new library designs. The episode closes with a rapid-fire rundown of Tanner's broader tech stack—Twin.macro, Zustand, Prettier, React Router v6 beta, Next.js, Pusher, Match Sorter, and more—before the hosts thank him for his contributions to the ecosystem.

Transcript

00:00:00 - Christopher Burns

Welcome back to the FSJam podcast. Today we have a very special guest. Sorry, I thought it was Lindsey Tanner for a moment. Tanner Linsley.

00:00:23 - Tanner Linsley

It happens. And actually, in middle school, people called me Lindsey Tanner sometimes, like my teachers, because it just seemed right to them.

00:00:36 - Christopher Burns

The thing is, whenever we did the register in the UK, you would always do your second name first, so I was "Burns, Chris." Christopher, Tanner has created loads of open source libraries, and I'm excited to dig into them. So here you are.

00:00:55 - Anthony Campolo

You have your own stack, even named after you.

00:01:00 - Tanner Linsley

Well, I didn't give it that name initially, if that counts at all.

00:01:03 - Anthony Campolo

But we actually know who gave you that name because he revealed it on our podcast. Swyx was the one who came up with it, right?

00:01:10 - Tanner Linsley

He did? There we go. Good old Sean.

00:01:15 - Christopher Burns

Currently in the stack, there are three main packages: React Charts, React Query, and React Table. You don't have to use all of them, or you could go all the way in and do all three. Which one is your favorite out of the three?

00:01:35 - Tanner Linsley

Favorite. I mean, favorite for different reasons, right? But React Query is great because it's really simple for what it's delivering to you. It's really easy to use. It's probably the most bang for your buck if you use React Query.

00:01:57 - Christopher Burns

I would agree. We use React Query in Everfund, and swapping out Apollo was just mind-blowing. As Apollo disappeared, React Query came in, and the benefits were obvious straight away. We could do a quick comparison of Apollo versus React Query. Would you want to do that?

00:02:22 - Tanner Linsley

I would love to do that, and then I would love to hear more about your experience. That sounds really exciting. But yeah, high level, React Query and Apollo are actually very similar. They're both server-state fetching libraries. The main difference is that Apollo is built specifically for GraphQL, and React Query is built as more of an agnostic layer. So you can use GraphQL if you want, but you can also fetch data however else you want, as long as it returns a promise, which is everything these days. So you could do REST APIs, GraphQL, gRPC, whatever you want. You can even do all of them at the same time in the same application. So that's the biggest difference.

The second biggest difference that isn't as big of a deal as people think, at least for most applications, is that Apollo, because it's GraphQL-centric, has built-in normalization for all of the cache under the hood. Normalization is really cool if you can do it correctly and make it easy to use. It can save space in memory. It can keep parts of your app up to date easier without as much work from you. But the tradeoffs are definitely there. It basically means you have to use GraphQL in a specific way if you want the normalization to behave correctly. I think a lot of that is bound to using Apollo's back end as well. Obviously, if you use Apollo on the back end, everything just works really great. Of course, you can use Apollo to consume other GraphQL APIs, but there's going to be small differences there.

The biggest difference is the normalization. There's really no normalization happening in React Query, but the ergonomics of query keys and things that we can talk about later kind of take away a lot of that pain for most people.

00:04:24 - Christopher Burns

The benefits of React Query have blown me away, especially some of the key functionalities it adds straight away. My favorite one is background refreshing and window focus refreshing.

00:04:42 - Tanner Linsley

Oh, yeah.

00:04:43 - Christopher Burns

So if we're deep into Redwood and they use Apollo as a default, they have this cool little higher-order component called a cell, and that handles basically all of the branching logic of a hook in the background. But when you swap it out for React Query instead of Apollo, that cell now instantly refreshes in the background. It instantly fetches if you've added the experimental features, and it's just crazy fast.

The second thing is the prefetching. The caveat is Apollo could have these things, but I never touched them in Apollo. For example, when you load a table view of 30 records, whenever the user starts scrolling on our application, we then go and quickly fetch the next one. So as soon as they click next, it is instantly available because it's already been prefetched with React Query. I really do love React Query.

00:05:59 - Tanner Linsley

That's great to hear. I guess you could say one of the best features of React Query is all of these defaults. In fact, there's a section in the docs that is the very first section you will hit, and it's called Important Defaults. Before I even tell you anything about how to write a query, it's like, you must read this, because some of the defaults that I think are the best features about it can catch you off guard.

You can ask anybody who's used React Query for the very first time. They load it up, they make a query, and they're like, sweet, it's up to date. Whoa, it just updated. Oh, it just updated again. What's happening here? And they don't even know that it's doing automatic background prefetching. They don't even know that window focusing is a thing or that network reconnection is triggering any of this stuff. And that's why those defaults are important to read. It's like designing those defaults to work for the majority of use cases. One of the best features, like you said.

00:07:07 - Christopher Burns

The only thing that caught me out with these defaults is cache time and stale time. They sound very familiar.

00:07:18 - Tanner Linsley

Absolutely. Cache time and stale time. If you were to use more traditional terms here, you could use max age. It's something that people are more familiar with if they're designing regular caching systems. Max age is this familiar term where it's like, okay, how long does this thing live?

In React Query, there's more to it than just max age, and that's why we split it up into two terms: stale time and cache time. In the most traditional sense of max age, it's cache time. That's how long the data sticks around in my cache in my application. And I think that by default is set to five minutes, because if you haven't fetched or used data that you've fetched for five minutes, let's get it out of there. Fantastic feature, in my opinion, about React Query. Highly underrated. It doesn't matter if you're using Redux or Apollo or most other systems, they just hold on to data forever.

So if your users are in the application for a long time fetching a lot of different assets, the memory is just going to keep going and going because it's just this never-ending cache. Some of them have LRUs, where they'll just keep the cache recycling. Some of them don't do it at all. React Query's cache time is, I think, a great 90% use case where if you have data in the cache that hasn't been displayed to the user — because if you're displaying data to the user, it's relevant, it should never be garbage collected — but if you have data that you're not showing to the user and you don't show it to the user for five minutes, then we evict it from the cache. And that's by default. Most people never touch cache time. So if you're listening and you're like, stale time, cache time, do I need to worry about these things? Cache time, you could probably just be like, meh, whatever. Who cares?

00:09:21 - Anthony Campolo

While we're on the subject of caching, we have talked a little bit in the past about Remix, and I know you're pretty excited about Remix. Remix has a lot of stuff going on with caching, so I'd be kind of curious to get some of your thoughts there, like what is it you think is exciting about Remix, and if you see any sort of ways that that's going to work with React Query.

00:09:45 - Tanner Linsley

The topic of Remix is really interesting. I haven't deployed any projects with it yet, but I've just been following it very closely. Remix is intriguing, not because of what it's doing in the client. We'll start with that. In the client, you have single page apps. We're doing React Query. There's this concept of after you have rendered the app, then you get into this side-effect state where you're fetching new data and whatnot.

Remix is just completely different because it's kind of a return to — I don't want to say the good old days, because PHP definitely did not give me good old days vibes — but it's a return to the concept that it's okay to run your logic on the server.

00:10:34 - Anthony Campolo

To think in a server-first way as you're developing.

00:10:37 - Tanner Linsley

And it's okay to design it in a very server-centric way. The browser becomes way more lightweight. So that's kind of the concept around Remix: you're doing a lot of these things like routing and data fetching and site building from the server perspective.

And to be honest, Remix is probably going to do what server components might do in the future, but do it much earlier. It will reduce the need for people to use libraries like React Query at all. I hate to say it, but it's true. If you have a site that you just need to fetch data and then display it to the user and you don't need to be constantly updating that data all the time, then Remix is going to be great because you can just fetch it all on the server, render it, send it to the user, and that's that. You don't even need React Query.

If you're talking about the core Remix experience, it'll be similar for server components in React. If you're fetching the data on the server and then you're showing it to the user, at the core of that experience, you won't need React Query to coordinate that fetching. However, there is a caveat to both of those things, and it's if you have data that is updating while your user is on a page, not when they're navigating around or doing anything like that. Remix is heavily founded on React Router version six. It's very grounded on the user navigation action to perform a lot of what it does. And that is fantastic because it makes up a huge majority of what people do on a website or an app — they just navigate around.

But for apps that need higher levels of synchronization, I envision a future where you will perform a lot of your SSR with something like Remix, or even as it is today, like people do a lot of SSR with Next. I like Next and I use Next.js right now because it's free and it's familiar and it's common. So that's what I use. React Query is easy to use with that. You just have to set up this SSR environment with React Query, where you're fetching the data on the server and then rehydrating it back into React Query's cache on the client. There is a future where that exists. It already exists for Next, and it probably won't be very different for Remix, to be honest. So if you want to take it to the next level and be updating your data on the client after you've done your server-side work, then React Query is still going to be around for a while.

Those are my thoughts. I also get a similar question a lot these days with server components coming out or being talked about, and Remix being talked about. It's like, what's going to happen to React Query? My opinion on that is that the traditional application that we see nowadays is going to be around for a long time. SPAs aren't going anywhere. Remix is going to have a certain level of buy-in that it will definitely integrate with, and same with server components. They're going to turn the ecosystem upside down.

00:14:07 - Anthony Campolo

Yeah, I definitely don't see the need for data fetching libraries going away anytime soon. I still see the need for that continuing to accelerate.

00:14:14 - Tanner Linsley

Those questions usually come from people who are afraid of technology changing yet again. And it will change, but it'll just be more eventual as of today, I think. Honestly, I'm biased, of course, but I think the best and easiest data fetching experience today is still just spinning up a React application using your favorite CLI tool, popping in React Query, and going to town.

00:14:42 - Christopher Burns

SSR, obviously React Query has the ability to do it, especially with Next.js, but you have also added a quick tutorial on how to do SSR without Next. Why is that interesting? RedwoodJS is getting pre-rendering of their own in the next few updates, and my busy brain wonders if they could be combined to prefetch a marketing website in Redwood using React Query.

00:15:18 - Tanner Linsley

Yeah, that second example. I know the documentation page you're probably looking at, and that second example is very raw, like it looks like it came from an Express app. And it's that way on purpose so that you can see the lowest-level APIs that you need to implement it theoretically, whether it's Express, Redwood, Next, or Remix. You can implement it into anything.

Oh, stale time was another thing that I think we might have glossed over a little bit.

00:15:54 - Christopher Burns

Yes. Cache time, in the background, I just let it do its thing normally. And stale time, though, is completely different.

00:16:01 - Tanner Linsley

Stale time is completely different. Think of it kind of like a max age, but instead of a max age for kicking something out of the cache, it's a max age for refreshing it from the server. It's like, how long are you willing to let your data go before you need to fetch it again from the server?

It catches people off guard because stale time out of the box is set to zero. So as soon as you fetch data, it's considered out of date. This is really aggressive. I know it's really aggressive, but it makes a lot of sense when you're just getting started because you just want things up to date all the time. And usually the cost of a network request is not that costly to just be fetching from the server all the time. If it's bringing down your system or your client experience, you have bigger problems. You want to optimize where you can.

And so I give people the example of fetching a Pokemon. This is usually an example everybody's familiar with. Hey, let's fetch Pokemon. How often do you think that Pokemon data is changing? Probably never.

00:17:19 - Anthony Campolo

Once every year when they add a bunch of new Pokemon.

00:17:22 - Tanner Linsley

Right? I mean, even just for an individual Pokemon, it's probably never changing. And so I tell people, if you want, you could set stale time to infinity on that, because you're not worried about that thing that you just fetched changing behind the scenes.

Now compare that with something like a shared document between two or three or four users. That's a different level of confidence. You're like, okay, that thing could have changed now, or somebody could have deleted it or whatever. You have no idea when that thing is changing. And so stale time is kind of like, how confident are you in keeping this thing around and displaying it to the user? One second, five seconds, ten seconds. It's there for that reason. So you can say, you know what, I'm going to fetch this item from the database. It's a shared to-do list, and there are a lot of users on this shared to-do list. So we're going to set the stale time to five seconds. Every five seconds, if the user hovers the page or clicks around or does something — it's not really like a timer. It's not every five seconds we're going to fetch it. That's an interval fetch, which is different. This is more like if the user is interacting with the data, every time the stale time is reached, we're going to refetch it from the server. So that's when you're going to interact with it more often. But again, I usually tell people that leaving it as a default is fine, as long as you realize that it's going to be fetching quite a bit from your server.

00:19:06 - Christopher Burns

Exactly. And I think as you go, you start thinking about how often this data is going to be fetched. We dynamically fetch our pricing page and obviously the pricing. So you think, well, we set it to a day and it doesn't matter if it's the most extreme of 24 hours before it's reset.

00:19:30 - Tanner Linsley

Yeah. I have column definitions for tables and for data visualization, those types of things. They're not so dynamic that they change every day, but they do come from the server. And they don't change often enough that I even have it set to a day. I have it just set to infinity. I'm just like, yeah, just reload the page and you'll get new column definitions if we ever update them.

00:19:56 - Christopher Burns

My final question with React Query before we move on to something else is the classic: do I need a state manager with React Query? I haven't used a global state manager with React Query yet, and I think I sit in the camp where I don't really see the need for one right now.

00:20:21 - Tanner Linsley

I'm in that camp. I think there are varying levels of what you would define as a global state manager. It depends on your definition of what that is. If you think that using global context with a useState at the top of it is a global state manager, I think you're wrong. I have a global context with a reducer on it that I use to keep track of what theme my user wants, dark or light. And I use it to keep track of if a certain menu button is expanded or not. Or I use it to keep track of if a specific modal is open. Those are, in my opinion, global states to an application. But I don't see why they would ever require something like a global state manager in the sense that we know it today. I don't need Redux to manage that. In a lot of these cases, I don't even need something like XState to manage it.

I think XState is a great tool for smaller levels of component logic that need very specialized state and machine management. But at the top level of an application, in the traditional sense that everybody's like, well, you need Redux, so you better install it and it's going to control everything in your application — that disappeared for me. It disappeared for me the day that I wrote the first internal version of React Query. I was like, wow, I just moved everything over into this server-state management hook. It wasn't even called React Query yet. And now all of a sudden, I looked over at my Redux reducers, my slices in Redux, and it's like, what's left here? Some modals, a dark theme mode. I do not need Redux for this stuff. So I ripped it all out and put it in one of those lighter-weight, atom-based libraries. And it just helps so you're not rerendering your whole app with a context provider type of thing. But I haven't used global state since then. 99% of my state is just in React Query. It's coming from the server. I think that's how it is for a lot of apps.

00:22:46 - Anthony Campolo

In terms of other things that you could use with React Query, we've talked about how it's agnostic to your API, so it can do GraphQL, but can do REST and can do kind of whatever. I'm not sure how much work you do with GraphQL, if you do any at all, but I'd be curious if you think there's a best practice in terms of do people tend to just use the fetch API to make those requests, or bring in something like GraphQL Request library? I think that's how the Redwood provider is doing it, but I'd be curious if you have any thoughts in that area.

00:23:20 - Tanner Linsley

You reminded me of a great article from Swizec Teller, and I think it was called "All the Reasons You Think You Need GraphQL That React Query Gives You" or something like that. And it kind of reminded me of that because if you want to use GraphQL with React Query, you can just use fetch. You just make a POST request. It's an HTTP POST, and you post with your query as a template string as the body to a GraphQL endpoint, and it just works. People forget how simple things can really be.

And if you're not comfortable doing that, then the next step above that is using something like GraphQL Request from — I think it's from Formidable Labs. I'm not sure. But GraphQL Request is a super lightweight wrapper around fetch.

00:24:20 - Anthony Campolo

GraphQL Request is actually from the Prisma people.

00:24:23 - Tanner Linsley

Prisma. That's it. Yeah, it was not Formidable.

00:24:25 - Anthony Campolo

But you're right. Formidable does Urql. There's all these right around the same kind of companies.

00:24:33 - Tanner Linsley

Prisma does it and it's great. You just use GraphQL Request and create a new client. Use that client inside of React Query's fetch functions and you're golden. You're not getting all this fancy normalization that you would with something that's full buy-in on GraphQL. Again, tradeoffs to discuss there.

00:24:56 - Anthony Campolo

I totally agree with all that. I think that's fantastic advice, and yeah, the exact same kind of sense I've gotten from this space.

00:25:04 - Tanner Linsley

Yeah. I think most people who are used to Apollo or used to Urql or something like that, they switch to React Query with something like GraphQL Request, and 90% of the time they're happy, unless they're heavily using subscriptions, which we can also talk about, or they're heavily relying on normalization, which in my opinion you shouldn't rely on. It should just be an option like an optimistic performance optimization. But yeah, most people are happy.

00:25:35 - Christopher Burns

As the stack is, there's the three libraries we spoke about, React Query, and there's two more. The next one to talk about would be React Table. We use this in Everfund as well, and I have good thoughts about this one too. My favorite thing that it allows us to do is easily select rows and then do an export function built on top of that. It just made that a lot easier.

00:26:11 - Tanner Linsley

Yeah, makes a lot of things a lot easier. It's definitely a unique approach. When did you start using React Table? Did you start using it in version six?

00:26:23 - Christopher Burns

It's been less than three months.

00:26:25 - Anthony Campolo

Talk about the history of some of these projects and how long you've been working on them, how they fit together. I think some of our listeners will already know. There'll be some others who are hearing about these for the first time.

00:26:36 - Tanner Linsley

Quick recap on React Query, then. It's only been around for a little over a year. Explosive growth. Incredible. I wish I could show you the npm download or GitHub star growth. It's crazy to see. I did not expect it to happen, but I definitely tried. I did not expect it to happen so quickly. It's only been around for a year, and I used it internally at Nozzle, at my startup, for about six months before that.

React Table is one of my oldest libraries that I'm still maintaining publicly. It's been around for three to four years, maybe three years. But it was kind of that first library that I wrote where I was like, this is going to save people a lot of time. It's going to save me a lot of time. The reason I asked you about the version that you started using it on is because, as you know, React Table today is just a hook. It's just useTable.

Well, in a galaxy far away, a long time ago, there was a component version, version five and six of React Table. It was not a hook; it was a component. And it would actually render markup and styles and everything for you. Everybody loved it. It got rid of a lot of headaches that you would normally have with building a table. But while it did all that, that was awesome, it created another huge problem. And that huge problem is markup and styles.

It's a problem because every single person has a different way of rendering stuff in React, like building their markup. Think about the way that you render your divs and your components and your layouts, your color scheme. You've got a thousand different CSS strategies that you can pick from, and it's probably changing every six months anyway. And then you have these UI elements that are common to a table: the dropdowns, the inputs, the filters, the checkboxes. And it's like, who am I as a library creator to say this is how those look? Then you start getting into, well, now I have to provide themes. Okay, now I need to provide a way for you to edit the class and the style tag for every single one of these elements that I'm creating. Oh, you want to replace the entire element yourself? Now I have to offer a way for you to do that. Once you provide markup to people, they're going to want ways to customize it from end to end on the spectrum and completely get rid of it or replace it. The amount of API surface area that you have to architect to allow your users to do that is absurd.

In a tweet about two years ago, Kent C. Dodds, a good friend of mine, said, here's a competition: somebody find a component that has the most options, the most props that you can pass to it. And I was like, I know it's React Table. It was insane. It was like 120 or something props that you could pass to React Table. Crazy amount because of the customization. People wanted it.

So I underwent this massive change from v6 to v7, and I ripped out all of the UI and it just became a hook. This is also after hooks came out and I was really excited about hooks. So version seven of React Table is just a hook. It doesn't do any markup for you. It has utilities built into it for helping you.

00:30:43 - Anthony Campolo

Kind of a clarification. So it's a React library, as you're saying, React Table. But how much does it or does it not leverage just the HTML table? How does it relate to that?

00:30:55 - Tanner Linsley

I wish I could show you, but as a podcast I'm going to have to tell you. So as a user of React Table, your experience would go like this. You'd have a component that you would create called MyTable, my special table. And this component is yours. You can do whatever you want with it. Inside it you would call React Table's useTable hook.

So you say useTable and you pass useTable a bunch of options. Obviously a lot less than it used to be, but still a lot of options. You pass it your data columns. You can pass it event handlers and behavior.

00:31:39 - Anthony Campolo

Did you have to rewrite SQL to do this?

00:31:42 - Tanner Linsley

Oh, no. We didn't write anything having to do with SQL at all. But we did have to write data manipulation logic because under the hood it's handling things like sorting, filtering, grouping, row selection. It's creating the underlying data model of rows and cells. And it does all that inside of this useTable hook and then wraps it all up into a table instance object and gives it back to you.

And that table instance object has so many great utilities on it. All of those utilities almost map perfectly to just building your own table. So from there you say, okay, how do you want to build your table? You could use divs. You could use table elements. In fact, you wouldn't even have to use table elements if you wanted. You could use React Table to just build a data model and then render it into D3. It's really interesting. But for the basic use case, you just say, okay, here's a table element. Well, React Table in that instance that you got back has a function on it called getTableProps. If you call this function, it gives you back an object of props that you're supposed to put on your main table element. So you just spread those onto your table element, and then you go down a row and you're like, okay, let's build some headers. And so you say reactTable.headers. And it has an array of header rows. And you say, okay, let's loop over those and create header rows.

If you get the idea, it's using that table instance, the model, and the functions that come from that instance. And you're building your own HTML table, but with the utilities of React Table. It's really hard to describe. It's better if you see it.

00:33:42 - Anthony Campolo

It makes perfect sense because I actually had a job interview once where you had to create a vanilla table with React. So I get the pain point, and I'm going to have to give it a try for sure.

00:33:53 - Tanner Linsley

Oh, yeah. If that was a take-home for a job interview, you could have just been like, useTable. Here are my columns. Here's my data. And then render a simple HTML table and wire it up to React.

00:34:06 - Anthony Campolo

The constraint was you can't use a library for that exact reason. They knew there are solutions out there, and they wanted to make sure you actually could do it yourself. And I understand that thought process, but at the same time, if you have a tool that can solve a problem, isn't that what you want?

00:34:20 - Tanner Linsley

So anybody who has done that, who has said, okay, I'm going to build my own — start getting into filtering logic, sorting logic, grouping logic. Get into all that, like sorting rows by multiple columns, descending, ascending, letting each column define its own interfaces for how to sort and filter and do all that. You get into the data manipulation part of it, and then you realize you have to make all that performant. You'll quickly come back to that table because you're going to notice that there's so much thought that goes into it. There's so much logic below the water on the iceberg of data tables. It's a difficult problem to solve.

00:35:11 - Christopher Burns

How I would describe React Table is almost like headless React, as there's no JSX related to it, as it's all through hooks. Another example would be React Aria by Adobe or React Hook Form. With these libraries you don't write any JSX. It's all in the form of hooks, and then you obviously put it in where that makes it really useful.

Our use case is you then basically make a generalized component called Table that has React Table wrapped in it, and then you just pass into it what you need. It can be a Pandora's box, though. As you said, there's a lot of functionality. But if you want to know the simplest one, it's when a marketing person goes, you got that table, and I've seen on another website that you can just tick boxes on the left and then export five rows. Just use React Table. It will save you.

00:36:21 - Tanner Linsley

And I love that you mentioned headless. That's exactly what it is. It's headless UI. It's UI component logic. Bring your own markup. Bring your own styles. And at the end of the day, it will save you. It might feel more painful in the first five minutes because you're like, wait, I have to build my own table elements. I have to do all my own stuff. I have to build my own styles for this table. But once you get over that hurdle and you realize that you can just use whatever component library you want, whatever styles you want.

So if you want to use a Material UI app and you're like, I want to use the Material UI table styles, you can. You just pop in the Material UI markup components and wire React Table up to it. You could do the same thing with Bootstrap or just build your own.

00:37:16 - Anthony Campolo

So what kind of styling do you like to do? You kind of dance around it. You're very noncommittal in terms of what styling library you like, but do you have one that you're interested in?

00:37:28 - Tanner Linsley

Yeah, I like to build my own. At least for Nozzle, styled components.

00:37:34 - Christopher Burns

Twin.macro. These are what I use myself. I sound like a nut job when I basically say Tailwind in JS, right?

00:37:49 - Tanner Linsley

It's Tailwind in JS. It's perfect.

00:37:51 - Christopher Burns

It's perfect, but you sound crazy.

00:37:55 - Tanner Linsley

It's T-I-J, T-W-I-J.

00:38:01 - Christopher Burns

We can obviously talk about that in a minute because I do also want to talk about Nozzle. But we need to finish the trifecta with React Charts. I don't use this one personally, so you're going to have to do the heavy lifting. So what's React Charts?

00:38:19 - Tanner Linsley

I actually have one more thing to talk about with React Table, and it's only going to take a minute.

00:38:24 - Christopher Burns

Go on, then.

00:38:25 - Tanner Linsley

I am writing the latest version of React Table v8. I'm rewriting the entire thing in TypeScript with a typed plugin system, and it's going to be ten times better, just generally. But that's it. That's all I wanted to say.

00:38:44 - Anthony Campolo

I don't know if you know this, but Chris likes TypeScript.

00:38:47 - Tanner Linsley

I know he does, and that's why I want to say it. But it's one of the hardest things I've ever done because library TypeScript is like 100 times harder than app TypeScript, which I've never even written. To be honest, I just know library TypeScript. But it's so exciting, yet so difficult. I just want everyone to know v8 is going to be full TypeScript. It's going to be crazy awesome. It's going to be a new gold standard for table-manipulation libraries, and it's going to be powering one of my newer projects that I have not really teased very much yet. It's going to be called React Table Pro, which is basically going to do everything, including the UI. So it's going to be fun.

00:39:40 - Anthony Campolo

If I had to do all that work, I would want to make sure people knew about it as well. I'm sure that took a ton of work to do.

00:39:46 - Tanner Linsley

So, Chris, think about all the work you've done to create that beautiful UI for React Table. Well, I'm going to handle that too.

00:39:54 - Christopher Burns

Well, I would say, how do you get access to it? But it's probably going to be GitHub Sponsors first. And I already sponsor you, so maybe I'll get early access.

00:40:07 - Tanner Linsley

Yeah. In fact, if you are a bronze tier or above.

00:40:12 - Christopher Burns

I think $10 a month, I think I am.

00:40:16 - Tanner Linsley

The bronze tier is $100. So that one has the experimental repo access flag. If you're bronze or up, you can technically look at all the experimental React Table code right now.

00:40:32 - Anthony Campolo

That's going to be like you get people to pay you to then find bugs in your new libraries. That's brilliant.

00:40:39 - Tanner Linsley

Yeah. But I mean, it's also if they want to have a say in the way that the library is built, then you've got to be able to look at it.

00:40:49 - Anthony Campolo

Yeah, that's what you want to think, right? I think that's really cool what you're doing there with GitHub Sponsors and just innovating in that space. I really enjoy seeing people do stuff like that.

00:41:00 - Tanner Linsley

It's fun, and we can talk more about it with Nozzle because there's a very unique relationship there that is different for me than it would be for anyone else. But we can talk about that more. Back to React Charts.

00:41:14 - Christopher Burns

I want to finish the trifecta. Well, the current trifecta. Maybe more will come, but React Charts, from what I understand, it's like a headless chart library as well.

00:41:31 - Tanner Linsley

This one's not really headless. So I worked on Chart.js a long time ago. I built version 2.0 with a friend, Everett Timberg. We took it over when it was basically abandoned by its owner. We rehabilitated it and built version 2.0. It became a very popular library. It already was. But after we did that and I moved on, I moved away from canvas because I hate the canvas API for drawing things. I just hate it. There's a lot of math. Then I moved to SVG because of the awesome built-in accessibility that SVG has for pointer devices, and clicking and hovering and styles and all that stuff.

And I was like, I wish I had Chart.js but for SVG. I decided I was going to build it in React, and instead of rewriting all of the same logic I wrote for Chart.js, I decided I was going to use D3 because why wouldn't you? D3 is amazing. So React Charts is kind of like my personal take on what it would look like if Chart.js was built in React and the internals were implemented with D3.

It's not something where you build your own. It's actually on the very other far end of the spectrum from headless. It's one component, Chart, and you pass it a bunch of options. What type, what axes do you want, where's your data? You tell it everything that you want it to show, and it's just one component, and it does it all. Under the hood it's using D3 to do all the computation for layout and everything, and then it's using React to render instead of D3's select package, with a healthy amount of opinionated design on top of it.

00:43:33 - Christopher Burns

I actually use, I think it's called, Visx by Airbnb.

00:43:41 - Tanner Linsley

This is a pertinent part of this discussion, so keep going.

00:43:45 - Christopher Burns

I didn't actually do any of the charts in my company. I was just like, pick the best chart library to you and we'll go with that. But this was also before we did React Query and React Table. So obviously we weren't using any of the three. And I don't know if we did the other two first, then we would follow with React Charts. So the true question is, why do you not use your own library?

00:44:18 - Tanner Linsley

I do use my own library, but it doesn't cover all the use cases. There's actually a good example on tanstack.com. If you go to tanstack.com, scroll down the page, you're going to see an OSS sponsor section. It's got a lot of little bubbles with people's heads in them from GitHub and whatnot. That bubble packing algorithm and the API around it — the bubble packing algorithm comes from D3. The API around it is actually D3 v6, and I didn't want to build that into React Charts. It doesn't make sense. It's not a very common chart type to put in there.

React Charts is designed for really simple use cases, drop in, and it's so opinionated that I don't even supply a pie chart or a circular chart component because pie charts are the devil, if you didn't know. They're the most impossible charts to reason about and read. When you show them to people, they just get confused or make bad assumptions about their data. So you can't even do a pie chart. It's just Cartesian layout charts — bars, lines.

00:45:36 - Anthony Campolo

That's very interesting that you're making opinionated choices, not just about how the library is supposed to work, but actually how the data is going to be visualized. And I think that's probably the right way to go for someone who has that kind of experience.

So what is your experience with data visualization? Because it sounds like you've done a lot of this chart work. I'm curious how you got into the charting area specifically, because it's fairly niche.

00:46:05 - Tanner Linsley

Nozzle is definitely one of the reasons that I'm in it because we have a lot of data. We have dashboards. We need to display that data to users in a really good way, a really easy way. And I'm very conservative with the way that I think about data visualization.

I think that there are really two great ways to show data for the 90% use case, and that is a table and either a line or a bar chart. And if you have a hard time displaying your data in any of those formats, then you probably need to look at your data again, look at the way that you're designing it and wanting to show it. Obviously, that doesn't cover every great use case because it probably isn't that great to show geographical data in a bar chart or a line chart or in a table, although you can. It's obviously not a rule, but to me it's a guideline that if you can't display it in a good way in the really raw, primitive data visualization elements, then you probably need to rethink how you're doing it.

00:47:20 - Anthony Campolo

Yeah. It also gets at the type of data we want to use these things for, which is usually business data. You're comparing costs or things like that over time. You want a visual way to think about these numbers. You don't want to complicate that. You want to just turn the numbers into some visual representation that you can look at and just get. So that's why I like that idea of keeping it as simple as possible.

00:47:45 - Tanner Linsley

And that's why when you look at all these fancy chart types that you can get from charting libraries — pie charts are notoriously difficult to reason about. Radar charts, I can't even understand why radar charts exist. Polar area charts. A lot of these circular charts are just terrible in my opinion.

00:48:06 - Anthony Campolo

Yeah, it's data visualization people getting a little out of hand.

00:48:09 - Tanner Linsley

Gauges. Gauges are hard to reason about when you compare them to just a regular progress bar. You start getting into nested donut charts. What the heck? You're just going to confuse your users at the expense of looking cool. It'll look great, throw a dark mode on there with a couple of nested donut charts and wow, it looks sexy, but at the major expense of confusing everybody as to what your data is trying to convey.

00:48:45 - Anthony Campolo

Bikeshedding.

00:48:47 - Tanner Linsley

Talking about React Charts a little more, I don't know if I would say you should use React Charts, Chris. It's a little bit past the experimental stage for me right now. I could even envision rewriting React Charts internally to just use D3 v6 and become more of a drop-in, opinionated component library for charts. And then if you need an escape hatch out of it, I would just recommend D3 right there in the React Charts documentation. So you're not missing out on much other than having a really fast and succinct way to declare a chart.

00:49:31 - Christopher Burns

That's awesome to hear. I guess I've not made bad decisions in my tech stack. The technical debt has been pushed away for another day. But obviously, those are the three main packages in the stack. You could say, if it was the MCU, you have your Thor, Iron Man, and Captain America, but there's also smaller libraries in there. Some of them are and are not classed as the stack, but which ones are your favorite?

00:50:10 - Tanner Linsley

Of the smaller ones? Let's see. React Virtual is one that actually needs a spot in the stack. It needs a spot on the site. It's for virtualization, which is — wow, we could talk about virtualization for probably an hour.

00:50:35 - Anthony Campolo

Virtualizing scrollable elements specifically. When I hear virtualization, I think VMs. You're definitely not talking about VMs, right?

00:50:42 - Tanner Linsley

We're talking about windowing, DOM windowing and virtualizing scrolling. That's what React Virtual is for. It's a fantastic library. React Virtual is great. That's a fun one. And it's also headless. It's just hooks. So a lot of virtualization libraries like React Window or React Virtualized — bless Brian, I love Brian — but I like dealing with hooks more than components because I have full control over what's being rendered.

So React Virtual is a hook, basically the same concept as React Table. You just call it with some of the information that it needs, which really all it needs is how many items are in your virtualizer. What is the parent element? So you pass a ref to it for the parent element that it's going to be inside. And then you can pass it things like how to estimate the size of each element. Then it gives you back, just like React Table, this Virtualizer object. And on that Virtualizer object it has some styles and all of the virtualized items with some of their properties, like each item has an index and a size and the start range of where it is in the x-y coordinate space. It's really pretty neat. If you just go to the sample page, I can send you guys a link that you can put into the podcast.

00:52:27 - Anthony Campolo

We'll have very extensive show notes for this one. Don't worry. I'm tracking all these links we're talking about.

00:52:32 - Tanner Linsley

There's a nice little sample section there that can show you what I'm talking about. So React Virtual is really fun.

00:52:38 - Anthony Campolo

I really like that idea of thinking about hooks as the base abstraction instead of components. I'm fairly new to React, but I'm a big history buff looking back through how React has developed. We originally sold this reusable component dream that I think didn't materialize at all. Your component ended up ridiculously highly coupled to every single thing it was doing because it had all your logic in it and how it displays. People just made everything their component.

00:53:11 - Anthony Campolo

It's been about breaking up that logic into ways that are actually reusable because the components weren't reusable, but we could find the parts that were within it that we could actually factor out. And that's what we're doing with all these hooks now.

00:53:25 - Tanner Linsley

Exactly. That's exactly what it is. Components are only half the game. It's reusable components as a concept. And then you have visual, like markup components and logic components. So that's definitely the other side of the coin.

And I'm so happy to be rid of higher-order components and render props and children-as-a-function rendering. It was terrible. But yeah, React Virtual subscribes to that methodology heavily. I also have React Form, and React Form is yet another form library, but it's not one that I would actually recommend anybody use. It's mostly experimental. I use it internally. I use it in Nozzle and it does an okay job, but it's poorly documented. It's not written in TypeScript. It has funky quirks to it that only I know about, to be honest. And it has an API that's very opinionated for my opinions, but not for everybody else's.

00:54:29 - Anthony Campolo

And out of any of these things, it's one that has a lot of really other good options. There's not a ton of super awesome chart and table libraries.

00:54:37 - Tanner Linsley

I would much quicker recommend to you — I would say use Formik or use React Final Form or use React Hook Form. Those three are fantastic options and they will get you far, much further than React Form will. React Form has some unique things about it. I focus on asynchronous, debounced validation strategies, which is not something you hear about in other form libraries.

00:55:10 - Anthony Campolo

What is that like? We like to go deep here, so why don't you actually define that term?

00:55:15 - Christopher Burns

Give us an example.

00:55:16 - Tanner Linsley

Most form libraries don't even mess around with async validation because it changes everything about the way that you send events up and down your tree. There's a lot of communication between different nested child components and hooks to parent form hooks when you're in a form library. Erik Rasmussen has React Final Form, which is actually built on Final Form JS, and that's all Final Form is. It's this awesome abstraction over event emitters and receptors for forms, and that's really a lot of what forms are.

When you turn those into asynchronous emitters and receptors, it really complicates things. Formik is, to my knowledge, ripping out the async functionality because of this, because it's just not worth it. So one use case would be if you want to handle validation for a username field. You're going to have various levels of validation. The first one is synchronous. Is there anything written in the text field at all? And that you can return immediately synchronously.

The next step is you're going to make sure that it's at least three characters long, which is also something you could do synchronously inside of the library. Is it three characters? No. Okay, return an error. If it passes those tests, then you might want to put it into a pending state and check server-side to see if the username is available. So it passes all those tests. They've typed in four characters, and you put it into a pending state and now it's asynchronous validation. You're checking the back end to see if the username is available and if it's okay. That works great.

But then what if the user keeps typing? Are you firing off five or six requests to the back end to see if the username is valid? So then you have to start thinking about debouncing. Debouncing that input outside of the form can get a little funky. You have to listen to that form state outside of your form. You have to debounce the asynchronous effect going out.

So that's the use case that's built into React Form — you can synchronously validate and then, if everything passes, you can asynchronously debounce further validations to the back end and have it all come back and resolve. It's a huge edge case. Probably not a lot of people experience it, and the entire library is architected in a special way for that one edge case, which is one of the reasons why it's not the best option out there.

00:58:18 - Christopher Burns

My other library that I have used, I think, is React Range.

00:58:30 - Tanner Linsley

React Ranger?

00:58:32 - Christopher Burns

Ranger. That's it.

00:58:34 - Tanner Linsley

React Ranger. I only have one more after this that I'd really want to talk about. But React Ranger is fun. Again, it's a headless hook. It's a lot of fun to use, but it's built for building range sliders. So you can do single-button range sliders. You can do multi-button range sliders for creating range boundaries.

00:59:04 - Anthony Campolo

And this, for people who aren't seeing this right now, I'm looking at it and it's basically like a volume control on YouTube. That's kind of what we're talking about.

00:59:11 - Tanner Linsley

Like a volume control, or if you want to say numbers between 20 and 80, you could put two sliders, two buttons on a slider, and they could drag the minimum and maximum.

00:59:23 - Christopher Burns

My favorite one is when a company does quantity-based pricing and they go, oh, this is how much it is as the scale goes upwards. So you pull the range value up and down. That can actually be quite complicated to code.

00:59:46 - Tanner Linsley

Yep. And this is trying to solve that issue. There's a single hook called — I think it's just useRange. No, it's useRanger. So you just say useRanger and you pass it your values, an onChange handler, min, max, step size, that type of thing. And it gives you back, again, just an object with a bunch of helpers on it.

It gives you back the handles object, which is the data model for how many handles you should be displaying. Each of those has prop getters on them. There's a getTrackProps. So you build your track and then you put the track props on it. It kind of just handles all that for you. It's really pretty nice. It's one that's easy for me to forget that I have, but I really do like it. It's only got 213 stars. I don't market it very much because it's not a massive use case, but it is a lot of fun.

01:00:40 - Christopher Burns

So what's the final library?

01:00:42 - Tanner Linsley

Final library is a really small one, and I don't know really anybody that even uses it, but it's called Swimmer.

01:00:50 - Christopher Burns

I looked this one up earlier. Interesting.

01:00:56 - Tanner Linsley

It is interesting. Basically it's a pooling or throttling utility for asynchronous tasks. And there are a lot of libraries out there that have similar functionality, like RxJS is one of them, that can do a lot of this type of stuff.

But I just needed something where, okay, I have a variable number of asynchronous tasks and I need to either schedule them or pool them all and say, okay, I got 100 tasks. I only want you to do five at a time. So you can set your concurrency limit to five.

01:01:41 - Anthony Campolo

It's a load balancer. It's a load balancer for your front end.

01:01:44 - Tanner Linsley

Yeah, it's a load balancer for asynchronous functions. And you can do it in two ways. There's a poolAll function where you can just say, okay, here's all of the tasks that I have. I want you to load-balance them all. And here's my concurrency limit. When it gets done, tell me about it. Give me the results of all of those functions. You can listen to events for tasks as they complete, handle them as they complete. Or you can just wait until they're all done. That's the poolAll function.

Then there is a second, lower-level pooling utility where you just create a pool. A pool is just like a queue. It's a task queue where it's always running and always listening. So you can say, here's a task queue and its concurrency is five. And then after you've created that pool, you can just start giving it new requests. So you can be like, okay, add 50, and then a couple seconds later, add 100 more, add whatever, and it's going to just keep managing that concurrency and notify you as those get done.

01:03:03 - Christopher Burns

You've had the TypeScript port waiting in your issues now for three years.

01:03:10 - Tanner Linsley

I think I finally have the skills to make this happen.

01:03:15 - Anthony Campolo

To pay those bills.

01:03:17 - Christopher Burns

To finish it up. So you could say this has been a whistle-stop tour of the TanStack. It's super interesting, but you dogfood all of these libraries. And if you don't know what dogfooding means, it's where you're using it yourself to build and also do other things with them as you do in your startup. So what is Nozzle?

01:03:43 - Tanner Linsley

Nozzle is six years old, I think. When we started it, our goal, which still remains the same to this day, is to basically reverse engineer Google search results. I know that's a mouthful, but what that means is that in every single company that's worried about marketing out there these days, you're going to have a team of SEOs, search engine optimizers. These are people who all they care about is writing content and making that content rank them higher on Google or Twitter or wherever.

And part of that marketing team's toolset is something called a rank tracker. So if I was — let's use TanStack as an example — I want to market TanStack. And so I have like 65 keywords in Google that I care about, 65 phrases. It could be like "React Query," "React Query vs," "best data fetching library." There's just a bunch of these phrases that when somebody types those phrases into Google, I want my stuff to show up. And I want to be higher than everybody else. So I could take those phrases, put them into Nozzle, and Nozzle will then go and record or harvest all the data from those search engine results either hourly, daily, monthly. We can do it however often you need, and take all of the data from that search engine result page and turn it into numbers and measurements and metrics.

We take all that data, stuff it into a massive database in Google BigQuery, and then we do a ton of data visualization with it and give you unlimited access to it. So a good example is I'm tracking these 65 keywords for TanStack. I can go into the React Query keyword and I can see who is ranking the highest for that specific keyword. I can see who my competitors are in Google, where they're ranking, why they're ranking high. Maybe it's because they have a video result. Maybe it's because they have a new blog post that pushed them up five or six ranks or whatever.

And we have metrics and data that not a lot of other rank trackers have. We have down-to-the-pixel measurement. Think about when you type something into Google and you're looking down all the results on the page. When you click on one of those results, that single result, we know how many pixels from the top of the screen and the left of the screen that result is. It's very granular.

We have thousands of terabytes of this data that we gather every day and we visualize and provide back to these companies. I'm trying to think of some notable companies that use Nozzle. I know there's people in the music industry. Maybe one of Apple, Spotify, or Google uses us to track where they're ranking for the top 100 Billboard songs when you type them into Google. Certain home improvement stores that you might have heard of from the United States use Nozzle to make sure that they're ranking high when you search "bathroom remodel" or something like that.

So that's what Nozzle does. And I know that's pretty specific and pretty niche, but it's come with a lot of challenges with managing large amounts of data, administrating that data. You've got React Query displaying that data. You've got React Table and React Charts. And so yeah, it's been the biggest reason for a lot of my open source projects.

01:08:04 - Christopher Burns

Could you say that you use Nozzle to gain insight on what next area needs rebuilding in the stack?

01:08:14 - Tanner Linsley

Yeah, absolutely. In my personal priority list, Nozzle comes first. It's my primary source of income. It's my startup, together with my other two co-founders. It's our baby. And if Nozzle needs, Nozzle gets.

So if, for whatever reason, tomorrow I wake up and React Form is no longer cutting it for Nozzle, I'm either going to rewrite it or I'm going to ditch it and build something else because I want it to be the best for Nozzle. Same thing with React Query. If tomorrow I wake up and all of a sudden React Query is no longer the best solution for Nozzle, you better believe that I'm going to either build that better solution or move on. And I think that's one of the things that makes my open source libraries a little different. I'm not just building them for open source proliferation.

01:09:17 - Anthony Campolo

You have a really interesting model, because the way I think about it, you're doing open core inside out. Open core is usually you have this core that's open source and you build services on top, but you have a core of a service that you're building open source libraries around. So it's inverting the model entirely. The product is what drives the open source instead of the other way around. And yeah, it's really cool. I don't know many other people who are doing it.

01:09:46 - Tanner Linsley

Yeah, that's a really good insight into what I'm doing. And whether it's with Nozzle or with something else in the future, that's probably how I'm going to keep doing things. I'm a firm believer in if you root yourself in an industry or in a problem deep enough, you are going to eventually find out how to solve it, how best to solve it, and you're going to want to share that idea with other people.

So by rooting myself into the problems of Nozzle, I've discovered the needs of many other SaaS products like Nozzle. And those open source libraries have definitely come from that problem space. We could sell Nozzle someday, and that's probably the best reason to explain why they're different.

I've been asked, why aren't these open source libraries part of Nozzle? It's like, well, because if I leave Nozzle, the open source libraries are going to need a maintainer. And it's me that's unique. Mostly because I'm a co-founder, and I feel bad using my situation as a cookie-cutter example for other people to follow because it's a unique position. If I were an employee at a company building open source software, I likely wouldn't be able to own it myself. It would probably be owned by the company. It's just because I am a co-founder and my other co-founders are okay with it.

I own the open source that I build while I'm doing Nozzle, and that's okay. And that's another big reason why I'm able to build all my open source, because I have this awesome startup that is doing well and sustaining me, and allowing me to not have to stress about open source to feed my family. That's a big difference. People who want to do open source full-time want to build open source so that they can get the most money out of it as possible.

I like getting money out of my open source. I like earning a side income on it. It keeps me motivated, keeps me honest, and wanting to make the libraries better. But I am very, very glad that it is not my primary source of income. Maybe it could be someday. And I'm trying to use that position of privilege to make the open source career space better. It's not do or die for me if open source sustains me or not. And that also makes me a great candidate to try out new things, to take big risks in using GitHub Sponsors, or to take big risks in building a whole new library that may or may not be the best library out there.

01:12:49 - Anthony Campolo

I bet there's a lot of people who've been listening to this episode and thinking, I'm so glad there's someone out there thinking about these problems because who has time to think about async debouncing in their forms? Not a lot of people. So I think a lot of people are very thankful that you have found this way to channel all of this work and all this thought into these libraries.

01:13:10 - Tanner Linsley

Yeah. And I guess continuing that thought process a little bit, these libraries are also part of my personal brand. My personal brand is, to me and should be to everybody, bigger than your company that you work for. It's bigger than the startup that you have, or it's bigger than whatever side project you're working on. Your personal brand follows you everywhere for the rest of your life.

And by working on these open source projects and maintaining them and making them as good as I can, that's a testament to my personal brand. So that's another big reason why I do it.

01:13:53 - Christopher Burns

As an entrepreneur myself, a tech founder too, what you say really does resonate. And when you're a founder, it puts you in a privileged position to stand out more, bring personality to a company, as you say. It's quite easy to look at this and think, Airbnb built that. But how many people in Airbnb really built it? Airbnb didn't build it. They paid people to do it.

So it's a really interesting perspective on how we can keep open source going, because open source is a marathon. At the end of the day, it's not a sprint.

01:14:41 - Tanner Linsley

No. And I think you just reminded me of another interesting angle I have on it, which is that my open source libraries have allowed me to get to know a lot of people on the internet. They've allowed me to increase my reach and my network.

Basically, I know so many people on Twitter now, so many developers through GitHub because of my open source work. And that reach is something that I've been able to put to good use for everything that I do — for my startup, for reaching out to companies who love my open source software, who also would probably really need Nozzle, or finding companies who need very specific help and support for my projects. I'm able to help them out personally and make a little bit of side cash here and there, and that's been really fun too. And it's helped me explore new avenues of career path.

Like, I built a course for React Query. I can tell you firmly that I do not want to be an educator. I don't want to build courses anymore. But I did it.

01:16:08 - Anthony Campolo

Built a specific skill set.

01:16:10 - Tanner Linsley

And I'm going to leave that to other people to do because it's just not something that I particularly enjoy. But it allowed me to do that and give it a good run.

01:16:23 - Anthony Campolo

Closing out here, Chris, anything you want to add? We should probably get his connects and all that. We're about out of time here.

01:16:32 - Christopher Burns

I obviously have so much that I can talk about and we share a lot of favorite libraries. But as a closing statement, what other libraries do you use in Nozzle? Obviously, Twin.macro is one of them. What else is in there?

01:16:50 - Tanner Linsley

I love Twin.macro because I love CSS-in-JS and I love Tailwind, so why not put them together? Some other tools that I use — let's shout out some more tools. Nobody can live without Prettier, so let's just put that in there. I use Zustand here and there. I think it's fun. It's kind of like Redux, but without using Redux.

I have a secret library I built called UseSelect, and it's basically like Downshift, but it's built just for hooks. So it's just a hook. This would be if Downshift were converted to hooks. That one's fun. Don't tell anybody about it because it's poorly documented. TinyColor2 is really fun for color manipulations.

What else do I use? I use React Router. React Router is great. I'm actually using React Router version six, the beta. Also not very documented, but easy enough to figure out. I really like React Router. React Icons is a great icon component library.

I think I'm using Pusher for WebSocket notifications. I use Next. I love Next. Next is great. I would use that instead of Create React App, to be honest. In fact, I do.

01:18:21 - Tanner Linsley

Nozzle is a full SPA. There's no server-side routing, but I still use Next and I trick it into thinking that it's Create React App. I'm not afraid to say it.

I use Moment because it's just got some things that haven't been replaced yet. I use Match Sorter from Kent C. Dodds. Everybody should use Match Sorter. It does what it says. It's for matching and sorting text. It's awesome.

I wish I didn't use Firebase for authentication, but I do because it scales really well for cheap. And I think that's it. We use Stripe. I use Reach for some utility stuff, like Reach's observe rect and window size components. But that's basically everything that I use in Nozzle. Everything else is homegrown.

01:19:18 - Christopher Burns

Wow. This has been an amazing episode. Thank you so much for coming on. I've been literally looking forward to this for ages, and we had to reschedule it as it was on a moving day for me, but I'm so glad we could get to it finally.

01:19:37 - Tanner Linsley

It's been fun.

01:19:38 - Anthony Campolo

Thanks, Tanner. Thanks for all the work you've done and all these amazing libraries. Thanks for coming on and walking us through them. We're definitely really excited for all this stuff, and we're incorporating as much of it into our own workflows as we can because we see a lot of benefit from these tools.

01:19:55 - Tanner Linsley

You bet.

01:19:57 - Christopher Burns

Thank you. And that's it.

01:20:01 - Tanner Linsley

See you guys. Later.

On this pageJump to section