<?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: Anthony Humphreys</title>
    <description>The latest articles on DEV Community by Anthony Humphreys (@anthonyhumphreys).</description>
    <link>https://dev.to/anthonyhumphreys</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F207292%2F498a1c11-8c4e-4956-a010-f2b3e7fc9d3d.jpg</url>
      <title>DEV Community: Anthony Humphreys</title>
      <link>https://dev.to/anthonyhumphreys</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/anthonyhumphreys"/>
    <language>en</language>
    <item>
      <title>Anvil</title>
      <dc:creator>Anthony Humphreys</dc:creator>
      <pubDate>Sat, 20 Jun 2026 11:37:18 +0000</pubDate>
      <link>https://dev.to/anthonyhumphreys/anvil-20nk</link>
      <guid>https://dev.to/anthonyhumphreys/anvil-20nk</guid>
      <description>&lt;p&gt;&lt;a href="https://www.anthonyhumphreys.dev/blog/posts/building-anvil" rel="noopener noreferrer"&gt;Cross post&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Building Anvil
&lt;/h1&gt;

&lt;p&gt;Anvil did not start as a 'stack'. It still isn't, really, it is three independent projects that have a bunch in common and compliment each other quite nicely, so I decided to bundle them in a monorepo. This also makes it easier to keep the shared pieces in sync, and to make sure the projects are actually useful together.&lt;/p&gt;

&lt;p&gt;The Anvil app started as a custom agentic coding harness inspired by T3Code: a practical way to run agents &lt;br&gt;
inside real projects without pretending a chat window was the whole development environment. I wanted something that&lt;br&gt;
could understand a repository, stay attached to a work item, preserve context across a delivery&lt;br&gt;
loop, and help me move from intent to implementation without scattering the important details&lt;br&gt;
across terminals, tabs, issue trackers, and whatever note I had optimistically named&lt;br&gt;
&lt;code&gt;plan-final-2.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That first version was deliberately narrow. It was built around the job I needed it to do: help&lt;br&gt;
agents work inside software projects with enough grounding to be useful and enough structure to be&lt;br&gt;
reviewable.&lt;/p&gt;

&lt;p&gt;Over time, that narrow tool started pulling in more of the surrounding workflow. Not scope creep for the fun of it,&lt;br&gt;
but because the lines between what counts as workflow, productivity and context all blurred into one, so having one place&lt;br&gt;
where the work is planned, defined, implemented, checked, and reviewed became more useful than a tool that only ran agents.&lt;br&gt;
I also really liked seeing the development of Claude's code review and codex similar features too. Only...they felt a bit lacking?&lt;br&gt;
And in Claude's case, extremely expensive. So I built things like the code review feature which runs customisable rubrics against the codebase,&lt;br&gt;
a PR or a commit, and has a pleasant UI to either post the feedback to the PR or action it.&lt;/p&gt;

&lt;p&gt;Code does not happen in isolation. It sits inside tickets, docs, branches, pull requests, design&lt;br&gt;
notes, deployment constraints, production incidents, and the tiny archaeological record of decisions&lt;br&gt;
that live in a repo. If an agent only sees the current prompt, it is under-informed. If a developer&lt;br&gt;
has to manually reconstruct context every time, the tool is not carrying enough weight.&lt;/p&gt;

&lt;h2&gt;
  
  
  From harness to ADE
&lt;/h2&gt;

&lt;p&gt;The name I keep coming back to is an ADE: an agentic development environment.&lt;/p&gt;

&lt;p&gt;That sounds a bit grand, so the useful definition is simpler: Anvil is becoming the place where the&lt;br&gt;
work item, repository, agent session, and development context sit together.&lt;/p&gt;

&lt;p&gt;The original harness was about running agents. The ADE shape is about supporting the whole path from&lt;br&gt;
problem to reviewed change:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;understand the work item&lt;/li&gt;
&lt;li&gt;inspect the relevant repository state&lt;/li&gt;
&lt;li&gt;plan the change against real constraints&lt;/li&gt;
&lt;li&gt;make the implementation&lt;/li&gt;
&lt;li&gt;run checks&lt;/li&gt;
&lt;li&gt;review the diff&lt;/li&gt;
&lt;li&gt;capture the reasoning&lt;/li&gt;
&lt;li&gt;connect the output back to the work item system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That loop is useful for developers, obviously. But the target is wider than developers.&lt;/p&gt;

&lt;p&gt;A product person should be able to understand what changed and why. A tester should be able to see&lt;br&gt;
the acceptance criteria, the affected area, and the risks worth checking. A technical lead should be&lt;br&gt;
able to review the work without piecing the story together from five separate systems. A support&lt;br&gt;
person should be able to connect a customer issue to the code path that actually changed.&lt;/p&gt;

&lt;p&gt;The point is not to make everyone write code. The point is to make the development context less&lt;br&gt;
fragmented.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repository awareness is the grounding layer
&lt;/h2&gt;

&lt;p&gt;The most important Anvil idea is still repo awareness.&lt;/p&gt;

&lt;p&gt;Agentic coding gets much better when the agent can read the project before making claims about it.&lt;br&gt;
That sounds obvious, but a lot of AI tooling still behaves as if a confident answer is roughly&lt;br&gt;
equivalent to an inspected codebase. It is not. Confidence without repo context is just a well-lit&lt;br&gt;
guess.&lt;/p&gt;

&lt;p&gt;Anvil treats the repository as the grounding layer. The agent should know the file tree, current&lt;br&gt;
diff, conventions, scripts, tests, docs, and local project rules. It should understand whether a&lt;br&gt;
change belongs in an existing module or whether the new abstraction it is about to invent is solving&lt;br&gt;
a real problem.&lt;/p&gt;

&lt;p&gt;That grounding matters because the work item alone is not enough. Tickets are useful, but they are&lt;br&gt;
usually compressed versions of reality. The repo contains the real constraints: the old migration,&lt;br&gt;
the wrapper nobody wants to touch, the half-finished test helper, the auth boundary that has to&lt;br&gt;
remain a real boundary.&lt;/p&gt;

&lt;p&gt;Anvil works best when the work item and repository context are held together. The ticket says what&lt;br&gt;
the change is for. The repo says how it can actually fit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The workspace idea
&lt;/h2&gt;

&lt;p&gt;The workspace concept borrows heavily from VS Code.&lt;/p&gt;

&lt;p&gt;That is intentional. VS Code got something very right: a workspace is not just a folder. It is the&lt;br&gt;
local operating context for a piece of work. Files, settings, extensions, terminals, tasks, source&lt;br&gt;
control, and developer habits all gather around that boundary.&lt;/p&gt;

&lt;p&gt;Anvil uses a similar idea, but aims it at development context rather than only editing context. A&lt;br&gt;
workspace can hold the repo, linked work items, agent sessions, notes, checks, and eventually more&lt;br&gt;
of the surrounding SDLC state.&lt;/p&gt;

&lt;p&gt;That matters because software teams rarely work on "a repo" in the abstract. They work on a&lt;br&gt;
workspace shaped by the current project, product area, branch, issue, environment, and release&lt;br&gt;
target. If Anvil can preserve that shape, agents can do more than answer prompts. They can work&lt;br&gt;
inside the same frame as the people around them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then came Anvil Registry
&lt;/h2&gt;

&lt;p&gt;Anvil Registry came from a different but related concern: supply-chain security.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install&lt;/code&gt; is a lot of trust hidden behind a short command. It can run lifecycle scripts. It can&lt;br&gt;
pull hundreds of packages into a project before anyone has looked at what changed. It can turn a&lt;br&gt;
typo, a compromised maintainer account, a package-confusion mistake, or a suspicious new release&lt;br&gt;
into executable code on a developer machine or CI runner.&lt;/p&gt;

&lt;p&gt;That problem exists with or without agents. Most teams already rely on a huge amount of third-party&lt;br&gt;
code, and the install path is one of the places where trust becomes execution very quickly. Anvil&lt;br&gt;
Registry started as a set of countermeasures for that problem: put a deliberate control point in&lt;br&gt;
front of dependency installs, make package decisions inspectable, and avoid treating upstream&lt;br&gt;
registry traffic as harmless just because it is normal.&lt;/p&gt;

&lt;p&gt;Anvil Registry puts a controlled gateway between package managers and upstream registries. The&lt;br&gt;
gateway speaks the npm registry shape that existing tools already understand, then applies policy&lt;br&gt;
before tarballs are handed over. In practical terms, it is trying to solve a few connected problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;make install traffic inspectable instead of invisible&lt;/li&gt;
&lt;li&gt;cache package metadata and tarballs so decisions are tied to the artefact that was actually seen&lt;/li&gt;
&lt;li&gt;apply deterministic policy before code reaches the project&lt;/li&gt;
&lt;li&gt;queue deeper analysis outside the hot install path&lt;/li&gt;
&lt;li&gt;give humans explainable decisions, reports, and override controls&lt;/li&gt;
&lt;li&gt;provide a safer local path for unknown repositories through the Node Base devcontainer image&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architecture is deliberately boring in the places where boring is a virtue. Package managers&lt;br&gt;
ask Registry for metadata and tarballs. Registry checks policy and package identity, proxies and&lt;br&gt;
caches upstream artefacts, records decisions, and hands work to an analysis worker. The worker can&lt;br&gt;
inspect manifests, package contents, provenance signals, file trees, lifecycle script usage, and&lt;br&gt;
other risk indicators. The CLI and admin surfaces then give people a way to explain a decision,&lt;br&gt;
scan packages, warm caches, review reports, and manage overrides.&lt;/p&gt;

&lt;p&gt;The important line is authority. AI-assisted review can help summarize suspicious patterns or point&lt;br&gt;
at things worth checking, but it does not get to be the enforcement layer. Deterministic policy owns&lt;br&gt;
the gate. Humans own the judgement.&lt;/p&gt;

&lt;p&gt;That makes Registry useful on its own, even if there is no agent anywhere near the repo. It gives&lt;br&gt;
developers and teams a more inspectable install path, a place to encode policy, and a way to review&lt;br&gt;
exceptions without pretending every dependency decision can live in someone's head.&lt;/p&gt;

&lt;p&gt;That project pushed Anvil beyond "how do agents edit code?" into "what infrastructure does agentic&lt;br&gt;
development need around it?"&lt;/p&gt;

&lt;p&gt;The agentic angle is a bonus, but it is a real one. Agents can move quickly through unfamiliar&lt;br&gt;
repositories, which means dependency changes can become part of a larger automated edit loop. In&lt;br&gt;
that world, having a checkpoint in front of install traffic matters even more. "The model probably&lt;br&gt;
noticed" is not a security boundary.&lt;/p&gt;

&lt;p&gt;It was also the first time I built a substantial project using Codex &lt;code&gt;/goal&lt;/code&gt;. That became its&lt;br&gt;
own post because the process mattered: write the spec first, let Codex work against a concrete&lt;br&gt;
target, keep running checks, and keep the human decision-making where it belongs. I wrote more about&lt;br&gt;
that build in &lt;a href="https://dev.to/blog/posts/building-anvil-registry-with-codex"&gt;Building Anvil Registry With Codex&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Registry is part of the same larger bet as Anvil Desktop: better delivery requires better context.&lt;br&gt;
Sometimes that context is a work item and a diff. Sometimes it is package identity, provenance,&lt;br&gt;
tarball contents, and install policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Now Anvil Cloud
&lt;/h2&gt;

&lt;p&gt;Anvil Cloud is the piece I was slowest to describe properly, because it sounds like a hosted-agent&lt;br&gt;
story if you say it too quickly.&lt;/p&gt;

&lt;p&gt;It is not that.&lt;/p&gt;

&lt;p&gt;The motivation is more specific: agents work best when the world they are editing is small,&lt;br&gt;
explicit, and inspectable. Cloud infrastructure is usually the opposite. A useful app quickly runs&lt;br&gt;
into auth, data, files, jobs, logs, queues, environment variables, deploy state, gateways, IAM, and&lt;br&gt;
provider-specific defaults. Those are awkward enough for humans. They are a very sharp surface for&lt;br&gt;
generated code.&lt;/p&gt;

&lt;p&gt;There are a few interesting projects circling this problem from different directions. &lt;a href="https://sst.dev/docs/" rel="noopener noreferrer"&gt;SST&lt;/a&gt;&lt;br&gt;
makes full-stack infrastructure much more approachable by defining app resources in code and keeping&lt;br&gt;
those resources linked to the application. &lt;a href="https://docs.lakebed.dev/" rel="noopener noreferrer"&gt;Lakebed&lt;/a&gt; pushes toward an&lt;br&gt;
agent-native shape: small TypeScript apps with a CLI/runtime that an agent can create, inspect, and&lt;br&gt;
deploy without wandering through a cloud console.&lt;/p&gt;

&lt;p&gt;I found SST to be amazing, but agents constantly trip over nuance as soon as you get to any sort of level of&lt;br&gt;
complexity. Lakebed is a very interesting approach, and I really love the pitch of 'a shitty cloud for shitty apps'.&lt;br&gt;
But I wanted something that &lt;em&gt;could&lt;/em&gt; be used for production apps, not just a playground. I wanted a shape that could be used &lt;br&gt;
for real work, but still small enough to be inspectable, testable, and reviewable before it becomes real infrastructure.&lt;/p&gt;

&lt;p&gt;Anvil Cloud is my version of that problem space, built around the Cell contract rather than&lt;br&gt;
raw infrastructure.&lt;/p&gt;

&lt;p&gt;An Anvil Cell is a small TypeScript app unit. It can contain server handlers, client UI, schema,&lt;br&gt;
endpoints, jobs, workflows, services, mounted agents, and declared capabilities. The important bit is&lt;br&gt;
the boundary: Cell code should use Anvil runtime primitives like &lt;code&gt;ctx.db&lt;/code&gt;, &lt;code&gt;ctx.files&lt;/code&gt;, &lt;code&gt;ctx.env&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;ctx.log&lt;/code&gt;, jobs, and workflows. It should not import AWS SDKs, SST, CDK, Terraform, Pulumi, or raw&lt;br&gt;
provider resources just to build a small app.&lt;/p&gt;

&lt;p&gt;The platform pieces exist to make that boundary useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runtime runs the same request contract locally, in tests, and behind deployment adapters.&lt;/li&gt;
&lt;li&gt;Builder emits the server bundle, client bundle, manifest, generated client metadata, and build
output.&lt;/li&gt;
&lt;li&gt;Guard checks imports, direct environment access, outbound fetches, and undeclared capabilities
before the app turns into provider work.&lt;/li&gt;
&lt;li&gt;Lens and the CLI expose manifests, logs, auth state, database state, workflows, services, and
diagnostics in forms humans and agents can inspect.&lt;/li&gt;
&lt;li&gt;Deployment adapters map the Cell's capabilities to provider resources. AWS is the first adapter,
but AWS is not the app contract.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cloud's job is not to "run an agent somewhere". It is to give agents and developers a smaller&lt;br&gt;
application model that can become real infrastructure without making the app author hand-roll the&lt;br&gt;
provider machinery.&lt;/p&gt;

&lt;p&gt;Cloud is early alpha. The local runtime, builder, Lens, CLI, auth, workflow, service, agent, and AWS&lt;br&gt;
preview work are moving together, but the important thing is the contract. If a Cell can&lt;br&gt;
be built, checked, inspected, and planned before it touches a provider, the system has a fighting&lt;br&gt;
chance of being useful without becoming a very confident infrastructure accident. This feels like a solved&lt;br&gt;
problem with terraform and SST, but those tools are not designed for agentic development. How many times have&lt;br&gt;
you deployed using terraform only to later find your lambda is missing a permission that is only needed at runtime?&lt;br&gt;
Or your SST app is failing because the agent generated a resource that is not actually supported by the provider?&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this is going
&lt;/h2&gt;

&lt;p&gt;The direction is clear enough now.&lt;/p&gt;

&lt;p&gt;Anvil started as an agentic coding harness because that was the immediate problem. It is becoming an&lt;br&gt;
ADE because the real problem is broader: development with agents needs clear boundaries around the&lt;br&gt;
work, the code, the dependencies, and the runtime.&lt;/p&gt;

&lt;p&gt;Anvil Registry added a boundary around dependency ingress.&lt;/p&gt;

&lt;p&gt;Anvil Cloud adds a boundary around the apps agents and developers build: the Cell contract, the&lt;br&gt;
runtime, the manifest, the checks, the inspection surface, and the adapter path to real providers.&lt;/p&gt;

&lt;p&gt;Together, they point at a more complete environment for building software with agents involved, but&lt;br&gt;
not with judgement outsourced to them. Developers should get deeper repo-aware assistance. Product,&lt;br&gt;
QA, support, and technical leadership should get clearer context. Teams should get a delivery loop&lt;br&gt;
with fewer mystery steps and more things you can inspect before they matter.&lt;/p&gt;

&lt;p&gt;The aim is to keep the useful parts of agentic development grounded: tied to the repo, checked&lt;br&gt;
against the work, explicit about risk, and boring enough to review before it reaches production.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>llm</category>
      <category>devex</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Anthony Humphreys</dc:creator>
      <pubDate>Sat, 03 Jan 2026 16:36:20 +0000</pubDate>
      <link>https://dev.to/anthonyhumphreys/-apj</link>
      <guid>https://dev.to/anthonyhumphreys/-apj</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/anthonyhumphreys" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F207292%2F498a1c11-8c4e-4956-a010-f2b3e7fc9d3d.jpg" alt="anthonyhumphreys"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/anthonyhumphreys/i-built-an-ai-tool-to-practise-gming-because-prep-isnt-the-same-as-practice-1amm" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;I Built an AI Tool to Practise GMing — Because “Prep” Isn’t the Same as “Practice”&lt;/h2&gt;
      &lt;h3&gt;Anthony Humphreys ・ Jan 3&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#ai&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#vercel&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#rpg&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>ai</category>
      <category>vercel</category>
      <category>rpg</category>
    </item>
    <item>
      <title>I Built an AI Tool to Practise GMing — Because “Prep” Isn’t the Same as “Practice”</title>
      <dc:creator>Anthony Humphreys</dc:creator>
      <pubDate>Sat, 03 Jan 2026 15:43:05 +0000</pubDate>
      <link>https://dev.to/anthonyhumphreys/i-built-an-ai-tool-to-practise-gming-because-prep-isnt-the-same-as-practice-1amm</link>
      <guid>https://dev.to/anthonyhumphreys/i-built-an-ai-tool-to-practise-gming-because-prep-isnt-the-same-as-practice-1amm</guid>
      <description>&lt;p&gt;I love TTRPGs. The storytelling, the improvisation, the moment when a table really clicks. However, I've only recently started GMing, and I love it.&lt;/p&gt;

&lt;p&gt;But I also noticed something odd:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;GMing is a performance role, and we’re expected to perform… without ever really being able to rehearse.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Actors rehearse. Musicians rehearse. Developers practise in sandboxes.&lt;br&gt;
Game Masters? We just… show up and hope for the best.&lt;/p&gt;

&lt;p&gt;That gap is why I built &lt;strong&gt;GMprentice&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prep ≠ Practice
&lt;/h2&gt;

&lt;p&gt;There are plenty of tools for GMs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Worldbuilders
&lt;/li&gt;
&lt;li&gt;Encounter generators
&lt;/li&gt;
&lt;li&gt;NPC name generators
&lt;/li&gt;
&lt;li&gt;AI tools that spit out lore or dialogue
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They’re all about &lt;strong&gt;prep&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But prep doesn’t teach you how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Handle players going off-script&lt;/li&gt;
&lt;li&gt;Make rulings under pressure&lt;/li&gt;
&lt;li&gt;Manage spotlight and pacing&lt;/li&gt;
&lt;li&gt;Recover when an encounter falls flat&lt;/li&gt;
&lt;li&gt;Improvise when the party surprises you (which they always do)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are &lt;em&gt;performance skills&lt;/em&gt;, And performance skills need practice.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Problem: There’s No Safe Place to Fail
&lt;/h2&gt;

&lt;p&gt;Most GMs I know don’t struggle with rules knowledge.&lt;br&gt;&lt;br&gt;
They struggle with confidence.&lt;/p&gt;

&lt;p&gt;You can’t really practise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Emotional scenes&lt;/li&gt;
&lt;li&gt;Awkward rulings&lt;/li&gt;
&lt;li&gt;High-stakes moments
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…without real people at the table, and failing in front of friends can feel awful.&lt;/p&gt;

&lt;p&gt;So most of us just learn by doing — publicly, imperfectly, sometimes painfully.&lt;/p&gt;

&lt;p&gt;I wanted a &lt;strong&gt;private, judgement-free space&lt;/strong&gt; where GMs could practise first.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why AI (and not just “ChatGPT for D&amp;amp;D”)
&lt;/h2&gt;

&lt;p&gt;GMprentice isn’t a single chatbot.&lt;/p&gt;

&lt;p&gt;The core idea is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Simulate a party, not an assistant.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple AI “players”&lt;/li&gt;
&lt;li&gt;Distinct personalities and playstyles&lt;/li&gt;
&lt;li&gt;Disagreements, misunderstandings, bad ideas&lt;/li&gt;
&lt;li&gt;The kind of chaos that makes GMing… GMing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal isn’t to generate content for you.&lt;br&gt;&lt;br&gt;
It’s to &lt;strong&gt;push back&lt;/strong&gt;, derail plans, and force you to react.&lt;/p&gt;

&lt;p&gt;In other words it is a sandbox for training, not shortcuts to running a campaign.&lt;/p&gt;




&lt;h2&gt;
  
  
  What GMprentice Is (and Isn’t)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;It is:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A rehearsal space&lt;/li&gt;
&lt;li&gt;A sandbox to test encounters, rulings, and roleplay&lt;/li&gt;
&lt;li&gt;A way to build confidence before game night&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;It isn’t:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A replacement for real players&lt;/li&gt;
&lt;li&gt;A replacement for the real creativity and art which is created in a real session&lt;/li&gt;
&lt;li&gt;A rules engine&lt;/li&gt;
&lt;li&gt;A content mill&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If anything, it’s closer to a flight simulator than a writing tool.&lt;/p&gt;




&lt;h2&gt;
  
  
  Building It as an Indie Maker
&lt;/h2&gt;

&lt;p&gt;From a technical perspective, the interesting challenge wasn’t “AI output quality”.&lt;/p&gt;

&lt;p&gt;It was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maintaining &lt;em&gt;consistent personalities&lt;/em&gt; across multiple agents&lt;/li&gt;
&lt;li&gt;Balancing unpredictability without turning things into nonsense&lt;/li&gt;
&lt;li&gt;Making failure feel useful, not frustrating&lt;/li&gt;
&lt;li&gt;Keeping the GM firmly in control&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ve been deliberate about avoiding the trap of “AI does everything”.&lt;br&gt;&lt;br&gt;
GMprentice works best when it &lt;strong&gt;resists you a little&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I built this app using Cursor, with extensive use of GPT 5.2 in 'Plan Mode' in Cursor, and Opus 4.5 for implementation and iteration of features. &lt;/p&gt;

&lt;p&gt;I made extensive use of the Cursor Browser feature to work on finer details of styling and layout, with implementation assistance from Opus 4.5 working as a pair programmer.&lt;/p&gt;

&lt;p&gt;This is the first time I've truly been &lt;strong&gt;shocked&lt;/strong&gt; by coding LLMs. GPT 5.2 was planning for edge cases and scenarios that I had not initially considered and that would've only emerged in end-to-end testing. Opus 4.5 implemented features and styling changes almost flawlessly. The only major missteps were around Auth0 configuration and creating a &lt;code&gt;middleware.ts&lt;/code&gt; instead of the newer &lt;code&gt;proxy.ts&lt;/code&gt; expected in a NextJS 16 app.&lt;/p&gt;

&lt;p&gt;This was a genuinely awe inspiring build, and I urge anyone cynical about AI coding agents, or who doesn't believe in their abilities to give them a try. You won't be disappointed. &lt;/p&gt;

&lt;p&gt;I have also found the cognitive load of building an app like this is like nothing else I have experienced. I'm no longer laser focused on styling and feature execution, but I'm thinking about the core user experience more, I'm thinking about user journeys, conversion opportunities, and product vision more than nuanced detail which is really just &lt;strong&gt;noise&lt;/strong&gt;. Using LLM coding agents this way frees you and empowers you to ship faster than ever, and deliver real production code that without LLM involvement - I probably wouldn't have had time to build something like this.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I Think This Matters
&lt;/h2&gt;

&lt;p&gt;Tabletop RPGs are growing faster than ever.&lt;br&gt;&lt;br&gt;
More people want to GM — but GMing is still intimidating.&lt;/p&gt;

&lt;p&gt;If we want more confident, inclusive, creative tables, we need better tools for &lt;em&gt;learning the craft&lt;/em&gt;, not just preparing notes.&lt;/p&gt;

&lt;p&gt;Practising privately shouldn’t be a luxury.&lt;br&gt;&lt;br&gt;
It should be normal.&lt;/p&gt;




&lt;h2&gt;
  
  
  What’s Next
&lt;/h2&gt;

&lt;p&gt;GMprentice just launched, and it’s very much a work in progress.&lt;/p&gt;

&lt;p&gt;I’m especially interested in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Feedback from experienced GMs&lt;/li&gt;
&lt;li&gt;How different playstyles feel in simulation&lt;/li&gt;
&lt;li&gt;Where AI helps — and where it absolutely shouldn’t&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re curious, you can check it out here:&lt;br&gt;&lt;br&gt;
👉 &lt;a href="https://gmprentice.app" rel="noopener noreferrer"&gt;https://gmprentice.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And if you’re a GM:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;What’s the one thing you wish you could practise &lt;em&gt;before&lt;/em&gt; a session?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>vercel</category>
      <category>rpg</category>
    </item>
    <item>
      <title>Advent of Code 2025: Human vs AI</title>
      <dc:creator>Anthony Humphreys</dc:creator>
      <pubDate>Wed, 03 Dec 2025 08:51:37 +0000</pubDate>
      <link>https://dev.to/anthonyhumphreys/advent-of-code-2025-human-vs-ai-73c</link>
      <guid>https://dev.to/anthonyhumphreys/advent-of-code-2025-human-vs-ai-73c</guid>
      <description>&lt;h2&gt;
  
  
  ❄️ What I’m Doing
&lt;/h2&gt;

&lt;p&gt;Every day I’ll solve the Advent of Code puzzle in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;JavaScript&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rust&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Python&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then I’ll ask a lineup of current AI coding models to produce their own solutions in the same three languages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GPT-5.1 Codex&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gemini 3 Pro&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Composer-1&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Opus-4.5&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sonnet-4.5&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So for each puzzle, there will be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;My human solutions (in three languages)
&lt;/li&gt;
&lt;li&gt;Five AI solutions (also in three languages each)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s essentially a coding “showdown,” but not a competition. The real aim is to explore how different kinds of reasoning appear in code, how approaches vary from model to model, and how my own thinking compares.&lt;/p&gt;

&lt;h2&gt;
  
  
  Methodology
&lt;/h2&gt;

&lt;p&gt;Not a lab-grade study, just a consistent, lightweight workflow so the comparison stays fair.&lt;/p&gt;

&lt;p&gt;For each challenge, after solving the problem manually, I will drop the challenge text into a txt file in the prompts folder, prepended with a brief prompt: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You are a developer taking on the Advent of Code Challenge 2025.&lt;br&gt;
Create a solution for this problem.&lt;br&gt;
This puzzle has two parts, solve both in the same solution. The program output should just be the two answers on separate lines.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I will aim to keep this prompt consistent across the days, only varying on the output format as needed. I then drop the input file into the inputs folder. In cursor, I select the model under test and then @ the file, the prompt, and the target directory, then hit run. No MCPs, no MAX mode, or anything else to avoid any confounding variables, and to minimise context bloat.&lt;/p&gt;

&lt;p&gt;I then run the run_solutions.py python script to verify the output and review the "thinking" of the model. I'll improve this and the reporting as I progress - but this works as a starting point.&lt;/p&gt;

&lt;p&gt;Once I've verified the output, I add the model directory to the .cursorignore&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: The model solves for all three languages and is able to reference its own prior solutions, e.g. it can start in Python and then translate that to JS and Rust. I appreciate it would be interesting to see how it stacks up when the model can only work in one language at a time, and I may run that as a second iteration of this experiment. However - the prompt does not instruct the model to execute in a particular language first, and I too can reference my own solution in other languages - so I thought this would be interesting in itself.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🎁 Why This Experiment?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;To understand how AI actually solves problems&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Advent of Code puzzles are a perfect testbed: small enough to be self-contained (and not burn too many tokens/£!), but clever enough to require genuine reasoning and creativity. Watching how different models break them down is already proving fascinating, and analysing the end results may yield some interesting insights.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;To improve my own fluency&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Solving each puzzle three times in three different languages forces me to think more deeply about patterns, algorithms, and idioms. It’s a great way to keep my skills sharp, and explore some different languages to what I use day to day.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;To observe differences in style and structure&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Where a model chooses brute force, I choose planning and analysis.&lt;/li&gt;
&lt;li&gt;Where I focus on solving the problem and don't worry about failure modes (as the code is only run with known inputs in a known environment, AI may be much more defensive and write more flexible code.&lt;/li&gt;
&lt;li&gt;Where AI might use packages to solve a problem, I may avoid it and stick to language features and standard lib.
These contrasts say a lot about how “AI thinking” manifests in code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;To build a dataset of human + AI approaches&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;By the end of AoC, even with only 12 challenges this year, that still gives me dozens of solutions across languages and models — plenty to analyse! I’ll have dozens of solutions that all answer the same questions from different angles. That’s an interesting resource in itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  🤖 What I’ll Be Sharing
&lt;/h2&gt;

&lt;p&gt;As the month goes on, I’ll post insights on things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;patterns the AIs gravitate toward&lt;/li&gt;
&lt;li&gt;performance of the output code (hey, perf stats can be fun)
&lt;/li&gt;
&lt;li&gt;common mistakes or blind spots&lt;/li&gt;
&lt;li&gt;whether models one-shot solutions or needed some handholding&lt;/li&gt;
&lt;li&gt;places where models outperform my first instincts or develop more novel solutions&lt;/li&gt;
&lt;li&gt;language-specific quirks (Rust borrow checker vs AI… pray for it)
&lt;/li&gt;
&lt;li&gt;what it feels like to “pair” with multiple coding models&lt;/li&gt;
&lt;li&gt;other random thoughts and musings on AI, Cursor and model nuances &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal isn’t to crown a winner. It’s to understand the landscape of coding in 2025, where AIs shine, where they show their limitations, and how the two complement each other. Will the models one-shot all the solutions or will it pivot into re-prompting or "pair-programming"?&lt;/p&gt;




&lt;h2&gt;
  
  
  🌟 Follow Along
&lt;/h2&gt;

&lt;p&gt;If you enjoy Advent of Code, programming language experiments, or the evolving relationship between developers and AI tooling, stick around. I’ll be posting reflections and curiosities throughout the month, followed by a round-up at the end of the challenges.&lt;/p&gt;

&lt;p&gt;Here’s to a December full of puzzles, head-scratching, learning, and some very weird debugging moments.&lt;/p&gt;

&lt;p&gt;Happy coding, and an even happier Advent. 🎅🔥&lt;/p&gt;

</description>
      <category>ai</category>
      <category>vibecoding</category>
      <category>adventofcode</category>
      <category>programming</category>
    </item>
    <item>
      <title>Introducing Edon</title>
      <dc:creator>Anthony Humphreys</dc:creator>
      <pubDate>Sun, 17 May 2020 01:29:49 +0000</pubDate>
      <link>https://dev.to/anthonyhumphreys/introducing-edon-1lme</link>
      <guid>https://dev.to/anthonyhumphreys/introducing-edon-1lme</guid>
      <description>&lt;h1&gt;
  
  
  What is Edon?
&lt;/h1&gt;

&lt;p&gt;Edon is the name I'm giving to a little corner of the internet, over on &lt;a href="https://github.com/anthonyhumphreys/edon" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; for the JavaScript community to engage in Deno development. I'll be keeping this repo up to date with the upstream repository, and will be regularly opening Pull Requests from this repo into Deno. Edon is founded on the idea that everyone should feel safe, supported and encouraged to contribute to open source. There is no space for any discrimination of any kind, or any behaviour which deters anyone from contributing. I believe in Learning in Public, mentoring and lifting others up, not bringing them down.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why does this exist?
&lt;/h1&gt;

&lt;p&gt;Mainting a separate repo and all that merging sounds like a nightmare, right? Well, I'll archive the repository once Deno has a solid Code of Conduct and the core contributors are seen to be taking their role in supporting a community more seriously.&lt;/p&gt;

&lt;p&gt;The Deno team has, so far, seemed reluctant to take the issue of not having a Code of Conduct seriously.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/denoland/deno/issues/79" rel="noopener noreferrer"&gt;Very early&lt;/a&gt; in the project someone opened an issue regarding the lack of a CoC&lt;br&gt;
&lt;em&gt;&lt;em&gt;CLOSED&lt;/em&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Soon after, [once again(&lt;a href="https://github.com/denoland/deno/issues/670" rel="noopener noreferrer"&gt;https://github.com/denoland/deno/issues/670&lt;/a&gt;), someone suggested adding a CoC. This time it was dismissed, preferring to focus on functionality, and code style.&lt;br&gt;
&lt;em&gt;&lt;em&gt;CLOSED&lt;/em&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/denoland/deno/pull/3517" rel="noopener noreferrer"&gt;A little over a year later&lt;/a&gt;, an incident occurs in discussing an issue, and a CoC is once again suggested.&lt;br&gt;
&lt;em&gt;&lt;em&gt;CLOSED&lt;/em&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/denoland/deno/issues/3954" rel="noopener noreferrer"&gt;Next&lt;/a&gt; another user suggested a CoC, this was dismissed with a link to another issue, with a comment&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I certainly agree that we should all be professional. But I don't like having all these non-necessary files in the root directory. If you want to add a section in the manual linking to a code of conduct that would be ok. Feel free to email me if there has been some problem (?) &lt;a href="mailto:ry@tinyclouds.org"&gt;ry@tinyclouds.org&lt;/a&gt; Closing without merge.&lt;br&gt;
This is a little bit of progress, recognising something is needed, but dismissing a CoC for cluttering the repo seems...misguided.&lt;br&gt;
&lt;em&gt;&lt;em&gt;CLOSED&lt;/em&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://github.com/denoland/deno/issues/5154" rel="noopener noreferrer"&gt;As 1.0 launch approached&lt;/a&gt;, someone proposed a CoC once again.&lt;br&gt;
&lt;em&gt;&lt;em&gt;LOCKED OFF TOPIC&lt;/em&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/denoland/deno/pull/5155" rel="noopener noreferrer"&gt;Yet another attempt&lt;/a&gt; was made to add a CoC&lt;br&gt;
&lt;em&gt;&lt;em&gt;CLOSED&lt;/em&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/denoland/deno/pull/5165" rel="noopener noreferrer"&gt;Finally&lt;/a&gt; a link is added to a CoC... &lt;em&gt;but wait&lt;/em&gt; it isn't Deno's CoC, but Rust's! Close enough right? Not really. Took a further commit to add an email address for concerns. Although this is sufficient to express expectations, it still feels like the least amount of effort being put into this issue.&lt;/p&gt;

&lt;p&gt;Unsurprisingly, &lt;a href="https://github.com/denoland/deno/issues/5171" rel="noopener noreferrer"&gt;issues&lt;/a&gt;, &lt;a href="https://github.com/denoland/deno/pull/5514" rel="noopener noreferrer"&gt;keep coming&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1554854044-80ab1a40e12b%3Fixlib%3Drb-1.2.1%26ixid%3DeyJhcHBfaWQiOjEyMDd9%26auto%3Dformat%26fit%3Dcrop%26w%3D2950%26q%3D80" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1554854044-80ab1a40e12b%3Fixlib%3Drb-1.2.1%26ixid%3DeyJhcHBfaWQiOjEyMDd9%26auto%3Dformat%26fit%3Dcrop%26w%3D2950%26q%3D80" alt="facepalm" width="800" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Why does it matter?
&lt;/h1&gt;

&lt;p&gt;I feel like I really shouldn't need to answer that question, but I expect I'll probably draw some flak for this post. This is an important issue, not only close to my heart, but a common issue in Open Source today.&lt;/p&gt;

&lt;p&gt;Please see the Contributors' Covenant &lt;a href="https://www.contributor-covenant.org/faq/" rel="noopener noreferrer"&gt;FAQs&lt;/a&gt; for more information.&lt;/p&gt;

&lt;p&gt;Please also check out these studies looking at the efficacy of Codes of Conduct in OSS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.win.tue.nl/~aserebre/SANER2017.pdf" rel="noopener noreferrer"&gt;Code of Conduct in Open Source Projects&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dl.acm.org/doi/abs/10.1145/3106237.3106246" rel="noopener noreferrer"&gt;Why modern open source projects fail&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://link.springer.com/chapter/10.1007/978-3-030-20883-7_7" rel="noopener noreferrer"&gt;Open Source Software Community Inclusion Initiatives to Support Women Participation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dl.acm.org/doi/abs/10.1145/3236024.3275441" rel="noopener noreferrer"&gt;Diversity and decorum in open source communities&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dl.acm.org/doi/pdf/10.5555/3290281.3290299" rel="noopener noreferrer"&gt;Patterns for regulating behavior in innovation communities&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pure.tue.nl/ws/portalfiles/portal/90786766/sereemot2017.pdf" rel="noopener noreferrer"&gt;Emotional Labor of Software Engineers&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://link.springer.com/article/10.1007/s10664-018-9659-9" rel="noopener noreferrer"&gt;Discovering community patterns in open-source: a systematic approach and its evaluation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dl.acm.org/doi/pdf/10.1145/3106237.3106246" rel="noopener noreferrer"&gt;Why Modern Open Source Projects Fail&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Adopting a Code of Conduct is not a magic bullet solution and should not be seen as such. It is instead a social contract, signalling to a community that a certain set of standards are expected, and signalling to potential contributors that they are engaging in a safe and supportive community. Building a community takes hard work, commitment, and above all, empathy.&lt;/p&gt;

&lt;h1&gt;
  
  
  So what next?
&lt;/h1&gt;

&lt;p&gt;Deno is a promising project. But it doesn't bode well if issues like this are flaring up and being dealt with in this manner at such an early stage.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Deploying a Gatsby site to Google Cloud Run</title>
      <dc:creator>Anthony Humphreys</dc:creator>
      <pubDate>Fri, 15 May 2020 21:47:29 +0000</pubDate>
      <link>https://dev.to/anthonyhumphreys/deploying-a-gatsby-site-to-google-cloud-run-4h93</link>
      <guid>https://dev.to/anthonyhumphreys/deploying-a-gatsby-site-to-google-cloud-run-4h93</guid>
      <description>&lt;h1&gt;
  
  
  Building the Gatsby site
&lt;/h1&gt;

&lt;p&gt;You don't need to do anything &lt;em&gt;particularly&lt;/em&gt; special to build a Gatsby site for deployment to Cloud Run, but there are some steps between building the project and seeing it live.&lt;/p&gt;

&lt;p&gt;For this tutorial (and for my blogs) I'll use &lt;a href="https://github.com/greglobinski/gatsby-starter-hero-blog" rel="noopener noreferrer"&gt;gatsby-starter-hero-blog&lt;/a&gt; starter.&lt;/p&gt;

&lt;p&gt;Getting up and running is simple (make sure you have the &lt;a href="https://www.gatsbyjs.org/docs/gatsby-cli/" rel="noopener noreferrer"&gt;gatsby cli&lt;/a&gt; installed correctly first)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gatsby new anthonyhumphreysdev https://github.com/greglobinski/gatsby-starter-blog&lt;/code&gt;,&lt;/p&gt;

&lt;p&gt;then you can run your site locally with&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gatsby develop&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After customising the template to your liking (have a poke around, check out the gatsby &amp;amp; starter docs for more guidance!), setting up some content and an initial post, it's time to deploy a test build.&lt;/p&gt;

&lt;p&gt;I decided to use &lt;a href="https://github.com/features/actions" rel="noopener noreferrer"&gt;GitHub Actions&lt;/a&gt; and &lt;a href="https://cloud.google.com/run" rel="noopener noreferrer"&gt;Cloud Run&lt;/a&gt; to do this. GitHub actions is the new kid on the block for CI/CD, but it's such a nice experience, especially being so closely coupled to your actual source repo. I use Cloud Run for Lexio and love its ease of use and general developer experience.&lt;/p&gt;

&lt;p&gt;You'll need to set up some environment variables for the gatsby starter and for the GitHub action workflow. These should be clear from the starter's docs, and from the source below. You can &lt;a href="https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets" rel="noopener noreferrer"&gt;set secrets in the GitHub repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can checkout the full action YAML &lt;a href="https://github.com/anthonyhumphreys/anthonyhumphreysdev/blob/master/.github/workflows/main.yml" rel="noopener noreferrer"&gt;here&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;I simply use the Node action to install dependencies and build the site.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup NodeJS&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10.x"&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|-&lt;/span&gt;
    &lt;span class="s"&gt;yarn global add gatsby-cli&lt;/span&gt;
    &lt;span class="s"&gt;yarn&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Gatsby Build&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yarn build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's all there really is to it as far as building the site goes - no different to building on your own machine...but we still need a few bits and pieces yet.&lt;/p&gt;

&lt;h1&gt;
  
  
  Cloud Run
&lt;/h1&gt;

&lt;p&gt;Before continuing, you'll need to provision a new service in Cloud Run (assuming you have a Google Cloud account and Project set up!). Make a note of the Service Account Email Address, Project ID, Service Name, as you will need these later.&lt;/p&gt;

&lt;h1&gt;
  
  
  Building and Deploying the Docker Image
&lt;/h1&gt;

&lt;p&gt;I had a few issues with the Gatsby Docker image so rolled my own...probably should've stuck with it and resolved my issues, but it worked so that's just a &lt;code&gt;// TODO: Use gatsby image&lt;/code&gt; instead!&lt;/p&gt;

&lt;h2&gt;
  
  
  Dockerfile
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;FROM nginx:latest&lt;/span&gt;

&lt;span class="s"&gt;COPY public /usr/share/nginx/html&lt;/span&gt;
&lt;span class="s"&gt;COPY nginxconf/nginx.conf /etc/nginx/nginx.conf&lt;/span&gt;

&lt;span class="s"&gt;EXPOSE &lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're not familiar with Docker - all that's happening here is I use the latest version of the &lt;a href="https://hub.docker.com/_/nginx" rel="noopener noreferrer"&gt;nginx image from dockerhub&lt;/a&gt;. I copy the files built in the previous step, which are in the &lt;code&gt;public&lt;/code&gt; directory, to the &lt;code&gt;/usr/share/nginx/html&lt;/code&gt; directory in the container, and then copy the &lt;code&gt;nginx.conf&lt;/code&gt; file from the project to the container too. The last thing I do is &lt;code&gt;EXPOSE 8080&lt;/code&gt; which opens up port 8080 for the container.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nginx Config
&lt;/h2&gt;

&lt;p&gt;I won't go into Nginx as a reverse proxy, there are plenty of blog posts about that around already. You can however find the config I used below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;events&lt;/span&gt; {}
&lt;span class="n"&gt;http&lt;/span&gt; {
    &lt;span class="n"&gt;server&lt;/span&gt; {
        &lt;span class="n"&gt;listen&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;;
        &lt;span class="n"&gt;server_tokens&lt;/span&gt; &lt;span class="n"&gt;off&lt;/span&gt;;
        &lt;span class="n"&gt;location&lt;/span&gt; / {
            &lt;span class="n"&gt;include&lt;/span&gt; /&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;nginx&lt;/span&gt;/&lt;span class="n"&gt;mime&lt;/span&gt;.&lt;span class="n"&gt;types&lt;/span&gt;;
            &lt;span class="n"&gt;autoindex&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;;
            &lt;span class="n"&gt;root&lt;/span&gt;   /&lt;span class="n"&gt;usr&lt;/span&gt;/&lt;span class="n"&gt;share&lt;/span&gt;/&lt;span class="n"&gt;nginx&lt;/span&gt;/&lt;span class="n"&gt;html&lt;/span&gt;;
            &lt;span class="n"&gt;index&lt;/span&gt;  &lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;htm&lt;/span&gt;;
            &lt;span class="n"&gt;try_files&lt;/span&gt; $&lt;span class="n"&gt;uri&lt;/span&gt; $&lt;span class="n"&gt;uri&lt;/span&gt;/ /&lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;html&lt;/span&gt;;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before I can push the image I need to setup GCloud in order to talk to Google's Cloud Registry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup GCloud&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GoogleCloudPlatform/github-actions/setup-gcloud@master&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;286.0.0"&lt;/span&gt;
    &lt;span class="na"&gt;service_account_email&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.RUN_SA_EMAIL }}&lt;/span&gt;
    &lt;span class="na"&gt;service_account_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GCLOUD_AUTH }}&lt;/span&gt;
    &lt;span class="na"&gt;project_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.RUN_PROJECT }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I build the image&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build Docker Image&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker build . -t "eu.gcr.io/$PROJECT_ID/$SERVICE_NAME:$GITHUB_SHA"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, I authenticate and publish the image&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Authenticate for gcr&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gcloud auth print-access-token | docker login -u oauth2accesstoken --password-stdin https://eu.gcr.io/$PROJECT_ID&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Push Docker Image to gcr&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker push eu.gcr.io/$PROJECT_ID/$SERVICE_NAME:$GITHUB_SHA&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final step is to deploy a new revision of the service to Cloud Run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|-&lt;/span&gt;
    &lt;span class="s"&gt;gcloud run deploy $SERVICE_NAME \&lt;/span&gt;
      &lt;span class="s"&gt;--quiet \&lt;/span&gt;
      &lt;span class="s"&gt;--region $RUN_REGION \&lt;/span&gt;
      &lt;span class="s"&gt;--image eu.gcr.io/$PROJECT_ID/$SERVICE_NAME:$GITHUB_SHA \&lt;/span&gt;
      &lt;span class="s"&gt;--platform managed \&lt;/span&gt;
      &lt;span class="s"&gt;--allow-unauthenticated&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding a post
&lt;/h2&gt;

&lt;p&gt;Simply add a new post under &lt;code&gt;content/posts&lt;/code&gt; following the naming convention, commit your changes and push - when your changes hit the master branch, the Action will run and update your site. Magic, right?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fy0bz09q9sllvdc08iszf.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fy0bz09q9sllvdc08iszf.gif" alt="Magic GIF" width="275" height="252"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hopefully you can now browse to the url for the service and see your brand new site! If I have skimmed over any steps or if anything isn't clear, hit me up on &lt;a href="https://twitter.com/aphumphreys" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; and I'll clear things up!&lt;/p&gt;

</description>
      <category>react</category>
      <category>gatsby</category>
      <category>gcp</category>
      <category>github</category>
    </item>
    <item>
      <title>useSyncedState</title>
      <dc:creator>Anthony Humphreys</dc:creator>
      <pubDate>Fri, 15 May 2020 21:45:58 +0000</pubDate>
      <link>https://dev.to/anthonyhumphreys/usesyncedstate-54k3</link>
      <guid>https://dev.to/anthonyhumphreys/usesyncedstate-54k3</guid>
      <description>&lt;h1&gt;
  
  
  Synced State Experiment
&lt;/h1&gt;

&lt;p&gt;After working on useLocalStorage, I wondered how hard it would be to sync state to persistent, distributed storage. For my 5th Day of 100 Days of code I decided to make a first attempt at this idea.&lt;/p&gt;

&lt;p&gt;I followed the same pattern as for building the useLocalStorage hook, extending the useState API, and triggering a useEffect on the state update to handle the state synchronization...asynchronously8.&lt;/p&gt;

&lt;p&gt;Without further ado, here's the code...I'll be working more on this. At the moment, this might be useful for a use case such as building a user profile. A common poor experience is filling out some information and &lt;em&gt;boom&lt;/em&gt; you've hit refresh, or swiped back on the trackpad...this scenario is already resolved by the localStorage hook, but I thought it would be cool to explore binding state to an API. The current implementation is geared around a REST API, so next steps will be to look at passing a query/mutation to the hook.&lt;/p&gt;

&lt;p&gt;I'm also thinking about how to hook this up to a useReducer, passing in the reducer function to determine how to manage state.&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;import&lt;/span&gt; &lt;span class="p"&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;useCallback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useReducer&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="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;State&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;success&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="nl"&gt;loading&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="nl"&gt;error&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="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;loading&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;syncedState&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;object&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;initialState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;State&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;success&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;loading&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;error&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reducer&lt;/span&gt; &lt;span class="o"&gt;=&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;State&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;State&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;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&lt;/span&gt;&lt;span class="dl"&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;success&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;loading&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;error&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="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;loading&lt;/span&gt;&lt;span class="dl"&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;success&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;loading&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;error&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="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&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;success&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;loading&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;error&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="nl"&gt;default&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;state&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;SYNC_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://localhost:3000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useSyncedState&lt;/span&gt; &lt;span class="o"&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;initialValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;syncUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;State&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Function&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;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useReducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reducer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;initialState&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;syncToServer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&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="na"&gt;valueToStore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;loading&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SYNC_URL&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="s2"&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;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&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;application/json&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="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;valueToStore&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&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="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&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;syncToClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&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="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;loading&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SYNC_URL&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="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&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;application/json&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;syncedState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;syncedValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setSyncedValue&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;object&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;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="k"&gt;try&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;syncedState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;syncToClient&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;syncedState&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;initialValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;initialValue&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;setValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&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;try&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;valueToStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Function&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;syncedValue&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="nf"&gt;setSyncedValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;valueToStore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&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="p"&gt;};&lt;/span&gt;

  &lt;span class="nf"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;syncToServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;syncedValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;delay&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;gt;&lt;/span&gt; &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeout&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;syncedValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;syncToServer&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;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;syncedValue&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Would be curious to hear anyone's thoughts on this, I can imagine lots of questions about the motivation and to be perfectly honest...it just seemed like a cool thing to put together 🤷‍♂️&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
    </item>
    <item>
      <title>useProgressiveLoading</title>
      <dc:creator>Anthony Humphreys</dc:creator>
      <pubDate>Fri, 15 May 2020 21:45:09 +0000</pubDate>
      <link>https://dev.to/anthonyhumphreys/useprogressiveloading-5f0b</link>
      <guid>https://dev.to/anthonyhumphreys/useprogressiveloading-5f0b</guid>
      <description>&lt;p&gt;If you've ever worked with a slow moving API that you just can't work around, you've probably written something along these lines already. I thought it would be handy to have this as a hook, to drop into loading components and not have to rewrite the same piece of logic umpteen times.&lt;/p&gt;

&lt;p&gt;There are definitely better UX patterns than this, and I am in no way advocating this as a good practice for loading behaviour, but sometimes you can't avoid unsavoury bits of UI like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useProgressiveLoading&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&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;Loading your profile is taking a litle longer than normal, please wait&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;Still loading, please wait a while longer...&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;Still loading your profile, thank you for your patience...&lt;/span&gt;&lt;span class="dl"&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;...&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;LoadingText&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;text&lt;/span&gt;&lt;span class="p"&gt;}&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;LoadingText&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  ...
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hook takes two parameters, the first is an array of times in seconds, the second is an array of strings. The principle is really simple, the hook creates a timeout for each timing passed, and will update the &lt;code&gt;text&lt;/code&gt; value each time the timeout fires. The two arrays must be 'balanced' in terms of length, or the hook will throw an error.&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;import&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="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useProgressiveLoading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Function&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;timings&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="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Still loading, please wait...&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;Still loading, please wait a while longer...&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;Still loading, thank you for your patience...&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="kr"&gt;string&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;timings&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="nx"&gt;strings&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`You passed &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;timings&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="s2"&gt; times and &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;strings&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="s2"&gt; - there should be the same number of each.`&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;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setText&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&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="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;timers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTimers&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;

  &lt;span class="nf"&gt;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="nx"&gt;timings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="na"&gt;delay&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="na"&gt;index&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTimeout&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="nf"&gt;setText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
        &lt;span class="nx"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;setTimers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;oldTimers&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;oldTimers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timer&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;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;timers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="nf"&gt;setText&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="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;timings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;strings&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;text&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;That's all there is to this one, its pretty simple!&lt;/p&gt;

&lt;p&gt;You can install this from &lt;a href="https://www.npmjs.com/package/@anthonyhumphreys/hooks" rel="noopener noreferrer"&gt;npm&lt;/a&gt; or check out the repo on &lt;a href="https://github.com/anthonyhumphreys/hooks" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As always, suggestions, improvements etc all welcome!&lt;/p&gt;

&lt;p&gt;This post was for day 4 of my &lt;a href="https://twitter.com/hashtag/100DaysOfCode" rel="noopener noreferrer"&gt;#100DaysOfCode&lt;/a&gt; challenge. &lt;a href="https://twitter.com/aphumphreys" rel="noopener noreferrer"&gt;Follow me on Twitter&lt;/a&gt; for more.&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
    </item>
    <item>
      <title>useBrowserStorage</title>
      <dc:creator>Anthony Humphreys</dc:creator>
      <pubDate>Fri, 15 May 2020 21:43:51 +0000</pubDate>
      <link>https://dev.to/anthonyhumphreys/usebrowserstorage-2nn9</link>
      <guid>https://dev.to/anthonyhumphreys/usebrowserstorage-2nn9</guid>
      <description>&lt;p&gt;For day 3 of my #100DaysOfCode challenge I thought I would expand and polish a hook I previously wrote (adapted from several examples online such as &lt;a href="https://medium.com/@andrewgbliss/react-custom-hook-uselocalstorage-afbde976c72b" rel="noopener noreferrer"&gt;this one&lt;/a&gt;) which wraps the useState hook and persists state in localStorage or sessionStorage depending on use case.&lt;/p&gt;

&lt;p&gt;The hook conforms to a mix of the localStorage and useState API.&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="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;setState&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useBrowserStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;key&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;value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;StorageType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LOCAL_STORAGE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is so simple to use, virtually a drop in replacement for useState and gives you state persistance and restoration. You can use &lt;code&gt;state&lt;/code&gt; as an ordinary state variable, and call &lt;code&gt;setState&lt;/code&gt; with either a string or a function, just like the setter for &lt;code&gt;useState&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That's it! Full hook code below, and published over at &lt;a href="https://www.npmjs.com/package/@anthonyhumphreys/hooks" rel="noopener noreferrer"&gt;npm&lt;/a&gt; with the code available on &lt;a href="https://github.com/anthonyhumphreys/hooks" rel="noopener noreferrer"&gt;GitHub&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;StorageType&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;LOCAL_STORAGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LOCAL_STORAGE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;SESSION_STORAGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SESSION_STORAGE&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useBrowserStorage&lt;/span&gt; &lt;span class="o"&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;initialValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;StorageType&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;storageProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;StorageType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LOCAL_STORAGE&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sessionStorage&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;storedValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setStoredValue&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;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="k"&gt;try&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;storedItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;storageProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;storedItem&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="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;storedItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;initialValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;initialValue&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;setValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;Function&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;try&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;valueToStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Function&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;storedValue&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="nf"&gt;setStoredValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;valueToStore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;storageProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&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;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;valueToStore&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&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="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;storedValue&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  UPDATE
&lt;/h3&gt;

&lt;p&gt;This was originally published as 'useLocalStorage' - but then I realised using session storage in a hook called that wouldn't make much sense. Naming things is hard!&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
    </item>
    <item>
      <title>useFakeAsync</title>
      <dc:creator>Anthony Humphreys</dc:creator>
      <pubDate>Fri, 15 May 2020 21:42:02 +0000</pubDate>
      <link>https://dev.to/anthonyhumphreys/usefakeasync-4jef</link>
      <guid>https://dev.to/anthonyhumphreys/usefakeasync-4jef</guid>
      <description>&lt;p&gt;You can see the hook takes a few simple parameters, including the familiar pairing of a callback function and a delay in milliseconds. This follows the shape of JavaScript's setTimeout and setInterval methods.&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;import&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="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;FakeAsyncState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;PENDING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PENDING&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;COMPLETE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;COMPLETE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ERROR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ERROR&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useFakeAsync&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Function&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;shouldError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="o"&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;chaos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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;state&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;FakeAsyncState&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;FakeAsyncState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PENDING&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&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="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NodeJS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Timeout&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;fail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;chaos&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;lt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;shouldError&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;fail&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;FakeAsyncState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COMPLETE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;delay&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="nf"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;FakeAsyncState&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;gt;&lt;/span&gt; &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timer&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;delay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chaos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;shouldError&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;state&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 hook also takes a 'shouldError' parameter so that an error condition can be forced.&lt;/p&gt;

&lt;p&gt;The fourth parameter is a little more interesting, 'chaos'. I added this to randomise a success or error condition.&lt;/p&gt;

&lt;p&gt;The state returned by the hook mimics a promise, it can either be pending, complete or in an error condition.&lt;/p&gt;

&lt;p&gt;Hopefully this will help testing behaviour across components, and avoiding those inevitable bugs that creep in when integrating a UI with an API, like stutters between loading and success states, for example.&lt;/p&gt;

&lt;p&gt;That's all! Go checkout the code on &lt;a href="https://github.com/anthonyhumphreys/hooks/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; or install my handy hooks library from &lt;a href="https://www.npmjs.com/package/@anthonyhumphreys/hooks" rel="noopener noreferrer"&gt;npm&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This post was for day 1 of my &lt;a href="https://twitter.com/hashtag/100DaysOfCode" rel="noopener noreferrer"&gt;#100DaysOfCode&lt;/a&gt; challenge. &lt;a href="https://twitter.com/aphumphreys" rel="noopener noreferrer"&gt;Follow me on Twitter&lt;/a&gt; for more.&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
    </item>
    <item>
      <title>useTSDX</title>
      <dc:creator>Anthony Humphreys</dc:creator>
      <pubDate>Fri, 15 May 2020 21:41:13 +0000</pubDate>
      <link>https://dev.to/anthonyhumphreys/usetsdx-2hdk</link>
      <guid>https://dev.to/anthonyhumphreys/usetsdx-2hdk</guid>
      <description>&lt;h1&gt;
  
  
  Publishing hooks
&lt;/h1&gt;

&lt;p&gt;One of the main outcomes I wanted from building this blog was to maintain a companion library of useful hooks. I'm writing most things in TypeScript these days (yeah I jumped on that hype train pretty hard, and haven't looked back...)&lt;/p&gt;

&lt;p&gt;Publishing a custom hook is as easy as publishing a component for react. That being said, I'd never published a library built with TypeScript so wasn't entirely sure what was needed. That's when I remembered &lt;a href="https://twitter.com/jaredpalmer" rel="noopener noreferrer"&gt;Jared Palmer's&lt;/a&gt; amazing TSDX CLI, I think I first heard about it on the &lt;a href="https://syntax.fm/" rel="noopener noreferrer"&gt;SyntaxFM&lt;/a&gt; podcast.&lt;/p&gt;

&lt;p&gt;I simply ran &lt;code&gt;npx tsdx create hooks&lt;/code&gt;, dropped my code in the &lt;code&gt;src&lt;/code&gt; directory, modified the github action included and hey presto, I've got a library live.&lt;/p&gt;

&lt;p&gt;I'll certainly be making more use of this tool, for libraries and React Components.&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>npm</category>
    </item>
    <item>
      <title>useGeolocation</title>
      <dc:creator>Anthony Humphreys</dc:creator>
      <pubDate>Fri, 15 May 2020 21:39:53 +0000</pubDate>
      <link>https://dev.to/anthonyhumphreys/usegeolocation-2ke7</link>
      <guid>https://dev.to/anthonyhumphreys/usegeolocation-2ke7</guid>
      <description>&lt;p&gt;One of the first custom hooks I wrote was to grab the user's location using the Geolocation API. I wrote it for a project with two requirements - to get a user's location on a button press, and to 'watch' a user's location to keep a map preview up to date.&lt;/p&gt;

&lt;p&gt;Let's cut straight to the code:&lt;/p&gt;

&lt;h3&gt;
  
  
  Usage (Single Location):
&lt;/h3&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;position&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="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useGeolocation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;GeolocationMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SINGLE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Usage (Watch Location):
&lt;/h3&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;position&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;locationHistory&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useGeolocation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;GeolocationMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WATCH&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;The hook is super simple to use. The first call returns a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/GeolocationPosition" rel="noopener noreferrer"&gt;position object&lt;/a&gt; or an &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/GeolocationPositionError" rel="noopener noreferrer"&gt;error&lt;/a&gt;, the second call will update 'position' every time the underlying hook receives an updated position from the Geolocation API, and will maintain an array of all positions observed in 'locationHistory'.&lt;/p&gt;

&lt;p&gt;You can check out the code over at &lt;a href="https://github.com/anthonyhumphreys/hooks" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; or install it from &lt;a href="https://www.npmjs.com/package/@anthonyhumphreys/hooks" rel="noopener noreferrer"&gt;npm&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The design of the underlying hook allows you to seamlessly switch between 'modes' too - so you could seamlessly transition between displaying a user's initial location and showing a user's journey as they follow directions, for example.&lt;/p&gt;

&lt;p&gt;Its that simple. This is one of the most attractive value propositions offered by hooks - abstracting away logic in an easily reusable and easy to consume manner.&lt;/p&gt;

&lt;h3&gt;
  
  
  The full hook code
&lt;/h3&gt;

&lt;p&gt;This is still a work in progress, types are incomplete etc.&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;import&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;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;GeolocationMode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;SINGLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;single&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;WATCH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;watch&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;GeolocationCoordinates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;accuracy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;altitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;altitudeAccuracy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;heading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;GeolocationResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GeolocationCoordinates&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;timestamp&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="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;GeolocationError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;GeolocationConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

  &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;IPositionState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GeolocationResponse&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;positionError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GeolocationError&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;positionLoading&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="nl"&gt;previousPositions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GeolocationResponse&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;defaultGeolocationConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GeolocationConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;maximumAge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;enableHighAccuracy&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useGeolocation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GeolocationMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;GeolocationMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SINGLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Boolean&lt;/span&gt; &lt;span class="o"&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;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GeolocationConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;defaultGeolocationConfig&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;positionState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setPositionState&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;IPositionState&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;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;positionError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;positionLoading&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;previousPositions&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;onGeolocationSuccess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;position&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;stop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;setPositionState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;oldState&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;oldState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;previousPositions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
              &lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;GeolocationMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SINGLE&lt;/span&gt;
                &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;oldState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&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="nx"&gt;oldState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;previousPositions&lt;/span&gt;
                      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;oldState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;previousPositions&lt;/span&gt;
                      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]),&lt;/span&gt;
                    &lt;span class="nx"&gt;oldState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&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="nx"&gt;setPositionState&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;onGeolocationError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setPositionState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;oldState&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;oldState&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="nx"&gt;setPositionState&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;GeolocationMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SINGLE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geolocation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCurrentPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nx"&gt;onGeolocationSuccess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;onGeolocationError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;config&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;GeolocationMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WATCH&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geolocation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;watchPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nx"&gt;onGeolocationSuccess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;onGeolocationError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;config&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="nx"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stop&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;positionState&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>
    </item>
  </channel>
</rss>
