<?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>DEV Community: Andy Richardson</title>
    <description>The latest articles on DEV Community by Andy Richardson (@andyrichardsonn).</description>
    <link>https://dev.to/andyrichardsonn</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%2F281628%2Fbe8f7597-ae12-44c1-b52b-74c3e1636a90.jpg</url>
      <title>DEV Community: Andy Richardson</title>
      <link>https://dev.to/andyrichardsonn</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/andyrichardsonn"/>
    <language>en</language>
    <item>
      <title>Packages don't solve distributed systems</title>
      <dc:creator>Andy Richardson</dc:creator>
      <pubDate>Tue, 08 Feb 2022 11:11:34 +0000</pubDate>
      <link>https://dev.to/andyrichardsonn/packages-dont-solve-distributed-systems-4g0</link>
      <guid>https://dev.to/andyrichardsonn/packages-dont-solve-distributed-systems-4g0</guid>
      <description>&lt;p&gt;Packages and libraries can be a great way to share functionality across different codebases. &lt;em&gt;React&lt;/em&gt;, &lt;em&gt;lodash&lt;/em&gt;, &lt;em&gt;express&lt;/em&gt; - all of these are used across many codebases without issue.&lt;/p&gt;

&lt;p&gt;So when it comes time to build out many backend services which operate within a similar domain - packaging logic can seem like a no brainier.&lt;/p&gt;

&lt;p&gt;But what if I told you that's a disaster waiting to happen?&lt;/p&gt;

&lt;h3&gt;
  
  
  The scaling problem 📈
&lt;/h3&gt;

&lt;p&gt;As companies grow, the business domain typically grows in complexity and size. Scaling this becomes a real challenge - both from a technical and organizational perspective.&lt;/p&gt;

&lt;p&gt;As problem solvers, we break things down into smaller parts; typically, the business domain is broken into smaller "subdomains" - each with their own teams and services (&lt;a href="https://en.wikipedia.org/wiki/Conway%27s_law"&gt;Conway's law&lt;/a&gt; in full effect).&lt;/p&gt;

&lt;p&gt;In an ideal world, each of those teams could work in &lt;em&gt;complete isolation&lt;/em&gt;, doing what they do best and only having to solve problems within their domain. &lt;/p&gt;

&lt;p&gt;In reality, an overlap of the domains of a business is common and significant overhead is introduced due to a need for cross-domain communication.&lt;/p&gt;

&lt;p&gt;The same problem applies to technical architecture; while ideally services would be bound by the constraints of their own existence (and not of other services), domain overlap makes this hard to achieve.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using packages 🎁
&lt;/h3&gt;

&lt;p&gt;At face value, this seems like a very simple problem to solve. "I’ll make a package which abstracts the business logic into on place so all services can use it” said every web developer, ever.&lt;/p&gt;

&lt;p&gt;While this seems like a no-brainer, what you end up with is a &lt;em&gt;distributed monolith&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;The efforts to solve scaling problems by breaking the domain down into services is undone when we move business logic into packages. We end up with a distributed system - albeit, one where services are &lt;strong&gt;coupled&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Coupling 👫
&lt;/h3&gt;

&lt;p&gt;Coupling is the deadly sin of a distributed system. If the goal is to work at maximum velocity without the overhead of cross-communication, coupling achieves the exact opposite.&lt;/p&gt;

&lt;p&gt;From the start of our careers as developers, we’re taught that code redundancy is bad, and that abstracting code into functions is good. While this is sometimes the case, it’s easy to get wrong, and if there’s anything worse than redundant code, it’s that one overly-abstracted function that does a milliion different things and is used across the whole system.&lt;/p&gt;

&lt;p&gt;When we put business logic into packages, we end up with this exact problem... but on a completely new level.&lt;/p&gt;

&lt;h4&gt;
  
  
  Runtime coupling
&lt;/h4&gt;

&lt;p&gt;The most obvious issue with distributing domain logic via packages is that, while the business logic might be nice and neatly placed in one place during development time, that won’t be the case at runtime. &lt;/p&gt;

&lt;p&gt;Business logic needs to be consistent - so by distributing business logic via a package, how do you ensure consistency? Versioning won’t solve this issue because differing services could be using different versions of a package at any one time. &lt;/p&gt;

&lt;p&gt;The solution I’ve seen over and over again is a monolithic release process; leading to slow CI times, a need for coordination amongst many teams, and volitile releases (due to transactional rollouts being outright impossible on a distributed system).&lt;/p&gt;

&lt;h4&gt;
  
  
  Code coupling
&lt;/h4&gt;

&lt;p&gt;Problems for sharing business logic in packages spread further than just runtime cohesion. As services are tied together by the ones and zeros they use to operate, this can lead to restrictions on what those services can do and how they can operate.&lt;/p&gt;

&lt;p&gt;Supported runtimes and dependencies of a package often fail to be updated as nobody is willing to take the risk to update to newer versions; lest it should cause the tightly knit tower of services to crumble. &lt;/p&gt;

&lt;p&gt;This can often be a bidirectional problem whereby a service needs to update a package to address a bug, but that would in turn require changes to a shared package (and subsequently, many services). &lt;/p&gt;

&lt;h3&gt;
  
  
  Ownership 🙋
&lt;/h3&gt;

&lt;p&gt;A less obvious consequence of using packages for business logic is the blurring of lines of ownership. In an architecture where services work within a &lt;em&gt;bounded context&lt;/em&gt; (more on this later), all aspects are self contained - logging, metrics, reads/writes etc.&lt;/p&gt;

&lt;p&gt;The same cannot be said when business logic from a package is running on any number of services at one time. Something as simple as “monitoring the creation of users” becomes a behemoth effort involving many services. &lt;/p&gt;

&lt;p&gt;Before you know it, someone has built a company-wide logging library, further worsening the service coupling problem.&lt;/p&gt;

&lt;p&gt;In the case of runtime issues, it is often unclear where the problem is located - the package, or the service. I’ve personally seen this lead to a culture of avoidant engineers who - understandably - don’t want to be accountable for breakages because there is no clarity on who is responsible for what.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making it work 🤔
&lt;/h3&gt;

&lt;p&gt;So by now, you likely understand why having many services share code can have a spreading impact on the stability, velocity and effectiveness of a distributed system and the teams behind it. What are our options?&lt;/p&gt;

&lt;h3&gt;
  
  
  Service Oriented Architecture 🏗
&lt;/h3&gt;

&lt;p&gt;While the term itself sounds advanced, it simply refers to building a distributed system using &lt;strong&gt;services&lt;/strong&gt; as the primitive building blocks. &lt;/p&gt;

&lt;p&gt;If a distributed system is a jigsaw, services are the pieces which together create the full picture.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bounded contexts 🔲
&lt;/h3&gt;

&lt;p&gt;A pragmatic solution to coupling, bounded contexts are intended to create distinct boundaries between the different domains of a system. &lt;/p&gt;

&lt;p&gt;Following the single responsibility principle, and coined by Eric Evans in &lt;a href="https://www.amazon.co.uk/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215"&gt;Domain Driven Design&lt;/a&gt;, the goal is to ensure that any given service has a clearly scoped responsibility.&lt;/p&gt;

&lt;p&gt;From a technical perspective, you can think of this as every running service being solely responsible for its existence, deployment, data and anything else relating to its domain.&lt;/p&gt;

&lt;p&gt;By having a single source of truth for the different domains within a distributed system, many of the challenges mentioned earlier cease to exist.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inevitable coupling 🚨
&lt;/h3&gt;

&lt;p&gt;While having clear distinct boundaries sounds nice, in practice, things are never that straight forward. Services are always going to have overlapping concerns - and thats okay. We can solve that logical coupling in a manner that introduces minimal runtime coupling and no code coupling.&lt;/p&gt;

&lt;h4&gt;
  
  
  The internet
&lt;/h4&gt;

&lt;p&gt;Okay so hear me out - there’s a way to communicate between machines using some wires and electricity - it’s going to take of soon!&lt;/p&gt;

&lt;p&gt;GraphQL, REST, SOAP, you name it, any one of these is going to do just fine; and while you might have a better time with some protocols over others, as long as the only point of coupling between services is a (preferably versioned) protocol for communicating, code coupling is out of the question.&lt;/p&gt;

&lt;h4&gt;
  
  
  It can still go wrong
&lt;/h4&gt;

&lt;p&gt;Excessive runtime coupling is still a possibility; and depending on how services communicate, a messy web of inter-dependent services is not impossible.&lt;/p&gt;

&lt;p&gt;That’s a whole other topic in itself - I’d recommend &lt;a href="https://www.youtube.com/watch?v=gfh-VCTwMw8"&gt;this talk&lt;/a&gt; by Jimmy Boggard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ownership 🙋
&lt;/h3&gt;

&lt;p&gt;Finally there’s the ownership angle. Those clear boundaries assigned to each service - assign them to people too.&lt;/p&gt;

&lt;p&gt;Accountability isn’t just about knowing who broke that thing, it’s about giving teams the ability to have a say in what they do, and take pride in following that through to the end.&lt;/p&gt;

&lt;p&gt;Its easy to say “not my problem” when some code that has been touched by 10+ different people breaks, but you’ll be hard pressed to find an engineer who is willing to respond like that when they proudly wrote all those bugs with their bare hands.&lt;/p&gt;

&lt;p&gt;Better yet, you’re more likely to see engineers be proactive in maintaining their work when they know they’re responsible for the state of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;So there you have it - hopefully this helps at least one team from ending up with a distributed monolith!&lt;/p&gt;

&lt;p&gt;This is just the tip of the iceberg, so if you're interested in finding out more, I'd recommend the following resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://samnewman.io/books/building_microservices/"&gt;Building Microservices&lt;/a&gt; (or basically anything else by Sam Newman)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.simplethread.com/youre-not-actually-building-microservices/"&gt;You're not actually building micro-services&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.co.uk/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215"&gt;Domain driven design&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=gfh-VCTwMw8"&gt;Avoiding Microservice Megadisasters&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Thanks for reading! &lt;/p&gt;

&lt;p&gt;If you enjoyed this post, be sure to react 🦄  or drop a comment below with any thoughts 🤔. &lt;/p&gt;

&lt;p&gt;You can also hit me up on twitter - &lt;a href="https://twitter.com/andyrichardsonn"&gt;@andyrichardsonn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Disclaimer: All thoughts and opinions expressed in this article are my own.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>microservices</category>
    </item>
    <item>
      <title>GraphQL requests over HTTP/s are slow</title>
      <dc:creator>Andy Richardson</dc:creator>
      <pubDate>Mon, 02 Aug 2021 13:00:51 +0000</pubDate>
      <link>https://dev.to/andyrichardsonn/graphql-requests-over-http-s-are-slow-d1p</link>
      <guid>https://dev.to/andyrichardsonn/graphql-requests-over-http-s-are-slow-d1p</guid>
      <description>&lt;p&gt;HTTP/s?&lt;/p&gt;

&lt;p&gt;What year do you think this is - 2020?&lt;/p&gt;

&lt;p&gt;It's 2021, and I'm here to tell you about a way you can make your GraphQL requests faster and more reliable - using WebSockets! 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  Traditional transport mechanisms
&lt;/h2&gt;

&lt;p&gt;Most people familiar with GraphQL are accustomed to using HTTP/s for &lt;a href="https://graphql.org/learn/queries/" rel="noopener noreferrer"&gt;&lt;em&gt;query&lt;/em&gt; and &lt;em&gt;mutation&lt;/em&gt;&lt;/a&gt; operations; and there's good reason for that! HTTP requests are easy to manage and scale thanks to their simple call-and-response nature. &lt;/p&gt;

&lt;p&gt;WebSockets on the other hand, while often more performant, require the management of many sustained connections. For "atomic" operations like queries and mutations, the runtime complexity and infrastructure costs introduced from using a long running socket has traditionally been an understandable deterrent.&lt;/p&gt;

&lt;p&gt;What if I were to tell you there's a way to have the best of both worlds?&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%2F1myxfg028xffkqufm0ma.gif" 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%2F1myxfg028xffkqufm0ma.gif" alt="Beff Jezos"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Managed sockets
&lt;/h2&gt;

&lt;p&gt;As serverless technology has continued to evolve, stateless solutions have been introduced to address the otherwise stateful task of managing sockets.&lt;/p&gt;

&lt;p&gt;In the case of AWS, &lt;a href="https://aws.amazon.com/blogs/compute/announcing-websocket-apis-in-amazon-api-gateway/" rel="noopener noreferrer"&gt;Big Jeff's managed WebSockets&lt;/a&gt; was designed to solve this problem. At the time or writing, one million "connection minutes" costs a measly $0.25.&lt;/p&gt;

&lt;p&gt;That isn't to say this is a perfect solution. The switch to a SAAS (sockets as a service) model involves requires a shift to a completely new API for working with sockets; and plug-and-play solutions are only just beginning to come about. &lt;/p&gt;

&lt;h2&gt;
  
  
  Improved performance (in theory)
&lt;/h2&gt;

&lt;p&gt;Before considering a switch from HTTP/s to WebSocket, it's best to run through why this might be worthwhile.&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%2Fpi2eh9gpuubawkpgibm9.gif" 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%2Fpi2eh9gpuubawkpgibm9.gif" alt="Big brain moment"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://en.wikipedia.org/wiki/HTTP_persistent_connection" rel="noopener noreferrer"&gt;some exception&lt;/a&gt;, every &lt;em&gt;query&lt;/em&gt; or &lt;em&gt;mutation&lt;/em&gt; made over HTTP/s requires a new connection to be established - and that isn't free.&lt;/p&gt;

&lt;p&gt;Opening and closing a connection requires some overhead which can introduce latency, thus making GraphQL requests take longer.&lt;/p&gt;

&lt;p&gt;By instead using a WebSocket to communicate with a GraphQL endpoint, the single connection is sustained for the lifetime of the client - therefore removing the per-request overhead found with HTTP/s.&lt;/p&gt;

&lt;p&gt;You can think of it like this:&lt;br&gt;
    &lt;strong&gt;HTTP/s:&lt;/strong&gt; 100 queries/mutations -&amp;gt; 100 connections&lt;br&gt;
    &lt;strong&gt;WebSocket:&lt;/strong&gt; 100 queries/mutations -&amp;gt; 1 connection &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For lower level details, check out this &lt;a href="https://stackoverflow.com/questions/14703627/websockets-protocol-vs-http#answer-14711517" rel="noopener noreferrer"&gt;stack overflow discussion&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Performance isn't the only advantage. WebSocket connections typically have better fault tolerance, meaning clients on unstable connections should have an easier time sending and receiving messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the theory
&lt;/h2&gt;

&lt;p&gt;While the theory makes sense, I wanted to see if a measurable difference can be seen when making requests over a single sustained connection.&lt;/p&gt;

&lt;p&gt;In order to get a result that measures the real-world impact + feasibility, rather than just the protocol overhead alone, I created an &lt;a href="https://github.com/andyrichardson/graphql-http-vs-ws" rel="noopener noreferrer"&gt;end-to-end project&lt;/a&gt; and benchmarked both protocols. &lt;/p&gt;

&lt;h2&gt;
  
  
  Performance results
&lt;/h2&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%2F14e7ijngj0bafot0o7pc.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%2F14e7ijngj0bafot0o7pc.png" alt="Fast connection results"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Results when using a 5g connection&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After testing this a number of times, I was relieved to see that there is a consistent performance improvement. But with the mere ~100ms difference in latency, I was a little disappointed.&lt;/p&gt;

&lt;p&gt;Realizing that this is still a roughly 30% improvement in speed, I decided to explore whether the latency reduction would be more evident on slower connections&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%2Fr6njlrp65ojn728n0cc1.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%2Fr6njlrp65ojn728n0cc1.png" alt="Perf results"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Results when using a simulated 3g connection&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;At this point, the impact became much more evident! With little to no effort or additional cost, I was able to measure an over half-a-second (~600ms) improvement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the switch
&lt;/h2&gt;

&lt;p&gt;So your GraphQL endpoint is already on serverless infrastructure, and you want to take the leap - what needs to be done?&lt;/p&gt;

&lt;p&gt;If you're already using GraphQL subscriptions (on serverless infrastructure) for push-based data, first off - give yourself a pat on the back you trend setter 👏! There's no work required other than configuring your client to send requests via the socket rather than HTTP/s. &lt;/p&gt;

&lt;p&gt;The likelihood however is that your endpoint isn't using GraphQL subscriptions. In the case of AWS, the serverless socket offering has been around for a few years but the work required to integrate this into existing sub-protocols has been fairly substantial.&lt;/p&gt;

&lt;p&gt;I've been working to change this and created &lt;a href="https://github.com/andyrichardson/subscriptionless" rel="noopener noreferrer"&gt;Subscriptionless&lt;/a&gt; which is a library designed to make socket-based GraphQL (queries, mutations, and subscriptions) simpler to implement on AWS's serverless infra.&lt;/p&gt;

&lt;p&gt;If you want to take the leap, check out &lt;a href="https://github.com/andyrichardson/subscriptionless" rel="noopener noreferrer"&gt;the repo&lt;/a&gt; for a guide and example project. You can also try out the &lt;a href="https://github.com/andyrichardson/graphql-http-vs-ws" rel="noopener noreferrer"&gt;end-to-end project repo&lt;/a&gt; which was used to do this performance comparison. &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;So there you have it - free network performance improvements at little to no cost!&lt;/p&gt;

&lt;p&gt;Do we even need HTTP/s for GraphQL? &lt;/p&gt;

&lt;p&gt;Do you see yourself using WebSockets more? &lt;/p&gt;

&lt;p&gt;Share your thoughts below 💬&lt;/p&gt;




&lt;p&gt;Thanks for reading! &lt;/p&gt;

&lt;p&gt;If you enjoyed this post, be sure to react 🦄  or drop a comment below with any thoughts 🤔. &lt;/p&gt;

&lt;p&gt;You can also hit me up on twitter - &lt;a href="https://twitter.com/andyrichardsonn" rel="noopener noreferrer"&gt;@andyrichardsonn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Disclaimer: All thoughts and opinions expressed in this article are my own.&lt;/p&gt;

</description>
      <category>graphql</category>
      <category>serverless</category>
      <category>aws</category>
      <category>node</category>
    </item>
    <item>
      <title>GraphQL pagination with DynamoDB - Putting it together</title>
      <dc:creator>Andy Richardson</dc:creator>
      <pubDate>Wed, 28 Apr 2021 10:15:07 +0000</pubDate>
      <link>https://dev.to/andyrichardsonn/graphql-pagination-with-dynamodb-putting-it-together-20mn</link>
      <guid>https://dev.to/andyrichardsonn/graphql-pagination-with-dynamodb-putting-it-together-20mn</guid>
      <description>&lt;p&gt;Assuming you have a good understanding of &lt;a href="https://dev.to/andyrichardsonn/graphql-pagination-with-dynamodb-cursor-specification-5d51"&gt;relay pagination&lt;/a&gt; and &lt;a href="https://dev.to/andyrichardsonn/graphql-pagination-with-dynamodb-dynamodb-pagination-425m"&gt;DynamoDB pagination&lt;/a&gt;, here's a rundown on how to get the two working in harmony 🥂.&lt;/p&gt;

&lt;h2&gt;
  
  
  🐙 Creating a resolver
&lt;/h2&gt;

&lt;p&gt;For the majority of this section, it's fair to assume that we're working inside of a resolver such as the following.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;usersResolver&lt;/span&gt; &lt;span class="o"&gt;=&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;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;after&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;last&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;before&lt;/span&gt; &lt;span class="p"&gt;},&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="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Determining direction
&lt;/h3&gt;

&lt;p&gt;Before querying the database, we first need to know which direction is being requested by the user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isForward&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The easiest way to do this is to see if the provided &lt;code&gt;first&lt;/code&gt; argument has a value. If so, we're working with forward pagination.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: Bi-directional pagination within a single query is allowed but is not advised.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Querying the Database
&lt;/h3&gt;

&lt;p&gt;For the query, most of the arguments passed through are going to be your bog-standard query; but there will be some additional attributes we need to pass through.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ScanIndexForward&lt;/code&gt; needs to be passed a boolean dependent on the direction of the query (i.e. &lt;code&gt;isForward&lt;/code&gt; from the previous example).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ExclusiveStartKey&lt;/code&gt; is going to be the client provided cursor (i.e. &lt;code&gt;before&lt;/code&gt; or &lt;code&gt;after&lt;/code&gt; arguments). The current SDK doesn't support the value &lt;code&gt;null&lt;/code&gt; so make sure to fall back to &lt;code&gt;undefined&lt;/code&gt; for cases where a cursor isn't present.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;documentClient&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;ScanIndexForward&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isForward&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ExclusiveStartKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;before&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;after&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="c1"&gt;// The rest of your query&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Page sizing
&lt;/h3&gt;

&lt;p&gt;A single query won't be enough to guarantee that the client provided page size is satisfied. In order to work around this, we're going to need to create a utility to iterate through one or more DynamoDB pages to populate our collection.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paginateQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;R&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;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DynamoDB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DocumentClient&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;async&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;R&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;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;acc&lt;/span&gt; &lt;span class="o"&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="nl"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DynamoDB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DocumentClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;QueryInput&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nb"&gt;Promise&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;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="nl"&gt;hasNextPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;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;remaining&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pageSize&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;promise&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;newItems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Items&lt;/span&gt; &lt;span class="o"&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;newAcc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;newItems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[])];&lt;/span&gt;

  &lt;span class="c1"&gt;// Query exhausted&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LastEvaluatedKey&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;{&lt;/span&gt;
      &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newAcc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;hasNextPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newItems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;remaining&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;// Page needs to be filled more&lt;/span&gt;
    &lt;span class="nx"&gt;newAcc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;pageSize&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="c1"&gt;// page full but hasNextPage unknown&lt;/span&gt;
    &lt;span class="nx"&gt;newItems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;remaining&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;paginateQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)({&lt;/span&gt;
      &lt;span class="na"&gt;params&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="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;ExclusiveStartKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LastEvaluatedKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nx"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newAcc&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;{&lt;/span&gt;
    &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newAcc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;hasNextPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;Having pieced this together, the previous DynamoDB query can now instead call this utility and the requested page size is passed along.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hasNextPage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;paginateQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;documentClient&lt;/span&gt;&lt;span class="p"&gt;)({&lt;/span&gt;
  &lt;span class="na"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;first&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;last&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;ScanIndexForward&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isForward&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ExclusiveStartKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;before&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;after&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="c1"&gt;// The rest of your query&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;h3&gt;
  
  
  Constructing Edges
&lt;/h3&gt;

&lt;p&gt;The response from DynamoDB equates to the &lt;em&gt;nodes&lt;/em&gt; in our response Edges. A cursor is also required to be colocated with these nodes.&lt;/p&gt;

&lt;p&gt;In this example, the query is on a table (rather than an index) so the keys required correspond to the &lt;em&gt;partition key&lt;/em&gt; and &lt;em&gt;sort key&lt;/em&gt; of the table.&lt;/p&gt;

&lt;p&gt;For index queries, see the &lt;em&gt;Cursor Construction&lt;/em&gt; section of the &lt;a href="https://dev.to/andyrichardsonn/graphql-pagination-with-dynamodb-dynamodb-pagination-425m"&gt;DynamoDB Pagination post&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cursorKeys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dateOfBirth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&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;edges&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;node&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;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cursorKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;agg&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="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;agg&lt;/span&gt;&lt;span class="p"&gt;,&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="nx"&gt;node&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="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;h4&gt;
  
  
  Correcting edge order
&lt;/h4&gt;

&lt;p&gt;While DynamoDB inverts sort-order when paginating backward, Relay does not. For this reason, the order of edges needs to be reversed if backward pagination is being used.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isForward&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reverse&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;h3&gt;
  
  
  Constructing PageInfo
&lt;/h3&gt;

&lt;p&gt;The task is almost complete! The final part to this pagination saga is to put together the &lt;em&gt;PageInfo&lt;/em&gt; response.&lt;/p&gt;

&lt;h4&gt;
  
  
  Cursors
&lt;/h4&gt;

&lt;p&gt;Assuming the edges have already been ordered correctly (see above) the start and end cursors can easily be set by getting the cursor values of the first and last edges.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pageInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;startCursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;endCursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Next pages
&lt;/h4&gt;

&lt;p&gt;Assuming the client is stateful, there's little-to-no need to tell the client whether there is a page available in the opposite direction. For this purpose, we can default to &lt;code&gt;false&lt;/code&gt; for &lt;code&gt;hasPreviousPage&lt;/code&gt; and &lt;code&gt;hasNextPage&lt;/code&gt; for forward and backward pagination, respectively.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pageInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;isForward&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;hasNextPage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;hasPreviousPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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="na"&gt;hasNextPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;hasPreviousPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hasNextPage&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;h3&gt;
  
  
  Final result
&lt;/h3&gt;

&lt;p&gt;Here's what our resolver looks like after putting all these parts together.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;usersResolver&lt;/span&gt; &lt;span class="o"&gt;=&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;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;after&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;last&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;before&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;isForward&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hasNextPage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;paginateQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;documentClient&lt;/span&gt;&lt;span class="p"&gt;)({&lt;/span&gt;
    &lt;span class="na"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;first&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;last&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;ScanIndexForward&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isForward&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;ExclusiveStartKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;before&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;after&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="c1"&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;cursorKeys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dateOfBirth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&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;edges&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;node&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;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cursorKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;agg&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="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;agg&lt;/span&gt;&lt;span class="p"&gt;,&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="nx"&gt;node&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="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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isForward&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reverse&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;pageInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;startCursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;endCursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;isForward&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;hasNextPage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;hasPreviousPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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="na"&gt;hasNextPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;hasPreviousPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hasNextPage&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;{&lt;/span&gt; &lt;span class="nx"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pageInfo&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;h1&gt;
  
  
  🚀 Conclusion
&lt;/h1&gt;

&lt;p&gt;If you've gotten this far - congratulations! You are now a pagination expert™ and are ready to take this on in the real world 🌍!&lt;/p&gt;

&lt;p&gt;For the sake of keeping this concise, I've left out a few additional steps (namely optimising cursors and input validation). If you would like to see that follow up post, be sure to let me know 💬.&lt;/p&gt;




&lt;p&gt;Thanks for reading! &lt;/p&gt;

&lt;p&gt;If you enjoyed this post, be sure to react 🦄  or drop a comment below with any thoughts 🤔. &lt;/p&gt;

&lt;p&gt;You can also hit me up on twitter - &lt;a href="https://twitter.com/andyrichardsonn"&gt;@andyrichardsonn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Disclaimer: All thoughts and opinions expressed in this article are my own.&lt;/p&gt;

</description>
      <category>graphql</category>
      <category>aws</category>
      <category>webdev</category>
      <category>node</category>
    </item>
    <item>
      <title>GraphQL pagination with DynamoDB - DynamoDB pagination
</title>
      <dc:creator>Andy Richardson</dc:creator>
      <pubDate>Wed, 28 Apr 2021 10:14:13 +0000</pubDate>
      <link>https://dev.to/andyrichardsonn/graphql-pagination-with-dynamodb-dynamodb-pagination-425m</link>
      <guid>https://dev.to/andyrichardsonn/graphql-pagination-with-dynamodb-dynamodb-pagination-425m</guid>
      <description>&lt;p&gt;Just like the aforementioned GraphQL pagination, DynamoDB also uses cursor based pagination. &lt;/p&gt;

&lt;p&gt;That being said, there are distinct differences that need to be taken into account 🔔.&lt;/p&gt;

&lt;h2&gt;
  
  
  🏎️ Traversal
&lt;/h2&gt;

&lt;p&gt;For any given query to DynamoDB, the following takes place:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In-order traversal takes place on the target table (order specified by the sort key)&lt;/li&gt;
&lt;li&gt;The provided &lt;em&gt;key condition expression&lt;/em&gt; is evaluated to find matching documents&lt;/li&gt;
&lt;li&gt;Optionally provided &lt;em&gt;filter expressions&lt;/em&gt; are used to additionally constrain the matching documents&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Inverting traversal order
&lt;/h3&gt;

&lt;p&gt;Traversal order can be inverted using the &lt;code&gt;ScanIndexForward&lt;/code&gt; attribute.&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%2Finzh49h085ym9c87wu0z.gif" 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%2Finzh49h085ym9c87wu0z.gif" alt="Inverting traversal order"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This (logically) inverted collection is traversed in reverse order, and the resulting items are also returned in reverse order.&lt;/p&gt;

&lt;h2&gt;
  
  
  📚 Paging
&lt;/h2&gt;

&lt;p&gt;As matches are found, DynamoDB adds them to a result set - a "page". If a &lt;code&gt;Limit&lt;/code&gt; is provided, DynamoDB will suspend traversal when the number of matches for the given &lt;em&gt;key condition expression&lt;/em&gt; reaches the limit.&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%2Fvz8lihpkq0djl8ylkljw.gif" 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%2Fvz8lihpkq0djl8ylkljw.gif" alt="Page size request"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately, the limit is applied prior to the filter expression evaluation; meaning a result set will never exceed the limit value, but can have a size smaller than the limit, while also having subsequent pages to follow.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔑 Cursors
&lt;/h2&gt;

&lt;p&gt;Upon the return of a page, assuming the collection hasn't been exhausted, DynamoDB provides a cursor in the form of a &lt;code&gt;LastEvaluatedKey&lt;/code&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%2F3mg2jy6ifz0qrg0ur2pb.gif" 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%2F3mg2jy6ifz0qrg0ur2pb.gif" alt="Fetching next page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Providing this value in subsequent queries via the &lt;code&gt;ExclusiveStartKey&lt;/code&gt; allows DynamoDB to continue where it left off.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exhausting a query
&lt;/h3&gt;

&lt;p&gt;In a similar fashion, this pattern can be used to retrieve all items in a query.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;exhaustQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&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="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DynamoDB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DocumentClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;QueryInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&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="o"&gt;&amp;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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;promise&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;newAgg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Items&lt;/span&gt; &lt;span class="o"&gt;||&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LastEvaluatedKey&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;newAgg&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;exhaustQuery&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="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;ExclusiveStartKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LastEvaluatedKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;newAgg&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;h3&gt;
  
  
  Cursor construction
&lt;/h3&gt;

&lt;p&gt;DynamoDB responses only provide a cursor for the position of the last evaluated element in the collection. There doesn't look to be official documentation on how cursors are constructed, but I've found the following thanks to some experimentation.&lt;/p&gt;

&lt;p&gt;It looks like DynamoDB needs two things to be able to continue where it left off:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The unique identity of the last visited item (i.e. primary key)&lt;/li&gt;
&lt;li&gt;The position in the index where said element exists (i.e. primary key of index/table)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the case of querying a table, the primary key typically consists of a partition (hash) and optionally a sort (range) key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// partition key (pk)&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Book&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;   &lt;span class="c1"&gt;// sort key (sk)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For querying an index, the same rule applies. We'll still need the attributes required to uniquely identify the element (primary key), but we also need the partition and (optional) sort attributes to get back to the previous position in the query.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// pk&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Book&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// sk, index pk&lt;/span&gt;
  &lt;span class="nx"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1618496921&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// index sk&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Thanks for reading! &lt;/p&gt;

&lt;p&gt;If you enjoyed this post, be sure to react 🦄  or drop a comment below with any thoughts 🤔. &lt;/p&gt;

&lt;p&gt;You can also hit me up on twitter - &lt;a href="https://twitter.com/andyrichardsonn" rel="noopener noreferrer"&gt;@andyrichardsonn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Disclaimer: All thoughts and opinions expressed in this article are my own.&lt;/p&gt;

</description>
      <category>graphql</category>
      <category>aws</category>
      <category>webdev</category>
      <category>node</category>
    </item>
    <item>
      <title>GraphQL pagination with DynamoDB - Cursor specification</title>
      <dc:creator>Andy Richardson</dc:creator>
      <pubDate>Wed, 28 Apr 2021 10:13:43 +0000</pubDate>
      <link>https://dev.to/andyrichardsonn/graphql-pagination-with-dynamodb-cursor-specification-5d51</link>
      <guid>https://dev.to/andyrichardsonn/graphql-pagination-with-dynamodb-cursor-specification-5d51</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Note: This is a multi-part series; if you already know about relay pagination, feel free to &lt;a href="https://dev.to/andyrichardsonn/graphql-pagination-with-dynamodb-dynamodb-pagination-425m"&gt;skip to the next section&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Relay's &lt;a href="https://relay.dev/graphql/connections.htm" rel="noopener noreferrer"&gt;Cursor Connection Specification&lt;/a&gt; has become the industry standard for pagination in GraphQL.&lt;/p&gt;

&lt;p&gt;Whether you use a Relay, Apollo, or urql, &lt;strong&gt;the spec is supported by many client side libraries&lt;/strong&gt;. This makes it an easy choice for those who don't want to implement their own client-side pagination logic (which is most of us).&lt;/p&gt;

&lt;h1&gt;
  
  
  💬 Pagination grammar
&lt;/h1&gt;

&lt;p&gt;Before we declare a pageable field, you're going to want to know about the following primitives.&lt;/p&gt;

&lt;h3&gt;
  
  
  Edge
&lt;/h3&gt;

&lt;p&gt;An &lt;a href="https://relay.dev/graphql/connections.htm#sec-Edges" rel="noopener noreferrer"&gt;&lt;em&gt;Edge&lt;/em&gt;&lt;/a&gt; is a container type which ensures that each item in a paginated response (&lt;em&gt;node&lt;/em&gt;) is accompanied with a position in the collection (&lt;em&gt;cursor&lt;/em&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UserEdge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  PageInfo
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://relay.dev/graphql/connections.htm#sec-undefined.PageInfo" rel="noopener noreferrer"&gt;&lt;em&gt;PageInfo&lt;/em&gt;&lt;/a&gt; is a metadata type which provides information about a pagination request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PageInfo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;startCursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;endCursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;hasNextPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;hasPreviousPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Connection
&lt;/h3&gt;

&lt;p&gt;A &lt;a href="https://relay.dev/graphql/connections.htm#sec-Connection-Types" rel="noopener noreferrer"&gt;&lt;em&gt;Connection&lt;/em&gt;&lt;/a&gt; is a container type which aggregates &lt;em&gt;Edge&lt;/em&gt; and &lt;em&gt;PageInfo&lt;/em&gt; values and is the root type of any paginated response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UserConnection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;UserEdge&lt;/span&gt;&lt;span class="p"&gt;!]!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;pageInfo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PageInfo&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  🎨 Creating a pageable field
&lt;/h1&gt;

&lt;p&gt;Adding pagination support to a field is pretty straightforward. The field needs to take the following four arguments - &lt;code&gt;first&lt;/code&gt;, &lt;code&gt;after&lt;/code&gt;, &lt;code&gt;last&lt;/code&gt;, and &lt;code&gt;before&lt;/code&gt; (more on this later) - and return a &lt;em&gt;Connection&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; 
    &lt;/span&gt;&lt;span class="n"&gt;before&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UserConnection&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  🏎️ Page traversal
&lt;/h1&gt;

&lt;p&gt;Paginated fields take four arguments which can be divided into two distinct groups.&lt;/p&gt;

&lt;h2&gt;
  
  
  Forward pagination
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://relay.dev/graphql/connections.htm#sec-Forward-pagination-arguments" rel="noopener noreferrer"&gt;Forward pagination&lt;/a&gt; is the process of fetching pages in a collection from top-to-bottom.&lt;/p&gt;

&lt;h3&gt;
  
  
  Arguments
&lt;/h3&gt;

&lt;p&gt;The arguments to use forward pagination are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;first&lt;/code&gt; - the number of items to return (required)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;after&lt;/code&gt; - a cursor resembling the position in the collection to start from (optional)&lt;/li&gt;
&lt;/ul&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%2F9c3wq5zlnby1yejgyya8.gif" 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%2F9c3wq5zlnby1yejgyya8.gif" alt="Forward pagination"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Retrieval
&lt;/h3&gt;

&lt;p&gt;As you can see above, the first paginated query will include only the &lt;code&gt;first&lt;/code&gt; argument. This tells the backend to start at the top of the collection.&lt;/p&gt;

&lt;p&gt;For subsequent queries the &lt;code&gt;after&lt;/code&gt; argument is used to instruct the backend where to continue from. This value corresponds to the &lt;em&gt;cursor&lt;/em&gt; of the last &lt;em&gt;edge&lt;/em&gt; on the previous response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backward pagination
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://relay.dev/graphql/connections.htm#sec-Backward-pagination-arguments" rel="noopener noreferrer"&gt;Backward pagination&lt;/a&gt; is the process of fetching pages in a collection from bottom-to-top.&lt;/p&gt;

&lt;h3&gt;
  
  
  Arguments
&lt;/h3&gt;

&lt;p&gt;The arguments to use backward pagination are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;last&lt;/code&gt; - the number of items to return (required)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;before&lt;/code&gt; - a cursor resembling the position in the collection to start from (optional)&lt;/li&gt;
&lt;/ul&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%2Fgd2t4d50xj4lgzbmhqrt.gif" 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%2Fgd2t4d50xj4lgzbmhqrt.gif" alt="Backward pagination"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Retrieval
&lt;/h3&gt;

&lt;p&gt;This second example might look familiar - that's because it's almost identical to forward pagination. &lt;/p&gt;

&lt;p&gt;Just like with forward pagination, the page size is sent in isolation for the first query (this time &lt;code&gt;last&lt;/code&gt; rather than &lt;code&gt;first&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Again, for subsequent queries, a cursor is provided to instruct the backend where to continue from (this time &lt;code&gt;before&lt;/code&gt; rather than &lt;code&gt;after&lt;/code&gt;). This value corresponds to the &lt;em&gt;cursor&lt;/em&gt; on the first (as opposed to last) &lt;em&gt;edge&lt;/em&gt; on the previous response. &lt;/p&gt;

&lt;p&gt;It's important to note, the sort order in which edges are returned is the same regardless of whether forward or backward pagination is used.&lt;/p&gt;




&lt;p&gt;Thanks for reading! &lt;/p&gt;

&lt;p&gt;If you enjoyed this post, be sure to react 🦄  or drop a comment below with any thoughts 🤔. &lt;/p&gt;

&lt;p&gt;You can also hit me up on twitter - &lt;a href="https://twitter.com/andyrichardsonn" rel="noopener noreferrer"&gt;@andyrichardsonn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Disclaimer: All thoughts and opinions expressed in this article are my own.&lt;/p&gt;

</description>
      <category>graphql</category>
      <category>aws</category>
      <category>webdev</category>
      <category>node</category>
    </item>
    <item>
      <title>How I exploited NPM downloads... and why you shouldn't trust them</title>
      <dc:creator>Andy Richardson</dc:creator>
      <pubDate>Tue, 09 Mar 2021 10:46:38 +0000</pubDate>
      <link>https://dev.to/andyrichardsonn/how-i-exploited-npm-downloads-and-why-you-shouldn-t-trust-them-4bme</link>
      <guid>https://dev.to/andyrichardsonn/how-i-exploited-npm-downloads-and-why-you-shouldn-t-trust-them-4bme</guid>
      <description>&lt;p&gt;Over this past month, I've managed to get a &lt;a href="https://www.npmjs.com/package/is-introspection-query" rel="noopener noreferrer"&gt;package, with little to no users&lt;/a&gt;, to accumulate &lt;strong&gt;over one million&lt;/strong&gt; downloads 🚀.&lt;/p&gt;

&lt;p&gt;It didn't cost any money, no laws were broken (I think) and it took little to no effort.&lt;/p&gt;

&lt;p&gt;Here's what you need to know about the downloads statistic on NPM.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔮 The illusion of downloads
&lt;/h2&gt;

&lt;p&gt;If you've ever looked at using a new package from NPM, the chances are you've considered the "Weekly Downloads" statistic. &lt;/p&gt;

&lt;p&gt;It's the first metric displayed on the page - so it must be useful information for the user... right?&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%2Fm04m8hy4cy5szhh63hkf.png" class="article-body-image-wrapper"&gt;&lt;img alt="Twitter poll" 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%2Fm04m8hy4cy5szhh63hkf.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A third of people who responded to &lt;a href="https://twitter.com/andyrichardsonn/status/1357629687619588097" rel="noopener noreferrer"&gt;this poll&lt;/a&gt; seemed to think so and even go as far as to say it has a large influence in their decision to adopt a new package.&lt;/p&gt;

&lt;p&gt;But here's the thing, it isn't a useful metric for the following two reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;there is a loose (at best) relationship between users and download counts&lt;/li&gt;
&lt;li&gt;the system is easily exploitable &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What is a download
&lt;/h3&gt;

&lt;p&gt;This was pretty well &lt;a href="https://blog.npmjs.org/post/92574016600/numeric-precision-matters-how-npm-download-counts-work.html" rel="noopener noreferrer"&gt;discussed on the NPM Blog&lt;/a&gt; but to summarise, it's any successful download of a package (tarball) from NPMs registry.&lt;/p&gt;

&lt;p&gt;NPM have openly stated that this this statistic has no consideration for the source (IP, user agent, etc). Meaning all downloads are equal, whether it be from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A user adding a new package to their project&lt;/li&gt;
&lt;li&gt;A CI run installing dependencies&lt;/li&gt;
&lt;li&gt;A bot downloading the package repeatedly to create the illusion of popularity &lt;em&gt;(there's some foreshadowing for you)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you can imagine, this means a project with frequent CI runs is likely to have more of an influence on download statistics than any set of individuals (especially when taking npm client caching into consideration).&lt;/p&gt;

&lt;h3&gt;
  
  
  Registries
&lt;/h3&gt;

&lt;p&gt;The abundance of registries is another reason why download counts aren't an accurate reflection of usage. NPM download counts only include downloads to the official NPM registry, and not registries such as &lt;a href="https://www.unpkg.com/" rel="noopener noreferrer"&gt;unpkg&lt;/a&gt; and &lt;a href="https://github.com/features/packages" rel="noopener noreferrer"&gt;github&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧑‍💻 Exploiting the system
&lt;/h2&gt;




&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; I've documented this to bring light to how easily exploitable download statistics are. However, I strongly advise that you don't do this as it is both dishonest an unnecessary drain on NPM Inc's resources.&lt;/p&gt;




&lt;p&gt;If you've read everything up until this point, you'll know that there isn't a need for any kind of "genius hacker exploit". &lt;/p&gt;

&lt;p&gt;Instead, all we need is some way of downloading a package many times.&lt;/p&gt;

&lt;p&gt;Running a script locally with some kind of cron job should do just fine - but that isn't too exciting... let's use serverless! &lt;/p&gt;

&lt;p&gt;You can check out the &lt;a href="https://github.com/andyrichardson/exploiting-npm-downloads" rel="noopener noreferrer"&gt;full repo here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a script
&lt;/h3&gt;

&lt;p&gt;For the Lambda, I created a function which takes the following arguments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;package&lt;/code&gt; - the package to download&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;probability&lt;/code&gt; - the likelihood of a download for a given run&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The latter argument is intended to add noise - simulating the variable nature of downloads over time.&lt;/p&gt;

&lt;p&gt;A "coin flip" takes place each run, with the &lt;code&gt;probability&lt;/code&gt; argument being used to weight the chance of success. If the flip is successful, the package is downloaded.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="kr"&gt;package&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;probability&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="c1"&gt;// Simulate coin flip&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;probability&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Flip fail&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;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Flip success&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;downloadPackage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="kr"&gt;package&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;h3&gt;
  
  
  Triggering the Lambda
&lt;/h3&gt;

&lt;p&gt;To get this script running routinely, a CloudWatch event was set up that triggers at a rate of once a minute.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// Terraform example&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_cloudwatch_event_rule"&lt;/span&gt; &lt;span class="s2"&gt;"lambda_trigger_rule"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"trigger-npm-install"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Trigger an NPM install"&lt;/span&gt;
  &lt;span class="nx"&gt;schedule_expression&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rate(1 minute)"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Example CloudWatch Event Rule in Terraform.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In order to do something when this event is triggered, an event target is set up, pointing to the Lambda with our required arguments. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_cloudwatch_event_target"&lt;/span&gt; &lt;span class="s2"&gt;"lambda"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_lambda_function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;install_package_lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;rule&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_cloudwatch_event_rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambda_trigger_rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;package&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"is-introspection-query"&lt;/span&gt;
    &lt;span class="nx"&gt;probability&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.8&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;&lt;em&gt;Example CloudWatch Event Target in Terraform.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 The result
&lt;/h2&gt;

&lt;p&gt;After deploying this for the duration of a week, the result is... well actually not that impressive; it turns out there aren't as many seconds in a week as I had expected 🤔.&lt;/p&gt;

&lt;p&gt;But alas, after some tweaks, we hit just under 1 million downloads per week!&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%2Fc2szqfqgl7kqko2pporl.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%2Fc2szqfqgl7kqko2pporl.png" alt="Download counts"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yes thats right, a package with &lt;strong&gt;literally 0 users&lt;/strong&gt; has more downloads than the likes of &lt;code&gt;urql&lt;/code&gt; and &lt;code&gt;mobx&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Are you seeing the problem now?&lt;/p&gt;

&lt;h3&gt;
  
  
  Download stats don't work
&lt;/h3&gt;

&lt;p&gt;Here's the thing, naive download statistics are useless at best and misleading at worst.&lt;/p&gt;

&lt;p&gt;The large graph on NPM's site, the culture of &lt;a href="https://twitter.com/kentcdodds/status/1305634347202928641" rel="noopener noreferrer"&gt;celebrating download&lt;/a&gt; &lt;a href="https://twitter.com/andyrichardsonn/status/1263041838019227648" rel="noopener noreferrer"&gt;counts&lt;/a&gt; online, the third party sites which show &lt;a href="https://www.npmtrends.com/" rel="noopener noreferrer"&gt;package download "trends&lt;/a&gt;". These all contribute to this narrative that NPM download counts provide some kind of insight into a packages popularity, and they just don't.&lt;/p&gt;

&lt;p&gt;Even ignoring the potential for malicious actors (like myself) the abundance of registries and caching implementations make these statistics less than useful.&lt;/p&gt;

&lt;h3&gt;
  
  
  "Popularity"
&lt;/h3&gt;

&lt;p&gt;Fortunately, NPM has a saving grace - the &lt;em&gt;popularity&lt;/em&gt; statistic! Let's just replace the download count with some of the more useful statistics... right?&lt;/p&gt;

&lt;p&gt;Well no - turns out the &lt;em&gt;popularity&lt;/em&gt; statistic seems to be the &lt;em&gt;downloads&lt;/em&gt; statistic in disguise. As you can see below, my package managed to surpass &lt;code&gt;@prisma/engines&lt;/code&gt; in terms of popularity. &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%2Fb4kehp71mr3ynp51xgb7.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%2Fb4kehp71mr3ynp51xgb7.png" alt="NPM search result"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's a quick comparison of the two packages side-by-side.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;@prisma/engines&lt;/th&gt;
&lt;th&gt;is-introspection-query&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;weekly downloads&lt;/td&gt;
&lt;td&gt;~100,000&lt;/td&gt;
&lt;td&gt;~800,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;stars&lt;/td&gt;
&lt;td&gt;264&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;forks&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;contributors&lt;/td&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;users&lt;/td&gt;
&lt;td&gt;probably not 0&lt;/td&gt;
&lt;td&gt;definitely 0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;If there's one thing you take away from this discussion, it's that downloads alone aren't a useful metric.&lt;/p&gt;

&lt;p&gt;While I've no doubt that NPM could create a popularity metric that aggregates a number of different attributes of a package (&lt;a href="https://npms.io/" rel="noopener noreferrer"&gt;npms.io&lt;/a&gt; has already done it), from now on, I'm going to do a little more background research before trusting the &lt;em&gt;downloads&lt;/em&gt; and &lt;em&gt;popularity&lt;/em&gt; metrics on NPM 🕵️.&lt;/p&gt;




&lt;p&gt;Hopefully, you found this interesting! If you have any thoughts or comments, feel free to drop them below or hit me up on twitter - &lt;a href="https://twitter.com/andyrichardsonn" rel="noopener noreferrer"&gt;@andyrichardsonn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Disclaimer: All thoughts and opinions expressed in this article are my own.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>security</category>
    </item>
    <item>
      <title>Whats new in Fielder V2</title>
      <dc:creator>Andy Richardson</dc:creator>
      <pubDate>Fri, 01 Jan 2021 12:34:39 +0000</pubDate>
      <link>https://dev.to/andyrichardsonn/whats-new-in-fielder-v2-5e8f</link>
      <guid>https://dev.to/andyrichardsonn/whats-new-in-fielder-v2-5e8f</guid>
      <description>&lt;p&gt;A &lt;a href="https://github.com/andyrichardson/fielder/releases/tag/v2.0.0"&gt;new major release&lt;/a&gt; of Fielder is now live 🚀🎉&lt;/p&gt;

&lt;p&gt;This latest release has been the result of countless hours exploring form design - here's what's new!&lt;/p&gt;

&lt;h2&gt;
  
  
  How we got here
&lt;/h2&gt;

&lt;p&gt;With the &lt;a href="https://dev.to/andyrichardsonn/why-we-need-another-form-library-fielder-4eah"&gt;initial release&lt;/a&gt; of Fielder, the goal was to create a form library that was unbound from existing form library constraints.&lt;/p&gt;

&lt;p&gt;Removing the rigidity of monolithic validation schemas, Fielder posed as a simpler solution for forms by coupling validation to fields rather than forms.&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;fieldProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fieldMeta&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useField&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="s2"&gt;username&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;value&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Username is required!&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Example field-first validation in Fielder V1&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This change in approach has proven to be effective in creating simple and flexible forms which can evolve over time &lt;a href="https://github.com/andyrichardson/fielder/issues/219"&gt;(with exception)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This &lt;a href="https://github.com/andyrichardson/fielder/releases/tag/v2.0.0"&gt;latest release&lt;/a&gt; compliments this mantra, continuing the focus on evolutionary and adaptive form design.&lt;/p&gt;

&lt;h2&gt;
  
  
  New features
&lt;/h2&gt;

&lt;p&gt;Along with bug fixes and optimizations, this release comes with two major features which are designed to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make validation even more flexible&lt;/li&gt;
&lt;li&gt;Remove the need for user-created form state&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Validation events
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://fielder.andyrichardson.dev/guides/validation#validation-events"&gt;Validation events&lt;/a&gt; are a new addition to Fielder which simplify specifying &lt;em&gt;when&lt;/em&gt; validation occurs and &lt;em&gt;what&lt;/em&gt; validation logic is executed for a given event.&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;usernameValidation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;trigger&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="c1"&gt;// Event agnostic validation (sync)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Username is required&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="c1"&gt;// Validation on submit (async)&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;trigger&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submit&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;isUsernameTaken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;taken&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Username is already taken&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this above example, you can see we are able to run more expensive async validation exclusively on validation events such as &lt;code&gt;submit&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Submission
&lt;/h3&gt;

&lt;p&gt;Complimenting the new validation events, there's a new &lt;code&gt;useSubmit&lt;/code&gt; hook which can be used for completion and progression in a form.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;See the new &lt;a href="https://fielder.andyrichardson.dev/guides/submission"&gt;submission guide&lt;/a&gt; in the docs&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isValidating&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hasSubmitted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleSubmit&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useSubmit&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Submit validation succeeded!&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="nx"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Trigger submission&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It returns a &lt;code&gt;handleSubmit&lt;/code&gt; function which guards submission logic from being called until validation completes.&lt;/p&gt;

&lt;p&gt;There's also additional state for tracking the status of async &lt;code&gt;submit&lt;/code&gt; validation (&lt;code&gt;isValidating&lt;/code&gt;) and for tracking whether the &lt;code&gt;handleSubmit&lt;/code&gt; function has been called (&lt;code&gt;hasSubmitted&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Combined with validation events, this hook provides all the necessary tools to do complex submit-specific validation without ever having to introduce your own state.&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;usernameValidation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;trigger&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Username is required&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trigger&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submit&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;isUsernameTaken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taken&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Username is already taken&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Form&lt;/span&gt; &lt;span class="o"&gt;=&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;usernameProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;usernameMeta&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useField&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;username&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;initialValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;usernameValidation&lt;/span&gt;  
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isValidating&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hasSubmitted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleSubmit&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useSubmit&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;values&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/submit-form&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;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&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;(&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;usernameProps&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&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;handleSubmit&lt;/span&gt;&lt;span class="si"&gt;}&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;isValidating&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Spinner&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Submit&lt;/span&gt;&lt;span class="dl"&gt;"&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;button&lt;/span&gt;&lt;span class="p"&gt;&amp;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;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;p&gt;Whether you're new or looking to update, head on over to the &lt;a href="https://fielder.andyrichardson.dev/"&gt;docs site&lt;/a&gt; to get started and be sure to check out the new &lt;a href="https://fielder.andyrichardson.dev/examples/static-forms"&gt;live examples&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Hopefully, you found this interesting! If you have any thoughts or comments, feel free to drop them below or hit me up on twitter - &lt;a href="https://twitter.com/andyrichardsonn"&gt;@andyrichardsonn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Disclaimer: All thoughts and opinions expressed in this article are my own.&lt;/p&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>We need to talk about state in React</title>
      <dc:creator>Andy Richardson</dc:creator>
      <pubDate>Thu, 10 Sep 2020 13:51:36 +0000</pubDate>
      <link>https://dev.to/andyrichardsonn/we-need-to-talk-about-state-in-react-57l5</link>
      <guid>https://dev.to/andyrichardsonn/we-need-to-talk-about-state-in-react-57l5</guid>
      <description>&lt;p&gt;Everyone loves an opportunity to slam on the big dog of frontend - React; but when it comes to state, it really is first class!&lt;/p&gt;

&lt;p&gt;Let's talk about state and explore how to make managing it a breeze.&lt;/p&gt;

&lt;h2&gt;
  
  
  Literal forms of state
&lt;/h2&gt;

&lt;p&gt;First off, it helps to understand the two forms that state can take in an application. &lt;/p&gt;

&lt;h3&gt;
  
  
  Explicit state
&lt;/h3&gt;

&lt;p&gt;In the case of modern React, this is &lt;code&gt;useState&lt;/code&gt; and &lt;code&gt;useReducer&lt;/code&gt;. Explicit state doesn't just come out of thin air - it has to be &lt;em&gt;explicitly&lt;/em&gt; created and managed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Derived state
&lt;/h3&gt;

&lt;p&gt;A psuedostate of sorts, derived state is a result of processing one or more states (explicit or derived).&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setInput&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Explicit state&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inputValid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;           &lt;span class="c1"&gt;// Derived state&lt;/span&gt;
  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;input&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;
  
  
  Choosing types of state
&lt;/h2&gt;

&lt;p&gt;Knowing whether to use explicit or derived state might seem challenging - but there's a really simple answer.&lt;/p&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Always use derived state where possible&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;



&lt;p&gt;Forgetting to stick with the above rule can lead to redundant state. &lt;/p&gt;

&lt;p&gt;Unlike redundant code, redundant state is a real problem that actually exists; and can have an impact on everything from performance to maintainability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Spotting redundancy
&lt;/h3&gt;

&lt;p&gt;If you've ever written something like the following - I know I have - you've probably been guilty of creating redundant state.&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isValid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsValid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;setIsValid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;value&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;A &lt;code&gt;useEffect&lt;/code&gt; call which immediately calls a &lt;code&gt;setState&lt;/code&gt; function is almost always an example of state that should be derived.&lt;/p&gt;

&lt;p&gt;It doesn't seem like the worst thing in the world, and on it's own, it probably isn't. That being said, if this pattern  exists, there's a good chance it exists in many places and can lead to a larger problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  useEffect hell
&lt;/h3&gt;

&lt;p&gt;Most of us have been on a project that has gone through &lt;em&gt;useEffect hell&lt;/em&gt;. Trying to fix that one bug but being unable to trace it because a single state change causes a flurry of new renders.&lt;/p&gt;

&lt;p&gt;The thing with &lt;code&gt;useEffect&lt;/code&gt; is it can cause a cascading number of state updates... which in turn, can cause subsequent &lt;code&gt;useEffect&lt;/code&gt; calls. This isn't an issue with the function itself - it's an issue with excessive state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tips for managing state
&lt;/h2&gt;

&lt;p&gt;If I had one piece of advice for managing state, it would be to keep it to a minimum... but I don't have one just one piece of advice - so here's some more!&lt;/p&gt;

&lt;h3&gt;
  
  
  Batch state updates
&lt;/h3&gt;

&lt;p&gt;When multiple state updates are being called at one time, it's useful to batch these together into one call.&lt;/p&gt;

&lt;h5&gt;
  
  
  With batching
&lt;/h5&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="p"&gt;[{&lt;/span&gt; &lt;span class="nx"&gt;fetching&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; 
  &lt;span class="na"&gt;fetching&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; 
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;useEffect&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="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;getData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;fetching&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&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;// State 1: { fetching: true, data: undefined }&lt;/span&gt;
&lt;span class="c1"&gt;// State 2: { fetching: false, data: 1234 }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Without batching
&lt;/h5&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;fetching&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setFetching&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setData&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;useEffect&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="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;getData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;setFetching&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;setData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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;// State 1: { fetching: true, data: undefined }&lt;/span&gt;
&lt;span class="c1"&gt;// State 2: { fetching: false, data: undefined } &lt;/span&gt;
&lt;span class="c1"&gt;// State 3: { fetching: false, data: 1234 }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Batched updates don't just mean fewer renders, there will be fewer possible states to deal with; making testing and reproductions much simpler.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Remember, fewer renders means fewer state changes&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Use fixtures
&lt;/h3&gt;

&lt;p&gt;Fixtures (or stories) are an incredible tool for understanding, modelling, and documenting all the states of your app. &lt;/p&gt;

&lt;p&gt;Find out more about fixtures &lt;a href="https://dev.to/andyrichardsonn/fixture-first-development-14fk"&gt;over here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Try useMemo more often
&lt;/h3&gt;

&lt;p&gt;It's surprising &lt;a href="https://dev.to/andyrichardsonn/why-i-almost-always-usememo-and-usecallback-4776"&gt;how much of an impact it can make&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Hopefully, you found this interesting! If you have any thoughts or comments, feel free to drop them below or hit me up on twitter - &lt;a class="mentioned-user" href="https://dev.to/andyrichardsonn"&gt;@andyrichardsonn&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Disclaimer: All thoughts and opinions expressed in this article are my own.&lt;/p&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Getting a Browser Extension Through Review</title>
      <dc:creator>Andy Richardson</dc:creator>
      <pubDate>Fri, 19 Jun 2020 10:47:41 +0000</pubDate>
      <link>https://dev.to/andyrichardsonn/getting-a-browser-extension-through-review-5cce</link>
      <guid>https://dev.to/andyrichardsonn/getting-a-browser-extension-through-review-5cce</guid>
      <description>&lt;p&gt;Browser extensions can be confusing at the best of times, but recently we've seen that the current state of affairs is causing web store reviews to take weeks and there is evidence to show that &lt;a href="https://blog.pushbullet.com/2020/05/13/lets-guess-what-google-requires-in-14-days-or-they-kill-our-extension/"&gt;nobody is safe&lt;/a&gt; from the wrath of the review teams. &lt;/p&gt;

&lt;p&gt;Needless to say, whether you're publishing your first browser extension or already have one listed, it's worth making sure your submissions are prepared for a rigorous review.&lt;/p&gt;

&lt;p&gt;Here's a quick run-through for how you can lock down your browser extension and increase your chance of getting your extension approved swiftly.&lt;/p&gt;

&lt;h1&gt;
  
  
  Timing your permissions
&lt;/h1&gt;

&lt;p&gt;The fewer permissions your extension requires to run, the faster it's likely going to get through review. One of the ways you can trim down permissions without changing functionality is by only requesting additional permissions on an ad-hoc basis.&lt;/p&gt;

&lt;h2&gt;
  
  
  👎 Install-time permissions
&lt;/h2&gt;

&lt;p&gt;The simplest approach, called "install-time permissions", involves an extension requesting, and being granted, permissions upon installation. Once installed, this allows the given extension to make use of its privileges at any time.&lt;/p&gt;

&lt;p&gt;As you can imagine, this is a pattern that does little to restrict extensions functioning outside of their advertised feature-set. Most other ecosystems (see &lt;a href="https://developer.android.com/guide/topics/permissions/overview#runtime_requests_android_60_and_higher"&gt;Android&lt;/a&gt;, &lt;a href="https://developer.apple.com/design/human-interface-guidelines/ios/app-architecture/requesting-permission/"&gt;IOS&lt;/a&gt; and &lt;a href="https://www.w3.org/TR/permissions/#requesting-more-permission"&gt;W3&lt;/a&gt;) have tried to avoid or phase out this approach in favor of runtime permissions.&lt;/p&gt;

&lt;p&gt;Consider an extension that granted install-time permissions for accessing all webpages and the microphone. Following installation, such an extension has the potential to do the following without requiring further approval:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read any webpage content  — bank details, messages, network requests&lt;/li&gt;
&lt;li&gt;Manipulate any webpage content  — inject adverts, political propaganda, &lt;a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ"&gt;Rick Astley videos&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Listen to any user events — see key presses (including passwords), clicks, network traffic&lt;/li&gt;
&lt;li&gt;Record audio at any time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mx-gGeMr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://formidable.com/uploads/install_prompt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mx-gGeMr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://formidable.com/uploads/install_prompt.png" alt="Chrome install prompt for &amp;lt;all_urls&amp;gt;"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Chrome install prompt for &lt;em&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Because permissions are granted in advance and the user has no way to regulate these permissions following installation, be prepared for meticulous reviews and publishing delays. &lt;/p&gt;

&lt;h2&gt;
  
  
  👍 Runtime permissions
&lt;/h2&gt;

&lt;p&gt;Runtime permissions are a safer option for both users and extension developers. They work in a similar fashion to most other platforms by requesting no special permissions at install time and instead, prompt the user for additional permissions as-and-when needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7xY1-kGS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://formidable.com/uploads/install_prompt-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7xY1-kGS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://formidable.com/uploads/install_prompt-1.png" alt="Chrome install prompt for optional permissions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Chrome install prompt for optional permissions&lt;/p&gt;

&lt;p&gt;While this can take more time to implement, by using this method users can have a better idea as to when and why an extension is using any privileges. This also provides a means for the user to use a given extension with limited functionality, as opposed to the all-or-nothing case that install-time permissions offer.&lt;/p&gt;

&lt;p&gt;The major caveat that comes with runtime permissions is the requirement that a permission request &lt;strong&gt;must be triggered inside of a user event handler&lt;/strong&gt;. In other words, there is no way to programmatically trigger a permission request unless it is immediately following a user gesture. &lt;/p&gt;

&lt;h1&gt;
  
  
  Requesting webpage permissions
&lt;/h1&gt;

&lt;p&gt;There are a &lt;a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/permissions#Host_permissions"&gt;few different ways&lt;/a&gt; a webpage can request permission to access a webpage — with some being better than others.&lt;/p&gt;

&lt;h2&gt;
  
  
  🥇 Active tab
&lt;/h2&gt;

&lt;p&gt;For cases where a content script is required to run on the current active tab and &lt;strong&gt;only&lt;/strong&gt; following a &lt;a href="https://developer.chrome.com/extensions/activeTab#invoking-activeTab"&gt;user invocation&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"activeTab"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Example use of active tab permissions in an extension manifest.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Because this permission is technically always granted at runtime (by user invocation), declaring it as an install-time permission is common practice and will not result in any warnings to the user.&lt;/p&gt;

&lt;p&gt;You might want to use this if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;your extension can be &lt;strong&gt;invoked by a user action&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;your extension only needs &lt;strong&gt;permission for the current tab&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🥈 Match patterns
&lt;/h2&gt;

&lt;p&gt;This is a host permission, intended for extensions where a content script is required to run on specific domains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"https://my.domain.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://*.domain.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Subdomain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;wildcard&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;or&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"optional_permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"*://my.domain.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Scheme&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;wildcard&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(https|http|ws)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"https://my.domain.com/*"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;wildcard&lt;/span&gt;&lt;span class="s2"&gt;"
],
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Example use of scoped host permissions in an extension manifest.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You might want to use this if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;your extension needs &lt;strong&gt;permission for specific known URLs&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;your extension &lt;strong&gt;can't use activeTab&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🥉 All URLs
&lt;/h2&gt;

&lt;p&gt;This is the &lt;strong&gt;broadest host permission scope&lt;/strong&gt; intended for extensions where a content script is required to run on all webpages without user invocation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;all_urls&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;or&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"optional_permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;all_urls&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Example use of  in an extension manifest.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You might want to use this if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;your extension needs &lt;strong&gt;permission for all URLs&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;your extension &lt;strong&gt;can't use activeTab&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Preventing unsafe code
&lt;/h1&gt;

&lt;p&gt;While reviewers will be spending a fair amount of time reviewing the code you submitted, that's not all that needs to be considered. There is likely a fair amount of code that is also included with your extension and has not been explicitly written by you (such as third-party dependencies).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Not only does your code need to be assessed for malicious content, dependencies are also an attack vector.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🛡 Content Security Policies (CSP)
&lt;/h2&gt;

&lt;p&gt;Just like websites, browser extensions can make use of a  &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP"&gt;Content Security Policy&lt;/a&gt; (CSP) to limit how code can be sourced and run in the browser.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"content_security_policy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"script-src 'self';"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Example use of a content security policy in an extension manifest.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;While we haven't yet found an automated solution for injecting safe checksums into the extension manifest at build time, there is a &lt;a href="https://github.com/slackhq/csp-html-webpack-plugin"&gt;webpack plugin&lt;/a&gt; to do this for webpages — an additional layer of security if your extension includes any HTML pages (such as a devtools panel).&lt;/p&gt;

&lt;h2&gt;
  
  
  🕵️‍♀️ Linting your extension
&lt;/h2&gt;

&lt;p&gt;The team over at Mozilla have created a great CLI for browser extensions called &lt;a href="https://github.com/mozilla/web-ext"&gt;web-ext&lt;/a&gt;. It includes a bunch of neat features for running, building, and testing your extension; and most importantly, it can lint extensions to flag vulnerabilities.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--33D7vit7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://formidable.com/uploads/screenshot_2020-05-21_at_11.43.02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--33D7vit7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://formidable.com/uploads/screenshot_2020-05-21_at_11.43.02.png" alt="Output from running web-ext lint"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Output from running &lt;em&gt;web-ext lint&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;From issues in the manifest to unsafe practices in your JS, this one catches a myriad of issues and &lt;strong&gt;is a must&lt;/strong&gt; for anyone building a browser extension. &lt;/p&gt;

&lt;p&gt;There's a &lt;a href="https://blog.mozilla.org/addons/2015/11/25/a-new-firefox-add-ons-validator/"&gt;chance that this will be used by store reviewers&lt;/a&gt; when assessing your extension so be sure to address any flagged issues before publishing.&lt;/p&gt;

&lt;h1&gt;
  
  
  Sealing the deal
&lt;/h1&gt;

&lt;p&gt;For the time being, browser extension reviews are being done by humans, not an automated program. Reviewers are the number one people to please as they have the power to accept or deny your listing onto the web store, so be sure to make their life as easy as possible and get them on your side.&lt;/p&gt;

&lt;h2&gt;
  
  
  📘 Document the purpose
&lt;/h2&gt;

&lt;p&gt;Try to document what the purpose of the extension is, and make it clear why the extension requests the permissions it does. &lt;/p&gt;

&lt;p&gt;For example, a "dark mode" extension that accesses your webcam without explanation could trigger alarm bells. If it's explained that the webcam is used to detect ambient light, this is less likely to be the case.&lt;/p&gt;

&lt;h2&gt;
  
  
  🗺 How did you get there
&lt;/h2&gt;

&lt;p&gt;Nobody likes reading minified code. If there is a build process to publish your extension, make sure to provide the source code and instructions on how to reproduce the built files.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Linking to the open source repo is another great way to gain trust.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🦺 Be conservative
&lt;/h2&gt;

&lt;p&gt;A secure extension which uses CSPs, minimal permissions, and minimal dependencies should take less time to review. &lt;/p&gt;

&lt;h1&gt;
  
  
  This process isn't perfect
&lt;/h1&gt;

&lt;p&gt;Even if you follow all these guidelines, there's no guarantee your project will be reviewed in a short amount of time. Manual reviews naturally take a long time and there are plenty of good reasons why some of the prior advice can't be followed.&lt;/p&gt;

&lt;p&gt;Our very own &lt;a href="https://github.com/FormidableLabs/urql-devtools"&gt;urql Devtools&lt;/a&gt; is a great example of this. Like many other devtools extensions, anyone with our extension installed will have &lt;a href="https://github.com/FormidableLabs/urql-devtools/blob/master/src/extension/content_script.ts"&gt;a content script&lt;/a&gt; run on every page they visit (i.e. install-time permissions for &lt;code&gt;&amp;lt;all_urls&amp;gt;&lt;/code&gt; ). &lt;/p&gt;

&lt;p&gt;Frankly, we don't want this to be the case, but the current state of browser extension permissions means it's just not possible to request permission to access a page at runtime when a user opens the devtools panel (as this does not constitute a "user gesture").&lt;/p&gt;

&lt;h3&gt;
  
  
  Can browser extensions be even safer in the future?
&lt;/h3&gt;

&lt;p&gt;Making suggestions for browser standards is well outside of my skill-set and I'm sure there are some very smart people actively working on this.&lt;/p&gt;

&lt;p&gt;One thing I would like to see is a continued movement towards reducing the number of browser extensions requesting permissions at install-time. The limitation in which extensions can only request permissions following a user gesture was clearly to prevent permission spam. I agree with the incentive but I suspect this decision has led to many extensions (including devtools extensions) being cornered into requesting more than permissions than they actually need. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YpDoT1o3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://formidable.com/uploads/frame_5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YpDoT1o3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://formidable.com/uploads/frame_5.png" alt="Permission prompt in Android 10"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Prevention of permission request spam in Android 10&lt;/p&gt;

&lt;p&gt;Hopefully, in the future, we'll see a process that follows in the footsteps of other platforms that have managed to give users dynamic permission control while also preventing permission request spam.&lt;/p&gt;

&lt;h1&gt;
  
  
  Additional resources
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/FormidableLabs/urql-devtools"&gt;Formidable urql Devtools on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.mozilla.org/en-US/kb/review-installed-extensions"&gt;Mozilla Review installed extensions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://extensionworkshop.com/documentation/develop/getting-started-with-web-ext/"&gt;Mozilla Getting started with web-ext&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.chrome.com/apps/permissions"&gt;Google Chrome permissions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.chrome.com/webstore/program_policies"&gt;Google Chrome webstore policy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Credits
&lt;/h1&gt;

&lt;p&gt;Special thanks to &lt;a href="https://twitter.com/umaar"&gt;Umar Hansa (@umaar)&lt;/a&gt; for the peer review!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post was made thanks to the the support of &lt;a href="https://formidable.com/"&gt;Formidable&lt;/a&gt;. Check out the &lt;a href="https://formidable.com/blog/2020/extension-reviews/"&gt;original post here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>security</category>
      <category>webextension</category>
    </item>
    <item>
      <title>urql Devtools: The Road to V1</title>
      <dc:creator>Andy Richardson</dc:creator>
      <pubDate>Fri, 22 May 2020 16:48:00 +0000</pubDate>
      <link>https://dev.to/andyrichardsonn/urql-devtools-the-road-to-v1-3okk</link>
      <guid>https://dev.to/andyrichardsonn/urql-devtools-the-road-to-v1-3okk</guid>
      <description>&lt;p&gt;In June 2019 we (the urql team) decided to set an adventurous goal of creating fully-featured developer tools for our GraphQL client.&lt;/p&gt;

&lt;p&gt;10+ months, 10+ contributors, and 100+ pull requests later, we're proud to announce our first major release! Read below for the journey on how we got there or skip to the announcement.&lt;/p&gt;

&lt;h2&gt;
  
  
  📦 Setting up an extension
&lt;/h2&gt;

&lt;p&gt;One of the biggest hurdles we encountered when creating our first devtools extension was the initial boilerplate. For good reason, web pages can't talk directly to extensions or devtools panels without a &lt;a href="https://twitter.com/andyrichardsonn/status/1243526227085987840"&gt;myriad of communication layers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We looked into existing implementations but found that, given their maturity, there was a lot more going on than just messaging. Because of this we decided to take our own approach by making use of an &lt;em&gt;EventTarget&lt;/em&gt; in our &lt;a href="https://developer.chrome.com/extensions/background_pages"&gt;background.js&lt;/a&gt; to route messages from our browser windows to their respective devtools panel.&lt;/p&gt;

&lt;p&gt;We've found that this simpler approach, while it does have caveats, has worked great for our use cases. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Having also received great feedback from others working on browser extensions in the community, keep your eyes peeled for a boilerplate project / tutorial from us in the near future!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  💬 Sending messages from urql
&lt;/h2&gt;

&lt;p&gt;Once messaging from the webpage to the devtools panel had been completed, it was time to put our money where our mouth is. urql has been created with extensibility in mind and we've been very vocal about this — so how did it do?&lt;/p&gt;

&lt;p&gt;(Un)surprisingly well! Without modifications to the core urql client we were able to make an exchange which could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;See all incoming messages and responses&lt;/li&gt;
&lt;li&gt;Inspect the state of the cache&lt;/li&gt;
&lt;li&gt;Trigger GraphQL requests (sent from the extension)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This more than sufficed for our early pre-releases, but as time went on we found that we wanted more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Introducing the debug target
&lt;/h3&gt;

&lt;p&gt;Implementation details are an important part of any developer tool and over time it became apparent that we needed to find a way to expose the internal events happening inside of exchanges. A few examples include network triggers and responses (&lt;em&gt;fetchExchange&lt;/em&gt;), cache invalidation (&lt;em&gt;cacheExchange&lt;/em&gt;), and any other events which would be useful for debugging purposes.&lt;/p&gt;

&lt;p&gt;In order to accommodate for this, we've added additional debugging capabilities to urql as of &lt;em&gt;v1.11.x.&lt;/em&gt; It works a little something like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The urql client creates a &lt;em&gt;debug source&lt;/em&gt; on creation&lt;/li&gt;
&lt;li&gt;A &lt;em&gt;dispatchDebug&lt;/em&gt; function is passed to every exchange&lt;/li&gt;
&lt;li&gt;Exchanges can call this function to dispatch debugging events (at any time)&lt;/li&gt;
&lt;li&gt;Anyone with access to the client can listen to these events&lt;/li&gt;
&lt;li&gt;More details can be found in the &lt;a href="https://formidable.com/open-source/urql/docs/advanced/debugging/"&gt;new debugging section&lt;/a&gt; of the docs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the devtools extension, this means that we can listen to debug messages coming from any exchanges and create a debugging experience for our users which is implementation agnostic - in other words, you could create a new exchange today and seamlessly have debugging info shown in the devtools extension just by calling &lt;em&gt;dispatchDebug&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🛠 Building out the panel
&lt;/h2&gt;

&lt;p&gt;At this point, we've now got messages coming from urql to the panel, so all we need to do now is create a webpage to present them... right? Well kind of...&lt;/p&gt;

&lt;h3&gt;
  
  
  Developer experience
&lt;/h3&gt;

&lt;p&gt;In our first few months of working on the "frontend" of our extension, we found the developer experience to be rough, at best. Live reloading just wasn't an option, many changes would require us to reload the whole extension, and triggering/mocking debug events was a painful experience. &lt;/p&gt;

&lt;p&gt;The biggest productivity booster, by far, was a few months in when we made the choice to &lt;a href="https://formidable.com/blog/2020/fixture-first/"&gt;develop using fixtures&lt;/a&gt;. This came with a whole host of benefits — from lowering the barrier to entry for new contributors, to faster updates thanks to the addition of hot reloading. &lt;/p&gt;

&lt;p&gt;It also gave our team much more confidence in changes being made. We can quickly see the changes to fixtures while reviewing a PR and all our expected states are now modeled. To add to that latter point, fixtures allowed us to implement visual regression testing which would prove to flag unexpected visual and functional anomalies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Design
&lt;/h3&gt;

&lt;p&gt;Many of us working on this project have some kind of design experience in one form or another. As for design experience with browser extensions... not so much.&lt;/p&gt;

&lt;p&gt;Data-heavy designs can be hard at the best of times; but add in the unique standards for browser panels such as smaller base font sizes, conservative use of spacing and color, and different interaction patterns - it doesn't take long to realize we're not in &lt;del&gt;Kansas&lt;/del&gt; Bootstrap anymore.&lt;/p&gt;

&lt;p&gt;While we're still learning, here are a few findings we've made on the way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Acknowledge existing design languages - Chrome and Firefox have some great devtools so use them as a starting point&lt;/li&gt;
&lt;li&gt;Aim for a highly static layout - theres going to be a lot of information on screen and having dynamic content such as panes appear and disappear can be more jarring than useful&lt;/li&gt;
&lt;li&gt;Keep your information hierarchy flat - there's just not enough space for a deeply nested hierarchy (i.e. elements such as h1, h2, h3, etc) so flatter is better&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🚀 The end result
&lt;/h2&gt;

&lt;p&gt;After all that work, we're happy to announce that urql Devtools v1 is out! Here's what it does to make your GraphQL development experience even better!&lt;/p&gt;

&lt;h3&gt;
  
  
  Event timeline
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Visualize all debugging events from your exchanges&lt;/li&gt;
&lt;li&gt;Track queries, mutations, and subscriptions that have been executed&lt;/li&gt;
&lt;li&gt;See network and cache updates, responses, and errors&lt;/li&gt;
&lt;li&gt;Locate which components are triggering GraphQL requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uLo85xHS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://formidable.com/uploads/a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uLo85xHS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://formidable.com/uploads/a.png" alt="Road%20to%20V1/A.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;events&lt;/em&gt; panel in urql Devtools v1.0.0&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache Explorer
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Explore an interactive overview of your cache&lt;/li&gt;
&lt;li&gt;See which results are coming from the cache&lt;/li&gt;
&lt;li&gt;Identify updates to cached data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VVuuLyOc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://formidable.com/uploads/b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VVuuLyOc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://formidable.com/uploads/b.png" alt="Road%20to%20V1/B.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;explorer&lt;/em&gt; panel in urql Devtools v1.0.0&lt;/p&gt;

&lt;h3&gt;
  
  
  Request tool
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Trigger GraphQL requests directly via urql client&lt;/li&gt;
&lt;li&gt;Easily change the state of your app&lt;/li&gt;
&lt;li&gt;Explore your backend schema&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--txZDxPDV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://formidable.com/uploads/c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--txZDxPDV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://formidable.com/uploads/c.png" alt="Road%20to%20V1/C.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;request&lt;/em&gt; panel in urql Devtools v1.0.0&lt;/p&gt;

&lt;h2&gt;
  
  
  🙏 We did it!
&lt;/h2&gt;

&lt;p&gt;A special thanks to everyone in the community who helped make this happen! &lt;/p&gt;

&lt;p&gt;Whether you contributed code, reported issues, created feature requests, or provided feedback — you've helped make the urql developer experience what it is! We look forward to seeing the ecosystem continue to blossom 🌻&lt;/p&gt;

&lt;p&gt;To find out more or get involved, check out the resources below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/FormidableLabs/urql-devtools"&gt;Devtools on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/FormidableLabs/urql"&gt;urql on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://spectrum.chat/urql"&gt;Spectrum chat&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://formidable.com/open-source/urql/docs/"&gt;Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This post was made thanks to the the support of &lt;a href="https://formidable.com/"&gt;Formidable&lt;/a&gt;. Check out the &lt;a href="https://formidable.com/blog/2020/urql-devtools/"&gt;original post here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Going back to basics: Conditionals</title>
      <dc:creator>Andy Richardson</dc:creator>
      <pubDate>Thu, 12 Mar 2020 12:43:21 +0000</pubDate>
      <link>https://dev.to/andyrichardsonn/going-back-to-basics-conditionals-369e</link>
      <guid>https://dev.to/andyrichardsonn/going-back-to-basics-conditionals-369e</guid>
      <description>&lt;p&gt;If you're reading this, there's a good chance you learned how to use conditionals years ago.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If not, throw Error("learn conditionals")&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Given how early on we learn about them, it's easy to overlook the impact of conditional management and how conditions can spiral out of control.&lt;/p&gt;

&lt;p&gt;Below are a few real world examples of code I've seen in the wild and some tips on how to avoid these traps!&lt;/p&gt;

&lt;h2&gt;
  
  
  Avoiding reassignment
&lt;/h2&gt;

&lt;p&gt;Starting off with the basics, if you're able to assign a value at declaration time, you have one less moving part in your code.&lt;/p&gt;

&lt;p&gt;Here's a basic example where reassignment is used.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;x&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;y&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;large&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;small&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the following example, we remove the need for reassignment by using a ternary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;large&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;small&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your language doesn't support ternaries, or you have more than two conditions to consider, you can opt for isolating your conditional logic into a function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: A good exception to this rule is when iterating through collections&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Flattening conditionals
&lt;/h2&gt;

&lt;p&gt;Another tip is to keep your conditions flat - even if this means reusing a value inside of your conditional check.&lt;/p&gt;

&lt;p&gt;This trims down the number of conditions you need to work with.&lt;/p&gt;

&lt;p&gt;Here's an example of nested conditionals.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&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;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;large&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;small&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unknown&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here is an identical function body but with flattened conditionals.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;large&lt;/span&gt;&lt;span class="dl"&gt;"&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;x&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;5&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;small&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;x&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unknown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example segues us nicely onto this next tip...&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling undesirable states early on
&lt;/h2&gt;

&lt;p&gt;Anyone who has used JavaScript callbacks likely recognises this pattern&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;doSomething&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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;handleError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;handleData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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;By handling undesirable states early on with a &lt;em&gt;conditional guard&lt;/em&gt;, we add a layer of safety to all successive code and remove the need to re-check.&lt;/p&gt;

&lt;p&gt;We can apply this pattern to our flattening conditionals example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;x&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unknown&lt;/span&gt;&lt;span class="dl"&gt;"&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;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;large&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="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;small&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember, &lt;strong&gt;the logic for this is identical to the earlier nested conditional example&lt;/strong&gt;. Which do you think is easier to read?&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;These few tips are a solid guideline for how to break out your code and can have a substantial impact on reducing the number of &lt;em&gt;moving parts&lt;/em&gt; and &lt;em&gt;interleaving conditions&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;When writing code with lots of conditional logic, remember to ask yourself&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What are the different resulting states/actions that can occur&lt;/li&gt;
&lt;li&gt;What conditions are required to meet these states&lt;/li&gt;
&lt;li&gt;How can I implement them in a way that is &lt;em&gt;mutually exclusive&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Keep things static, return often, and take a moment to think about your logic.&lt;/p&gt;




&lt;p&gt;Hopefully, you found this interesting! If you have any thoughts or comments, feel free to drop them below or hit me up on twitter - &lt;a class="mentioned-user" href="https://dev.to/andyrichardsonn"&gt;@andyrichardsonn&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclaimer: All opinions expressed in this article are my own.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>Fixture-First Development</title>
      <dc:creator>Andy Richardson</dc:creator>
      <pubDate>Wed, 04 Mar 2020 10:43:19 +0000</pubDate>
      <link>https://dev.to/andyrichardsonn/fixture-first-development-14fk</link>
      <guid>https://dev.to/andyrichardsonn/fixture-first-development-14fk</guid>
      <description>&lt;p&gt;When you hear the word "Storybook," you probably think &lt;em&gt;UI libraries&lt;/em&gt;. Tools like &lt;a href="https://github.com/storybookjs/storybook"&gt;Storybook&lt;/a&gt; and &lt;a href="https://github.com/react-cosmos/react-cosmos"&gt;Cosmos&lt;/a&gt; have been around for a few years now and do a pretty awesome job of presenting UI components in isolation. What most don't consider, however, is how these tools can go beyond just presenting UI components.&lt;/p&gt;

&lt;p&gt;Let's talk about this!&lt;/p&gt;

&lt;h2&gt;
  
  
  Thinking in state
&lt;/h2&gt;

&lt;p&gt;Consider the typical &lt;em&gt;Button&lt;/em&gt; component in a UI library. When designing or implementing this component, one of the key considerations we ask ourselves is&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What states will this button have?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Things might start off with a few simple states such as &lt;em&gt;default&lt;/em&gt; and &lt;em&gt;disabled&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Then comes the interactive states such as &lt;em&gt;hovered&lt;/em&gt; and &lt;em&gt;active&lt;/em&gt;...&lt;/p&gt;

&lt;p&gt;Then &lt;em&gt;primary&lt;/em&gt; and &lt;em&gt;secondary&lt;/em&gt;...&lt;/p&gt;

&lt;p&gt;Then &lt;em&gt;primary disabled hovered&lt;/em&gt; and &lt;em&gt;secondary disabled hovered&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Before you know it, you have many states to consider and keep tracking.&lt;/p&gt;

&lt;p&gt;This is when creating fixtures (or stories) starts to provide some real benefit. A fixture is a way of &lt;strong&gt;fixing&lt;/strong&gt; the state of a component and modelling it in a browser environment. By doing this, we document our many states and also provide a means for quickly reproducing them during development and testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compositional components
&lt;/h3&gt;

&lt;p&gt;Moving higher up the component tree, it's easy to lose this state-first way of thinking about components. As scope increases, the core &lt;a href="https://dev.to/andyrichardsonn/simplifying-react-component-testing-1fmi"&gt;responsibilities of components&lt;/a&gt; don't change&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rendering output&lt;/li&gt;
&lt;li&gt;Triggering side effects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While fixtures don't always help us demonstrate side effects, we can always use them as a means of modelling state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working in isolation
&lt;/h2&gt;

&lt;p&gt;One of the first pages in the official React docs - &lt;a href="https://reactjs.org/docs/components-and-props.html"&gt;Components and Props&lt;/a&gt; - states the following.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Components let you split the UI into independent, reusable pieces, and &lt;strong&gt;think about each piece in isolation.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Somewhere along the way, I think we forgot &lt;strong&gt;this&lt;/strong&gt; is why we, as a community, chose to use React and not a page-scoped solution such as jQuery.&lt;/p&gt;

&lt;p&gt;While focusing on integration is clearly important, there's huge value in being able to change and test components in isolation.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this looks like
&lt;/h3&gt;

&lt;p&gt;Here's an example of a Page component that has many states that are dependent on a network request and its response:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GBA7zacw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://formidable.com/uploads/kapture_2020-02-19_at_16.19.33.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GBA7zacw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://formidable.com/uploads/kapture_2020-02-19_at_16.19.33.gif" alt="Fixture development"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Believe it or not, everything you see above was made in total isolation and without spinning up the full site. Note how we can simulate states of our GraphQL client such as &lt;em&gt;fetching&lt;/em&gt; and &lt;em&gt;error&lt;/em&gt; without any magic — just fixtures and state.&lt;/p&gt;

&lt;p&gt;Because React allows us to think of each piece in isolation, there's much less overhead required to do front-end work than you might think. Sure, we eventually need to bring everything together, but that is a small part of the whole development process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating Fixtures
&lt;/h2&gt;

&lt;p&gt;Depending on what tool you choose to use, the way in which you create fixtures will differ, but the process will almost always be the same.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Find the component you want to work on
&lt;/h3&gt;

&lt;p&gt;Each project is different but you'll likely want to create fixtures for &lt;strong&gt;macro-components&lt;/strong&gt; such as pages, forms, cards and modals.&lt;/p&gt;

&lt;p&gt;For this example let's assume we're working with a page component that makes a GraphQL request and presents the state of that request to the user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PostsPage&lt;/span&gt; &lt;span class="o"&gt;=&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;getPostsState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refetch&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useQuery&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="nx"&gt;gql&lt;/span&gt;&lt;span class="s2"&gt;`
      query GetPosts {
        posts {
          id
          title
          content
        }
      }
    `&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;getPostsState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetching&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;(&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ContentCentered&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Spinner&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/ContentCentered&lt;/span&gt;&lt;span class="err"&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getPostsState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&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;(&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ContentCentered&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Icon&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;warning&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&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="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;getPosts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/ContentCentered&lt;/span&gt;&lt;span class="err"&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getPostsState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&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;(&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ContentCentered&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Icon&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;empty&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;No&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="nx"&gt;found&lt;/span&gt;&lt;span class="o"&gt;!&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/ContentCentered&lt;/span&gt;&lt;span class="err"&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Content&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;getPostsState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PostCard&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;post&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="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Content&lt;/span&gt;&lt;span class="err"&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;h3&gt;
  
  
  2. Setup the props and contexts for all key states
&lt;/h3&gt;

&lt;p&gt;Once a component has been decided, it's time to work out what key states would be useful to have in a fixture. In our case, the key states of this page component are&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetching&lt;/li&gt;
&lt;li&gt;Error&lt;/li&gt;
&lt;li&gt;Empty list&lt;/li&gt;
&lt;li&gt;Populated list&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's an example fixture that mocks the key states noted above for the &lt;em&gt;PostsPage&lt;/em&gt; component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchingState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;executeQuery&lt;/span&gt;&lt;span class="p"&gt;:&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="na"&gt;fetching&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;errorState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;executeQuery&lt;/span&gt;&lt;span class="p"&gt;:&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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Something went wrong&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;emptyState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;executeQuery&lt;/span&gt;&lt;span class="p"&gt;:&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="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;posts&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dataState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;executeQuery&lt;/span&gt;&lt;span class="p"&gt;:&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="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;posts&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="mi"&gt;1&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="s2"&gt;My post&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;fetching&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;GraphqlProvider&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;fetchingState&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PostsPage&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;GraphqlProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;GraphqlProvider&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;errorState&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PostsPage&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;GraphqlProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;GraphqlProvider&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;emptyState&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PostsPage&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;GraphqlProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;GraphqlProvider&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;dataState&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PostsPage&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;GraphqlProvider&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;Since hooks have replaced high-order components, you're going to find yourself mocking contexts more often, so get used to it!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: Most libraries don't document how to mock their context so you might have to dive into some code (or do some console.logs) to find out what the different states of the context look like.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We've now added &lt;a href="https://github.com/FormidableLabs/urql/blob/master/docs/advanced/testing.md"&gt;dedicated documentation on mocking out context&lt;/a&gt; to the urql docs and encourage other library authors to do the same!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3. Develop within those fixtures
&lt;/h3&gt;

&lt;p&gt;Once your fixtures are in place, you can test, style, and change logic within components quickly and without distraction! 🎉&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5Raz1Lkp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://formidable.com/uploads/kapture_2020-02-24_at_10.43.53.2020-02-24_10_53_03.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5Raz1Lkp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://formidable.com/uploads/kapture_2020-02-24_at_10.43.53.2020-02-24_10_53_03.gif" alt="Development with fixtures"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fixtures can also be used for automated testing such as visual regression, component snapshots, and functional testing.&lt;/p&gt;

&lt;p&gt;Be mindful of changes that should be tested on a site-wide deployment, such as changes to network requests, hooking into new context, or just adding a component to the site for the first time. As mentioned earlier, this won't be too often, but in these cases, integration testing is the way to go.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding out more
&lt;/h2&gt;

&lt;p&gt;Hopefully, if you've gotten this far, you're interested in trying this out for yourself!&lt;/p&gt;

&lt;p&gt;I've put together &lt;a href="https://github.com/andyrichardson/fixture-example-project"&gt;an example repo&lt;/a&gt; that contains source code and live examples (including those used in this post) demonstrating the use of fixtures in a real-world project.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fixtures of network requests &amp;amp; responses&lt;/li&gt;
&lt;li&gt;Fixtures of modals, forms, and validation&lt;/li&gt;
&lt;li&gt;Fixtures of ui components&lt;/li&gt;
&lt;li&gt;Visual regression testing (using... you guessed it, fixtures)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, a huge shoutout to the contributors of the project &lt;a href="http://reactcosmos.org/"&gt;React Cosmos&lt;/a&gt; who have made a great tool and documentation on developing with fixtures!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post was made thanks to the the support of &lt;a href="https://formidable.com/"&gt;Formidable&lt;/a&gt;. Check out the &lt;a href="https://formidable.com/blog/2020/fixture-first/"&gt;original post here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

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