Skip to content

State Management with Apollo Client (Reactive Variables)

This article was written over 18 months ago and may contain information that is out of date. Some content may be relevant but please refer to the relevant official documentation or available resources for the latest information.

Introduction

State management is one of the most common problems for frontend developers and mobile developers. Most of the new frameworks come with a built-in state management system like Vuex and Pinia for Vue, and Svelte Store for Svelte. But because of the lack of state management in the ecosystem of React (Also because React is a library, not a framework), developers have to use variance state management systems like Redux and XState. But the problem is that the state management system is not easy to use, and they usually add more code to the final build bundle. Most of projects nowadays use GraphQL, and the most popular tool for GraphQL is Apollo. But did you know that you can use Apollo itself as a state management library for your project instead of adding more code to the final build bundle? That's what we are going to do in this tutorial.

What is GraphQL?

GraphQL is a query language for APIs, and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.

Apollo Client

Apollo Client is a client for the Apollo GraphQL server. It is a library that allows you to easily integrate GraphQL queries and mutations into your React application. It is a state management system for your React application, and it has a lot of state management features behind the senses that you can use to manage your application state as an alternative to Redux and XState.

Reactive Variables

Reactive variables are new features that came with Apollo Client 3. From the documentation:

"Reactive variables are a useful mechanism for representing local state outside of the Apollo Client cache."

And their power comes from the fact that they are separated from the cache and you can use them to store any kind of data type or structure.

Create your first Reactive variable in Apollo

Create a reactive variable with the makeVar method, like so:

import { makeVar } from '@apollo/client';

const isDarkModeVar = makeVar(false);

and then you can use isDarkModeVar() like so:

console.log(isDarkModeVar());
// Output: false

isDarkModeVar(true);

console.log(isDarkModeVar());
// Output: true

Also you can use useReactiveVar hook to get the value of the variable inside React components:

import { useReactiveVar } from '@apollo/client';
import { isDarkModeVar } from './variables/isDarkMode.js';

export function HomePage() {
  const isDarkMode = useReactiveVar(isDarkModeVar);

  return (
    <div>
      <h1>
        {isDarkMode ? 'Dark Mode' : 'Light Mode'}
      </h1>
    </div>
  );
}

Reactive components in the Queries

For more advanced use, you can use the reactive variables in the Apollo client in the GraphQL queries as well, and update it automatically, for example:

Let’s say you want to fetch a list of products, and want to save them.

export const GET_PRODUCT_ITEMS = gql`
  query GetProductItems {
    productItems @client
  }
`;

And now we should initialize a reactive variable productItemsVar for example:

import { makeVar } from '@apollo/client';

export const productItemsVar = makeVar([]);

In cache.js let’s do this:

import { InMemoryCache } from '@apollo/client';
import { productItemsVar } from './variables/productItemsVar.js';

export const cache = new InMemoryCache({
  typePolicies: {
    Query: {
      fields: {
        productItems: {
          read() {
            return productItemsVar();
          }
        }
      }
    }
  }
});

And now we can use productItemsVar instead of querying it from the APIs each time.

Wrapping Up

Apollo Reactive variables are great features in Apollo 3+, and you can save a lot of work by using them instead of Redux and XState, especially if you are already using Apollo client and GraphQL in your project. Also, its power comes from its simplicity. Just create a global state variable by one line of code.

This Dot is a consultancy dedicated to guiding companies through their modernization and digital transformation journeys. Specializing in replatforming, modernizing, and launching new initiatives, we stand out by taking true ownership of your engineering projects.

We love helping teams with projects that have missed their deadlines or helping keep your strategic digital initiatives on course. Check out our case studies and our clients that trust us with their engineering.

You might also like

React Conf 2024 Review cover image

React Conf 2024 Review

A lot has happened since the last React Conf, so 2024 was action-packed. The conference lasted two days in Las Vegas and was focused on all the new stuff coming with React v19. Day one was centered around React 19/Server Components, and day two was React Native. There were some exciting reveals, fantastic talks, and demos. This post will cover the conference's big news, specifically the main keynotes from days 1 and 2. Day 1 Keynote The main keynote included several speakers from the React team, and early in the talk they mentioned a goal of “Make it easy for anyone to build great user experiences.” This sounds great but I was curious to see exactly what they meant by this since it seems like React has arguably gotten more complex with the introduction of Server Components and related API’s. The keynote was split into 3 sections: State of React - Lauren Tan Lauren shared the staggering adoption React has received over the years and thanked all the contributors and the community. She also highlighted the amazing ecosystem of packages, tools, and frameworks. It’s hard to argue against these points, but React seems to be in a weird in-between state. The framework is headed in this new direction but doesn’t feel quite there yet. React 19 A few team members collaborated on a walkthrough of React 19, which included all the new features and a bunch of quality-of-life improvements. The main highlights were: * Server Rendering & Suspense - One of the coolest bits was showing how to co-locate and load stylesheets and other assets in components. “HTML Composition for Bundlers, Libraries, & Apps” * Hydration - This was a fan favorite, and the TL;DR is fewer hydration errors with better messages and debuggability. * Actions - This is one of my favorite new features, as it feels like something that was missing from the React toolkit for a long time. The new Actions API is a “First-class pattern for asynchronous data updates in response to user input”. It includes Remix inspired form behaviors and API’s that replace a lot of boilerplate for handling async operations like loading and error states. * JSX Improvements One of the most applauded announcements was the changes to refs, which are now automatically passed to every component 🙌 React Compiler !! Yes, we’ve all heard the news by now. They are finally open sourcing the compiler that they teased over 2 years ago. React compiler understands React code/rules and is able to automatically optimize/memoize our application code. One of the big complaints of React has long been that there’s a lot of mental overhead and thought that needs to be put into optimizing and building fast components. So, the goal is to remove that burden from the developers so that they can focus on writing their business logic. One of the slides mentioned that the compiler has the following behaviors: Dead Code Elimination, Constant Propagation, Type Inference, Alias Analysis, Mutable Lifetime Inference, HIR, and Diagnostics. I’m not going to pretend to know all those things, but I’ve already heard a lot of positive feedback about how it works and how advanced it is. The team mentioned that they are already running the compiler in production on Instagram.com and some of their other websites. Day 2 Keynote Day 2 opened with sharing the incredible growth in downloads and adoptions that React Native has had (78M downloads in 2023). They shared some meta info about the recent releases and talked about all the partners helping to push the framework forward. They mentioned the Meta investment in React Native and shared about some of the ways that they are using it internally in places like Facebook Marketplace. Next they shared about all the work that Microsoft is doing with React Native for things like supporting building Windows apps with React Native. I was surprised to learn that the Windows Start Bar is a React Native app 🤯. New Architecture "Rewrite React Native to be synchronous so it’s possible to be concurrent" I was surprised when I read this since these two things seem to conflict with one another. The new architecture allows for the concurrent mode in React. This section opened by describing the original architecture of React Native (JavaScript code with a bridge for passing information back and forth from the native layer) The new architecture replaces “the bridge” with a concept called “JSI (JavaScript Interface)”. Instead of message passing, it uses memory sharing between the JS and Native layers. The big announcement was that the new architecture has been moved into Beta and they have released a bunch of things to help with compatibility between the old and new architecture to minimize breakages when upgrading. Production Ready Apps The section opened by talking about all the complexities and pieces involved with shipping a high-quality app to Android and iOS. If you’re a mobile developer, you know it’s quite a lot. They highlighted a new section of the documentation that provides details about “React Native Frameworks”. They are recommending using a “Framework” with React Native, just like they are recommending for React on the web. I had never even considered the idea of a “React Native Framework” before, so I thought that was pretty surprising. I guess it makes sense that Expo is a Framework but I’m not aware of any others like it. So the recommendation from the React Native documentation is: “Use Expo”. Which leads to the next part of the keynote…. Expo This part of the talk went more in-depth on a lot of the complexities with mobile development that Expo helps you solve. They highlighted an amazing suite of modules that they have dubbed the “Expo SDK”. The SDK consists of well-defined modules that handle some type of integration with the underlying Native platforms. They also have a Modules API to help build your own Native modules. Another new feature that I wasn’t aware of is API routes with Expo Router. How does that even work??? The next thing they mentioned was a feature called CNG (Continuous Native Generation). I’ve played with this a bit and it’s pretty nice. Expo will generate the iOS and Android projects/code that is needed to build the final application. The sales pitch is that this will help with upgrades if you aren’t managing and changing your native code by yourself. If you’ve upgraded a React Native app before you know how painful this can be. I am interested to see if CNG is practical in the real world. In my experience most projects have ended up ejected from expo and require managing your own native code. This section wrapped up with talking about builds and deployments with Expo Application Services (EAS). I don’t have experience with EAS yet personally but builds and deployments have always been the biggest pain point in my experience. I would definitely give it a try on my next project. As someone who has worked with React Native and Expo quite a bit over the years, it truly has come a really long way. Expo is amazing. Summary There were a ton of really exciting announcements and content just between the two keynotes. My impression from the conference is that a new era of React is kicking off and it’s really exciting. I’d hate to be one of the people betting against React as they’ve shown time and time again that they are willing to take risks and innovate in this space. It’s going to be a busy year in the React ecosystem as more frameworks adopt Server Components and more applications give it a try. There were a bunch of other really incredible talks in the conference and I’m really excited to write about a couple of them in particular. I wish I could have attended in person but the live stream didn’t disappoint!...

Why is My React Reducer Called Twice and What the Heck is a Pure Function? cover image

Why is My React Reducer Called Twice and What the Heck is a Pure Function?

Why is My React Reducer Called Twice and What the Heck is a Pure Function? In a recent project, we encountered an interesting issue: our React reducer was dispatching twice, producing incorrect values, such as incrementing a number in increments of two. We hopped on a pairing session and started debugging. Eventually, we got to the root of the problem and learned the importance of pure functions in functional programming. This article will explain why our reducer was being dispatched twice, what pure functions are, and how React's strict mode helped us identify a bug in our code. The Issue We noticed that our useReducer hook was causing the reducer function to be called twice for every action dispatched. Initially, we were confused about this behavior and thought it might be a bug in React. Additionally, we had one of the dispatches inside a useEffect, which caused it to be called twice due to React strict mode, effectively firing the reducer four times and further complicating our debugging process. However, we knew that React's strict mode caused useEffect to be called twice, so it didn't take very long to realize that the issue was not with React but with how we had implemented our reducer function. React Strict Mode React's strict mode is a tool for highlighting potential problems in an application. It intentionally double-invokes specific lifecycle methods and hooks (like useReducer and useEffect) to help developers identify side effects. This behavior exposed our issue, as we had reducers that were not pure functions. What is a Pure Function? A pure function is a function that: - Is deterministic: Given the same input, always returns the same output. - Does Not Have Side Effects: Does not alter any external state or have observable interactions with the outside world. In the context of a reducer, this means the function should not: - Modify its arguments - Perform any I/O operations (like network requests or logging) - Generate random numbers - Depend on any external state Pure functions are predictable and testable. They help prevent bugs and make code easier to reason about. In the context of React, pure functions are essential for reducers because they ensure that the state transitions are predictable and consistent. The Root Cause: Impure Reducers Our reducers were not pure functions. They were altering external state and had side effects, which caused inconsistent behavior when React's strict mode double-invoked them. This led to unexpected results and made debugging more difficult. The Solution: Make Reducers Pure To resolve this issue, we refactored our reducers to ensure they were pure functions. Here's an extended example of how we transformed an impure reducer into a pure one in a more complex scenario involving a task management application. Let's start with the initial state and action types: ` And here's the impure reducer similar to what we had initially: ` This reducer is impure because it directly modifies the state object, which is a side effect. To make it pure, we must create a new state object for every action and return it without modifying the original state. Here's the refactored pure reducer: ` Key Changes: - Direct State Modification: In the impure reducer, the state is directly modified (e.g., state.tasks.push(action.payload)). This causes side effects and violates the principles of pure functions. - Side Effects: The impure reducer included side effects such as logging and direct state changes. The pure reducer eliminates these side effects, ensuring consistent and predictable behavior. I've created an interactive example to demonstrate the difference between impure and pure reducers in a React application. Despite the RESET_TASKS action being implemented similarly in both reducers, you'll notice that the impure reducer does not reset the tasks correctly. This problem happens because the impure reducer directly modifies the state, leading to unexpected behavior. Check out the embedded StackBlitz example below: Conclusion Our experience with the reducer dispatching twice was a valuable lesson in the importance of pure functions in React. Thanks to React's strict mode, we identified and fixed impure reducers, leading to more predictable and maintainable code. If you encounter similar issues, ensure your reducers are pure functions and leverage React strict mode to catch potential problems early in development. By embracing functional programming principles, you can write cleaner, more reliable code that is easier to debug and maintain....

How to Truncate Strings Easily with CSS cover image

How to Truncate Strings Easily with CSS

You'll often need to truncate text when working with user interfaces, especially when displaying content within a limited space. CSS provides a straightforward way to handle this scenario, ensuring that long text strings are cut off gracefully without affecting the overall layout. CSS Truncation Techniques Single-Line Truncation If you want to truncate a single line of text, CSS provides a simple solution. The key properties to use here are overflow, white-space, and text-overflow. ` Explanation of properties: * white-space: nowrap: This ensures the text stays on a single line, preventing wrapping. * overflow: hidden: This hides any content that overflows the container. * text-overflow: ellipsis: This adds the ellipsis (…) at the end of the truncated text. Multi-Line Truncation While truncating a single line of text is common, sometimes you may want to display multiple lines but still cut off the text when it exceeds a certain number of lines. You can use a combination of CSS properties such as -webkit-line-clamp along with the display and overflow properties. ` Live example: Explanation of properties: * display: -webkit-box: This is a legacy flexbox-like display property that works with the -webkit-line-clamp property. * -webkit-line-clamp: Specifies the number of lines to show before truncating. * -webkit-box-orient: vertical: Ensures the box is laid out vertically for proper multi-line truncation. * overflow: hidden: Prevents content overflow. * text-overflow: ellipsis: Adds an ellipsis after the truncated content. Why Use CSS for Text Truncation Over JavaScript Techniques? While it’s possible to truncate text using JavaScript, CSS is often a better choice for this task for several reasons. Let's explore why CSS-based truncation techniques are generally preferred over JavaScript. Performance Efficiency CSS operates directly within the browser's layout engine, meaning it doesn’t require additional processing or event handling as JavaScript does. When using JavaScript to truncate text, the script needs to run on page load (or after DOM manipulation), and sometimes, it needs to listen for events such as window resizing to adjust truncation. This can introduce unnecessary overhead, especially in complex or resource-constrained environments like mobile devices. CSS, on the other hand, is declarative. Once applied, it allows the browser to handle text rendering without any further execution or processing. This leads to faster load times and a smoother user experience. Simplicity and Maintainability CSS solutions are much simpler to implement and maintain than their JavaScript counterparts. All it takes is a few lines of CSS to implement truncation. In contrast, a JavaScript solution would require you to write and maintain a function that manually trims strings, inserts ellipses, and re-adjusts the text whenever the window is resized. Here's the JavaScript Truncation Example to compare the complexity: JavaScript Truncation Example: ` At the example above, we truncated the text to 50 characters which may be 1 line on large screens and 6 lines on mobile and in that case we will need to add more code to truncate it responsively. As you can see, the CSS solution we used earlier is more concise and readable, whereas the JavaScript version is more verbose and requires managing the string length manually. Responsiveness Without Extra Code With CSS, truncation can adapt automatically to different screen sizes and layouts. You can use relative units (like percentages or vw/vh), media queries, or flexbox/grid properties to ensure the text truncates appropriately in various contexts. If you were using JavaScript, you’d need to write additional logic to detect changes in the viewport size and update the truncation manually. This would likely involve adding event listeners for window resize events, which can degrade performance and lead to more complex code. CSS Example for Responsive Truncation: ` To achieve this in JavaScript, you’d need to add more code to handle the width adjustments dynamically, making it more complex to maintain and troubleshoot. Separation of Concerns CSS handles the presentation layer of your website, while JavaScript should focus on dynamic functionality or data manipulation. By keeping truncation logic within your CSS, you're adhering to the principle of separation of concerns, where each layer of your web application has a clear, well-defined role. Using JavaScript for visual tasks like truncation mixes these concerns, making your codebase harder to maintain, debug, and scale. CSS is purpose-built for layout and visual control, and truncation is naturally a part of that domain. Browser Support and Cross-Browser Consistency Modern CSS properties like text-overflow and -webkit-line-clamp are widely supported across all major browsers. This means that CSS solutions for truncation are generally consistent and reliable. JavaScript solutions, on the other hand, may behave differently depending on the browser environment and require additional testing and handling for cross-browser compatibility. While older browsers may not support specific CSS truncation techniques (e.g., multi-line truncation), fallback options (like single-line truncation) can be easily managed through CSS alone. With JavaScript, more complex logic might be required to handle such situations. Reduced Risk of Layout Shifting JavaScript-based text truncation risks causing layout shifting, especially during initial page loads or window resizes. The browser may need to recalculate the layout multiple times, leading to content flashing or jumpy behavior as text truncation is applied. CSS-based truncation is applied as part of the browser’s natural rendering flow, eliminating this risk and ensuring a smoother experience for the user. Conclusion CSS is the optimal solution for truncating text in most cases due to its simplicity, efficiency, and responsiveness. It leverages the power of the browser’s rendering engine, avoids the overhead of JavaScript, and keeps your code clean and maintainable. While JavaScript truncation has its use cases, CSS should always be your go-to solution for truncating strings, especially in static or predictable layouts. If you like this post, check out the other CSS posts on our blog!...

Vercel BotID: The Invisible Bot Protection You Needed cover image

Vercel BotID: The Invisible Bot Protection You Needed

Nowadays, bots do not act like “bots”. They can execute JavaScript, solve CAPTCHAs, and navigate as real users. Traditional defenses often fail to meet expectations or frustrate genuine users. That’s why Vercel created BotID, an invisible CAPTCHA that has real-time protections against sophisticated bots that help you protect your critical endpoints. In this blog post, we will explore why you should care about this new tool, how to set it up, its use cases, and some key considerations to take into account. We will be using Next.js for our examples, but please note that this tool is not tied to this framework alone; the only requirement is that your app is deployed and running on Vercel. Why Should You Care? Think about these scenarios: - Checkout flows are overwhelmed by scalpers - Signup forms inundated with fake registrations - API endpoints draining resources with malicious requests They all impact you and your users in a negative way. For example, when bots flood your checkout page, real customers are unable to complete their purchases, resulting in your business losing money and damaging customer trust. Fake signups clutter the app, slowing things down and making user data unreliable. When someone deliberately overloads your app’s API, it can crash or become unusable, making users angry and creating a significant issue for you, the owner. BotID automatically detects and filters bots attempting to perform any of the above actions without interfering with real users. How does it work? A lightweight first-party script quickly gathers a high set of browser & environment signals (this takes ~30ms, really fast so no worry about performance issues), packages them into an opaque token, and sends that token with protected requests via the rewritten challenge/proxy path + header; Vercel’s edge scores it, attaches a verdict, and checkBotId() function simply reads that verdict so your code can allow or block. We will see how this is implemented in a second! But first, let’s get started. Getting Started in Minutes 1. Install the SDK: ` 1. Configure redirects Wrap your next.config.ts with BotID’s helper. This sets up the right rewrites so BotID can do its job (and not get blocked by ad blockers, extensions, etc.): ` 2. Integrate the client on public-facing pages (where BotID runs checks): Declare which routes are protected so BotID can attach special headers when a real user triggers those routes. We need to create instrumentation-client.ts (place it in the root of your application or inside a src folder) and initialize BotID once: ` instrumentation-client.ts runs before the app hydrates, so it’s a perfect place for a global setup! If we have an inferior Next.js version than 15.3, then we would need to use a different approach. We need to render the React component inside the pages or layouts you want to protect, specifying the protected routes: ` 3. Verify requests on your server or API: ` - NOTE: checkBotId() will fail if the route wasn’t listed on the client, because the client is what attaches the special headers that let the edge classify the request! You’re all set - your routes are now protected! In development, checkBotId() function will always return isBot = false so you can build without friction. To disable this, you can override the options for development: ` What happens on a failed check? In our example above, if the check failed, we return a 403, but it is mostly up to you what to do in this case; the most common approaches for this scenario are: - Hard block with a 403 for obviously automated traffic (just what we did in the example above) - Soft fail (generic error/“try again”) when you want to be cautious. - Step-up (require login, email verification, or other business logic). Remember, although rare, false positives can occur, so it’s up to you to determine how you want to balance your fail strategy between security, UX, telemetry, and attacker behavior. checkBotId() So far, we have seen how to use the property isBot from checkBotId(), but there are a few more properties that you can leverage from it. There are: isHuman (boolean): true when BotID classifies the request as a real human session (i.e., a clear “pass”). BotID is designed to return an unambiguous yes/no, so you can gate actions easily. isBot (boolean): We already saw this one. It will be true when the request is classified as automated traffic. isVerifiedBot (boolean): Here comes a less obvious property. Vercel maintains and continuously updates a comprehensive directory of known legitimate bots from across the internet. This directory is regularly updated to include new legitimate services as they emerge. This could be helpful for allowlists or custom logic per bot. We will see an example in a sec. verifiedBotName? (string): The name for the specific verified bot (e.g., “claude-user”). verifiedBotCategory? (string): The type of the verified bot (e.g., “webhook”, “advertising”, “ai_assistant”). bypassed (boolean): it is true if the request skipped BotID check due to a configured Firewall bypass (custom or system). You could use this flag to avoid taking bot-based actions when you’ve explicitly bypassed protection. Handling Verified Bots - NOTE: Handling verified bots is available in botid@1.5.0 and above. It might be the case that you don’t want to block some verified bots because they are not causing damage to you or your users, as it can sometimes be the case for AI-related bots that fetch your site to give information to a user. We can use the properties related to verified bots from checkBotId() to handle these scenarios: ` Choosing your BotID mode When leveraging BotID, you can choose between 2 modes: - Basic Mode: Instant session-based protection, available for all Vercel plans. - Deep Analysis Mode: Enhanced Kasada-powered detection, only available for Pro and Enterprise plan users. Using this mode, you will leverage a more advanced detection and will block the hardest to catch bots To specify the mode you want, you must do so in both the client and the server. This is important because if either of the two does not match, the verification will fail! ` Conclusion Stop chasing bots - let BotID handle them for you! Bots are and will get smarter and more sophisticated. BotID gives you a simple way to push back without slowing your customers down. It is simple to install, customize, and use. Stronger protection equals fewer headaches. Add BotID, ship with confidence, and let the bots trample into a wall without knowing what’s going on....

Let's innovate together!

We're ready to be your trusted technical partners in your digital innovation journey.

Whether it's modernization or custom software solutions, our team of experts can guide you through best practices and how to build scalable, performant software that lasts.

Prefer email? hi@thisdot.co