<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Kevin Simons</title>
    <description>The latest articles on Forem by Kevin Simons (@ksimons).</description>
    <link>https://forem.com/ksimons</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F941649%2Fa5071ce7-2cd9-4a8a-b922-8d6e579fa1fc.jpeg</url>
      <title>Forem: Kevin Simons</title>
      <link>https://forem.com/ksimons</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/ksimons"/>
    <language>en</language>
    <item>
      <title>Fixing a 3 second lockup in our app by switching from Apollo Client to URQL</title>
      <dc:creator>Kevin Simons</dc:creator>
      <pubDate>Thu, 09 Feb 2023 09:16:53 +0000</pubDate>
      <link>https://forem.com/ksimons/fixing-a-3-second-lockup-in-our-app-by-switching-from-apollo-client-to-urql-39j6</link>
      <guid>https://forem.com/ksimons/fixing-a-3-second-lockup-in-our-app-by-switching-from-apollo-client-to-urql-39j6</guid>
      <description>&lt;p&gt;A few weeks ago, the Kitemaker team was working on diagnosing a performance problem one of our larger customers reported. A few seconds after loading Kitemaker, the app would freeze up for several seconds before becoming responsive again. After digging into the problem, we were able to pinpoint that the lockup happened during the fetching of data from our servers. Kitemaker has a two-phased approach to loading data - first the data is loaded from a local offline cache (IndexedDB) in order to get a snappy load, and then we do a fetch from the server to ensure the client always has the latest data. Based on the timing, we could see it was the fetch from the server that was causing the freeze.&lt;/p&gt;

&lt;p&gt;There we bunch of places in the code that could be to blame for such a performance issue. Maybe it was our client-side search indexing hogging the CPU? Maybe it was an issue of loading a lot of data all at once into our state management library &lt;a href="https://recoiljs.org/"&gt;Recoil&lt;/a&gt;? It wasn’t until we did some analysis using Chrome’s profiling tools that we spotted the culprit:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EQbJP8no--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0bnrnph5teta7jgsocrq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EQbJP8no--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0bnrnph5teta7jgsocrq.png" alt="Chrome performance trace of Apollo" width="880" height="714"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the network responses were received, &amp;gt; 3 seconds was spent in our GraphQL client, &lt;a href="https://www.apollographql.com/docs/react/"&gt;Apollo&lt;/a&gt;. Specifically, we were spending a bunch of time and a ton of CPU cycles in the cache layer (InMemoryCache) of Apollo that we’d &lt;em&gt;specifically&lt;/em&gt; tried (and apparently failed) to disable.&lt;/p&gt;

&lt;p&gt;We suspected we needed to take some drastic action and rip out the Apollo client. But before we get into the details, let’s take a quick walk down memory lane to see how we got here.&lt;/p&gt;

&lt;h2&gt;
  
  
  A brief history of GraphQL at Kitemaker
&lt;/h2&gt;

&lt;p&gt;We started with GraphQL at Kitemaker on day 1. And in the beginning, we were a pretty traditional-looking GraphQL application. Each screen fetched just the queries it needed from the server and we used Apollo’s state management for everything. This meant Apollo’s InMemoryCache cached all of our data and we frequently wrote updaters to keep that cache in sync after mutations and subscription events.&lt;/p&gt;

&lt;p&gt;However, we began to hit some real challenges with Apollo in terms of making it reliable when our users had bad/no internet. Writing optimistic cache updaters was an error-prone pain and we never got the offline experience (persisting mutations client-side until the client came back online, etc.) to work the way we wanted it to.&lt;/p&gt;

&lt;p&gt;Therefore, we decided to drop Apollo’s state management in favor of writing our own syncing layer, which was still based on GraphQL. Since the basic functionality of GraphQL was working fine with the Apollo client, we decided to stick with it. We configured it to not do any caching (we thought) and we went on our way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ApolloClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;getLink&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;InMemoryCache&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;fragmentMatcher&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;defaultOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;watchQuery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;fetchPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no-cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;errorPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ignore&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;fetchPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no-cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;errorPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;all&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Apollo’s normalization
&lt;/h2&gt;

&lt;p&gt;So if we’d disabled Apollo’s caching, why were we getting massive amounts of time spent in Apollo’s InMemoryCache? Turns out, the cache actually does two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Caches the results of queries (what we disabled)&lt;/li&gt;
&lt;li&gt;Normalized caching&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What’s normalization mean in this case? If you have a GraphQL query that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;query MyQuery {
  space {
    labels {
      id
      name
      color
      createdAt
      updatedAt
    }
    workItems {
      id
      title
      description
      labels {
        id
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apollo will attempt to match the various objects by ID and create a graph of all of the data in the cache. This means, if the &lt;code&gt;labels&lt;/code&gt; on &lt;code&gt;workItems&lt;/code&gt; are the same IDs as the ones at the top level of the &lt;code&gt;space&lt;/code&gt; query, Apollo will recognize this and build a graph of the data, where accessing &lt;code&gt;labels&lt;/code&gt; via the &lt;code&gt;workItems&lt;/code&gt; will include all of the properties like &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;color&lt;/code&gt; even though they weren’t specified. It’s pretty neat! It’s also very costly when you have a lot of data, especially when you’re never actually accessing that graph that Apollo builds.&lt;/p&gt;

&lt;p&gt;We tried ripping out the cache entirely, but we didn’t manage to get it to work. It was time to find a new client.&lt;/p&gt;

&lt;h2&gt;
  
  
  URQL to the rescue
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://formidable.com/open-source/urql/"&gt;URQL&lt;/a&gt; is a lightweight GraphQL client that had been on our radar for a while. It’s &lt;em&gt;almost&lt;/em&gt; a drop-in replacement for Apollo if you’re not using Apollo for your state management. However, unlike Apollo, it only (by default) provides caching of GraphQL query results - it does not perform any normalization of the data.&lt;/p&gt;

&lt;p&gt;In the places where we were using the Apollo client directly (calling methods like &lt;code&gt;query()&lt;/code&gt; and &lt;code&gt;mutation()&lt;/code&gt;), switching to URQL was just a matter of massaging parameters into the correct format. Similarly, for the places where we used React hooks (like &lt;code&gt;useQuery()&lt;/code&gt; and &lt;code&gt;useMutation()&lt;/code&gt;), switching was basically figuring out that the hooks took arguments in a slightly different format and returned some extra objects/callbacks (that we didn’t really care about).&lt;/p&gt;

&lt;p&gt;Error handling was a bit of work since there were some places where the Apollo client threw exceptions where URQL just quietly returned an error. Overall though, URQLs &lt;a href="https://formidable.com/open-source/urql/docs/api/core/#combinederror"&gt;CombinedError&lt;/a&gt; (which wraps GraphQL errors and network errors together in a single object) is easy to work with and helped us clean up our error handling code.&lt;/p&gt;

&lt;p&gt;Additionally, we created a bit more work for ourselves by upgrading the library we use for GraphQL subscriptions over web sockets, moving from the seemingly unmaintained &lt;a href="https://github.com/apollographql/subscriptions-transport-ws"&gt;subscriptions-transport-ws&lt;/a&gt; to the active &lt;a href="https://formidable.com/open-source/urql/docs/advanced/subscriptions/#setting-up-graphql-ws"&gt;graphql-ws&lt;/a&gt; project (which is URLQ’s library of choice for subscriptions).&lt;/p&gt;

&lt;p&gt;The work took about a week including upgrading some Apollo Server libraries while we were at it.&lt;/p&gt;

&lt;p&gt;After we switched to URQL, the performance timeline looked like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--awbWol59--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c54so14cmot9wasrlpej.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--awbWol59--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c54so14cmot9wasrlpej.png" alt="Chrome performance trace of URQL" width="880" height="869"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While there’s still some room for improvement, the lockup is gone and the app spends much less time processing the response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping it up
&lt;/h2&gt;

&lt;p&gt;Having made the switch of libraries, we were delighted to see that the application no longer locked up after fetching data in the background. Three seconds we were previously wasting processing data into a format we didn’t need was gone. As an added bonus, we upgraded some GraphQL-related dependencies to their latest-and-greatest versions. If any team is using Apollo but doesn’t require any of the caching or state management aspects, we definitely recommend taking a peek at URQL.&lt;/p&gt;




&lt;p&gt;We're building Kitemaker, an alternative to your issue tracker that helps product managers, designers, and engineers collaborate better from ideation to delivery. &lt;a href="https://kitemaker.co"&gt;Click here to learn more&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Images:&lt;br&gt;
&lt;a href="https://unsplash.com/photos/2Yj6MBvJ0sg"&gt;Top photo by Stanos&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>graphql</category>
      <category>performance</category>
    </item>
    <item>
      <title>How to write great one-pagers, PRDs, Specs, and more</title>
      <dc:creator>Kevin Simons</dc:creator>
      <pubDate>Thu, 19 Jan 2023 09:39:52 +0000</pubDate>
      <link>https://forem.com/ksimons/how-to-write-great-one-pagers-prds-specs-and-more-3plp</link>
      <guid>https://forem.com/ksimons/how-to-write-great-one-pagers-prds-specs-and-more-3plp</guid>
      <description>&lt;p&gt;When building a product, it’s important that everyone on the team has a shared understanding of what to build, why it’s important, and a plan for how to make it real. The most common way to get the team aligned is to write a document containing all the required information.&lt;/p&gt;

&lt;p&gt;These documents go by many names: one- or six-pagers, PRDs, specification documents, project posters, problem-to-solve documents, and many more. Internally, we just refer to them as feature or bug descriptions, so we'll just call them descriptions here. They all have a similar purpose: to align the team what needs to be built and why, and to outline a plan on how to execute.&lt;/p&gt;

&lt;p&gt;We have written many descriptions for teams in larger tech companies and startups. Here are some of our key takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Start with the team. Don’t start writing a descriptions until you have talked things through with the team. Some PMs don’t involve engineers and designers from the start, but we see that most collaboration challenges within teams are easily solved by having the collaboration start early. Writing great descriptions should be a collaborative task where all the knowledge and expertise of the team should be utilized. By having the team be a part of discovering and understanding user problems and figuring out solutions, you'll get a better product and a more engaged team.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Know your audience. You are writing to a team of product managers, designers, engineers, and anyone else involved in the development process. It’s key to know what they care about and need to know in order to do a good job, as it differs between roles. Which takes us to the next point.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Know what you need to convey. This is going to be different for every team and each feature. However, there are some best practices. Some formats like Jobs-To-Be-Done, user stories, and the templates &lt;a href="https://www.lennysnewsletter.com/i/683946/pagers-prds" rel="noopener noreferrer"&gt;here&lt;/a&gt; covers most of these in various forms. But in general, these are the core parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write about the user context, the problem to be solved, and why this is important for the user and the business. People make better choices and are more motivated when they understand the business context of what they are working on. To us at Kitemaker, understanding users' problems is a vital part of the process. We won't build anything new until we understand these problems. We will follow up throughout the building process and verify that the problems are solved when the new thing is shipped.&lt;/li&gt;
&lt;li&gt;Be clear about what is expected to be built. There are various ways to do this. F.ex. user stories with acceptance criteria or Jobs-To-Be-Done are a popular models. When we build Kitemaker, we typically describe in enough detail how things are expected to work, and put a lot of the specification into the design files, so we are all on the same page. However, we make sure we don’t over-design the feature upfront, as we want to explore at the same time as we build.&lt;/li&gt;
&lt;li&gt;Make a plan for how to get there. This could involve technical designs, a development plan, the specific tasks that need to be delivered, and so forth. To us, this mostly means a list of things that need to be designed and code that needs to be written. Our engineers love to-dos, so there is usually a long list of them 🙂&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Make it concise and to the point. People don’t read documents because they like them, they read them because they contain information that is important to them. Practice writing concisely, and going back to the first three points, information that is not important to share should be put somewhere else.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Make it a living document. In most teams, the document isn’t updated when things change during development. Often one finds that a certain solution needs to be adjusted because of new learnings or because better technical solutions have been found. If someone coming in later in the cycle (like QA) having a nicely written up-to-date description is the easiest way for them to catch up on the context and the intended goal.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Real-world example from Kitemaker
&lt;/h2&gt;

&lt;p&gt;There is no one-size-fits-all approach to writing descriptions, so you need to figure out what works best for you and your team. However, seeing real-world examples might inspire you to find new ways to write them. Here are some examples from descriptions we have written for &lt;a href="https://kitemaker.co/" rel="noopener noreferrer"&gt;Kitemaker&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffpe7u856y7bhb6frrh6m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffpe7u856y7bhb6frrh6m.png" alt="Kitemaker work item placeholder"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the snippet we use to start all new initiatives in Kitemaker (the included todo is there to ensure we never forget tracking):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Why
[Explain why this is needed, cite users and background.
Provide a helpful context for the team to solve this]

What
[Explain the solution or bet we want to make]

How
[] Add tracking
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use the “Why” section to describe the background, the problem users are experiencing, and the context of why this is important.&lt;/p&gt;

&lt;p&gt;In some tools you may want to link to user research here. We use the Kitemaker’s insights module for this (coming soon), allowing us to have everything in the same tool and connected to where the team executed.&lt;/p&gt;

&lt;p&gt;Here is an example of a “Why” section that we are currently using in Kitemaker:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsfzjep53apa1navtzqt0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsfzjep53apa1navtzqt0.png" alt="Description why section"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We use the “What” section to describe what we believe is the solution. Sometimes on high level items (like roadmap themes) it will only be a description of the hypothesis for solutions, while on concrete deliveries we will include designs and, when needed, more detailed explanation of the feature.&lt;/p&gt;

&lt;p&gt;Here is an example from when we built a new version of “Insights” in Kitemaker. In this case we flesh out an outline in the document, and the design files contains the details of the functionality.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmntlsqcfxfzg2c4wctpj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmntlsqcfxfzg2c4wctpj.png" alt="Description what section"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The “How” section is where we make the plan for how to get things . We write a lot of todos in Kitemaker, since they are easy to use and connect directly to commits and PRs in Github/GitLab.&lt;/p&gt;

&lt;p&gt;Here is an example from a new feature we were working on, where a bug resulted in some edge cases where deletion could cause users to get a 404 error:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffx34zzruydkbn7k0va89.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffx34zzruydkbn7k0va89.png" alt="Description how section"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We don’t believe there is only one way to write great descriptions. Some have great success using models like Shape up, Jobs-To-Be-Done and/or user stories. Others choose a less structured approach like we do in &lt;a href="https://kitemaker.co" rel="noopener noreferrer"&gt;Kitemaker&lt;/a&gt;. All are good ways to do it, as long as the audience that reads it find it easy to comprehend and it contains the information they need to do a great job. The important part is to figure out what works for you and your team to get alignment and a common understanding.&lt;/p&gt;

&lt;p&gt;Want to learn how &lt;a href="https://kitemaker.co" rel="noopener noreferrer"&gt;Kitemaker&lt;/a&gt; will make these documents front and center for your team? Send us a &lt;a href="//mailto:hi@kitemaker.co"&gt;ping&lt;/a&gt;, and we’ll help you set it up!&lt;/p&gt;

&lt;p&gt;Images: &lt;a href="https://unsplash.com/photos/sggw4-qDD54" rel="noopener noreferrer"&gt;Toop photo by Annie Spratt&lt;/a&gt;&lt;/p&gt;

</description>
      <category>product</category>
      <category>agile</category>
      <category>teamwork</category>
      <category>productdevelopment</category>
    </item>
    <item>
      <title>Lessons learned from moving to Recoil.js</title>
      <dc:creator>Kevin Simons</dc:creator>
      <pubDate>Wed, 12 Oct 2022 11:49:39 +0000</pubDate>
      <link>https://forem.com/ksimons/lessons-learned-from-moving-to-recoiljs-3c09</link>
      <guid>https://forem.com/ksimons/lessons-learned-from-moving-to-recoiljs-3c09</guid>
      <description>&lt;p&gt;At &lt;a href="https://kitemaker.co" rel="noopener noreferrer"&gt;Kitemaker&lt;/a&gt;, we recently made the leap to &lt;a href="https://recoiljs.org/" rel="noopener noreferrer"&gt;Recoil.js&lt;/a&gt; for our React state management needs. Before using Recoil, Kitemaker used a simple state management solution built upon &lt;code&gt;useReducer()&lt;/code&gt;. We built Kitemaker to be super fast, responding to every user interaction instantly. However, in organizations with lots of data, we sometimes had a difficult time achieving this due to unnecessary re-renders. Kitemaker has a sync engine under the hood that is constantly syncing data in the background between clients. With &lt;code&gt;useReducer()&lt;/code&gt; this always triggered a top-down re-render and we had to rely on memoization to keep things snappy.&lt;/p&gt;

&lt;p&gt;We reached for Recoil to help us minimize re-renders in order to keep Kitemaker fast and responsive regardless of what changes are flowing in via background syncs. We chose it over other competing frameworks like MobX because we liked the explicitness of its API and its similarity to Redux which we were already familiar with. Additionally, the fact that the Meta team designed Recoil for the purpose of building performant UIs with large datasets was a major draw.&lt;/p&gt;

&lt;p&gt;While we’ve been very happy with the decision and we have seen the benefits we’d hoped for, it did come with some gotchas.&lt;/p&gt;

&lt;h2&gt;
  
  
  A little terminology
&lt;/h2&gt;

&lt;p&gt;Let’s get everyone on the same page about Recoil’s terminology first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;em&gt;atom&lt;/em&gt; is a piece of state stored by Recoil. In Kitemaker, we have items for things like work items, labels, users, comments and more&lt;/li&gt;
&lt;li&gt;A &lt;em&gt;selector&lt;/em&gt; is a derived piece of state. It fetches data from atoms (or other selectors), and transforms them in some way. An example of a selector in Kitemaker would be “fetch all of the comments for this particular work item”&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Write fine-grained selectors
&lt;/h2&gt;

&lt;p&gt;In order to make your UI perform well with Recoil, it’s import to understand what actually causes renders. Fortunately, it’s pretty simple - if a selector returns a simple value (string, number, boolean), it triggers a re-render whenever that value changes. If a selector returns an object or an array, Recoil re-renders if the returned value is not referentially equal (i.e. &lt;code&gt;===&lt;/code&gt;) to the previous value.&lt;/p&gt;

&lt;p&gt;What this means in practice is that you want to write selectors that are quite fine-grained. Stick to simple values, avoiding arrays and objects where possible.&lt;/p&gt;

&lt;p&gt;Take for example the board view in Kitemaker:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4z0wpveuprnvkd225ifq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4z0wpveuprnvkd225ifq.png" alt="Kitemaker board"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s say we want to render the title of a card. We could write a selector that returns the entire atom, but if we did this, every single time that atom changed, we’d cause a re-render of the component. We don’t care about &lt;em&gt;all&lt;/em&gt; the properties of that atom, we just want the title, so we should write a selector like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;workItemTitleSelector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;selectorFamily&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WorkItemTitle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;workItemId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;workItems&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workItemId&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// only fetch what we need!&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;WorkItemCard&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;workItemId&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;workItemId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;workItemTitle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRecoilValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;workItemTitleSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workItemId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;workItemTitle&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we’re only fetching the title, so only changes to the title will cause a re-render. Win!&lt;/p&gt;

&lt;h2&gt;
  
  
  Dealing with selectors that return objects and arrays
&lt;/h2&gt;

&lt;p&gt;Before we said you should try to return as specific a value as possible from your selectors. But what if we have no choice but to return an object or an array from your selectors? For example, in Kitemaker, we often return a sorted list of IDs that we’ll then render in some sort of a list or board. Unfortunately, Recoil just doesn’t handle these cases very well out of the box.&lt;/p&gt;

&lt;p&gt;We &lt;a href="https://github.com/facebookexperimental/Recoil/issues/1416#issuecomment-1044953271" rel="noopener noreferrer"&gt;found a solution&lt;/a&gt; that allowed us to provide our own equality checks for selectors (or selector families). We have a custom selector family implementation that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;equalSelectorFamily&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;P&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;SerializableParam&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EqualSelectorFamilyOptions&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;P&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;selectorFamily&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;P&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_inner`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;priorValues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;selectorFamily&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;P&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;param&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;P&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;latest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prior&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;priorValues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prior&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prior&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;prior&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;priorValues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The idea is that if two consecutive executions of the selector result in the same value (as defined by the supplied equality operator), the selector just returns the old value, thus preserving referential equality and preventing a re-render.&lt;/p&gt;

&lt;p&gt;To use it we do something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filteredWorkItemsByStatusSelector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;equalSelectorFamily&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FiltereWorkItemsByStatus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;spaceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filterString&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;spaceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;filterString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;itemIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getFilteredWorkItemsSomehow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;spaceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filterString&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="na"&gt;itemIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isEqual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now if the two arrays returned are equal (as per lodash’s isEqual function in this case), we don’t re-render.&lt;/p&gt;

&lt;p&gt;Hopefully Recoil will solve this for us in the future in a proper way 🤞There’s a promising but &lt;a href="https://github.com/facebookexperimental/Recoil/issues/1416#issuecomment-1038849014" rel="noopener noreferrer"&gt;yet undocumented&lt;/a&gt; &lt;code&gt;equality&lt;/code&gt; parameter on selector &lt;code&gt;cachePolicy&lt;/code&gt; that should allow selectors to use value equality instead of referential equality. However, in our initial testing, this still failed to prevent re-renders from selectors that returned objects or arrays.&lt;/p&gt;

&lt;h2&gt;
  
  
  Leverage transactions
&lt;/h2&gt;

&lt;p&gt;In Kitemaker, we have some complex operations. For example, adding a backlog to a space in Kitemaker creates a new board object, adds various columns to it, and more.&lt;/p&gt;

&lt;p&gt;In order to minimize re-renders (and to prevent users from seeing weird intermediate states) we want to apply all of our changes in a single atomic operation. Fortunately, Recoil has an awesome solution for this - &lt;a href="https://recoiljs.org/docs/api-reference/core/useRecoilTransaction" rel="noopener noreferrer"&gt;transactions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the case of Kitemaker, we always use transactions inside of a &lt;code&gt;useRecoilCallback()&lt;/code&gt; function (though there is a &lt;code&gt;useRecoilTransaction_UNSTABLE()&lt;/code&gt; hook as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;BulkItemCreator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useBulkCreateExample&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;useRecoilCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;transact_UNSTABLE&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;creator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BulkItemCreator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;transact_UNSTABLE&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="c1"&gt;// all of these set() calls happen in one atomic operation&lt;/span&gt;
              &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;itemAtomFamily&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
              &lt;span class="p"&gt;}&lt;/span&gt;
              &lt;span class="c1"&gt;// you can use get() here, but only for atoms, not selectors&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Simple example of using a transaction&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SomeComponent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bulkCreate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useBulkCreateExample&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleClick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;bulkCreate&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;creator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;creator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bob&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;234&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Susan&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleClick&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Create&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These transactions are considered an unstable feature (thus the naming) but in our experience they work great. There are some gotchas however - the main one being that you may not access any selectors inside of a transaction. This is because selectors are not updated as the individual operations of a transaction are applied to the recoil state, so the data in the selectors would be stale. This hasn’t been a big problem for us as the data fetching needs of this transaction code tends to be very simple. Also there’s a performance overhead with creating the snapshot inside of a transaction, so keep this in mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch your atom count
&lt;/h2&gt;

&lt;p&gt;As the number of atoms in Recoil increases, there’s some performance impact. There is a lot of bookkeeping for dependency checking, etc. inside of Recoil that does not scale linearly. Additionally, whenever you need to grab a snapshot, it gets slower based on how large your set of atoms is. As a result, you need to think a bit about how you bucket your data into atoms. Does every single object need to be a separate atom? Or maybe if you always access a list of items together as a unit, you can shove the entire list into an atom. Then you can still write nice specific selectors on top (possibly using the equality tricks above) to fetch just the data you need to minimize re-renders.&lt;/p&gt;

&lt;p&gt;Of course selectors are not free and in some cases changes to data may cause selectors to be frequently reevaluated. That means it’s a bit of a balancing act between specific selectors and managing your atom count. A rule of thumb is to group up atoms that are generally rendered together and which change relatively infrequently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping it up
&lt;/h2&gt;

&lt;p&gt;To summarize, using Recoil we were able to eliminate unnecessary renders in React. Instead of top-down, full screen re-renders with a bunch of memoization, we now have a very minimal number of re-renders whenever our data changes. However, Recoil isn’t magic. It really pays to take the time to understand what things causes re-renders and other performance issues so you can avoid those landmines in your implementation.&lt;/p&gt;

&lt;p&gt;Want to learn more about Recoil or how we built Kitemaker? Always happy to chat on &lt;a href="https://twitter.com/ksimons" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; or &lt;a href="//mailto:hi@kitemaker.co"&gt;email&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>react</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
