<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: John Detlefs</title>
    <description>The latest articles on Forem by John Detlefs (@jdetle).</description>
    <link>https://forem.com/jdetle</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%2F197007%2F7ce064df-555b-458f-973a-a51e8ea2a443.jpeg</url>
      <title>Forem: John Detlefs</title>
      <link>https://forem.com/jdetle</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jdetle"/>
    <language>en</language>
    <item>
      <title>A Tip on Reducing Complexity While Coding in React and Typescript</title>
      <dc:creator>John Detlefs</dc:creator>
      <pubDate>Tue, 07 Apr 2020 23:10:34 +0000</pubDate>
      <link>https://forem.com/jdetle/a-tip-on-reducing-complexity-while-coding-in-react-and-typescript-17b8</link>
      <guid>https://forem.com/jdetle/a-tip-on-reducing-complexity-while-coding-in-react-and-typescript-17b8</guid>
      <description>&lt;p&gt;A few years ago, I was talking to a very talented engineer about what I was working on, lamenting that the product was 'over-engineered', he stopped me and told me 'over-engineering' is a misnomer. When great engineering teams successfully collaborate, the result is a well built product that satisfies company objectives. What I was calling 'over-engineering' happened when workers sought out and attached themselves to complex issues without thoughtful attempts to reduce their complexity.&lt;/p&gt;

&lt;p&gt;Some people call it 'speculative generality', I call it 'conflation'. Sometimes this happens because a developer wants to challenge themselves by finding a one-size-fits-all solution to their problem. Some of us do it because communication between product and technical management breaks down to the point where we don't effectively make features more manageable. Regardless, most developers are guilty of falling into this trap. I know I am.&lt;/p&gt;

&lt;p&gt;At work we ran into this problem with our tables. We have a bunch of different tables throughout our CRUD admin app, some tables are server-side paginated, some load the data all at once. Some of them are data-rich tables, for those we use &lt;a href="https://github.com/gregnb/mui-datatables"&gt;https://github.com/gregnb/mui-datatables&lt;/a&gt;, since we're using material-ui. We have some tables which are meant to act as form inputs for selecting items. There are a bunch of tables in our app!&lt;/p&gt;

&lt;p&gt;This rich set of feature requirements can create a maintenance problem for your application, as we have found out. When building things from scratch, the desire to be clever and to adhere to Dont Repeat Yourself (DRY) can pull even best devs towards an inefficient approach. &lt;/p&gt;

&lt;h4&gt;
  
  
  Exclusive Tables
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KdHewOxP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/DOBG4kx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KdHewOxP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/DOBG4kx.png" alt="Exclusive Venn"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Tables with Overlap
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8xgVXZup--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/ZoSzU9W.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8xgVXZup--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/ZoSzU9W.png" alt="Overlap Venn"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Key
&lt;/h5&gt;

&lt;p&gt;A: &lt;code&gt;ServerSideDataTable&lt;/code&gt;&lt;br&gt;
B: &lt;code&gt;ClientSideDataTable&lt;/code&gt;&lt;br&gt;
C: &lt;code&gt;GeneralDataTable&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Before building anything, we can't say with certainty that there will exist any code to share between the table that handles server-side paginated data and the table that handles data fetched on mount. Experience tells us that there will be some opportunity to share code, so it is easy to fall into the trap of a building one table to target the set of features encapsulated by the intersection, &lt;code&gt;GeneralDataTable&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For us, this approach became a maintenance burden. If our experience is any indication, the way that your codebase (ab)uses Typescript might be an indicator of conflation causing complexity. Naively, the props exclusively for the &lt;code&gt;ServerSideTable&lt;/code&gt;, the non overlap A disjoint C, would likely be expressed via "maybe" types. Say we've done a bad job and our code is documented poorly. If we use maybe types, our lack of documentation is even worse! Without the benefit of a tight contract established by our type for C, we lose the ability to have the use of C define what props it requires. We could use &lt;a href="https://github.com/sindresorhus/type-fest/blob/master/source/merge-exclusive.d.ts"&gt;merge-exclusive&lt;/a&gt; to have either all types for A or all types for B. This still leads to the complexity of managing the logic for what are things without complete logical overlap in the same component.&lt;/p&gt;

&lt;p&gt;What we've done is break our &lt;code&gt;GeneralDataTable&lt;/code&gt; into &lt;code&gt;ServerSideDataTable&lt;/code&gt; and &lt;code&gt;ClientSideDataTable&lt;/code&gt;. With this approach, the core logic for two fundamentally different tasks is kept distinct. We can define the type of props that are necessary for this logic in a way that is easy for all Typescript users to understand. This has already caught errors, and reduced the difficulty to juggle multiple concerns in our &lt;code&gt;GeneralDataTable&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The core takeaway here is that DRY should be applied judiciously to code, but maybe not to so judiciously to your component architecture. Premature abstractions can slow you down and reduce how powerful your type-checking can be for you. Coupling your components to distinct features with their own requirements lets you build &lt;code&gt;n&lt;/code&gt; components that are each focused on one job, rather than building one component that is handles &lt;code&gt;n&lt;/code&gt; jobs. &lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>architecture</category>
    </item>
    <item>
      <title>CORS and SameSite Cookies Got You Down? An Effective Workaround for Browser Security Policies</title>
      <dc:creator>John Detlefs</dc:creator>
      <pubDate>Wed, 01 Apr 2020 22:23:17 +0000</pubDate>
      <link>https://forem.com/jdetle/cors-and-samesite-cookies-got-you-down-an-effective-workaround-for-browser-security-policies-2i8m</link>
      <guid>https://forem.com/jdetle/cors-and-samesite-cookies-got-you-down-an-effective-workaround-for-browser-security-policies-2i8m</guid>
      <description>&lt;h1&gt;
  
  
  Of CORS My Request Failed
&lt;/h1&gt;

&lt;p&gt;Like many of my colleagues I haven't ever felt like I really understood what Cross Origin Resource Sharing (CORS) policies accomplish. I feel like when I try to learn more about it, I understand it even less. CORS security policies are like the worlds worst child safety lock. Whenever I have looked at information behind it, the explanations are usually very good at explaining what the moving parts are, but rarely give a good explanation for why CORS policies exist. For more background look at these CORS explanations: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS"&gt;MDN&lt;/a&gt;, &lt;a href="https://www.codecademy.com/articles/what-is-cors"&gt;Codecademy&lt;/a&gt;, &lt;a href="https://portswigger.net/web-security/cors"&gt;Port Swigger&lt;/a&gt;, &lt;a href="https://auth0.com/blog/cors-tutorial-a-guide-to-cross-origin-resource-sharing/"&gt;Auth0&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is so much content to look at, but really for most developers this stuff should be abstracted so you don't have to think about it. I doubt very many of us could give a comprehensive breakdown of what a browser request lifecycle looks like. Really though, you can get a long way without deeply understanding this.&lt;/p&gt;

&lt;p&gt;As far as I understand it, CORS security defaults on the browser default to the most restrictive rules, any outbound request from content on a domain that isn't making a request to that same domain will be blocked. This is a good thing. Content in the DOM is inherently mutable, so its possible that bad things could happen after your browser renders your HTML/CSS/Javascript. &lt;/p&gt;

&lt;p&gt;Your browser is essentially telling you "Hey, if you want to make this request you better put in the work of lying about where its coming from on &lt;em&gt;your&lt;/em&gt; server. Get out of here with your funny business!" Servers responding to requests could do work to maintain whitelists for multiple domains, but whitelists are hard to secure and backend developers are justifiably hesitant to make changes to stuff like that.&lt;/p&gt;

&lt;p&gt;At Meshify, we've put a fair amount of resources into using Dockerized NGINX servers in tandem with Create-React-App for most of our applications. As a result of this work we've done we are able to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Share Cookies from authenticated requests between pods in our Kubernetes infrastructure&lt;/li&gt;
&lt;li&gt;Develop against a local service that will work with our API with parity against production behavior&lt;/li&gt;
&lt;li&gt;Develop against secure WebSockets locally&lt;/li&gt;
&lt;li&gt;Develop against Service Workers locally&lt;/li&gt;
&lt;li&gt;Maintain legacy apps to keep them working with stricter security standards&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Before you get really started
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;You'll need a config that builds to a directory, in our case its called "build", and our build script is "yarn build"&lt;/li&gt;
&lt;li&gt;Install Docker&lt;/li&gt;
&lt;li&gt;Install Homebrew&lt;/li&gt;
&lt;li&gt;After installing homebrew, &lt;code&gt;brew install mkcert&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Creating the certs and templates
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;mkdir templates&lt;/code&gt; in your project's root directory&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mkdir templates/localcerts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mkdir templates/nginx&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Add your Dockerfile in your root directory
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM nginx:stable-alpine
COPY templates/nginx /usr/share/nginx/templates
COPY templates/localcerts /usr/share/nginx/certs
WORKDIR /usr/share/nginx/html
COPY build /usr/share/nginx/html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;em&gt;IMPORTANT&lt;/em&gt;: That last line build is whatever the name of your build directory is, copy paste errors can trip you up! &lt;/p&gt;

&lt;h4&gt;
  
  
  Adding some build scripts
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scripts: {
    ...
    "build:docker": "yarn build &amp;amp;&amp;amp; docker build -t &amp;lt;WHATEVER_YOU_WANT_TO_NAME_YOUR_CONTAINER&amp;gt; .",
    "mkcert": "mkcert -key-file ./templates/localcerts/key.pem -cert-file ./templates/localcerts/cert.pem admin-react.local *.admin-react.local",
    "start:docker": "cross-env REACT_APP_ENV=development PORT=3009 DANGEROUSLY_DISABLE_HOST_CHECK=true react-scripts start"
    ...
}

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



&lt;h4&gt;
  
  
  Adding your nginx config
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Added proxy_host and upstream_addr for better view of proxied requests
log_format extended '${D}remote_addr - ${D}remote_user [${D}time_local] ${D}proxy_host - ${D}upstream_addr '
                    '"${D}request" ${D}status ${D}body_bytes_sent '
                    '"${D}http_referer" "${D}http_user_agent"'
                    'rt=${D}request_time uct="${D}upstream_connect_time" uht="${D}upstream_header_time" urt="${D}upstream_response_time"';

access_log /var/log/nginx/access.log extended;

upstream api_server {
  server ${BACKEND_SERVER};
}

server {
  listen 443 ssl;
  listen [::]:443 ssl;
  ssl_certificate /usr/share/nginx/certs/cert.pem;
  ssl_certificate_key /usr/share/nginx/certs/key.pem;
  server_name admin-react.local;

  # redirect server error pages to the static page /50x.html
  #
  error_page   500 502 503 504  /50x.html;

  location = /50x.html {
    root   /usr/share/nginx/html;
  }
  # Create React App Specific
  location /sockjs-node/ {
    proxy_pass ${FRONTEND_URL}/sockjs-node/;
    proxy_http_version 1.1;
    proxy_set_header Upgrade ${D}http_upgrade;
    proxy_set_header Connection "upgrade";
  }
  location /api/&amp;lt;YOUR_WEBSOCKET_ENDPOINT&amp;gt; {
    proxy_http_version 1.1;
    proxy_set_header Upgrade ${D}http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_pass ${BACKEND_URL}stream;
  }
  # It might need to change depending on what your api url looks like
  location /api/ {
    add_header 'Host' api_server always;
    add_header 'Access-Control-Allow-Origin' "${D}http_origin" always;
    add_header 'Access-Control-Allow-Credentials' 'true' always;
    add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
    add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With' always;    
    # required to be able to read Authorization header in frontend
    if (${D}request_method = 'OPTIONS') {
      # Tell client that this pre-flight info is valid for 20 days
      add_header 'Access-Control-Allow-Origin' "${D}http_origin" always;
      add_header 'Access-Control-Max-Age' 1728000;
      add_header 'Content-Type' 'text/plain charset=UTF-8';
      add_header 'Content-Length' 0;
      return 204;
    }
    proxy_pass ${BACKEND_URL};
  }
  location / {
    proxy_pass ${FRONTEND_URL};
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Add your docker-compose file
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: "2.4"
services:
  &amp;lt;WHATEVER YOU WANT YOUR SERVICE NAME TO BE&amp;gt;:
    image: &amp;lt;YOUR DOCKER IMAGE NAME&amp;gt;:latest
    ports:
      - "3006:443"
    environment:
      - D=$$
      - FRONTEND_URL=http://host.docker.internal:3009/
      - BACKEND_SERVER=&amp;lt;YOUR_BACKEND_SERVER_URL_WITHOUT_API&amp;gt;
      - BACKEND_URL=&amp;lt;YOUR_BACKEND_SERVER_URL_WITH_API&amp;gt; 
    command: /bin/sh -c "envsubst &amp;lt; /usr/share/nginx/templates/localhost.conf &amp;gt; /etc/nginx/conf.d/localhost.conf &amp;amp;&amp;amp; exec nginx -g 'daemon off;'"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Caveat!
&lt;/h4&gt;

&lt;p&gt;Throughout these examples youll see text like &lt;code&gt;&amp;lt;YOUR_BACKEND_SERVER_URL_WITH_API&amp;gt;&lt;/code&gt;. I'm expecting you to substitute these with your own endpoints and text. Trailing slashes can be important. If you're running your app and getting 404's, its possible your API slashes are mismatched with your NGINX config. Be careful! &lt;/p&gt;

&lt;h4&gt;
  
  
  Getting it all started
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;yarn mkcert&lt;/code&gt; in your root directory&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;yarn build:docker&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;yarn start:docker&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;In a separate window, run &lt;code&gt;docker-compose up&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Go to &lt;a href="https://localhost:3006"&gt;https://localhost:3006&lt;/a&gt; click through the security warnings about self signed certs&lt;/li&gt;
&lt;/ol&gt;

&lt;h5&gt;
  
  
  Meshify's scheme for interacting with a third party service
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--e22c6JCr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/usdt6YH.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--e22c6JCr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/usdt6YH.png" alt="Imgur image of Meshify's scheme for interacting with a third party service"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In our case, we forward requests matching the string 'periscope' to a standalone service that we made to handle some business logic. The standalone service could also be lambdas or some other endpoint that we own. In this instance we get to use the cookie from an authenticated user in that standalone service to make another request to the API to ensure that user has permissions to read what they are accessing.&lt;/p&gt;

&lt;p&gt;My brilliant coworker Danil did most of the heavy lifting getting this setup working smoothly with NGINX. Kubernetes works especially well with this setup, COMMAND and ENVIRONMENT exists in Kubernetes configuration the same way it does here, so there are few changes necessary. &lt;/p&gt;

&lt;p&gt;I encourage you to comment here if you have any trouble getting this running and wish you luck in breaking out of the cage that your browser puts you in!&lt;/p&gt;

</description>
      <category>docker</category>
      <category>nginx</category>
      <category>react</category>
      <category>security</category>
    </item>
    <item>
      <title>Memoization, Generators, Virtualization, Oh my! Building a High Performance Directory Component in  React</title>
      <dc:creator>John Detlefs</dc:creator>
      <pubDate>Fri, 10 Jan 2020 22:54:08 +0000</pubDate>
      <link>https://forem.com/jdetle/memoization-generators-virtualization-oh-my-building-a-high-performance-directory-component-in-react-3efm</link>
      <guid>https://forem.com/jdetle/memoization-generators-virtualization-oh-my-building-a-high-performance-directory-component-in-react-3efm</guid>
      <description>&lt;p&gt;Developers often pretend to know what they're doing, especially when they're insecure newer developers like myself! Sometimes we happen to stumble upon interesting patterns, think they're elegant, and get attached to them rather than using the solution that performs better. In the course of building a file directory, I gleaned some interesting insights into recursion, search, memoization, virtualization, and generator functions. The path getting there exposed me to concepts that I haven't really dealt with since my algorithms course in college. Fortunately, my first slow-but-elegant solution, a recursive react component, was supplanted by the use of generator functions in &lt;code&gt;react-vtree&lt;/code&gt;, an equally interesting technology. Dealing with folder-based file systems has been one of the more rewarding small features I've had the opportunity to work in my short career. &lt;/p&gt;

&lt;p&gt;The idea of a folder-based file system is a ubiquitous abstraction in software. A folder-based filesystem exists as a tree structure. Each folder either contains files which can be thought of as leaf nodes in the tree structure or folders that have the aforementioned folder as a parent.&lt;/p&gt;

&lt;p&gt;A Glossary for Terms in this Post:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Tree&lt;/strong&gt; ← A set of elements where each element has only one parent, which may be itself (called a root node). All paths to a root node are unique → &lt;strong&gt;Directory&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node&lt;/strong&gt; ←  Any element in the tree → &lt;strong&gt;Folder or File&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leaf&lt;/strong&gt; ←  Any node in the tree without children → *&lt;em&gt;File&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Viewing a set of folders in a directory reveals a clear hierarchy in that we can conditionally render children based on some a folder's particular ‘hide/show’ icon that handles click and keypress events.&lt;/p&gt;

&lt;p&gt;In the course of building a new product for my employer, Meshify, we worked on building a Directory that could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Search by folder or filename and highlight matched text from the search&lt;/li&gt;
&lt;li&gt;Highlight a selected folder based on a url &lt;code&gt;folderId&lt;/code&gt; parameter&lt;/li&gt;
&lt;li&gt;Show and hide folder contents from click events&lt;/li&gt;
&lt;li&gt;Be able to handle ~10K+ folders without crashing or being overly laggy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wish I could say that I knew what I was doing when I started working on this problem. The first two insights I had regarded how to store and pass folder data and how to search recursively across folders. &lt;/p&gt;

&lt;p&gt;Each folder in the list contains a parent folder id.  Using this relationship, the list can be iterated over to return a set of children belonging to that  folder. We should only have to do this once, invalidating data only on changes to the list of folders. This is the perfect case for a lookup table and memoization. In my case, I decided on a &lt;code&gt;Map&lt;/code&gt; data structure and the &lt;code&gt;useMemo&lt;/code&gt; hook. It’s worth noting that the use of object and memoization tools from another framework can work as well. &lt;/p&gt;

&lt;p&gt;While being sure to write meaningful tests on different mocked folder lists, I built out the functionality for creating a memoized map that recomputes data associated with&lt;br&gt;
The code that I ended up setting on looks like the folder provider in this example &lt;a href="https://codesandbox.io/embed/recursing-chandrasekhar-q7z8r?fontsize=14&amp;amp;hidenavigation=1&amp;amp;theme=dark"&gt;Folder Provider&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you want to take anything away from the code above, the most useful part in my mind me was this code snippet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;childrenMatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;annotatedRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;children&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;childContainsMatch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// same as .some(item =&amp;gt; item == true)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;A child of a folder can contain a match to search text such that if any folder matches the search text somewhere deep in the tree, all of the folders in the path between the root folders and that folder have the information requisite to display their contents. The folder might need to be open even when a folder does not match the search text provided. In the case that a folder contains other folders, we need to use recursion to search those child folders for any elements that match independent of that folder's depth. &lt;/p&gt;

&lt;p&gt;By knowing that we are guaranteed a return when we reach a folder without any children (you can think of this as a file if that helps), we should avoid potential stack overflow errors. The array method &lt;code&gt;Array.prototype.some&lt;/code&gt; in this context will exit as soon as it finds one true return from &lt;code&gt;childContainsMatch&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Given this map, we are able to build out a Directory component that handles most of the work we need to do (in theory, more to be revealed). &lt;br&gt;
Initially, the component I built look something like this:&lt;/p&gt;
&lt;h2&gt;
  
  
  Control Flow for Folder Component
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Get Folder Info from Map given folder id&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;If&lt;/em&gt; the folder has children:

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;If&lt;/em&gt; search text is present:

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;If&lt;/em&gt; this folder name matches search:

&lt;ul&gt;
&lt;li&gt;Render Name with highlighted search text, show/hide icon with event handlers&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Else&lt;/em&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;If&lt;/em&gt; this folder contains children that match or this folder is set to open:

&lt;ul&gt;
&lt;li&gt;Map across this folders children, return new Folder component for each&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Else&lt;/em&gt;:

&lt;ul&gt;
&lt;li&gt;Render name &amp;amp; show/hide icon with event handlers&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;If&lt;/em&gt; the folder is set to open: 

&lt;ul&gt;
&lt;li&gt;Map across children, return new Folder component for each&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Else&lt;/em&gt; (is a leaf node):

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;If&lt;/em&gt; search text is present:

&lt;ul&gt;
&lt;li&gt;If name matches search:

&lt;ul&gt;
&lt;li&gt;Render file name with search highlighting&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Else&lt;/em&gt;:

&lt;ul&gt;
&lt;li&gt;Render file name&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you can see, in the case that a folder has children, the Folder component renders itself recursively! Some of you may not think that’s cool, but it’s the first time I've had a compelling need to use recursion with a React component and I think it’s damn cool. &lt;/p&gt;

&lt;p&gt;Unfortunately, this scheme doesn't perform amazingly with large lists of folders. After some investigation, it was pretty clear that there weren't unnecessary re-renders or obviously slow performance issues in the &lt;code&gt;FolderProvider&lt;/code&gt; component. The unfortunate truth was that, in some cases, we were simply rendering too many things at once. Without changing any backend APIs, the best solution seemed to be virtualization. After using Twitter to ask what the current state of virtualization was,  I was made aware of react-window. Scrolling through the readme of react-window led me to &lt;a href="https://github.com/Lodin/react-vtree"&gt;react-vtree&lt;/a&gt;. The npm package "provides a lightweight and flexible solution for rendering large tree structures", just what I was looking for.&lt;/p&gt;

&lt;p&gt;Would it surprise you if I told you that this added even more complexity to the problem? &lt;/p&gt;

&lt;p&gt;&lt;code&gt;react-vtree&lt;/code&gt; is a quick and practical introduction to the utility of generator functions, as well as virtualization. The core functionality of react-vtree lies in a &lt;code&gt;treeWalker&lt;/code&gt; generator function that is taken as a prop.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In the component enclosing the scope of the tree walker funciton&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;annotatedFolderMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;searchText&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;FolderContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;treeWalker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;refresh&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;stack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
   &lt;span class="nx"&gt;rootFolders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;folder&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="nx"&gt;annotatedFolderMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;folder&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;searchText&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;isVisible&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;folder&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;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stack&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentFolder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pop&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;isOpen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nx"&gt;refresh&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;currentFolderData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;id&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;currentFolder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;children&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;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;children&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;child&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="nx"&gt;annotatedFolderMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentFolder&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;searchText&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;isVisible&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&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="k"&gt;else&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;searchText&lt;/span&gt; &lt;span class="o"&gt;===&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;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&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="p"&gt;}&lt;/span&gt; 
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The function &lt;code&gt;treeWalker&lt;/code&gt; here is an example of lazily computed values. The tree that consumes the treeWalker function, looks up the default state for whether the particular folder is open, call this variable &lt;code&gt;defaultIsOpen&lt;/code&gt;. The tree then sends that data back to the &lt;code&gt;treeWalker&lt;/code&gt; function through the line &lt;code&gt;const {value, done} = iter.next(defaultIsOpen)&lt;/code&gt;. The const &lt;code&gt;isOpen&lt;/code&gt; in the while loop is set through that call to &lt;code&gt;iter.next&lt;/code&gt;. No data is collected unless we are sure it is a member of an open directory or it is a root folder. It is worth noting that the tree walker function is not as lazy as it could be, in that data that is not being rendered can still be collected as a result of calling this generator. This generator function is called any time a node's is open state is changed via the provided &lt;code&gt;toggle&lt;/code&gt; function. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;react-vtree&lt;/code&gt; is build on top of &lt;a href="https://github.com/bvaughn/react-window"&gt;react-window&lt;/a&gt;. &lt;code&gt;react-window&lt;/code&gt; is a virtualization tool, which means that it only renders items that are viewable within your window. The savings are two-fold, less unneccessary data is saved and no unnecessary nodes are rendered. Of course, there is no longer the interesting use of recursion; one can take solace in the fact that this solution uses some of the most modern features of Javascript and the react ecosystem to suitably render thousands of folders &lt;em&gt;blazingly&lt;/em&gt; fast.&lt;/p&gt;

&lt;p&gt;Heres a gif of the final product:&lt;br&gt;
&lt;a href="https://i.giphy.com/media/VGDq42LETidA9RjY0F/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/VGDq42LETidA9RjY0F/giphy.gif" alt="A searchable directory component"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In retrospect, the process of building this component mirrored the adage "make it work, make it pretty, and then make it fast". I wish I could say I knew what I was doing, but I happened to luckily stumble on a convenient separation of concerns. By separating the data concerns from the actual rendered view, the process of refactoring this work to go from using a bespoke, recursive tree component to a virtualized tree with &lt;code&gt;react-vtree&lt;/code&gt; was remarkably painless.&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Cant use the blessed approach just yet? A quick way to solve browser redirects after auth using react-router</title>
      <dc:creator>John Detlefs</dc:creator>
      <pubDate>Mon, 25 Nov 2019 01:02:30 +0000</pubDate>
      <link>https://forem.com/jdetle/cant-use-the-blessed-approach-one-way-to-solve-browser-redirects-after-auth-using-react-router-1o9d</link>
      <guid>https://forem.com/jdetle/cant-use-the-blessed-approach-one-way-to-solve-browser-redirects-after-auth-using-react-router-1o9d</guid>
      <description>&lt;p&gt;Kent Dodds delineates a pretty good approach to auth in a react app here: &lt;a href="https://kentcdodds.com/blog/authentication-in-react-applications"&gt;https://kentcdodds.com/blog/authentication-in-react-applications&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're using react-router and useContext in your app just auth that way. Its very convenient to have &lt;code&gt;AuthenticatedApp&lt;/code&gt; and &lt;code&gt;UnauthenticatedApp&lt;/code&gt; as the sides of your ternary in that authentication check. &lt;/p&gt;

&lt;p&gt;If for some reason you cant get this in as a ticket and you need to solve redirects quickly in an additive fashion (very little refactoring necessary) use &lt;code&gt;location.state&lt;/code&gt;! Location state lets us pass data on a router action like &lt;code&gt;history.push&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Assuming you already have redirecting unauthenticated users to an unauthenticated view, the method of fixing bad redirects generalizes to two parts&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find the react router redirect &lt;code&gt;to&lt;/code&gt; prop or history.push action that is causing that redirect and refactor it to this. &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Before
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;Redirect to='login' /&amp;gt; (could also be) /&amp;gt;&lt;/code&gt; or this&lt;code&gt;history.push({pathname: 'login', state: { redirectPathName: pathname, redirectSearchParams: search})&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  After
&lt;/h2&gt;



&lt;div class="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;pathname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;search&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useLocation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;// in route declaration&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Redirect&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="na"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;redirectPathName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;redirectSearchParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;search&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Find the login method that pushes to the new authenticated route.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Before
&lt;/h2&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;handleLogin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;push&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useHistory&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
   &lt;span class="c1"&gt;// handle login stuff&lt;/span&gt;
   &lt;span class="c1"&gt;// onSuccess do this&lt;/span&gt;
   &lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&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;h2&gt;
  
  
  After
&lt;/h2&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;handleLogin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;push&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useHistory&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;state&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useLocation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
   &lt;span class="c1"&gt;// handle login stuff&lt;/span&gt;
   &lt;span class="c1"&gt;// onSuccess do this&lt;/span&gt;
   &lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;redirectPathname&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;redirectPathName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;search&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;redirectSearchParams&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="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&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;



</description>
      <category>react</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>ux</category>
    </item>
    <item>
      <title>Your app should be using query/search params</title>
      <dc:creator>John Detlefs</dc:creator>
      <pubDate>Sun, 24 Nov 2019 19:46:21 +0000</pubDate>
      <link>https://forem.com/jdetle/your-app-should-be-using-query-search-params-kj1</link>
      <guid>https://forem.com/jdetle/your-app-should-be-using-query-search-params-kj1</guid>
      <description>&lt;p&gt;If you're a long term web dev who is thinking "Of course I should be using query params! I've been using them since before you were born.", feel free to read something else.&lt;/p&gt;

&lt;p&gt;Like many other of my contemporaries, there are concepts we can go years without learning. The use of query parameters or search parameters in single page apps was one such thing for me, and hopefully some of you as well! &lt;/p&gt;

&lt;p&gt;Query parameters / search parameters are a part of the official url standard. Take this url for example:&lt;br&gt;
&lt;a href="https://www.youtube.com/watch?v=y8EQFhj8ca4"&gt;https://www.youtube.com/watch?v=y8EQFhj8ca4&lt;/a&gt;, the bit after the question mark &lt;code&gt;v=y8EQFhj8ca4&lt;/code&gt; is a query parameter. If you go to that page and investigate in the console.&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;substring&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="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v&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;You'll get access to the id of that video, &lt;code&gt;y8EQFhj8ca4&lt;/code&gt;. I haven't checked but I'd bet serious money that id is used to fetch data in the client-side javascript without the necessity of defining a route for every video. This is one valuable use case for query parameters.&lt;/p&gt;

&lt;p&gt;The core realization to be made is this: &lt;b&gt;&lt;em&gt;Query params make your urls shareable&lt;/em&gt;&lt;/b&gt;.&lt;/p&gt;

&lt;p&gt;Don't see what I mean? Take an instance like a paginated table, each page of the table corresponds to a unique fetch or set of fetches for that page's data. Each fetch takes a "limit" argument, the number of rows we are looking to fetch, and an "offset" argument, the index at which we want to start fetching the rows. Assume they are always ordered the same way for the purpose of this discussion.&lt;/p&gt;

&lt;p&gt;In that past I have made the mistake of managing the current page number and rows per page via React's &lt;code&gt;useState&lt;/code&gt; hook, and building that into a larger &lt;code&gt;usePagination&lt;/code&gt; hook. A clear limitation of this approach would be that whenever we took a url while from viewing some page that was not on the default state, that state was not saved anywhere and could not be reconstructed.&lt;/p&gt;

&lt;p&gt;By using query params, we can react to the url to set determine current &lt;code&gt;limit&lt;/code&gt; state and &lt;code&gt;offset&lt;/code&gt; values to be sent to our fetch. When changing a page, we can use a react-router 5.1 and some url parsing to get what we need.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// import react, react-router...&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;push&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useHistory&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;pathname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;search&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useLocation&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;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;substring&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;// convert to numbers&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;limit&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="nx"&gt;pageNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pageNumber&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// fetch using offset on your own&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;pageNumber&lt;/span&gt;
&lt;span class="c1"&gt;// an event handler you might pass to your table&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handlePageChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newPage&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;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;search&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`?limit=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;pageNumber=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;By using routes and our router to our advantage we this approach means that when viewing page number 10 of some table, an end user can take this link, share it with a coworker, and they should get the same view. (Assuming the data hasn't changed in that time), either way, it creates a much better experience to the end user. &lt;/p&gt;

&lt;h2&gt;
  
  
  Functions and utilities used while learning this stuff
&lt;/h2&gt;

&lt;p&gt;URLSearchParams&lt;br&gt;
react-router, specifically:&lt;br&gt;
useHistory from react-router&lt;br&gt;
useLocation from react-router&lt;br&gt;
the &lt;code&gt;use-query-params&lt;/code&gt; package on npm&lt;br&gt;
mui-datatables&lt;/p&gt;

&lt;p&gt;--&lt;/p&gt;

&lt;p&gt;I'm trying not to punish myself by creating the perfect blog, so if anything is unclear or if you have anything you want to add, feel free to comment. I'm sure there is stuff I haven't brought up that is relevant!&lt;/p&gt;

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