<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Mike Jolley</title>
	<atom:link href="https://mikejolley.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://mikejolley.com</link>
	<description>Mike Jolley is a software engineer at Automattic building things with PHP, JavaScript, React, and WordPress.</description>
	<lastBuildDate>Thu, 11 May 2023 23:10:03 +0000</lastBuildDate>
	<language>en-GB</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://i0.wp.com/mikejolley.com/wp-content/uploads/2022/12/mjtetris6.png?fit=32%2C32&#038;ssl=1</url>
	<title>Mike Jolley</title>
	<link>https://mikejolley.com</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">96265069</site>	<item>
		<title>Building a Windows 98 Retro Gaming PC on the Athlon XP Platform</title>
		<link>https://mikejolley.com/2023/05/12/building-a-windows-98-retro-gaming-pc-on-the-athlon-xp-platform/</link>
		
		<dc:creator><![CDATA[Mike Jolley]]></dc:creator>
		<pubDate>Thu, 11 May 2023 23:10:03 +0000</pubDate>
				<category><![CDATA[Retro Gaming]]></category>
		<category><![CDATA[Computing]]></category>
		<category><![CDATA[DOS]]></category>
		<category><![CDATA[gaming]]></category>
		<category><![CDATA[retro]]></category>
		<category><![CDATA[win98]]></category>
		<guid isPermaLink="false">https://mikejolley.com/?p=56267</guid>

					<description><![CDATA[Undoubtedly, the convenience of sites like GOG.com, and software such as DOSBox, has made emulating and playing classic PC games on modern hardware more popular and more accessible than ever. Despite this, if you&#8217;re someone who relishes tinkering with real hardware, building an authentic system can be a truly satisfying and nostalgic experience. That is [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Undoubtedly, the convenience of sites like GOG.com, and software such as DOSBox, has made emulating and playing classic PC games on modern hardware more popular and more accessible than ever.</p>



<p class="wp-block-paragraph">Despite this, if you&#8217;re someone who relishes <em>tinkering with real hardware</em>, building an authentic system can be a truly satisfying and nostalgic experience.</p>



<p class="wp-block-paragraph">That is why I wanted to put together my own retro PC. I chose <em>Socket 462</em> and the <em>Athlon XP</em> platform—a great line of processors from the turn of the millennium which I loved back in the day.</p>



<p class="wp-block-paragraph">My goal was to create a single machine that could handle DOS games from the 90s all the way up to Windows games from the early 2000&#8217;s, and for that purpose, Windows 98 SE seemed like the best fit.</p>



<p class="wp-block-paragraph">Running Windows 98, you get real mode DOS (something removed in Windows ME), plenty of chipset and processor options, great driver support, and of course access to <a href="https://www.htasoft.com/u98sesp/">unofficial service packs</a> to fix USB drives, networking, and performance issues.</p>



<p class="wp-block-paragraph">I recently completed my build (pictured below!) and in this post I’ll go through the components I tested, problems I faced, and what I learned in the hope that it helps anyone else looking to build something similar. </p>



<figure class="wp-block-image size-large"><a href="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/Moment-App-20230417001315217-2.jpg?ssl=1"><img data-recalc-dims="1" fetchpriority="high" decoding="async" width="768" height="1024" data-attachment-id="56268" data-permalink="https://mikejolley.com/2023/05/12/building-a-windows-98-retro-gaming-pc-on-the-athlon-xp-platform/moment-app-20230417001315217-2/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/Moment-App-20230417001315217-2.jpg?fit=1536%2C2048&amp;ssl=1" data-orig-size="1536,2048" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;1683763240&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Moment-App-20230417001315217-2" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/Moment-App-20230417001315217-2.jpg?fit=225%2C300&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/Moment-App-20230417001315217-2.jpg?fit=768%2C1024&amp;ssl=1" src="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/Moment-App-20230417001315217-2.jpg?resize=768%2C1024&#038;ssl=1" alt="A beige PC and Monitor running Transport Tycoon on DOS" class="wp-image-56268" srcset="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/Moment-App-20230417001315217-2.jpg?resize=768%2C1024&amp;ssl=1 768w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/Moment-App-20230417001315217-2.jpg?resize=225%2C300&amp;ssl=1 225w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/Moment-App-20230417001315217-2.jpg?resize=1152%2C1536&amp;ssl=1 1152w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/Moment-App-20230417001315217-2.jpg?w=1536&amp;ssl=1 1536w" sizes="(max-width: 768px) 100vw, 768px" /></a><figcaption class="wp-element-caption">I&#8217;m writing this post to the sweet beats from <em>Transport Tycoon</em></figcaption></figure>



<h2 class="wp-block-heading">Defining requirements</h2>



<p class="wp-block-paragraph">Before jumping onto eBay and grabbing random components, I&#8217;d suggest deciding on a socket type early on, otherwise you&#8217;ll be overwhelmed with choice. Decide what type of system you want to build and research what parts are readily available and within budget.</p>



<p class="wp-block-paragraph">If you at least know what type of CPU you’ll use, you’ll have an easier time searching for components, and things like RAM should be mostly interchangeable should you need to test multiple motherboards with different chipsets.</p>



<p class="wp-block-paragraph">For myself, I decided early on I wanted to build a Socket 462 machine—its more or less age appropriate (Athlon XP launched in 2001), powerful enough to run games from the early 2000’s, parts are easy to obtain, and it was the CPU I used in my youth.&nbsp;</p>



<p class="wp-block-paragraph">So my requirements were:</p>



<ul class="wp-block-list">
<li>Components from the early 2000s to run a wide variety of games in DOS and Windows 98</li>



<li>Socket 462 Motherboard, preferably mATX to fit within a horizontal case</li>



<li>Athlon XP processor</li>



<li>AGP graphics port</li>



<li>At least 2xPCI slots (ISA would have been nice for sound, but boards with ISA are rare for Socket 462, so I made this concession)</li>



<li>Decent sound card with a Midi port and decent DOS support (lots more on this later)</li>



<li>512MB RAM (maximum recommended for Windows 98)</li>



<li>Lots of beige</li>
</ul>



<p class="wp-block-paragraph">The case I picked certainly met the beige requirement; the CS101 from <a href="http://www.evercase.co.uk">Evercase</a> in the UK. They are retro styled modern cases that are inexpensive. It was actually cheaper to pick up a brand new case than get a crusty old one from eBay when I was looking!</p>



<figure class="wp-block-image aligncenter size-full is-resized"><a href="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/CS101-001-600x600-1.jpg?ssl=1"><img data-recalc-dims="1" decoding="async" data-attachment-id="56270" data-permalink="https://mikejolley.com/2023/05/12/building-a-windows-98-retro-gaming-pc-on-the-athlon-xp-platform/cs101-001-600x600-1/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/CS101-001-600x600-1.jpg?fit=600%2C600&amp;ssl=1" data-orig-size="600,600" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="CS101-001-600&amp;#215;600-1" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/CS101-001-600x600-1.jpg?fit=300%2C300&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/CS101-001-600x600-1.jpg?fit=600%2C600&amp;ssl=1" src="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/CS101-001-600x600-1.jpg?resize=374%2C374&#038;ssl=1" alt="" class="wp-image-56270" width="374" height="374" srcset="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/CS101-001-600x600-1.jpg?w=600&amp;ssl=1 600w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/CS101-001-600x600-1.jpg?resize=300%2C300&amp;ssl=1 300w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/CS101-001-600x600-1.jpg?resize=150%2C150&amp;ssl=1 150w" sizes="(max-width: 374px) 100vw, 374px" /></a><figcaption class="wp-element-caption">The CS101 fits an mATX board</figcaption></figure>



<p class="wp-block-paragraph">I found a graphics card early on. While many folks recommend the GeForce 4 Ti line of cards for Windows 98 and DOS, these are now quite hard to come by for a fair price. I instead picked up a cheap GeForce 5700 FX from Facebook marketplace. </p>



<figure class="wp-block-image aligncenter size-full"><a href="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/Graphic-Card-MSI-Nvidia-GEFORCE-FX-5700-LE-Small.jpeg?ssl=1"><img data-recalc-dims="1" decoding="async" width="320" height="248" data-attachment-id="56271" data-permalink="https://mikejolley.com/2023/05/12/building-a-windows-98-retro-gaming-pc-on-the-athlon-xp-platform/graphic-card-msi-nvidia-geforce-fx-5700-le-small/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/Graphic-Card-MSI-Nvidia-GEFORCE-FX-5700-LE-Small.jpeg?fit=320%2C248&amp;ssl=1" data-orig-size="320,248" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;1&quot;}" data-image-title="Graphic-Card-MSI-Nvidia-GEFORCE-FX-5700-LE-Small" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/Graphic-Card-MSI-Nvidia-GEFORCE-FX-5700-LE-Small.jpeg?fit=300%2C233&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/Graphic-Card-MSI-Nvidia-GEFORCE-FX-5700-LE-Small.jpeg?fit=320%2C248&amp;ssl=1" src="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/Graphic-Card-MSI-Nvidia-GEFORCE-FX-5700-LE-Small.jpeg?resize=320%2C248&#038;ssl=1" alt="" class="wp-image-56271" srcset="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/Graphic-Card-MSI-Nvidia-GEFORCE-FX-5700-LE-Small.jpeg?w=320&amp;ssl=1 320w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/Graphic-Card-MSI-Nvidia-GEFORCE-FX-5700-LE-Small.jpeg?resize=300%2C233&amp;ssl=1 300w" sizes="(max-width: 320px) 100vw, 320px" /></a></figure>



<p class="wp-block-paragraph">It&#8217;s an AGP 8x card with 256MB ram and decent performance. It also supports DirectX 9. The only downside is you&#8217;re unable to use older, preferred drivers. I used 56.64 in my build, from March 2004.</p>



<h2 class="wp-block-heading">Finding the right chipset, sound card, and CPU combination</h2>



<p class="wp-block-paragraph">I’m writing this post having built my system, so I am now in a position where I’ve made several mis-steps selecting motherboards which you can learn from. I actually went through several different motherboards with VIA and SIS chipsets to find ”the one”, as well as a few different sound card options and CPUs.&nbsp;</p>



<h3 class="wp-block-heading">The Athlon XP-M Processors</h3>



<p class="wp-block-paragraph">First off let’s cover the CPU I opted for in the end; Athlon XP-M 2600+.&nbsp;</p>



<figure class="wp-block-image aligncenter size-full is-resized"><a href="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/s-l1600.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" data-attachment-id="56272" data-permalink="https://mikejolley.com/2023/05/12/building-a-windows-98-retro-gaming-pc-on-the-athlon-xp-platform/s-l1600/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/s-l1600.png?fit=902%2C923&amp;ssl=1" data-orig-size="902,923" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="s-l1600" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/s-l1600.png?fit=293%2C300&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/s-l1600.png?fit=902%2C923&amp;ssl=1" src="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/s-l1600.png?resize=311%2C317&#038;ssl=1" alt="" class="wp-image-56272" width="311" height="317" srcset="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/s-l1600.png?w=902&amp;ssl=1 902w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/s-l1600.png?resize=293%2C300&amp;ssl=1 293w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/s-l1600.png?resize=768%2C786&amp;ssl=1 768w" sizes="auto, (max-width: 311px) 100vw, 311px" /></a></figure>



<p class="wp-block-paragraph">The Athlon XP desktop processors are great; really good performance, but one thing they lack is the ability to change the multiplier via software—the XP-M allows this. </p>



<p class="wp-block-paragraph">This is super useful when playing speed-sensitive games (such as Theme Park, Wing Commander, Civilization) because you can slow the system down to 486 speeds (using the <a href="https://www.vogons.org/viewtopic.php?t=38613">setmul</a> utility) without additional software running in the background.</p>



<p class="wp-block-paragraph">One of the motherboards I obtained did not like this processor so consider this when searching. Once I had discovered the versatility of the XP-M, I made support for this a requirement.</p>



<h3 class="wp-block-heading">Sound cards w/ DOS support</h3>



<p class="wp-block-paragraph">This is one of the most difficult things I found to get working. It’s no secret, ISA based sound cards are superior for DOS, but unless you find an exotic, obscure Socket 462 motherboard with an ISA port, its out of the question. So I stuck with PCI. I’ll cover the sound cards in more detail later on in the post, but for now we’ll just mention the underlying technologies used for these sound cards to support DOS.</p>



<p class="wp-block-paragraph">Essentially for a PCI based sound card to talk to DOS applications it needs one of the following things available (in order of DOS compatibility):</p>



<ul class="wp-block-list">
<li>A SB-LINK/PC-PCI cable from the motherboard to the sound card so the two can communicate</li>



<li>Distributed DMA (DDMA) support at chipset level</li>



<li>A sound card with it’s own version of DDMA (such as ESS Solo cards which have their own TDMA method)</li>



<li>Some kind of software/proprietary solution</li>
</ul>



<p class="wp-block-paragraph"><em>SB-Link</em> is not common on Socket 462 motherboards. I couldn&#8217;t find one so I think it’s safe to assume its as rare as an ISA port.</p>



<p class="wp-block-paragraph">Jumping to <em>TDMA</em> (ESS Solo cards) some people have had good success with these, particularly on VIA chipsets), but I had issues with some games like Monkey Island crashing, so I didn’t go this route in the end even though I really liked the card itself (and its ESSFM chip which sounds really nice).</p>



<p class="wp-block-paragraph">As for the software solutions, they vary based on the card you use. SoundBlaster Live! cards can use software, as do some others like Yamaha YMFs, and generally compatibility is good, however, they take up vital memory and often require EMM386 to be loaded to function, so compatibility with certain older DOS games can be limited.</p>



<p class="wp-block-paragraph"><strong>DDMA</strong> is the way to go in my opinion. The challenge is finding a chipset that supports it. For SIS based chipsets, its fairly straightforward. Pretty much everything before and including the 963L chipset can support DDMA. For VIA, you have more limited options; VT82C586, VT82C596, and VT82C686A. Anything newer won’t support DDMA and you’ll be stuck using a software based TSR which uses up vital memory in DOS.</p>



<p class="wp-block-paragraph"><a href="https://www.vogons.org/viewtopic.php?f=62&amp;t=70375">More details on chipsets can be found here.</a></p>



<h3 class="wp-block-heading">Motherboard Testing</h3>



<p class="wp-block-paragraph">When searching for a suitable motherboard, I found that these were the most important things to check for:</p>



<ol class="wp-block-list">
<li>BIOS features &#8211; Ideally, control over PCI IRQ assignments (which I found was more common in VIA chipsets). Searching for the model you can often find the original manual which shows what features were available.</li>



<li>Is it an OEM board? &#8211; Typically OEM boards from companies like HP have locked down BIOS and can also include proprietary connectors for USB and case controls. Google the model number—I found <a href="https://theretroweb.com/">https://theretroweb.com/</a> to be a vital resource in determining board suitability.</li>



<li>Form factor &#8211; All my picks were mATX because of the case I wanted. If you have an ATX case you’ll have more options.</li>



<li>Capacitors &#8211; Look for bulging or leaky caps. One of my boards had some bad capacitors and quickly died. If you have power issues, its likely related to the capacitors.</li>



<li>Special requirements such as DDMA support for sound cards.</li>
</ol>



<p class="wp-block-paragraph">I tested the following motherboards to find one I was happy with.</p>



<figure class="wp-block-table alignwide"><table><thead><tr><th>Motherboard</th><th>Chipset</th><th>Pros</th><th>Cons</th><th>Conclusion</th></tr></thead><tbody><tr><td>PC Chips M810LM</td><td>SiS 730</td><td>Supposedly has DDMA support. Midi port.</td><td>Older chipset. No support for XP-M CPU. Older type of RAM.</td><td>Wasn’t worth using due to the CPU limitations.</td></tr><tr><td>PC Chips M863G</td><td>SiS 741 / 963L</td><td>DDMA support. Reasonable performance.</td><td>Decent enough BIOS but but quite to VIA standards.</td><td>The best of the bunch for compatibility. More notes below.</td></tr><tr><td>Foxconn 741M01C-GX-6L</td><td>SiS 741 / 963L</td><td>DDMA support.&nbsp;</td><td>Bad capacitors. Decent enough BIOS but but quite to VIA standards.</td><td>I liked it, but the board died and was not repairable. </td></tr><tr><td>ASUS A7M266-M</td><td>AMD 761 / VIA VT82C686B</td><td>DDMA support.&nbsp;Midi port.</td><td>HP OEM &#8211; poor bios and weird headers</td><td>Board had power on issues and limited BIOS. </td></tr><tr><td>MSI 6734</td><td>VIA VT8235</td><td>Reliable and fast board! Good BIOS options.</td><td>No DDMA</td><td>I liked it but sound card compatibility is a bummer. Keeping this board as a backup.</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">I settled with the <strong>PC Chips M863G</strong>. While I do believe it was originally a budget board, It has DDMA support and reasonable performance, and so far its been reliable.</p>



<p class="wp-block-paragraph">I did face a few issues with this board but I was able to overcome them:</p>



<ul class="wp-block-list">
<li>Upper Memory in DOS &#8211; Onboard devices such as ethernet used up upper memory blocks in DOS preventing the use of Expanded memory (unable to set page frame). The fix was to disable ethernet in the BIOS—you can toggle them back on as and when needed.</li>



<li>IRQ Assignments &#8211; The BIOS settings were fairly limited. There was no way to reserve IRQ slots, and I had conflicts between the sound card and USB controllers. The fix was to disable the USB controllers before installing the sound card, manually set the IRQ in windows, and then re-enable USB. This sorted out the conflict.</li>
</ul>



<figure class="wp-block-image aligncenter size-large is-resized"><a href="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/s-l1600.jpg?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" data-attachment-id="56275" data-permalink="https://mikejolley.com/2023/05/12/building-a-windows-98-retro-gaming-pc-on-the-athlon-xp-platform/s-l1600-2/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/s-l1600.jpg?fit=1600%2C1200&amp;ssl=1" data-orig-size="1600,1200" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="s-l1600" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/s-l1600.jpg?fit=300%2C225&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/s-l1600.jpg?fit=1024%2C768&amp;ssl=1" src="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/s-l1600.jpg?resize=422%2C316&#038;ssl=1" alt="" class="wp-image-56275" width="422" height="316" srcset="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/s-l1600.jpg?resize=1024%2C768&amp;ssl=1 1024w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/s-l1600.jpg?resize=300%2C225&amp;ssl=1 300w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/s-l1600.jpg?resize=768%2C576&amp;ssl=1 768w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/s-l1600.jpg?resize=1536%2C1152&amp;ssl=1 1536w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/s-l1600.jpg?w=1600&amp;ssl=1 1600w" sizes="auto, (max-width: 422px) 100vw, 422px" /></a><figcaption class="wp-element-caption">M810LM</figcaption></figure>



<p class="wp-block-paragraph">The runner up was the MSI with VIA Chipset but unfortunately the lack of DDMA support was the deal breaker there. If you can get the ESS SOLO card working with it, it would be a decent pick because it did have some decent BIOS and better performance than the SiS boards.</p>



<h3 class="wp-block-heading">Choosing a Sound Card</h3>



<p class="wp-block-paragraph">Unlike the GPU, finding a decent sound card can be tough. We’ve already gone over the technologies cards can use to maintain DOS support, but still, finding something decent that meets those requirements is difficult because there are so many options out there.</p>



<p class="wp-block-paragraph">With our motherboard supporting DDMA, finding a card which takes advantage of this was paramount. Here is a list of the cards I tested across my motherboards and systems.</p>



<figure class="wp-block-table alignwide"><table><thead><tr><th>Make/Model</th><th>Pros</th><th>Cons</th><th>Conclusion</th></tr></thead><tbody><tr><td>ESS SOLO 1</td><td>Sounds great in DOS and Windows. TDMA works in DOS without extra software.</td><td>TDMA didn’t work in some of the games I tried.</td><td>Interesting sounds that I liked. TDMA didn’t work for all games in my system so I chose the Yamaha instead.</td></tr><tr><td>SoundBlaster Live! CT4830</td><td>Sounded okay—certainly not the worst. Good compatibility in DOS and easy to setup.</td><td>Software TSR needed for DOS support.</td><td>Decent compatibility, but you’re forced to use it’s own MIDI in DOS so I couldn’t use it for my MT-32.</td></tr><tr><td>C-Media CMI8738</td><td>Cheap</td><td>Cheap</td><td>Didn’t work in DOS and was buggy.</td></tr><tr><td>Yamaha XG YMF724F-V</td><td>DDMA support. Real OPL3 chip. Sounds amazing.</td><td>Rare. No XG Midi in DOS.</td><td>MIDI in DOS would have been nice, but I am using MT-32 so it doesn’t matter. Sounds great and my pick of the bunch.</td></tr><tr><td>ALS4000</td><td>DDMA support*</td><td>Terrible drivers in Windows-no VxD support. *I didn’t test in DOS thoroughly due to the issues in Windows.</td><td>Didn’t seem suitable for Windows 98.</td></tr><tr><td>Forte Media FM801A</td><td>Cheap</td><td>Cheap</td><td>Didn’t work in DOS and was buggy.</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">Your experiences may vary but I would choose either the ESS SOLO (if you’re on a chipset without DDMA support), or the Yamaha if you can get one. Both had decent DOS support but the Yamaha had the edge due to its DDMA support, and it worked better in Monkey Island Talky Edition which was important to me <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<figure class="wp-block-image size-large"><a href="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/s-l1600-1.jpg?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="682" data-attachment-id="56277" data-permalink="https://mikejolley.com/2023/05/12/building-a-windows-98-retro-gaming-pc-on-the-athlon-xp-platform/s-l1600-1/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/s-l1600-1.jpg?fit=1600%2C1066&amp;ssl=1" data-orig-size="1600,1066" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="s-l1600-1" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/s-l1600-1.jpg?fit=300%2C200&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/s-l1600-1.jpg?fit=1024%2C682&amp;ssl=1" src="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/s-l1600-1.jpg?resize=1024%2C682&#038;ssl=1" alt="" class="wp-image-56277" srcset="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/s-l1600-1.jpg?resize=1024%2C682&amp;ssl=1 1024w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/s-l1600-1.jpg?resize=300%2C200&amp;ssl=1 300w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/s-l1600-1.jpg?resize=768%2C512&amp;ssl=1 768w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/s-l1600-1.jpg?resize=1536%2C1023&amp;ssl=1 1536w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/s-l1600-1.jpg?w=1600&amp;ssl=1 1600w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></a><figcaption class="wp-element-caption">YMF724F-V</figcaption></figure>



<p class="wp-block-paragraph">In DOS, the Yamaha emulates a SoundBlaster Pro and gives access to the Midi port for external synthesisers. FM sound great, and in Windows you get XG midi which again, sounds fantastic. I wish that was also available in DOS but I guess you cannot have it all!</p>



<figure class="wp-block-image size-large"><a href="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/IMG_2353.jpg?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="795" height="1061" data-attachment-id="56316" data-permalink="https://mikejolley.com/img_2353-2/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/IMG_2353-edited.jpg?fit=795%2C1061&amp;ssl=1" data-orig-size="795,1061" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="IMG_2353" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/IMG_2353-edited.jpg?fit=225%2C300&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/IMG_2353-edited.jpg?fit=767%2C1024&amp;ssl=1" src="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/IMG_2353-edited.jpg?resize=795%2C1061&#038;ssl=1" alt="" class="wp-image-56316" srcset="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/IMG_2353-edited.jpg?w=795&amp;ssl=1 795w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/IMG_2353-edited.jpg?resize=225%2C300&amp;ssl=1 225w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/IMG_2353-edited.jpg?resize=767%2C1024&amp;ssl=1 767w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/IMG_2353-edited.jpg?resize=768%2C1025&amp;ssl=1 768w" sizes="auto, (max-width: 795px) 100vw, 795px" /></a><figcaption class="wp-element-caption">The Yamaha config utility when DDMA is supported</figcaption></figure>



<h2 class="wp-block-heading">Putting it all together</h2>



<p class="wp-block-paragraph">Aside from the above, I finished the system off with 512MB RAM and a modern SFX PSU (which the case required). I had a spare HDD and DVD drive so I didn&#8217;t need to purchase those.</p>



<p class="wp-block-paragraph">I also added and optional Gotek Flash Drive (instead of a floppy drive) because internal floppy drives can be unreliable. I actually picked up an external floppy drive to copy old disks I own to the Gotek for convenience. </p>



<p class="wp-block-paragraph">Just including the parts I <em>used</em> and paid for, this is a breakdown of the costs for my build:</p>



<figure class="wp-block-table"><table><tbody><tr><td><strong>Case:</strong> <br>CS101</td><td>£30</td></tr><tr><td><strong>PSU: </strong><br>be quiet! SFX Power 3 300W 80 Plus Bronze</td><td>£50</td></tr><tr><td><strong>CPU: </strong><br>Athlon XP-M 2600+</td><td>£30</td></tr><tr><td><strong>GPU:</strong><br>GeForce 5700 FX</td><td>£20</td></tr><tr><td><strong>Motherboard:</strong><br>PC Chips M863G</td><td>£30</td></tr><tr><td><strong>Sound Card:</strong><br>Yamaha XG YMF724F-V</td><td>£60</td></tr><tr><td><strong>Floppy Drive:</strong><br>Gotek Flash Floppy</td><td>£40</td></tr><tr><td><strong>RAM:</strong><br>512MB SDRAM</td><td>£15</td></tr><tr><td><strong>Total</strong></td><td>£275</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">Considering some of the rarer parts I obtained, I think this ended up being fairly reasonable. The largest expense was that Sound Card—I don’t see many Yamaha cards crop up on eBay!</p>



<p class="wp-block-paragraph">For the budget conscious, the CS101 has an optional PSU you can add for £20, desktop Athlon XP CPUs are cheap (I picked up a 2400+ for £10), and SoundBlaster Live! cards are readily available for £10, so you  could easily get a comparable system for under £150 if you wanted.</p>



<h2 class="wp-block-heading">Benchmarks</h2>



<p class="wp-block-paragraph">I ran 3D Mark 99 and 2000 benchmarks just to see how things ran. This is using Nvidia 56.64 drivers (older drivers won&#8217;t work with the FX 5700 card I have).</p>



<figure class="wp-block-image size-full"><a href="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/99.jpeg?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="800" height="600" data-attachment-id="56300" data-permalink="https://mikejolley.com/2023/05/12/building-a-windows-98-retro-gaming-pc-on-the-athlon-xp-platform/attachment/99/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/99.jpeg?fit=800%2C600&amp;ssl=1" data-orig-size="800,600" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="99" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/99.jpeg?fit=300%2C225&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/99.jpeg?fit=800%2C600&amp;ssl=1" src="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/99.jpeg?resize=800%2C600&#038;ssl=1" alt="" class="wp-image-56300" srcset="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/99.jpeg?w=800&amp;ssl=1 800w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/99.jpeg?resize=300%2C225&amp;ssl=1 300w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/99.jpeg?resize=768%2C576&amp;ssl=1 768w" sizes="auto, (max-width: 800px) 100vw, 800px" /></a><figcaption class="wp-element-caption">3D Mark 99 results</figcaption></figure>



<figure class="wp-block-image size-full"><a href="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/3dmark2000.jpeg?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="768" data-attachment-id="56298" data-permalink="https://mikejolley.com/2023/05/12/building-a-windows-98-retro-gaming-pc-on-the-athlon-xp-platform/3dmark2000-2/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/3dmark2000.jpeg?fit=1024%2C768&amp;ssl=1" data-orig-size="1024,768" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="3dmark2000" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/3dmark2000.jpeg?fit=300%2C225&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/3dmark2000.jpeg?fit=1024%2C768&amp;ssl=1" src="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/3dmark2000.jpeg?resize=1024%2C768&#038;ssl=1" alt="" class="wp-image-56298" srcset="https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/3dmark2000.jpeg?w=1024&amp;ssl=1 1024w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/3dmark2000.jpeg?resize=300%2C225&amp;ssl=1 300w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2023/05/3dmark2000.jpeg?resize=768%2C576&amp;ssl=1 768w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></a><figcaption class="wp-element-caption">3D mark 2000 results</figcaption></figure>



<p class="wp-block-paragraph">I don&#8217;t have any machines to compare to, but I’m happy with the performance under Windows. I have not installed too many games yet, but the newest game I&#8217;ve tried so far is <em>Sim City 4</em> (from 2003) which runs flawlessly.</p>



<p class="wp-block-paragraph">As for DOS gaming, well, I&#8217;ve hooked up my Roland MT-32 and I&#8217;ve been enjoying the classics; Transport Tycoon, Lemmings, The Secret of Monkey Island, Little Big Adventure, and so on. I&#8217;ve had very few compatibility issues with this setup so far so I&#8217;m pleased with the system overall.</p>



		<figure class="wp-block-jetpack-videopress jetpack-videopress-player aligncenter" style="" >
			<div class="jetpack-videopress-player__wrapper"> <iframe title="VideoPress Video Player" aria-label='VideoPress Video Player' width='421' height='750' src='https://videopress.com/embed/jcMeqLw7?cover=1&amp;autoPlay=0&amp;controls=1&amp;loop=0&amp;muted=0&amp;persistVolume=1&amp;playsinline=0&amp;preloadContent=metadata&amp;useAverageColor=1&amp;hd=0' frameborder='0' allowfullscreen data-resize-to-parent="true" allow='clipboard-write'></iframe><script src='https://v0.wordpress.com/js/next/videopress-iframe.js?m=1674852142'></script></div>
			<figcaption>Enjoy the Monkey Island theme on my Roland MT-32</figcaption>
			
		</figure>
		


<p class="wp-block-paragraph">If you&#8217;re curious about my software setup:</p>



<ul class="wp-block-list">
<li>The launcher in the video above is <a href="https://forums.launchbox-app.com/files/file/934-launchbox-for-ms-dos-special-edition/">Launch Box for MSDOS</a></li>



<li>I have three configs to choose from when entering DOS
<ul class="wp-block-list">
<li>Expanded Memory (emm386.exe and <a href="https://github.com/FDOS/himemX">HimemX</a>)</li>



<li>Extended Memory (just HimemX)</li>



<li>Conventional Memory</li>
</ul>
</li>



<li>I have several bat files to load drivers:
<ul class="wp-block-list">
<li><code>sound.bat</code> which initialises the Yamaha sound card</li>



<li><code>mouse.bat</code> which loads up Cute Mouse drivers</li>



<li><code>mpu.bat</code> which loads up <a href="https://github.com/bjt42/softmpu">SoftMPU</a> if a game requires it.</li>



<li><code>LoadCD.bat</code> which loads CDROM drivers only when needed (to save on memory)</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph">My expanded memory config leaves around 610kb conventional memory free which is enough for all the games I&#8217;ve tried thus far.</p>



<h2 class="wp-block-heading">Resources</h2>



<p class="wp-block-paragraph">I wanted to finish off by listing a bunch of incredible resources I used to research parts, issues, solutions, and everything in between. They were vital to the success of this build.</p>



<ul class="wp-block-list">
<li><a href="https://www.vogons.org/">Vogons Forums</a> &#8211; Covers hardware, software, DOS and everything. Great to search if you run into an issue, or want to know if anyone has used a particular piece of hardware before.</li>



<li><a href="http://vogonsdrivers.com/">Vogons Vintage Driver Library</a> &#8211; Very useful because most of the things you get from eBay won’t have driver disks.</li>



<li><a href="https://www.philscomputerlab.com/">Phils Computer Lab</a> &#8211; Reviews of hardware, tutorials, software, and drivers. A great resource for retro computing information.</li>



<li><a href="https://theretroweb.com/">The Retro Web</a> &#8211; Lots of data on old motherboards.</li>



<li><a href="https://archive.org/">Archive.org</a> &#8211; For software and drivers. Also check out the wayback machine if you need to get a driver from a site that no longer exists.</li>
</ul>
]]></content:encoded>
					
		
		<enclosure url="https://videos.files.wordpress.com/jcMeqLw7/monkeyisland-1.mov" length="292349899" type="video/quicktime" />

		<post-id xmlns="com-wordpress:feed-additions:1">56267</post-id>	</item>
		<item>
		<title>Retro game review: Mr. Gimmick (NES)</title>
		<link>https://mikejolley.com/2022/11/06/retro-game-review-mr-gimmick-nes/</link>
		
		<dc:creator><![CDATA[Mike Jolley]]></dc:creator>
		<pubDate>Sun, 06 Nov 2022 16:33:47 +0000</pubDate>
				<category><![CDATA[Retro Gaming]]></category>
		<guid isPermaLink="false">https://mikejolley.com/?p=56036</guid>

					<description><![CDATA[This is a video review of Mr. Gimmick on the NES posted on YouTube.]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">This is a video review of Mr. Gimmick on the NES posted on YouTube. </p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-4-3 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" class="youtube-player" width="640" height="360" src="https://www.youtube.com/embed/tgFDyuxixIw?version=3&#038;rel=1&#038;showsearch=0&#038;showinfo=1&#038;iv_load_policy=1&#038;fs=1&#038;hl=en-GB&#038;autohide=2&#038;wmode=transparent" allowfullscreen="true" style="border:0;" sandbox="allow-scripts allow-same-origin allow-popups allow-presentation allow-popups-to-escape-sandbox"></iframe>
</div></figure>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">56036</post-id>	</item>
		<item>
		<title>Twenty Twenty-One in review</title>
		<link>https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/</link>
		
		<dc:creator><![CDATA[Mike Jolley]]></dc:creator>
		<pubDate>Wed, 29 Dec 2021 17:47:58 +0000</pubDate>
				<category><![CDATA[Personal]]></category>
		<category><![CDATA[recap]]></category>
		<guid isPermaLink="false">https://mikejolley.com/?p=55243</guid>

					<description><![CDATA[I&#8217;ve not been writing much lately, but I have been keeping busy! As 2021 draws to a close, I wanted to reflect on everything I&#8217;ve done the past year. Things I learned, things I built, and what&#8217;s to come for 2022. My 7th Year at Automattic I&#8217;m approximately halfway through my 7th year at Automattic! [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">I&#8217;ve not been writing much lately, but I have been keeping busy! As 2021 draws to a close, I wanted to reflect on everything I&#8217;ve done the past year. Things I learned, things I built, and what&#8217;s to come for 2022.</p>



<span id="more-55243"></span>



<h2 class="wp-block-heading" id="my-7th-year-at-automattic">My 7th Year at Automattic</h2>



<p class="wp-block-paragraph">I&#8217;m approximately halfway through my 7th year at Automattic! </p>



<p class="wp-block-paragraph">I&#8217;ve been working as an Individual Contributor (IC) with mostly JavaScript/TypeScript/React and PHP. I switched from working exclusively with PHP in 2019, and I&#8217;m having a great time working with Javascript, despite its quirks.</p>



<p class="wp-block-paragraph">This year&#8217;s <a href="https://github.com/mikejolley">contributor graph from GitHub</a> looks like this:</p>



<figure class="wp-block-image size-full"><a href="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-14.54.13.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="761" height="524" data-attachment-id="55245" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/screenshot-2021-12-23-at-14-54-13/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-14.54.13.png?fit=761%2C524&amp;ssl=1" data-orig-size="761,524" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Screenshot-2021-12-23-at-14.54.13" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-14.54.13.png?fit=300%2C207&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-14.54.13.png?fit=761%2C524&amp;ssl=1" src="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-14.54.13.png?resize=761%2C524&#038;ssl=1" alt="" class="wp-image-55245" srcset="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-14.54.13.png?w=761&amp;ssl=1 761w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-14.54.13.png?resize=300%2C207&amp;ssl=1 300w" sizes="auto, (max-width: 761px) 100vw, 761px" /></a><figcaption>Contribution stats from this year</figcaption></figure>



<p class="wp-block-paragraph">My contributions in the WooCommerce org were mainly within <a href="https://github.com/woocommerce/woocommerce-gutenberg-products-block">the WooCommerce Blocks repo</a>. We&#8217;ve been building out both the new Cart and Checkout experience and the <a href="https://github.com/woocommerce/woocommerce-gutenberg-products-block/tree/trunk/src/StoreApi">Store API that powers it</a>—this will eventually land in WooCommerce core. </p>



<p class="wp-block-paragraph">I&#8217;m still really excited about the Store API and the headless applications it enables (<a href="https://hollerwp.com/mike-jolley-interview/">I chatted about this in an interview earlier in the year</a>). It&#8217;s now reaching maturity and an acceptable level of stability, so I think it could land officially in core soon (hopefully 2022).</p>



<figure class="wp-block-image size-full"><a href="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-21.13.01.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="848" height="602" data-attachment-id="55275" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/screenshot-2021-12-23-at-21-13-01/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-21.13.01.png?fit=848%2C602&amp;ssl=1" data-orig-size="848,602" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Screenshot-2021-12-23-at-21.13.01" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-21.13.01.png?fit=300%2C213&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-21.13.01.png?fit=848%2C602&amp;ssl=1" src="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-21.13.01.png?resize=848%2C602&#038;ssl=1" alt="" class="wp-image-55275" srcset="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-21.13.01.png?w=848&amp;ssl=1 848w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-21.13.01.png?resize=300%2C213&amp;ssl=1 300w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-21.13.01.png?resize=768%2C545&amp;ssl=1 768w" sizes="auto, (max-width: 848px) 100vw, 848px" /></a><figcaption>Yes, the Store API supports carts!</figcaption></figure>



<p class="wp-block-paragraph">I had a little more confidence contributing to <a href="https://github.com/WordPress/gutenberg">Gutenberg</a> this year and received contributor status. That means I can merge things on GitHub. I worked on:</p>



<ul class="wp-block-list"><li><a href="https://github.com/WordPress/gutenberg/pull/33928">Some improvements to default selections in the Combobox Component</a></li><li>Again in Combobox, <a href="https://github.com/WordPress/gutenberg/pull/34090">a fix to only force expanded state if the input has focus</a></li><li><a href="https://github.com/WordPress/gutenberg/pull/33572">Fixing console errors when an Inner Block is deleted</a></li><li><a href="https://github.com/WordPress/gutenberg/pull/30667">Fixing a memory leak in the useForceUpdate hook</a></li><li>&#8230;and some other things which were not merged.</li></ul>



<p class="wp-block-paragraph">One cool thing we did this year was (as a team) to start the <a href="https://epicreact.dev/">Epic React course</a>. It covers the fundamentals and more advanced patterns, each with video explanation and coding exercises. Going into the course, I knew a fair amount about React Hooks, but it clearly explained how they worked in more depth and why. I realise it&#8217;s pretty expensive, but if it&#8217;s within your budget or your employer is willing to pay for it, it&#8217;s well worth doing, in my opinion.</p>



<p class="wp-block-paragraph">Outside of work, I&#8217;ve dabbled with headless sites and GraphQL. I created a <a href="https://morrics-magical-cauldron.netlify.app/">fun little app for generating random content for Dungeons and Dragons</a>, which I stuck on Netlify. </p>



<p class="wp-block-paragraph">The application has a WordPress backend that uses WP GraphQL—<a href="https://mikejolley.com/2021/03/02/headless-wordpress-cookie-based-login-using-graphql/">I wrote a series of posts covering this, including how to create a login system</a> that may be worth reading if doing something similar.</p>



<p class="wp-block-paragraph">I believe I&#8217;ve done a much better job balancing <em>work and life</em> this year, which is evident to me from my contributor graphs&#8217; many voids (compared to previous years). Being able to switch off and do other things, especially on weekends, has helped with stress and burn-out, and I&#8217;m feeling much more relaxed in general—a positive change.</p>



<p class="wp-block-paragraph">On my list for 2022; contribute to Gutenberg more, some leadership training should I want to go back to a lead role, and continue levelling up React and TypeScript skills.</p>



<h2 class="wp-block-heading" id="astrophotography">Astrophotography</h2>



<p class="wp-block-paragraph">Looking back, I didn&#8217;t realise just how many incredible images I captured during 2021! I&#8217;ve got a decent dedicated astrophotography camera now (ASI 2600 MM), which is a mono camera that I&#8217;m using with narrowband filters. Compared to my previous cameras, it produces clean, low-noise images. It takes longer to process a colour image because of the number of frames needed per filter, but it is worth it.</p>



<p class="wp-block-paragraph">My greatest banes are accurate focussing and the weather. A new &#8220;dome&#8221; is on my wishlist that will allow me to be better prepared for good weather conditions without having to drag 30kg of gear from the garage down the bottom of the garden!</p>



<div data-carousel-extra='{&quot;blog_id&quot;:1,&quot;permalink&quot;:&quot;https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/&quot;}'  class="wp-block-jetpack-tiled-gallery alignwide is-style-rectangular has-rounded-corners-6"><div class="tiled-gallery__gallery"><div class="tiled-gallery__row"><div class="tiled-gallery__col" style="flex-basis:71.19480%"><figure class="tiled-gallery__item"><img decoding="async" data-attachment-id="55263" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51441819155_299e7e045b_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51441819155_299e7e045b_k.jpg?fit=2048%2C1463&amp;ssl=1" data-orig-size="2048,1463" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51441819155_299e7e045b_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51441819155_299e7e045b_k.jpg?fit=300%2C214&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51441819155_299e7e045b_k.jpg?fit=1024%2C732&amp;ssl=1" data-attachment-id="55263" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51441819155_299e7e045b_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51441819155_299e7e045b_k.jpg?fit=2048%2C1463&amp;ssl=1" data-orig-size="2048,1463" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51441819155_299e7e045b_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51441819155_299e7e045b_k.jpg?fit=300%2C214&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51441819155_299e7e045b_k.jpg?fit=1024%2C732&amp;ssl=1" role="button" tabindex="0" aria-label="Open image 1 of 10 in full-screen"srcset="https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51441819155_299e7e045b_k-1024x732.jpg?strip=info&#038;w=600&#038;ssl=1 600w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51441819155_299e7e045b_k-1024x732.jpg?strip=info&#038;w=900&#038;ssl=1 900w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51441819155_299e7e045b_k-1024x732.jpg?strip=info&#038;w=1200&#038;ssl=1 1200w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51441819155_299e7e045b_k-1024x732.jpg?strip=info&#038;w=1500&#038;ssl=1 1500w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51441819155_299e7e045b_k-1024x732.jpg?strip=info&#038;w=1800&#038;ssl=1 1800w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51441819155_299e7e045b_k-1024x732.jpg?strip=info&#038;w=2000&#038;ssl=1 2000w" alt="" data-height="1463" data-id="55263" data-link="https://mikejolley.com/?attachment_id=55263" data-url="https://mikejolley.com/wp-content/uploads/2021/12/51441819155_299e7e045b_k-1024x732.jpg" data-width="2048" src="https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51441819155_299e7e045b_k-1024x732.jpg?ssl=1" data-amp-layout="responsive"/></figure></div><div class="tiled-gallery__col" style="flex-basis:28.80520%"><figure class="tiled-gallery__item"><img decoding="async" data-attachment-id="55254" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51010665681_8384c17f22_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51010665681_8384c17f22_k.jpg?fit=2048%2C1553&amp;ssl=1" data-orig-size="2048,1553" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;\u00a9 Michael Jolley&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51010665681_8384c17f22_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51010665681_8384c17f22_k.jpg?fit=300%2C227&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51010665681_8384c17f22_k.jpg?fit=1024%2C777&amp;ssl=1" data-attachment-id="55254" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51010665681_8384c17f22_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51010665681_8384c17f22_k.jpg?fit=2048%2C1553&amp;ssl=1" data-orig-size="2048,1553" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;\u00a9 Michael Jolley&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51010665681_8384c17f22_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51010665681_8384c17f22_k.jpg?fit=300%2C227&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51010665681_8384c17f22_k.jpg?fit=1024%2C777&amp;ssl=1" role="button" tabindex="0" aria-label="Open image 2 of 10 in full-screen"srcset="https://i1.wp.com/mikejolley.com/wp-content/uploads/2021/12/51010665681_8384c17f22_k-1024x777.jpg?strip=info&#038;w=600&#038;ssl=1 600w,https://i1.wp.com/mikejolley.com/wp-content/uploads/2021/12/51010665681_8384c17f22_k-1024x777.jpg?strip=info&#038;w=900&#038;ssl=1 900w,https://i1.wp.com/mikejolley.com/wp-content/uploads/2021/12/51010665681_8384c17f22_k-1024x777.jpg?strip=info&#038;w=1200&#038;ssl=1 1200w,https://i1.wp.com/mikejolley.com/wp-content/uploads/2021/12/51010665681_8384c17f22_k-1024x777.jpg?strip=info&#038;w=1500&#038;ssl=1 1500w,https://i1.wp.com/mikejolley.com/wp-content/uploads/2021/12/51010665681_8384c17f22_k-1024x777.jpg?strip=info&#038;w=1800&#038;ssl=1 1800w,https://i1.wp.com/mikejolley.com/wp-content/uploads/2021/12/51010665681_8384c17f22_k-1024x777.jpg?strip=info&#038;w=2000&#038;ssl=1 2000w" alt="" data-height="1553" data-id="55254" data-link="https://mikejolley.com/?attachment_id=55254" data-url="https://mikejolley.com/wp-content/uploads/2021/12/51010665681_8384c17f22_k-1024x777.jpg" data-width="2048" src="https://i1.wp.com/mikejolley.com/wp-content/uploads/2021/12/51010665681_8384c17f22_k-1024x777.jpg?ssl=1" data-amp-layout="responsive"/></figure><figure class="tiled-gallery__item"><img decoding="async" data-attachment-id="55252" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/50866876818_c01914ae20_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/50866876818_c01914ae20_k.jpg?fit=2048%2C2048&amp;ssl=1" data-orig-size="2048,2048" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="50866876818_c01914ae20_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/50866876818_c01914ae20_k.jpg?fit=300%2C300&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/50866876818_c01914ae20_k.jpg?fit=1024%2C1024&amp;ssl=1" data-attachment-id="55252" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/50866876818_c01914ae20_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/50866876818_c01914ae20_k.jpg?fit=2048%2C2048&amp;ssl=1" data-orig-size="2048,2048" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="50866876818_c01914ae20_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/50866876818_c01914ae20_k.jpg?fit=300%2C300&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/50866876818_c01914ae20_k.jpg?fit=1024%2C1024&amp;ssl=1" role="button" tabindex="0" aria-label="Open image 3 of 10 in full-screen"srcset="https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/50866876818_c01914ae20_k-1024x1024.jpg?strip=info&#038;w=600&#038;ssl=1 600w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/50866876818_c01914ae20_k-1024x1024.jpg?strip=info&#038;w=900&#038;ssl=1 900w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/50866876818_c01914ae20_k-1024x1024.jpg?strip=info&#038;w=1200&#038;ssl=1 1200w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/50866876818_c01914ae20_k-1024x1024.jpg?strip=info&#038;w=1500&#038;ssl=1 1500w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/50866876818_c01914ae20_k-1024x1024.jpg?strip=info&#038;w=1800&#038;ssl=1 1800w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/50866876818_c01914ae20_k-1024x1024.jpg?strip=info&#038;w=2000&#038;ssl=1 2000w" alt="" data-height="2048" data-id="55252" data-link="https://mikejolley.com/?attachment_id=55252" data-url="https://mikejolley.com/wp-content/uploads/2021/12/50866876818_c01914ae20_k-1024x1024.jpg" data-width="2048" src="https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/50866876818_c01914ae20_k-1024x1024.jpg?ssl=1" data-amp-layout="responsive"/></figure></div></div><div class="tiled-gallery__row"><div class="tiled-gallery__col" style="flex-basis:25.07379%"><figure class="tiled-gallery__item"><img decoding="async" data-attachment-id="55255" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51088318909_e7cfd92a05_h/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51088318909_e7cfd92a05_h.jpg?fit=1516%2C1500&amp;ssl=1" data-orig-size="1516,1500" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;\u00a9 Michael Jolley&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51088318909_e7cfd92a05_h" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51088318909_e7cfd92a05_h.jpg?fit=300%2C297&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51088318909_e7cfd92a05_h.jpg?fit=1024%2C1013&amp;ssl=1" data-attachment-id="55255" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51088318909_e7cfd92a05_h/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51088318909_e7cfd92a05_h.jpg?fit=1516%2C1500&amp;ssl=1" data-orig-size="1516,1500" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;\u00a9 Michael Jolley&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51088318909_e7cfd92a05_h" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51088318909_e7cfd92a05_h.jpg?fit=300%2C297&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51088318909_e7cfd92a05_h.jpg?fit=1024%2C1013&amp;ssl=1" role="button" tabindex="0" aria-label="Open image 4 of 10 in full-screen"srcset="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51088318909_e7cfd92a05_h-1024x1013.jpg?strip=info&#038;w=600&#038;ssl=1 600w,https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51088318909_e7cfd92a05_h-1024x1013.jpg?strip=info&#038;w=900&#038;ssl=1 900w,https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51088318909_e7cfd92a05_h-1024x1013.jpg?strip=info&#038;w=1200&#038;ssl=1 1200w,https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51088318909_e7cfd92a05_h-1024x1013.jpg?strip=info&#038;w=1500&#038;ssl=1 1500w,https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51088318909_e7cfd92a05_h-1024x1013.jpg?strip=info&#038;w=1516&#038;ssl=1 1516w" alt="" data-height="1500" data-id="55255" data-link="https://mikejolley.com/?attachment_id=55255" data-url="https://mikejolley.com/wp-content/uploads/2021/12/51088318909_e7cfd92a05_h-1024x1013.jpg" data-width="1516" src="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51088318909_e7cfd92a05_h-1024x1013.jpg?ssl=1" data-amp-layout="responsive"/></figure></div><div class="tiled-gallery__col" style="flex-basis:43.96004%"><figure class="tiled-gallery__item"><img decoding="async" data-attachment-id="55262" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51425329420_fd8e4cc022_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51425329420_fd8e4cc022_k.jpg?fit=2048%2C1152&amp;ssl=1" data-orig-size="2048,1152" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51425329420_fd8e4cc022_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51425329420_fd8e4cc022_k.jpg?fit=300%2C169&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51425329420_fd8e4cc022_k.jpg?fit=1024%2C576&amp;ssl=1" data-attachment-id="55262" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51425329420_fd8e4cc022_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51425329420_fd8e4cc022_k.jpg?fit=2048%2C1152&amp;ssl=1" data-orig-size="2048,1152" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51425329420_fd8e4cc022_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51425329420_fd8e4cc022_k.jpg?fit=300%2C169&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51425329420_fd8e4cc022_k.jpg?fit=1024%2C576&amp;ssl=1" role="button" tabindex="0" aria-label="Open image 5 of 10 in full-screen"srcset="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51425329420_fd8e4cc022_k-1024x576.jpg?strip=info&#038;w=600&#038;ssl=1 600w,https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51425329420_fd8e4cc022_k-1024x576.jpg?strip=info&#038;w=900&#038;ssl=1 900w,https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51425329420_fd8e4cc022_k-1024x576.jpg?strip=info&#038;w=1200&#038;ssl=1 1200w,https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51425329420_fd8e4cc022_k-1024x576.jpg?strip=info&#038;w=1500&#038;ssl=1 1500w,https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51425329420_fd8e4cc022_k-1024x576.jpg?strip=info&#038;w=1800&#038;ssl=1 1800w,https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51425329420_fd8e4cc022_k-1024x576.jpg?strip=info&#038;w=2000&#038;ssl=1 2000w" alt="" data-height="1152" data-id="55262" data-link="https://mikejolley.com/?attachment_id=55262" data-url="https://mikejolley.com/wp-content/uploads/2021/12/51425329420_fd8e4cc022_k-1024x576.jpg" data-width="2048" src="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51425329420_fd8e4cc022_k-1024x576.jpg?ssl=1" data-amp-layout="responsive"/></figure></div><div class="tiled-gallery__col" style="flex-basis:30.96617%"><figure class="tiled-gallery__item"><img decoding="async" data-attachment-id="55264" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51615024686_9e257c6b04_c-1/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51615024686_9e257c6b04_c-1.jpg?fit=800%2C640&amp;ssl=1" data-orig-size="800,640" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51615024686_9e257c6b04_c-1" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51615024686_9e257c6b04_c-1.jpg?fit=300%2C240&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51615024686_9e257c6b04_c-1.jpg?fit=800%2C640&amp;ssl=1" data-attachment-id="55264" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51615024686_9e257c6b04_c-1/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51615024686_9e257c6b04_c-1.jpg?fit=800%2C640&amp;ssl=1" data-orig-size="800,640" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51615024686_9e257c6b04_c-1" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51615024686_9e257c6b04_c-1.jpg?fit=300%2C240&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51615024686_9e257c6b04_c-1.jpg?fit=800%2C640&amp;ssl=1" role="button" tabindex="0" aria-label="Open image 6 of 10 in full-screen"srcset="https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51615024686_9e257c6b04_c-1.jpg?strip=info&#038;w=600&#038;ssl=1 600w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51615024686_9e257c6b04_c-1.jpg?strip=info&#038;w=800&#038;ssl=1 800w" alt="" data-height="640" data-id="55264" data-link="https://mikejolley.com/?attachment_id=55264" data-url="https://mikejolley.com/wp-content/uploads/2021/12/51615024686_9e257c6b04_c-1.jpg" data-width="800" src="https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51615024686_9e257c6b04_c-1.jpg?ssl=1" data-amp-layout="responsive"/></figure></div></div><div class="tiled-gallery__row"><div class="tiled-gallery__col" style="flex-basis:32.28981%"><figure class="tiled-gallery__item"><img decoding="async" data-attachment-id="55256" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51104557550_a981b9b7e7_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51104557550_a981b9b7e7_k.jpg?fit=2048%2C1638&amp;ssl=1" data-orig-size="2048,1638" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;\u00a9 Michael Jolley&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51104557550_a981b9b7e7_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51104557550_a981b9b7e7_k.jpg?fit=300%2C240&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51104557550_a981b9b7e7_k.jpg?fit=1024%2C819&amp;ssl=1" data-attachment-id="55256" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51104557550_a981b9b7e7_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51104557550_a981b9b7e7_k.jpg?fit=2048%2C1638&amp;ssl=1" data-orig-size="2048,1638" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;\u00a9 Michael Jolley&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51104557550_a981b9b7e7_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51104557550_a981b9b7e7_k.jpg?fit=300%2C240&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51104557550_a981b9b7e7_k.jpg?fit=1024%2C819&amp;ssl=1" role="button" tabindex="0" aria-label="Open image 7 of 10 in full-screen"srcset="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51104557550_a981b9b7e7_k-1024x819.jpg?strip=info&#038;w=600&#038;ssl=1 600w,https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51104557550_a981b9b7e7_k-1024x819.jpg?strip=info&#038;w=900&#038;ssl=1 900w,https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51104557550_a981b9b7e7_k-1024x819.jpg?strip=info&#038;w=1200&#038;ssl=1 1200w,https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51104557550_a981b9b7e7_k-1024x819.jpg?strip=info&#038;w=1500&#038;ssl=1 1500w,https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51104557550_a981b9b7e7_k-1024x819.jpg?strip=info&#038;w=1800&#038;ssl=1 1800w,https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51104557550_a981b9b7e7_k-1024x819.jpg?strip=info&#038;w=2000&#038;ssl=1 2000w" alt="" data-height="1638" data-id="55256" data-link="https://mikejolley.com/?attachment_id=55256" data-url="https://mikejolley.com/wp-content/uploads/2021/12/51104557550_a981b9b7e7_k-1024x819.jpg" data-width="2048" src="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51104557550_a981b9b7e7_k-1024x819.jpg?ssl=1" data-amp-layout="responsive"/></figure><figure class="tiled-gallery__item"><img decoding="async" data-attachment-id="55260" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51153300593_997b28999b_h/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51153300593_997b28999b_h.jpg?fit=1338%2C1338&amp;ssl=1" data-orig-size="1338,1338" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51153300593_997b28999b_h" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51153300593_997b28999b_h.jpg?fit=300%2C300&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51153300593_997b28999b_h.jpg?fit=1024%2C1024&amp;ssl=1" data-attachment-id="55260" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51153300593_997b28999b_h/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51153300593_997b28999b_h.jpg?fit=1338%2C1338&amp;ssl=1" data-orig-size="1338,1338" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51153300593_997b28999b_h" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51153300593_997b28999b_h.jpg?fit=300%2C300&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51153300593_997b28999b_h.jpg?fit=1024%2C1024&amp;ssl=1" role="button" tabindex="0" aria-label="Open image 8 of 10 in full-screen"srcset="https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51153300593_997b28999b_h-1024x1024.jpg?strip=info&#038;w=600&#038;ssl=1 600w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51153300593_997b28999b_h-1024x1024.jpg?strip=info&#038;w=900&#038;ssl=1 900w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51153300593_997b28999b_h-1024x1024.jpg?strip=info&#038;w=1200&#038;ssl=1 1200w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51153300593_997b28999b_h-1024x1024.jpg?strip=info&#038;w=1338&#038;ssl=1 1338w" alt="" data-height="1338" data-id="55260" data-link="https://mikejolley.com/?attachment_id=55260" data-url="https://mikejolley.com/wp-content/uploads/2021/12/51153300593_997b28999b_h-1024x1024.jpg" data-width="1338" src="https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51153300593_997b28999b_h-1024x1024.jpg?ssl=1" data-amp-layout="responsive"/></figure><figure class="tiled-gallery__item"><img decoding="async" data-attachment-id="55259" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51121972748_c1a2e3c0e7_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51121972748_c1a2e3c0e7_k.jpg?fit=2048%2C1639&amp;ssl=1" data-orig-size="2048,1639" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;\u00a9 Michael Jolley&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51121972748_c1a2e3c0e7_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51121972748_c1a2e3c0e7_k.jpg?fit=300%2C240&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51121972748_c1a2e3c0e7_k.jpg?fit=1024%2C820&amp;ssl=1" data-attachment-id="55259" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51121972748_c1a2e3c0e7_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51121972748_c1a2e3c0e7_k.jpg?fit=2048%2C1639&amp;ssl=1" data-orig-size="2048,1639" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;\u00a9 Michael Jolley&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51121972748_c1a2e3c0e7_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51121972748_c1a2e3c0e7_k.jpg?fit=300%2C240&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51121972748_c1a2e3c0e7_k.jpg?fit=1024%2C820&amp;ssl=1" role="button" tabindex="0" aria-label="Open image 9 of 10 in full-screen"srcset="https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51121972748_c1a2e3c0e7_k-1024x820.jpg?strip=info&#038;w=600&#038;ssl=1 600w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51121972748_c1a2e3c0e7_k-1024x820.jpg?strip=info&#038;w=900&#038;ssl=1 900w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51121972748_c1a2e3c0e7_k-1024x820.jpg?strip=info&#038;w=1200&#038;ssl=1 1200w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51121972748_c1a2e3c0e7_k-1024x820.jpg?strip=info&#038;w=1500&#038;ssl=1 1500w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51121972748_c1a2e3c0e7_k-1024x820.jpg?strip=info&#038;w=1800&#038;ssl=1 1800w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51121972748_c1a2e3c0e7_k-1024x820.jpg?strip=info&#038;w=2000&#038;ssl=1 2000w" alt="" data-height="1639" data-id="55259" data-link="https://mikejolley.com/?attachment_id=55259" data-url="https://mikejolley.com/wp-content/uploads/2021/12/51121972748_c1a2e3c0e7_k-1024x820.jpg" data-width="2048" src="https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51121972748_c1a2e3c0e7_k-1024x820.jpg?ssl=1" data-amp-layout="responsive"/></figure></div><div class="tiled-gallery__col" style="flex-basis:67.71019%"><figure class="tiled-gallery__item"><img decoding="async" data-attachment-id="55261" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51220978081_4a294b1ed5_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51220978081_4a294b1ed5_k.jpg?fit=1639%2C2048&amp;ssl=1" data-orig-size="1639,2048" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;Mike Jolley 2021&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51220978081_4a294b1ed5_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51220978081_4a294b1ed5_k.jpg?fit=240%2C300&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51220978081_4a294b1ed5_k.jpg?fit=820%2C1024&amp;ssl=1" data-attachment-id="55261" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51220978081_4a294b1ed5_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51220978081_4a294b1ed5_k.jpg?fit=1639%2C2048&amp;ssl=1" data-orig-size="1639,2048" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;Mike Jolley 2021&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51220978081_4a294b1ed5_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51220978081_4a294b1ed5_k.jpg?fit=240%2C300&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51220978081_4a294b1ed5_k.jpg?fit=820%2C1024&amp;ssl=1" role="button" tabindex="0" aria-label="Open image 10 of 10 in full-screen"srcset="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51220978081_4a294b1ed5_k-820x1024.jpg?strip=info&#038;w=600&#038;ssl=1 600w,https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51220978081_4a294b1ed5_k-820x1024.jpg?strip=info&#038;w=900&#038;ssl=1 900w,https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51220978081_4a294b1ed5_k-820x1024.jpg?strip=info&#038;w=1200&#038;ssl=1 1200w,https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51220978081_4a294b1ed5_k-820x1024.jpg?strip=info&#038;w=1500&#038;ssl=1 1500w,https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51220978081_4a294b1ed5_k-820x1024.jpg?strip=info&#038;w=1639&#038;ssl=1 1639w" alt="" data-height="2048" data-id="55261" data-link="https://mikejolley.com/?attachment_id=55261" data-url="https://mikejolley.com/wp-content/uploads/2021/12/51220978081_4a294b1ed5_k-820x1024.jpg" data-width="1639" src="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51220978081_4a294b1ed5_k-820x1024.jpg?ssl=1" data-amp-layout="responsive"/></figure></div></div></div></div>



<p class="wp-block-paragraph">You can see more of <a href="https://www.flickr.com/photos/mikejolley/albums/72157716956571452/with/50728331512/">my Astro images on Flickr.</a></p>



<p class="wp-block-paragraph">Unfortunately, I&#8217;ve had to take a short hiatus from imaging during these darker months because my telescope is buried in the garage while we remodel the house. Still, hopefully, by February, I can start imaging again. I&#8217;m hoping to revisit the <em>Orion Nebula</em>—my favourite target—in narrowband.</p>



<h2 class="wp-block-heading" id="other-photography">Other Photography</h2>



<p class="wp-block-paragraph">Getting out and about has been a challenge, given current events. I&#8217;ve not ventured far, which makes taking photos a little more tricky, especially when you&#8217;re a beginner and struggle with the subject matter. That said, we did do some travelling in the Summer months, and I took some lovely photos. A few of my favourites are below.</p>



<div data-carousel-extra='{&quot;blog_id&quot;:1,&quot;permalink&quot;:&quot;https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/&quot;}'  class="wp-block-jetpack-tiled-gallery alignwide is-style-rectangular"><div class="tiled-gallery__gallery"><div class="tiled-gallery__row"><div class="tiled-gallery__col" style="flex-basis:69.06508%"><figure class="tiled-gallery__item"><img decoding="async" data-attachment-id="55282" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51090665712_fae91f62a9_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51090665712_fae91f62a9_k.jpg?fit=2048%2C1536&amp;ssl=1" data-orig-size="2048,1536" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;\u00a9 Michael Jolley&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51090665712_fae91f62a9_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51090665712_fae91f62a9_k.jpg?fit=300%2C225&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51090665712_fae91f62a9_k.jpg?fit=1024%2C768&amp;ssl=1" data-attachment-id="55282" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51090665712_fae91f62a9_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51090665712_fae91f62a9_k.jpg?fit=2048%2C1536&amp;ssl=1" data-orig-size="2048,1536" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;\u00a9 Michael Jolley&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51090665712_fae91f62a9_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51090665712_fae91f62a9_k.jpg?fit=300%2C225&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51090665712_fae91f62a9_k.jpg?fit=1024%2C768&amp;ssl=1" role="button" tabindex="0" aria-label="Open image 1 of 8 in full-screen"srcset="https://i1.wp.com/mikejolley.com/wp-content/uploads/2021/12/51090665712_fae91f62a9_k-1024x768.jpg?strip=info&#038;w=600&#038;ssl=1 600w,https://i1.wp.com/mikejolley.com/wp-content/uploads/2021/12/51090665712_fae91f62a9_k-1024x768.jpg?strip=info&#038;w=900&#038;ssl=1 900w,https://i1.wp.com/mikejolley.com/wp-content/uploads/2021/12/51090665712_fae91f62a9_k-1024x768.jpg?strip=info&#038;w=1200&#038;ssl=1 1200w,https://i1.wp.com/mikejolley.com/wp-content/uploads/2021/12/51090665712_fae91f62a9_k-1024x768.jpg?strip=info&#038;w=1500&#038;ssl=1 1500w,https://i1.wp.com/mikejolley.com/wp-content/uploads/2021/12/51090665712_fae91f62a9_k-1024x768.jpg?strip=info&#038;w=1800&#038;ssl=1 1800w,https://i1.wp.com/mikejolley.com/wp-content/uploads/2021/12/51090665712_fae91f62a9_k-1024x768.jpg?strip=info&#038;w=2000&#038;ssl=1 2000w" alt="" data-height="1536" data-id="55282" data-link="https://mikejolley.com/?attachment_id=55282" data-url="https://mikejolley.com/wp-content/uploads/2021/12/51090665712_fae91f62a9_k-1024x768.jpg" data-width="2048" src="https://i1.wp.com/mikejolley.com/wp-content/uploads/2021/12/51090665712_fae91f62a9_k-1024x768.jpg?ssl=1" data-amp-layout="responsive"/></figure></div><div class="tiled-gallery__col" style="flex-basis:30.93492%"><figure class="tiled-gallery__item"><img decoding="async" data-attachment-id="55281" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/50947465608_1cbda5c892_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/50947465608_1cbda5c892_k.jpg?fit=2048%2C2048&amp;ssl=1" data-orig-size="2048,2048" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;\u00a9 Michael Jolley&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="50947465608_1cbda5c892_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/50947465608_1cbda5c892_k.jpg?fit=300%2C300&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/50947465608_1cbda5c892_k.jpg?fit=1024%2C1024&amp;ssl=1" data-attachment-id="55281" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/50947465608_1cbda5c892_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/50947465608_1cbda5c892_k.jpg?fit=2048%2C2048&amp;ssl=1" data-orig-size="2048,2048" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;\u00a9 Michael Jolley&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="50947465608_1cbda5c892_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/50947465608_1cbda5c892_k.jpg?fit=300%2C300&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/50947465608_1cbda5c892_k.jpg?fit=1024%2C1024&amp;ssl=1" role="button" tabindex="0" aria-label="Open image 2 of 8 in full-screen"srcset="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/50947465608_1cbda5c892_k-1024x1024.jpg?strip=info&#038;w=600&#038;ssl=1 600w,https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/50947465608_1cbda5c892_k-1024x1024.jpg?strip=info&#038;w=900&#038;ssl=1 900w,https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/50947465608_1cbda5c892_k-1024x1024.jpg?strip=info&#038;w=1200&#038;ssl=1 1200w,https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/50947465608_1cbda5c892_k-1024x1024.jpg?strip=info&#038;w=1500&#038;ssl=1 1500w,https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/50947465608_1cbda5c892_k-1024x1024.jpg?strip=info&#038;w=1800&#038;ssl=1 1800w,https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/50947465608_1cbda5c892_k-1024x1024.jpg?strip=info&#038;w=2000&#038;ssl=1 2000w" alt="" data-height="2048" data-id="55281" data-link="https://mikejolley.com/?attachment_id=55281" data-url="https://mikejolley.com/wp-content/uploads/2021/12/50947465608_1cbda5c892_k-1024x1024.jpg" data-width="2048" src="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/50947465608_1cbda5c892_k-1024x1024.jpg?ssl=1" data-amp-layout="responsive"/></figure><figure class="tiled-gallery__item"><img decoding="async" data-attachment-id="55283" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51291894333_69add65143_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51291894333_69add65143_k.jpg?fit=2048%2C1365&amp;ssl=1" data-orig-size="2048,1365" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51291894333_69add65143_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51291894333_69add65143_k.jpg?fit=300%2C200&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51291894333_69add65143_k.jpg?fit=1024%2C683&amp;ssl=1" data-attachment-id="55283" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51291894333_69add65143_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51291894333_69add65143_k.jpg?fit=2048%2C1365&amp;ssl=1" data-orig-size="2048,1365" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51291894333_69add65143_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51291894333_69add65143_k.jpg?fit=300%2C200&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51291894333_69add65143_k.jpg?fit=1024%2C683&amp;ssl=1" role="button" tabindex="0" aria-label="Open image 3 of 8 in full-screen"srcset="https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51291894333_69add65143_k-1024x683.jpg?strip=info&#038;w=600&#038;ssl=1 600w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51291894333_69add65143_k-1024x683.jpg?strip=info&#038;w=900&#038;ssl=1 900w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51291894333_69add65143_k-1024x683.jpg?strip=info&#038;w=1200&#038;ssl=1 1200w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51291894333_69add65143_k-1024x683.jpg?strip=info&#038;w=1500&#038;ssl=1 1500w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51291894333_69add65143_k-1024x683.jpg?strip=info&#038;w=1800&#038;ssl=1 1800w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51291894333_69add65143_k-1024x683.jpg?strip=info&#038;w=2000&#038;ssl=1 2000w" alt="" data-height="1365" data-id="55283" data-link="https://mikejolley.com/?attachment_id=55283" data-url="https://mikejolley.com/wp-content/uploads/2021/12/51291894333_69add65143_k-1024x683.jpg" data-width="2048" src="https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51291894333_69add65143_k-1024x683.jpg?ssl=1" data-amp-layout="responsive"/></figure></div></div><div class="tiled-gallery__row"><div class="tiled-gallery__col" style="flex-basis:30.77985%"><figure class="tiled-gallery__item"><img decoding="async" data-attachment-id="55285" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51290965837_207b35b0b0_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51290965837_207b35b0b0_k.jpg?fit=2048%2C1536&amp;ssl=1" data-orig-size="2048,1536" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51290965837_207b35b0b0_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51290965837_207b35b0b0_k.jpg?fit=300%2C225&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51290965837_207b35b0b0_k.jpg?fit=1024%2C768&amp;ssl=1" data-attachment-id="55285" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51290965837_207b35b0b0_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51290965837_207b35b0b0_k.jpg?fit=2048%2C1536&amp;ssl=1" data-orig-size="2048,1536" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51290965837_207b35b0b0_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51290965837_207b35b0b0_k.jpg?fit=300%2C225&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51290965837_207b35b0b0_k.jpg?fit=1024%2C768&amp;ssl=1" role="button" tabindex="0" aria-label="Open image 4 of 8 in full-screen"srcset="https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51290965837_207b35b0b0_k-1024x768.jpg?strip=info&#038;w=600&#038;ssl=1 600w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51290965837_207b35b0b0_k-1024x768.jpg?strip=info&#038;w=900&#038;ssl=1 900w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51290965837_207b35b0b0_k-1024x768.jpg?strip=info&#038;w=1200&#038;ssl=1 1200w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51290965837_207b35b0b0_k-1024x768.jpg?strip=info&#038;w=1500&#038;ssl=1 1500w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51290965837_207b35b0b0_k-1024x768.jpg?strip=info&#038;w=1800&#038;ssl=1 1800w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51290965837_207b35b0b0_k-1024x768.jpg?strip=info&#038;w=2000&#038;ssl=1 2000w" alt="" data-height="1536" data-id="55285" data-link="https://mikejolley.com/?attachment_id=55285" data-url="https://mikejolley.com/wp-content/uploads/2021/12/51290965837_207b35b0b0_k-1024x768.jpg" data-width="2048" src="https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51290965837_207b35b0b0_k-1024x768.jpg?ssl=1" data-amp-layout="responsive"/></figure></div><div class="tiled-gallery__col" style="flex-basis:34.61008%"><figure class="tiled-gallery__item"><img decoding="async" data-attachment-id="55287" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51407051288_e1cc74e739_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51407051288_e1cc74e739_k.jpg?fit=2048%2C1365&amp;ssl=1" data-orig-size="2048,1365" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51407051288_e1cc74e739_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51407051288_e1cc74e739_k.jpg?fit=300%2C200&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51407051288_e1cc74e739_k.jpg?fit=1024%2C683&amp;ssl=1" data-attachment-id="55287" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51407051288_e1cc74e739_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51407051288_e1cc74e739_k.jpg?fit=2048%2C1365&amp;ssl=1" data-orig-size="2048,1365" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51407051288_e1cc74e739_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51407051288_e1cc74e739_k.jpg?fit=300%2C200&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51407051288_e1cc74e739_k.jpg?fit=1024%2C683&amp;ssl=1" role="button" tabindex="0" aria-label="Open image 5 of 8 in full-screen"srcset="https://i1.wp.com/mikejolley.com/wp-content/uploads/2021/12/51407051288_e1cc74e739_k-1024x683.jpg?strip=info&#038;w=600&#038;ssl=1 600w,https://i1.wp.com/mikejolley.com/wp-content/uploads/2021/12/51407051288_e1cc74e739_k-1024x683.jpg?strip=info&#038;w=900&#038;ssl=1 900w,https://i1.wp.com/mikejolley.com/wp-content/uploads/2021/12/51407051288_e1cc74e739_k-1024x683.jpg?strip=info&#038;w=1200&#038;ssl=1 1200w,https://i1.wp.com/mikejolley.com/wp-content/uploads/2021/12/51407051288_e1cc74e739_k-1024x683.jpg?strip=info&#038;w=1500&#038;ssl=1 1500w,https://i1.wp.com/mikejolley.com/wp-content/uploads/2021/12/51407051288_e1cc74e739_k-1024x683.jpg?strip=info&#038;w=1800&#038;ssl=1 1800w,https://i1.wp.com/mikejolley.com/wp-content/uploads/2021/12/51407051288_e1cc74e739_k-1024x683.jpg?strip=info&#038;w=2000&#038;ssl=1 2000w" alt="" data-height="1365" data-id="55287" data-link="https://mikejolley.com/?attachment_id=55287" data-url="https://mikejolley.com/wp-content/uploads/2021/12/51407051288_e1cc74e739_k-1024x683.jpg" data-width="2048" src="https://i1.wp.com/mikejolley.com/wp-content/uploads/2021/12/51407051288_e1cc74e739_k-1024x683.jpg?ssl=1" data-amp-layout="responsive"/></figure></div><div class="tiled-gallery__col" style="flex-basis:34.61008%"><figure class="tiled-gallery__item"><img decoding="async" data-attachment-id="55284" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51291891558_7c7c207147_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51291891558_7c7c207147_k.jpg?fit=2048%2C1365&amp;ssl=1" data-orig-size="2048,1365" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51291891558_7c7c207147_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51291891558_7c7c207147_k.jpg?fit=300%2C200&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51291891558_7c7c207147_k.jpg?fit=1024%2C683&amp;ssl=1" data-attachment-id="55284" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51291891558_7c7c207147_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51291891558_7c7c207147_k.jpg?fit=2048%2C1365&amp;ssl=1" data-orig-size="2048,1365" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51291891558_7c7c207147_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51291891558_7c7c207147_k.jpg?fit=300%2C200&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51291891558_7c7c207147_k.jpg?fit=1024%2C683&amp;ssl=1" role="button" tabindex="0" aria-label="Open image 6 of 8 in full-screen"srcset="https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51291891558_7c7c207147_k-1024x683.jpg?strip=info&#038;w=600&#038;ssl=1 600w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51291891558_7c7c207147_k-1024x683.jpg?strip=info&#038;w=900&#038;ssl=1 900w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51291891558_7c7c207147_k-1024x683.jpg?strip=info&#038;w=1200&#038;ssl=1 1200w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51291891558_7c7c207147_k-1024x683.jpg?strip=info&#038;w=1500&#038;ssl=1 1500w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51291891558_7c7c207147_k-1024x683.jpg?strip=info&#038;w=1800&#038;ssl=1 1800w,https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51291891558_7c7c207147_k-1024x683.jpg?strip=info&#038;w=2000&#038;ssl=1 2000w" alt="" data-height="1365" data-id="55284" data-link="https://mikejolley.com/?attachment_id=55284" data-url="https://mikejolley.com/wp-content/uploads/2021/12/51291891558_7c7c207147_k-1024x683.jpg" data-width="2048" src="https://i2.wp.com/mikejolley.com/wp-content/uploads/2021/12/51291891558_7c7c207147_k-1024x683.jpg?ssl=1" data-amp-layout="responsive"/></figure></div></div><div class="tiled-gallery__row"><div class="tiled-gallery__col" style="flex-basis:47.06454%"><figure class="tiled-gallery__item"><img decoding="async" data-attachment-id="55288" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51292714200_28e77af50e_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51292714200_28e77af50e_k.jpg?fit=2048%2C1536&amp;ssl=1" data-orig-size="2048,1536" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51292714200_28e77af50e_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51292714200_28e77af50e_k.jpg?fit=300%2C225&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51292714200_28e77af50e_k.jpg?fit=1024%2C768&amp;ssl=1" data-attachment-id="55288" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51292714200_28e77af50e_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51292714200_28e77af50e_k.jpg?fit=2048%2C1536&amp;ssl=1" data-orig-size="2048,1536" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51292714200_28e77af50e_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51292714200_28e77af50e_k.jpg?fit=300%2C225&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51292714200_28e77af50e_k.jpg?fit=1024%2C768&amp;ssl=1" role="button" tabindex="0" aria-label="Open image 7 of 8 in full-screen"srcset="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51292714200_28e77af50e_k-1024x768.jpg?strip=info&#038;w=600&#038;ssl=1 600w,https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51292714200_28e77af50e_k-1024x768.jpg?strip=info&#038;w=900&#038;ssl=1 900w,https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51292714200_28e77af50e_k-1024x768.jpg?strip=info&#038;w=1200&#038;ssl=1 1200w,https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51292714200_28e77af50e_k-1024x768.jpg?strip=info&#038;w=1500&#038;ssl=1 1500w,https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51292714200_28e77af50e_k-1024x768.jpg?strip=info&#038;w=1800&#038;ssl=1 1800w,https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51292714200_28e77af50e_k-1024x768.jpg?strip=info&#038;w=2000&#038;ssl=1 2000w" alt="" data-height="1536" data-id="55288" data-link="https://mikejolley.com/?attachment_id=55288" data-url="https://mikejolley.com/wp-content/uploads/2021/12/51292714200_28e77af50e_k-1024x768.jpg" data-width="2048" src="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51292714200_28e77af50e_k-1024x768.jpg?ssl=1" data-amp-layout="responsive"/></figure></div><div class="tiled-gallery__col" style="flex-basis:52.93546%"><figure class="tiled-gallery__item"><img decoding="async" data-attachment-id="55286" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51407763360_9a636e8023_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51407763360_9a636e8023_k.jpg?fit=2048%2C1365&amp;ssl=1" data-orig-size="2048,1365" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51407763360_9a636e8023_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51407763360_9a636e8023_k.jpg?fit=300%2C200&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51407763360_9a636e8023_k.jpg?fit=1024%2C683&amp;ssl=1" data-attachment-id="55286" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/51407763360_9a636e8023_k/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51407763360_9a636e8023_k.jpg?fit=2048%2C1365&amp;ssl=1" data-orig-size="2048,1365" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="51407763360_9a636e8023_k" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51407763360_9a636e8023_k.jpg?fit=300%2C200&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/51407763360_9a636e8023_k.jpg?fit=1024%2C683&amp;ssl=1" role="button" tabindex="0" aria-label="Open image 8 of 8 in full-screen"srcset="https://i1.wp.com/mikejolley.com/wp-content/uploads/2021/12/51407763360_9a636e8023_k-1024x683.jpg?strip=info&#038;w=600&#038;ssl=1 600w,https://i1.wp.com/mikejolley.com/wp-content/uploads/2021/12/51407763360_9a636e8023_k-1024x683.jpg?strip=info&#038;w=900&#038;ssl=1 900w,https://i1.wp.com/mikejolley.com/wp-content/uploads/2021/12/51407763360_9a636e8023_k-1024x683.jpg?strip=info&#038;w=1200&#038;ssl=1 1200w,https://i1.wp.com/mikejolley.com/wp-content/uploads/2021/12/51407763360_9a636e8023_k-1024x683.jpg?strip=info&#038;w=1500&#038;ssl=1 1500w,https://i1.wp.com/mikejolley.com/wp-content/uploads/2021/12/51407763360_9a636e8023_k-1024x683.jpg?strip=info&#038;w=1800&#038;ssl=1 1800w,https://i1.wp.com/mikejolley.com/wp-content/uploads/2021/12/51407763360_9a636e8023_k-1024x683.jpg?strip=info&#038;w=2000&#038;ssl=1 2000w" alt="" data-height="1365" data-id="55286" data-link="https://mikejolley.com/?attachment_id=55286" data-url="https://mikejolley.com/wp-content/uploads/2021/12/51407763360_9a636e8023_k-1024x683.jpg" data-width="2048" src="https://i1.wp.com/mikejolley.com/wp-content/uploads/2021/12/51407763360_9a636e8023_k-1024x683.jpg?ssl=1" data-amp-layout="responsive"/></figure></div></div></div></div>



<p class="wp-block-paragraph">I&#8217;m currently rocking a Nikon z7ii. I love the ergonomics of the Nikon, and some of the new Z lenses are impressively sharp.</p>



<h2 class="wp-block-heading" id="movies-games-and-videos">Movies, Games and Videos</h2>



<p class="wp-block-paragraph">Let&#8217;s be honest. 2021 wasn&#8217;t great for cinema (understandably). The streaming releases are convenient, but you lose some of the magic. Still, if I had to pick my favourites (note, I have not yet seen the new <em>Spiderman</em> Movie or <em>Matrix Resurrections</em>):</p>



<ul class="wp-block-list"><li><em>Shang-Chi</em> for me has probably been the best Marvel Movie this year. Likeable lead, nice origin story, good fights.</li><li><em>The Suicide Squad </em>for it&#8217;s dark humour. So much better than the first pitiful attempt. Some surprises too; James Gunn was not kidding when he said no-character was safe.</li><li><em>Ghostbusters Afterlife</em> was full of nostalga and did not dissapoint. Some of the characters did lack development and probably could have been cut without impacting anything, but overall it worked, Paul Rudd and Mckenna Grace were great, and it was so nice to see the original cast back in action.</li></ul>



<p class="wp-block-paragraph">There have been a few modern gaming titles this year I&#8217;ve enjoyed:</p>



<ul class="wp-block-list"><li><em>Deathloop</em> &#8211; I&#8217;ve not beaten this yet. It&#8217;s like groundhog day, with guns and lots of mystery.</li><li><em>Cyberpunk 2077</em> &#8211; This received a lot of stick for being a buggy mess, but I didn&#8217;t experience many issues on PS5. Great story, fun gameplay. Kinda like Blade Runner meets Grand Theft Auto.</li><li><em>Mass Effect Trology</em> &#8211; Probably the best sci-fi RPGs of all time re-released on modern consoles. Number one shows it&#8217;s age a bit, but 2 and 3 are so good. Spent countless hours in this universe.</li><li>Super <em>Mario 3D World</em> &#8211; I don&#8217;t think there is a bad mario game. Not as good as <em>Odyssey</em>, but me and my son enjoyed it.</li></ul>



<p class="wp-block-paragraph">I reckon I&#8217;ve played way more retro games this year. <a href="https://www.twitch.tv/muffinkiller86">I started streaming my retro game playthroughs on Twitch</a>. I play bad, die a lot, and don&#8217;t have many followers, but it&#8217;s good fun, and I&#8217;d be playing anyway. It&#8217;s definitely given me an excuse to break out the old consoles again and experiment with mods, upscaling, and modern RGB cables.</p>



<figure class="wp-block-image size-large"><a href="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-22.23.08.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="573" data-attachment-id="55294" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/screenshot-2021-12-23-at-22-23-08/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-22.23.08.png?fit=1255%2C702&amp;ssl=1" data-orig-size="1255,702" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Screenshot-2021-12-23-at-22.23.08" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-22.23.08.png?fit=300%2C168&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-22.23.08.png?fit=1024%2C573&amp;ssl=1" src="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-22.23.08.png?resize=1024%2C573&#038;ssl=1" alt="" class="wp-image-55294" srcset="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-22.23.08.png?resize=1024%2C573&amp;ssl=1 1024w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-22.23.08.png?resize=300%2C168&amp;ssl=1 300w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-22.23.08.png?resize=768%2C430&amp;ssl=1 768w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-22.23.08.png?w=1255&amp;ssl=1 1255w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></a><figcaption>I play beat &#8217;em up games Tuesday nights.</figcaption></figure>



<h2 class="wp-block-heading" id="everything-else">Everything else&#8230;</h2>



<ul class="wp-block-list"><li>For my retro streaming, this year I&#8217;ve procured a <em>Sega Master System</em>, an modded <em>Super Famicom</em>, <em>Gamecube</em>, a <em>ZX Spectrum +2</em> (my old one died), and a <em>BBC Micro</em>. I&#8217;m amassing quite a collection!</li><li>I bravely modded my Sega Mega Drive to play NTSC games at 60hz.</li><li>I bravely tried to fix my <em>Spectrum 48k </em>and managed to replace the keyboard, but it ultimetely died. RIP.</li><li>I&#8217;ve used Arduino before, but this year I got my first Raspberry Pi to play with. I built a <a href="https://thepihut.com/products/picade-raspberry-pi-arcade-machine-10-display">Picade</a> to emulate arcade games and I love it!</li></ul>



<figure class="wp-block-image size-large"><a href="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/06/picade.jpg?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="640" data-attachment-id="54692" data-permalink="https://mikejolley.com/2021/06/24/using-batocera-linux-on-a-picade-arcade-machine/picade/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/06/picade.jpg?fit=8049%2C5031&amp;ssl=1" data-orig-size="8049,5031" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;2&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;NIKON Z 7_2&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;1624554655&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;58&quot;,&quot;iso&quot;:&quot;100&quot;,&quot;shutter_speed&quot;:&quot;0.016666666666667&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="picade" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/06/picade.jpg?fit=300%2C188&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/06/picade.jpg?fit=1024%2C640&amp;ssl=1" src="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/06/picade.jpg?resize=1024%2C640&#038;ssl=1" alt="Photo of a Picade Arcade Machine" class="wp-image-54692" srcset="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/06/picade.jpg?resize=1024%2C640&amp;ssl=1 1024w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/06/picade.jpg?resize=300%2C188&amp;ssl=1 300w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/06/picade.jpg?resize=768%2C480&amp;ssl=1 768w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/06/picade.jpg?resize=1536%2C960&amp;ssl=1 1536w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/06/picade.jpg?resize=2048%2C1280&amp;ssl=1 2048w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/06/picade.jpg?w=3000&amp;ssl=1 3000w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></a><figcaption>Picade I built this year for arcade titles, powered by a Raspberry Pi 4</figcaption></figure>



<ul class="wp-block-list"><li>My Dungeons and Dragons group (nerd alert) which started in Lockdown have been playing weekly throughout 2021 online using <a href="https://roll20.net/welcome">Roll20</a>. This game is a lot of fun and we have some much needed laughs.</li></ul>



<div class="wp-block-image"><figure class="aligncenter size-full is-resized"><a href="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/morric.jpg?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" data-attachment-id="55298" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/morric/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/morric.jpg?fit=768%2C768&amp;ssl=1" data-orig-size="768,768" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;1&quot;}" data-image-title="morric" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/morric.jpg?fit=300%2C300&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/morric.jpg?fit=768%2C768&amp;ssl=1" src="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/morric.jpg?resize=250%2C250&#038;ssl=1" alt="" class="wp-image-55298" width="250" height="250" srcset="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/morric.jpg?w=768&amp;ssl=1 768w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/morric.jpg?resize=300%2C300&amp;ssl=1 300w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/morric.jpg?resize=150%2C150&amp;ssl=1 150w" sizes="auto, (max-width: 250px) 100vw, 250px" /></a><figcaption>I&#8217;m a wizard!</figcaption></figure></div>



<ul class="wp-block-list"><li>I also had a go at being Dungeon Master for the first time which was a little scary, but it all worked out well; we did the <em>Rick and Morty </em>dungeon. <a href="https://www.reddit.com/r/DnD/comments/peenc7/oc_lost_dungeon_of_rickedness_npc_tokens/">I made lots of custom assets for the NPCs</a> and a custom dungeon with <a href="https://dungeondraft.net/">Dungeondraft</a>. It looked much better visually that the source material in my opinion.</li></ul>



<figure class="wp-block-image size-full is-resized"><a href="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-21.54.21.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" data-attachment-id="55291" data-permalink="https://mikejolley.com/2021/12/29/twenty-twenty-one-in-review/screenshot-2021-12-23-at-21-54-21/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-21.54.21.png?fit=731%2C413&amp;ssl=1" data-orig-size="731,413" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Screenshot-2021-12-23-at-21.54.21" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-21.54.21.png?fit=300%2C169&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-21.54.21.png?fit=731%2C413&amp;ssl=1" src="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-21.54.21.png?resize=720%2C406&#038;ssl=1" alt="" class="wp-image-55291" width="720" height="406" srcset="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-21.54.21.png?w=731&amp;ssl=1 731w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/Screenshot-2021-12-23-at-21.54.21.png?resize=300%2C169&amp;ssl=1 300w" sizes="auto, (max-width: 720px) 100vw, 720px" /></a><figcaption>The stream was a disaster (lousy video), but the players had fun in my D&amp;D one-shot.</figcaption></figure>



<ul class="wp-block-list"><li>Our little family is growing. Dexter is now 3 years old. He&#8217;s definetely a handful, and working from home can sometimes be a challenge with all the distractions. But we manage. He also enjoys games and loves Mario (well, Luigi more, so he knows his stuff).</li></ul>



<figure class="wp-block-image size-large"><a href="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/mario.jpg?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1490" height="1118" data-attachment-id="55300" data-permalink="https://mikejolley.com/mario-2/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/mario-edited.jpg?fit=1490%2C1118&amp;ssl=1" data-orig-size="1490,1118" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="mario" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/mario-edited.jpg?fit=300%2C225&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/mario-edited.jpg?fit=1024%2C768&amp;ssl=1" src="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/mario-edited.jpg?resize=1490%2C1118&#038;ssl=1" alt="" class="wp-image-55300" srcset="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/mario-edited.jpg?w=1490&amp;ssl=1 1490w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/mario-edited.jpg?resize=300%2C225&amp;ssl=1 300w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/mario-edited.jpg?resize=1024%2C768&amp;ssl=1 1024w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/12/mario-edited.jpg?resize=768%2C576&amp;ssl=1 768w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></a></figure>



<ul class="wp-block-list"><li>Next year there will be a new challenge with Baby #2 arriving in March! I&#8217;m quite fond of the name <em>Chuckie</em> but that might be a hard sell to my wife. We shall see&#8230;</li></ul>



<p class="wp-block-paragraph">Thanks for reading my little round-up of 2021, and have a happy new year!</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">55243</post-id>	</item>
		<item>
		<title>Using Batocera Linux on a Picade Arcade Machine</title>
		<link>https://mikejolley.com/2021/06/24/using-batocera-linux-on-a-picade-arcade-machine/</link>
		
		<dc:creator><![CDATA[Mike Jolley]]></dc:creator>
		<pubDate>Thu, 24 Jun 2021 17:52:03 +0000</pubDate>
				<category><![CDATA[Retro Gaming]]></category>
		<category><![CDATA[gaming]]></category>
		<category><![CDATA[picade]]></category>
		<category><![CDATA[raspberry-pi]]></category>
		<guid isPermaLink="false">https://mikejolley.com/?p=54651</guid>

					<description><![CDATA[I just finished building a Picade which works flawlessly with Retropie (a Linux based OS for retro gaming on Raspberry Pi). This is a smaller sibling to my big arcade machine also running on a Pi 🙂 If you&#8217;re wanting to use a different OS such as RecalBox or Batocera, it can be a little [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">I just finished <a href="https://shop.pimoroni.com/products/picade?variant=29210087489619">building a Picade</a> which works flawlessly with <a href="https://retropie.org.uk/">Retropie</a> (a Linux based OS for retro gaming on Raspberry Pi). This is a smaller sibling to my big arcade machine also running on a Pi <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p class="wp-block-paragraph">If you&#8217;re wanting to use a different OS such as <a href="https://www.recalbox.com/">RecalBox</a> or <a href="https://batocera.org/">Batocera</a>, it can be a little challenging to get working because the <a href="https://shop.pimoroni.com/products/picade-x-hat-usb-c">included Picade X Hat</a> (which controls sound and the joystick) only has a setup script for Retropie. Officially, other distros are not supported. </p>



<p class="wp-block-paragraph">I had been using <meta charset="utf-8">Recalbox on my other arcade machine, but I wanted to try Batocera this time; I love its simplicity, UI, and the default themes look great! </p>



<p class="wp-block-paragraph">Here are the steps I had to take to get it working with the Picade. This assumes you have a Mac, <span style="font-size: revert; color: initial;">Raspberry Pi 4</span>, and a USB keyboard for setup.</p>



<span id="more-54651"></span>



<h2 class="wp-block-heading">Install Batocera Linux on an SD Card</h2>



<p class="wp-block-paragraph">Go ahead and <a href="https://batocera.org/download">download the Batocera image for your Pi</a>. You need to flash this to an SD Card first (use <a href="https://www.balena.io/etcher/">Etcher</a>). Once flashed you can pop it in your Pi and turn it on. Batocera will be installed.</p>



<p class="has-background wp-block-paragraph" style="background-color:#f4cb8b">Note that when booting for the first time, audio may be <strong>very</strong> loud, and volume controls won&#8217;t work. You can turn off music from the main menu, sound options which should suffice until we get the Picade setup correctly.</p>



<h3 class="wp-block-heading">Bonus Step: Changing storage from ext4 to ExFat</h3>



<p class="wp-block-paragraph">Batocera, unlike RecalBox and Retropie, uses <a href="https://en.wikipedia.org/wiki/Ext4">ext4</a> storage for Roms. If you&#8217;re managing your Roms over the network, or you have a Linux based PC, this is not a problem, you can leave it as-is. I however had quite a lot of data to transfer from my Mac, so to make this easier I formatted the storage as ExFat so I can copy things straight to the SD Card.</p>



<p class="wp-block-paragraph">To do this, turn on the Pi and load Batocera. From the main menu go to <em>System Settings &gt; Developer &gt; Format a disk</em> and change <em>ext4</em> to <em>ExFat</em>. Any data you&#8217;ve already copied will be deleted so do this step first if you intend to.</p>



<h2 class="wp-block-heading">Configure <meta charset="utf-8">Batocera for Picade Support</h2>



<p class="wp-block-paragraph">Now for the fun part. The Picade &#8220;Hat&#8221; (it&#8217;s a chip that sits atop the Pi) <a href="https://github.com/pimoroni/picade-hat/blob/master/install.sh">comes with an installer that gets it working with Retropie.</a> This won&#8217;t work on Batocera (or other distros) though, so we need to set up the important parts manually.</p>



<p class="wp-block-paragraph">Pop the SD Card into your Mac. A disk named <code>BATOCERA</code> will be mounted. We&#8217;ll be editing files here.</p>



<h3 class="wp-block-heading">Step 1: Install the Picade overlay</h3>



<p class="wp-block-paragraph">The picade-hat installer compiles and creates a <code>picade.dtbo</code> file, but we don&#8217;t have access to the <code>dtc</code> tool it uses on Batocera, so we need to build it ourselves. </p>



<p class="wp-block-paragraph">You can do this by <a href="https://github.com/pimoroni/picade-hat.git">checking out the Picade-Hat Git repository</a>, <a href="https://formulae.brew.sh/formula/dtc">installing dtc</a> on your mac, and then running the <code>dtc</code> command in the picade-hat directory.</p>



<p class="wp-block-paragraph">Alternatively, download the version I built below (it&#8217;s zipped):</p>



<div class="wp-block-file aligncenter"><a id="wp-block-file--media-17e0dcca-0d8b-4e60-a57d-13c6b167626f" href="https://mikejolley.com/wp-content/uploads/2021/06/picade.dtbo_.zip">picade.dtbo_</a><a href="https://mikejolley.com/wp-content/uploads/2021/06/picade.dtbo_.zip" class="wp-block-file__button wp-element-button" download aria-describedby="wp-block-file--media-17e0dcca-0d8b-4e60-a57d-13c6b167626f">Download</a></div>



<p class="wp-block-paragraph">With your `picade.dtbo` file in hand, <strong>copy it to your SD Card inside the <code>/overlays/</code> directory.</strong></p>



<h3 class="wp-block-heading">Step 2: Edit the config.txt file</h3>



<p class="wp-block-paragraph">On your SD Card, find the <code>config.txt</code> file and open it up in your text editor. At the bottom of the file, add the following lines:</p>


<pre class="wp-block-code"><span><code class="hljs">dtoverlay=picade
dtparam=audio=off</code></span></pre>


<p class="wp-block-paragraph">This tells Batocera to load the Picade overlay, which enables hardware support for the joystick and audio.</p>



<p class="wp-block-paragraph">Also in this config, <em>optionally</em>, we can set up what keys the Picade joystick will map to. To better support RetroArch, I chose to map my joystick to the default RetroArch controls:</p>


<pre class="wp-block-code"><span><code class="hljs language-php"><span class="hljs-comment"># key mapping</span>
dtparam=button1=<span class="hljs-number">30</span> <span class="hljs-comment"># A Key</span>
dtparam=button2=<span class="hljs-number">31</span> <span class="hljs-comment"># S Key</span>
dtparam=button3=<span class="hljs-number">16</span> <span class="hljs-comment"># Q Key</span>
dtparam=button4=<span class="hljs-number">44</span> <span class="hljs-comment"># Z Key</span>
dtparam=button5=<span class="hljs-number">45</span> <span class="hljs-comment"># X Key</span>
dtparam=button6=<span class="hljs-number">17</span> <span class="hljs-comment"># W Key</span>
dtparam=enter=<span class="hljs-number">23</span> <span class="hljs-comment"># I Key</span>
dtparam=escape=<span class="hljs-number">1</span> <span class="hljs-comment"># Escape</span>
dtparam=coin=<span class="hljs-number">54</span> <span class="hljs-comment"># Right Shift</span>
dtparam=start=<span class="hljs-number">28</span> <span class="hljs-comment"># Enter</span></code></span></pre>


<p class="wp-block-paragraph">When you&#8217;re done, save the file and eject the card. You can now put it back in your Pi and boot Batocera.</p>



<h3 class="wp-block-heading">Step 3: SSH into <meta charset="utf-8">Batocera</h3>



<p class="wp-block-paragraph">This isn&#8217;t as scary as it sounds. From another computer on your network you need to connect to Batocera using SSH. <strong>First ensure Batocera is connected to the same wifi network.</strong> On the main menu, go to <em>Network</em> and set up your wifi details. </p>



<p class="wp-block-paragraph">Now from your other computer connect using:</p>


<pre class="wp-block-code"><span><code class="hljs language-bash">ssh root@batocera.local</code></span></pre>


<p class="wp-block-paragraph">It may ask you to trust the certificate (answer <em>yes</em>), then will prompt for the SSH password which is <code>linux</code>.</p>



<figure class="wp-block-image aligncenter size-large"><a href="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/06/Screenshot-2021-06-24-at-11.42.02.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="626" height="704" data-attachment-id="54670" data-permalink="https://mikejolley.com/2021/06/24/using-batocera-linux-on-a-picade-arcade-machine/screenshot-2021-06-24-at-11-42-02/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/06/Screenshot-2021-06-24-at-11.42.02.png?fit=626%2C704&amp;ssl=1" data-orig-size="626,704" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Screenshot-2021-06-24-at-11.42.02" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/06/Screenshot-2021-06-24-at-11.42.02.png?fit=267%2C300&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/06/Screenshot-2021-06-24-at-11.42.02.png?fit=626%2C704&amp;ssl=1" src="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/06/Screenshot-2021-06-24-at-11.42.02.png?resize=626%2C704&#038;ssl=1" alt="" class="wp-image-54670" srcset="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/06/Screenshot-2021-06-24-at-11.42.02.png?w=626&amp;ssl=1 626w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/06/Screenshot-2021-06-24-at-11.42.02.png?resize=267%2C300&amp;ssl=1 267w" sizes="auto, (max-width: 626px) 100vw, 626px" /></a><figcaption class="wp-element-caption">Connected via SSH</figcaption></figure>



<p class="wp-block-paragraph">We can now download the picade rules and sound config files and copy them to the correct places using the following commands:</p>


<pre class="wp-block-code"><span><code class="hljs language-bash">mount -o remount,rw /boot
curl https://raw.githubusercontent.com/pimoroni/picade-hat/master/etc/udev/rules.d/10-picade.rules --output 10-picade.rules
curl https://raw.githubusercontent.com/pimoroni/picade-hat/master/etc/asound.conf --output asound.conf
mv 10-picade.rules /userdata/system/udev/rules.d/
mv asound.conf /etc/
batocera-save-overlay
reboot</code></span></pre>


<p class="wp-block-paragraph">This will configure and reboot Batocera and you should be good to go!</p>



<p class="has-black-color has-pale-pink-background-color has-text-color has-background wp-block-paragraph">Edit: Batocera 31/32 seem to be working slightly differently. In 32 (dev version), the sound file is not required at all (yay!). And for the rules file, you may need to also copy this to <code>/etc/udev/rules.d/</code> for things to work!</p>



<p class="wp-block-paragraph">The only other tweak you may want to make is an edit in <code>batocera.conf</code> to make RetroArch easier to use. If you open that file up in an editor via SSH:</p>


<pre class="wp-block-code"><span><code class="hljs">nano /userdata/system/batocera.conf</code></span></pre>


<p class="wp-block-paragraph">I&#8217;d suggest adding a shortcut key to open the menu when running RetroArch. I set it up so that pressing <em>start and select </em>(front two buttons on Picade) opens the main menu:</p>


<pre class="wp-block-code"><span><code class="hljs language-php"><span class="hljs-keyword">global</span>.retroarch.input_menu_toggle_gamepad_combo = <span class="hljs-string">"4"</span></code></span></pre>


<p class="wp-block-paragraph">After saving changes, reboot the system. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1fa84.png" alt="🪄" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<hr class="wp-block-separator has-css-opacity"/>



<p class="wp-block-paragraph">Thanks to <a href="https://github.com/djibux">djibux</a> who&#8217;s instructions on <a href="https://github.com/pimoroni/picade-hat/issues/33">fixing RecalBox</a> helped steer me in the right direction.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">54651</post-id>	</item>
		<item>
		<title>Holler Box Interview</title>
		<link>https://mikejolley.com/2021/04/08/holler-box-interview/</link>
		
		<dc:creator><![CDATA[Mike Jolley]]></dc:creator>
		<pubDate>Thu, 08 Apr 2021 00:05:37 +0000</pubDate>
				<category><![CDATA[Personal]]></category>
		<guid isPermaLink="false">https://mikejolley.com/?p=54340</guid>

					<description><![CDATA[I was interviewed by Scott Bolinger over on the Holler Box blog which covered my thoughts on Headless WP, my work on the WooCommerce Store API, and 🌶 the WooThemes acquisition by Automattic.]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">I was interviewed by <a href="https://hollerwp.com/mike-jolley-interview/">Scott Bolinger over on the Holler Box blog</a> which covered my thoughts on Headless WP, my work on the WooCommerce Store API, and <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f336.png" alt="🌶" class="wp-smiley" style="height: 1em; max-height: 1em;" /> the WooThemes acquisition by Automattic.</p>



<figure class="wp-block-embed is-type-wp-embed is-provider-holler-box wp-block-embed-holler-box"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="NV2WFvQKc2"><a href="https://hollerwp.com/mike-jolley-interview/">Catching Up with Mike Jolley &#8211; Jigoshop, Headless WooCommerce, and more</a></blockquote><iframe loading="lazy" class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;Catching Up with Mike Jolley &#8211; Jigoshop, Headless WooCommerce, and more&#8221; &#8212; HollerBox" src="https://hollerwp.com/mike-jolley-interview/embed/#?secret=OZlcDxUe77#?secret=NV2WFvQKc2" data-secret="NV2WFvQKc2" width="500" height="282" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">54340</post-id>	</item>
		<item>
		<title>Headless WordPress: User Registration with ReactJS &#038; WPGraphQL</title>
		<link>https://mikejolley.com/2021/03/26/headless-wordpress-user-registration-using-reactjs-wpgraphql/</link>
		
		<dc:creator><![CDATA[Mike Jolley]]></dc:creator>
		<pubDate>Fri, 26 Mar 2021 00:15:23 +0000</pubDate>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[GraphQL]]></category>
		<category><![CDATA[Headless]]></category>
		<category><![CDATA[React]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://mikejolley.com/?p=54225</guid>

					<description><![CDATA[How I built the user registration form in a ReactJS app using WordPress and WPGraphQL]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Our account system is taking shape now that we have <a href="https://mikejolley.com/2021/03/02/headless-wordpress-cookie-based-login-using-graphql/">login</a>, <a href="https://mikejolley.com/2021/03/08/headless-wordpress-log-out-using-graphql-reactjs/">logout</a>, and <a href="https://mikejolley.com/2021/03/15/headless-wordpress-password-reset-with-reactjs-wpgraphql/">password reset</a> implemented. Next we need to handle user registrations!</p>



<p class="wp-block-paragraph">Lucky for us, WPGraphQL provides us with a <a href="https://www.wpgraphql.com/docs/users/#register-user">user registration mutation we can use out of the box</a>.</p>



<span id="more-54225"></span>


<pre class="wp-block-code"><span><code class="hljs language-javascript">mutation registerUser {
  registerUser(input: {
    <span class="hljs-attr">username</span>:<span class="hljs-string">"new_user"</span>
  }) {
    clientMutationId
    user {
      name
      slug
    }
  }
}</code></span></pre>


<p class="wp-block-paragraph">All we need to is build some client code to consume this. In this post I&#8217;ll be demonstrating this using ReactJS and the <a href="https://www.apollographql.com/docs/react/api/core/ApolloClient/">Apollo Client</a> package (which I <a href="https://mikejolley.com/2021/03/08/headless-wordpress-log-out-using-graphql-reactjs/#setting-up-apollo-client">covered in a previous post</a>) to make our requests to the server where WordPress and <meta charset="utf-8">WPGraphQL are running.</p>



<h2 class="wp-block-heading">Custom hooks</h2>



<p class="wp-block-paragraph">When we create a registration form it will need a way to send the form data to the server. For this we can create some custom React hooks.</p>



<p class="wp-block-paragraph"><code>useRegisterMutation</code> </p>



<p class="wp-block-paragraph">This hook provides us with a function named <code><meta charset="utf-8">registerMutation</code> that accepts a <em>username</em>, <em>email</em>, and <em>password</em> and then uses Apollo Client <code><a href="https://www.apollographql.com/docs/react/data/mutations/#usemutation-api">useMutation</a></code> hook to make the GraphQL request. This function returns the result as a JavaScript promise. </p>


<pre class="wp-block-code"><span><code class="hljs language-javascript"><span class="hljs-comment">/**
 * External dependencies
 */</span>
<span class="hljs-keyword">import</span> { gql, useMutation } <span class="hljs-keyword">from</span> <span class="hljs-string">'@apollo/client'</span>;

<span class="hljs-keyword">const</span> REGISTER = gql<span class="hljs-string">`
	mutation RegisterUser(
		$username: String!
		$email: String!
		$password: String!
	) {
		registerUser(
			input: { username: $username, email: $email, password: $password }
		) {
			clientMutationId
		}
	}
`</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> useRegisterMutation = <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
	<span class="hljs-keyword">const</span> &#91; mutation, mutationResults ] = useMutation( REGISTER );

	<span class="hljs-keyword">const</span> registerMutation = <span class="hljs-function">(<span class="hljs-params"> username, email, password </span>) =&gt;</span> {
		<span class="hljs-keyword">return</span> mutation( {
			<span class="hljs-attr">variables</span>: {
				username,
				email,
				password,
			},
		} );
	};

	<span class="hljs-keyword">return</span> { registerMutation, <span class="hljs-attr">results</span>: mutationResults };
};</code></span></pre>


<p class="wp-block-paragraph">This hook can be used like this:</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript"><span class="hljs-keyword">const</span> { registerMutation, results } = useRegisterMutation();</code></span></pre>


<p class="wp-block-paragraph"><code>useRegistration</code></p>



<p class="wp-block-paragraph">I created another custom hook that consumes <meta charset="utf-8"><code>useRegisterMutation</code> and does a little more handling for things such as error states and status that our registration form will later use.</p>



<p class="wp-block-paragraph">One thing we do need to handle is error codes coming back from the WPGraphQL API—the codes are descriptive but need to be converted into a more human readable format if they are being displayed to the user:</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript"><span class="hljs-keyword">const</span> errorCodes = {
	<span class="hljs-attr">invalid_username</span>:
		<span class="hljs-string">'Invalid username or email address. Please check it and try again.'</span>,
	<span class="hljs-attr">invalid_email</span>: <span class="hljs-string">'Invalid email address. Please check it and try again.'</span>,
	<span class="hljs-attr">incorrect_password</span>:
		<span class="hljs-string">'Incorrect password. Please try again, or reset your password.'</span>,
	<span class="hljs-attr">empty_username</span>: <span class="hljs-string">'Please provide your username.'</span>,
	<span class="hljs-attr">empty_password</span>: <span class="hljs-string">'Please provide your password.'</span>,
};</code></span></pre>


<p class="wp-block-paragraph">The hook itself uses the mutation hook we made earlier, tracks the status of requests (idle, resolving, resolved), and stores any error messages we get back from the server.</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> useRegistration = <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
	<span class="hljs-keyword">const</span> &#91;error, setError] = useState(<span class="hljs-literal">null</span>);
	<span class="hljs-keyword">const</span> &#91;status, setStatus] = useState(<span class="hljs-string">'idle'</span>);
	<span class="hljs-keyword">const</span> { registerMutation } = useRegisterMutation();

	<span class="hljs-keyword">const</span> register = <span class="hljs-function">(<span class="hljs-params">username, email, password</span>) =&gt;</span> {
		setError(<span class="hljs-literal">null</span>);
		setStatus(<span class="hljs-string">'resolving'</span>);
		<span class="hljs-keyword">return</span> registerMutation(username, email, password)
			.then(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
				setStatus(<span class="hljs-string">'resolved'</span>);
			})
			.catch(<span class="hljs-function">(<span class="hljs-params">errors</span>) =&gt;</span> {
				setError(errorCodes&#91;errors.message] || errors.message);
				setStatus(<span class="hljs-string">'resolved'</span>);
			});
	};

	<span class="hljs-keyword">return</span> {
		register,
		error,
		status,
	};
};</code></span></pre>


<h2 class="wp-block-heading">Creating the user registration form</h2>



<p class="wp-block-paragraph">The registration form needs fields for username, email address, and password. It also needs to use the <code>status</code> and <code>error</code> state from the <code>useRegistration</code> hook we created earlier.</p>



<p class="wp-block-paragraph">A basic version of the form in React would be as follows (I am using <code>useState</code> here to store field values).</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript"><span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { useRegistration } <span class="hljs-keyword">from</span> <span class="hljs-string">'hooks'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> RegisterForm = <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
	<span class="hljs-keyword">const</span> &#91; username, setUsername ] = useState( <span class="hljs-string">''</span> );
	<span class="hljs-keyword">const</span> &#91; email, setEmail ] = useState( <span class="hljs-string">''</span> );
	<span class="hljs-keyword">const</span> &#91; password, setPassword ] = useState( <span class="hljs-string">''</span> );
	<span class="hljs-keyword">const</span> { register, error, status } = useRegistration();

	<span class="hljs-keyword">const</span> onRegister = <span class="hljs-function">(<span class="hljs-params"> e </span>) =&gt;</span> {
		e.preventDefault();
		register( username, email, password );
	};

	<span class="hljs-keyword">return</span> (
		<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span>
			<span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{</span> <span class="hljs-attr">onRegister</span> }
			<span class="hljs-attr">className</span>=<span class="hljs-string">"register-form"</span>
			<span class="hljs-attr">autoComplete</span>=<span class="hljs-string">"on"</span>
		&gt;</span>
			{ error &amp;&amp; <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"error-notice"</span>&gt;</span>{ error }<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span> }
			<span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">{</span> `<span class="hljs-attr">field-username</span>` }&gt;</span>Username<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
			<span class="hljs-tag">&lt;<span class="hljs-name">input</span>
				<span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span>
				<span class="hljs-attr">id</span>=<span class="hljs-string">"field-username"</span>
				<span class="hljs-attr">value</span>=<span class="hljs-string">{</span> <span class="hljs-attr">username</span> }
				<span class="hljs-attr">autoComplete</span>=<span class="hljs-string">"username"</span>
				<span class="hljs-attr">onChange</span>=<span class="hljs-string">{</span> ( <span class="hljs-attr">value</span> ) =&gt;</span> setUsername( value ) }
				disabled={ status === 'resolving' }
			/&gt;
			<span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">{</span> `<span class="hljs-attr">field-email</span>` }&gt;</span>Email<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
			<span class="hljs-tag">&lt;<span class="hljs-name">input</span>
				<span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span>
				<span class="hljs-attr">id</span>=<span class="hljs-string">"field-email"</span>
				<span class="hljs-attr">value</span>=<span class="hljs-string">{</span> <span class="hljs-attr">email</span> }
				<span class="hljs-attr">autoComplete</span>=<span class="hljs-string">"email"</span>
				<span class="hljs-attr">onChange</span>=<span class="hljs-string">{</span> ( <span class="hljs-attr">value</span> ) =&gt;</span> setEmail( value ) }
				disabled={ status === 'resolving' }
			/&gt;
			<span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">{</span> `<span class="hljs-attr">field-password</span>` }&gt;</span>Password<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
			<span class="hljs-tag">&lt;<span class="hljs-name">input</span>
				<span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span>
				<span class="hljs-attr">autoComplete</span>=<span class="hljs-string">"new-password"</span>
				<span class="hljs-attr">value</span>=<span class="hljs-string">{</span> <span class="hljs-attr">password</span> }
				<span class="hljs-attr">onChange</span>=<span class="hljs-string">{</span> ( <span class="hljs-attr">value</span> ) =&gt;</span> setPassword( value ) }
				disabled={ status === 'resolving' }
			/&gt;
			<span class="hljs-tag">&lt;<span class="hljs-name">button</span>
				<span class="hljs-attr">className</span>=<span class="hljs-string">"button"</span>
				<span class="hljs-attr">onClick</span>=<span class="hljs-string">{</span> <span class="hljs-attr">onRegister</span> }
				<span class="hljs-attr">disabled</span>=<span class="hljs-string">{</span> <span class="hljs-attr">status</span> === <span class="hljs-string">'resolving'</span> }
			&gt;</span>
				Create Account
			<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
		<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span></span>
	);
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> RegisterForm;</code></span></pre>


<p class="wp-block-paragraph">With this in place, our form looks like this:</p>



<figure class="wp-block-image size-large"><a href="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-25-at-23.26.13.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="410" data-attachment-id="54249" data-permalink="https://mikejolley.com/2021/03/26/headless-wordpress-user-registration-using-reactjs-wpgraphql/screenshot-2021-03-25-at-23-26-13/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-25-at-23.26.13.png?fit=1530%2C612&amp;ssl=1" data-orig-size="1530,612" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Screenshot-2021-03-25-at-23.26.13" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-25-at-23.26.13.png?fit=300%2C120&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-25-at-23.26.13.png?fit=1024%2C410&amp;ssl=1" src="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-25-at-23.26.13.png?resize=1024%2C410&#038;ssl=1" alt="" class="wp-image-54249" srcset="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-25-at-23.26.13.png?resize=1024%2C410&amp;ssl=1 1024w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-25-at-23.26.13.png?resize=300%2C120&amp;ssl=1 300w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-25-at-23.26.13.png?resize=768%2C307&amp;ssl=1 768w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-25-at-23.26.13.png?w=1530&amp;ssl=1 1530w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></a><figcaption>Registration form</figcaption></figure>



<h3 class="wp-block-heading">Adding some basic form validation</h3>



<p class="wp-block-paragraph">To avoid making erroneous requests we can add some client side validation to our fields. We can add these validation rules in our form submit handler.</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript"><span class="hljs-keyword">const</span> &#91; passwordError, setPasswordError ] = useState( <span class="hljs-string">''</span> );

<span class="hljs-keyword">const</span> onRegister = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
	e.preventDefault();
	setPasswordError( <span class="hljs-string">''</span> );
	<span class="hljs-keyword">if</span> ( username.length === <span class="hljs-number">0</span> ) {
		setPasswordError( <span class="hljs-string">'Please enter a username.'</span> );
		<span class="hljs-keyword">return</span>;
	}
	<span class="hljs-keyword">if</span> ( email.length === <span class="hljs-number">0</span> ) {
		setPasswordError( <span class="hljs-string">'Please enter your email address.'</span> );
		<span class="hljs-keyword">return</span>;
	}
	<span class="hljs-keyword">if</span> ( password.length === <span class="hljs-number">0</span> ) {
		setPasswordError( <span class="hljs-string">'Please enter a password.'</span> );
		<span class="hljs-keyword">return</span>;
	}
	register(username, email, password);
};</code></span></pre>


<p class="wp-block-paragraph">With this change in place, registration will only be attempted if the username, email, and password fields are populated. This could be improved further by adding email format validation and so on.</p>



<h3 class="wp-block-heading">Improving the password field</h3>



<p class="wp-block-paragraph">Our password field could be improved by giving some indication of password strength. For this I chose to use the <a href="https://www.npmjs.com/package/react-password-strength-bar">React Password Strength Bar</a> package. You can customise the <meta charset="utf-8"><em>scoreWords</em> and <meta charset="utf-8"><em>shortScoreWord</em> however you please to reflect password strength. For fun, I&#8217;m using a <a href="https://en.wikipedia.org/wiki/Dungeons_%26_Dragons">D&amp;D</a> theme <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>


<pre class="wp-block-code"><span><code class="hljs language-javascript">&lt;PasswordStrengthBar
	password={ password }
	scoreWords={ &#91;
		<span class="hljs-string">'critical fail'</span>,
		<span class="hljs-string">'okay'</span>,
		<span class="hljs-string">'good'</span>,
		<span class="hljs-string">'strong'</span>,
		<span class="hljs-string">'critical roll!'</span>,
	] }
	shortScoreWord={ <span class="hljs-string">'critical fail'</span> }
/&gt;</code></span></pre>


<p class="wp-block-paragraph">In the above snippet we provide the current password, and it will access the strength and display it to the user:</p>



<figure class="wp-block-image size-large"><a href="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-25-at-23.06.28.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="210" data-attachment-id="54244" data-permalink="https://mikejolley.com/2021/03/26/headless-wordpress-user-registration-using-reactjs-wpgraphql/screenshot-2021-03-25-at-23-06-28/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-25-at-23.06.28.png?fit=1528%2C314&amp;ssl=1" data-orig-size="1528,314" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Screenshot-2021-03-25-at-23.06.28" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-25-at-23.06.28.png?fit=300%2C62&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-25-at-23.06.28.png?fit=1024%2C210&amp;ssl=1" src="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-25-at-23.06.28.png?resize=1024%2C210&#038;ssl=1" alt="" class="wp-image-54244" srcset="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-25-at-23.06.28.png?resize=1024%2C210&amp;ssl=1 1024w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-25-at-23.06.28.png?resize=300%2C62&amp;ssl=1 300w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-25-at-23.06.28.png?resize=768%2C158&amp;ssl=1 768w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-25-at-23.06.28.png?w=1528&amp;ssl=1 1528w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></a><figcaption>The password field and PasswordStrengthBar component</figcaption></figure>



<p class="wp-block-paragraph">You can see the <a href="https://github.com/mikejolley/morrics-magical-cauldron/blob/main/src/pages/account/login/register-form.js">final form on GitHub here</a>.</p>



<h2 class="wp-block-heading">Wrapping up</h2>



<p class="wp-block-paragraph">Our registration system is complete. The only other change I made (on the server side) was to disable new user emails:</p>


<pre class="wp-block-code"><span><code class="hljs language-php">remove_action( <span class="hljs-string">'register_new_user'</span>, <span class="hljs-string">'wp_send_new_user_notifications'</span> );</code></span></pre>


<p class="wp-block-paragraph">You can see a working example of the registration form in&nbsp;<a href="https://morrics-magical-cauldron.netlify.app/account/create-account">my D&amp;D app here</a>, and the&nbsp;<a href="https://github.com/mikejolley/morrics-magical-cauldron/tree/main/src">source code is also public on GitHub.</a></p>



<p class="wp-block-paragraph">As I&#8217;ve said before, it&#8217;s very useful that&nbsp;<a href="https://www.wpgraphql.com/">WPGraphQL</a>&nbsp;includes some of the account mutations out of the box. Whilst this is not unique to GraphQL (we could use the WP REST API if we wanted), I have found it very easy to work with from the client side. </p>


<div class="wp-post-series-box series-headless-wordpress wp-post-series-box--expandable">
			<input id="collapsible-series-headless-wordpress69c1fb6854c5e" class="wp-post-series-box__toggle_checkbox" type="checkbox">
	
	<label
		class="wp-post-series-box__label"
					for="collapsible-series-headless-wordpress69c1fb6854c5e"
			tabindex="0"
				>
		<p class="wp-post-series-box__name wp-post-series-name">
			This is post 4 of 4 in the series <em>&ldquo;Headless WordPress&rdquo;</em>		</p>
			</label>

			<div class="wp-post-series-box__posts">
			<ol>
									<li><a href="https://mikejolley.com/2021/03/02/headless-wordpress-cookie-based-login-using-graphql/">Headless WordPress: Cookie Based Login using GraphQL</a></li>
									<li><a href="https://mikejolley.com/2021/03/08/headless-wordpress-log-out-using-graphql-reactjs/">Headless WordPress: Log-out using GraphQL &#038; ReactJS</a></li>
									<li><a href="https://mikejolley.com/2021/03/15/headless-wordpress-password-reset-with-reactjs-wpgraphql/">Headless WordPress: Password Reset with ReactJS &#038; WPGraphQL</a></li>
									<li><span class="wp-post-series-box__current">Headless WordPress: User Registration with ReactJS &#038; WPGraphQL</span></li>
							</ol>
		</div>
	</div>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">54225</post-id>	</item>
		<item>
		<title>Headless WordPress: Password Reset with ReactJS &#038; WPGraphQL</title>
		<link>https://mikejolley.com/2021/03/15/headless-wordpress-password-reset-with-reactjs-wpgraphql/</link>
		
		<dc:creator><![CDATA[Mike Jolley]]></dc:creator>
		<pubDate>Mon, 15 Mar 2021 20:19:56 +0000</pubDate>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[GraphQL]]></category>
		<category><![CDATA[Headless]]></category>
		<category><![CDATA[React]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://mikejolley.com/?p=54064</guid>

					<description><![CDATA[How I built the password reset system in a ReactJS app using WordPress and WP GraphQL]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Previously I demonstrated how we <a href="https://mikejolley.com/2021/03/02/headless-wordpress-cookie-based-login-using-graphql/">can log in to a WordPress site from a headless app using cookies</a>, and how to handle <a href="https://mikejolley.com/2021/03/08/headless-wordpress-log-out-using-graphql-reactjs/">logging out</a>—this also covered <a href="https://mikejolley.com/2021/03/08/headless-wordpress-log-out-using-graphql-reactjs/#setting-up-apollo-client">setting up Apollo Client with React</a>. In this post I&#8217;ll show how to build a password reset form!</p>



<span id="more-54064"></span>



<h2 class="wp-block-heading">Required GraphQL mutations</h2>



<p class="wp-block-paragraph">Unlike login and logout, <a href="https://www.wpgraphql.com/">WP GraphQL</a> actually provides the mutations we require out of the box.</p>



<ul class="wp-block-list"><li><a href="https://www.wpgraphql.com/docs/users/#send-password-reset-email">Send Password Reset Email</a> &#8211; This mutation requires a user name or email address and will trigger a password reset request to be sent to the user.</li><li><a href="https://www.wpgraphql.com/docs/users/#reset-user-password">Reset User Password</a> &#8211; This mutation takes input of a username, the password reset key, and a new password. It sets the new password for the given user.</li></ul>



<p class="wp-block-paragraph">To see these mutations and others available to you, within WordPress admin you can head to <em>GraphQL &gt; GraphQL IDE</em>.</p>



<figure class="wp-block-image size-large"><a href="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-04-at-20.08.16.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="1024" height="627" data-attachment-id="54069" data-permalink="https://mikejolley.com/2021/03/15/headless-wordpress-password-reset-with-reactjs-wpgraphql/screenshot-2021-03-04-at-20-08-16/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-04-at-20.08.16.png?fit=1218%2C746&amp;ssl=1" data-orig-size="1218,746" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Screenshot-2021-03-04-at-20.08.16" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-04-at-20.08.16.png?fit=300%2C184&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-04-at-20.08.16.png?fit=1024%2C627&amp;ssl=1" src="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-04-at-20.08.16.png?resize=1024%2C627&#038;ssl=1" alt="" class="wp-image-54069" srcset="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-04-at-20.08.16.png?resize=1024%2C627&amp;ssl=1 1024w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-04-at-20.08.16.png?resize=300%2C184&amp;ssl=1 300w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-04-at-20.08.16.png?resize=768%2C470&amp;ssl=1 768w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-04-at-20.08.16.png?w=1218&amp;ssl=1 1218w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></a><figcaption>These are the mutations we will use</figcaption></figure>



<h2 class="wp-block-heading">Custom hooks</h2>



<p class="wp-block-paragraph">To interact with these mutations I created some custom React hooks. Note that I&#8217;m using <a href="https://github.com/apollographql/apollo-client">Apollo Client</a> to query the GraphQL API <a href="https://mikejolley.com/2021/03/08/headless-wordpress-log-out-using-graphql-reactjs/#setting-up-apollo-client">which I covered in my previous post.</a></p>



<h3 class="wp-block-heading"><code>useSendPasswordResetEmailMutation</code></h3>



<p class="wp-block-paragraph">This hook provides a <code>sendPasswordResetEmail</code> function. It takes a single parameter, <code>username</code> (which can also be an email address). It returns a promise allowing you to do something once the reset email has been sent.</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript"><span class="hljs-keyword">import</span> { gql, useMutation } <span class="hljs-keyword">from</span> <span class="hljs-string">'@apollo/client'</span>;

<span class="hljs-keyword">const</span> QUERY = gql<span class="hljs-string">`
	mutation SendPasswordResetEmail($username: String!) {
		sendPasswordResetEmail(input: { username: $username }) {
			clientMutationId
		}
	}
`</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> useSendPasswordResetEmailMutation = <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
	<span class="hljs-keyword">const</span> &#91; mutation, mutationResults ] = useMutation( QUERY );

	<span class="hljs-keyword">const</span> sendPasswordResetEmail = <span class="hljs-function">(<span class="hljs-params"> username </span>) =&gt;</span> {
		<span class="hljs-keyword">return</span> mutation( {
			<span class="hljs-attr">variables</span>: {
				username,
			},
		} );
	};

	<span class="hljs-keyword">return</span> { sendPasswordResetEmail, <span class="hljs-attr">results</span>: mutationResults };
};</code></span></pre>


<h3 class="wp-block-heading"><code>useResetUserPasswordMutation</code></h3>



<p class="wp-block-paragraph">We also need a custom hook to <em>set</em> a new password. This is used after the user has clicked the link in the <em>password reset</em> email. </p>



<p class="wp-block-paragraph">My implementation of the custom hook returns a function that expects to be passed the login, a new password, and the &#8220;key&#8221; given to the user. Once validated, the user password will be updated, otherwise the promise will throw an error.</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript"><span class="hljs-keyword">import</span> { gql, useMutation } <span class="hljs-keyword">from</span> <span class="hljs-string">'@apollo/client'</span>;

<span class="hljs-keyword">const</span> QUERY = gql<span class="hljs-string">`
	mutation ResetUserPassword(
		$key: String!
		$login: String!
		$password: String!
	) {
		resetUserPassword(
			input: { key: $key, login: $login, password: $password }
		) {
			clientMutationId
		}
	}
`</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> useResetUserPasswordMutation = <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
	<span class="hljs-keyword">const</span> &#91; mutation, mutationResults ] = useMutation( QUERY );

	<span class="hljs-keyword">const</span> resetUserPassword = <span class="hljs-function">(<span class="hljs-params"> key, login, password </span>) =&gt;</span> {
		<span class="hljs-keyword">return</span> mutation( {
			<span class="hljs-attr">variables</span>: {
				key,
				login,
				password,
			},
		} );
	};

	<span class="hljs-keyword">return</span> { resetUserPassword, <span class="hljs-attr">results</span>: mutationResults };
};</code></span></pre>


<p class="wp-block-paragraph">These hooks would be used as follows:</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript"><span class="hljs-keyword">const</span> { sendPasswordResetEmail } = useSendPasswordResetEmailMutation();
<span class="hljs-keyword">const</span> { resetUserPassword } = useResetUserPasswordMutation();

<span class="hljs-comment">// You would use these within an event handler or callback! It returns a promise.</span>
sendPasswordResetEmail( <span class="hljs-string">'test@email.com'</span> );
resetUserPassword( key, <span class="hljs-string">'test@email.com'</span>, <span class="hljs-string">'newpassword'</span> );</code></span></pre>


<p class="wp-block-paragraph">Whilst these mutations can be used directly, in my app I combined them into a single custom hook which handles status and errors. <a href="https://github.com/mikejolley/morrics-magical-cauldron/blob/main/src/hooks/use-reset-password.js">You can see this code on GitHub here (the useResetPassword hook).</a></p>



<h2 class="wp-block-heading">Fixing the Password Reset Email</h2>



<p class="wp-block-paragraph">One gotcha I encountered was that the password reset email sent by WordPress links to the actual WordPress domain, rather than the headless site. We need to edit the email from WordPress to fix this—<em>annoyingly</em> the only way to do this is to replace the entire contents of the email, but you can customise this as much as you want which is a bonus.</p>


<pre class="wp-block-code"><span><code class="hljs language-php shcb-code-table"><span class='shcb-loc'><span>add_filter( <span class="hljs-string">'retrieve_password_message'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">( $message, $key, $user_login, $user_data )</span> </span>{
</span></span><span class='shcb-loc'><span>	$site_name = wp_specialchars_decode( get_option( <span class="hljs-string">'blogname'</span> ), ENT_QUOTES );
</span></span><span class='shcb-loc'><span>	$message   = __( <span class="hljs-string">'Someone has requested a password reset for the following account:'</span> ) . <span class="hljs-string">"\r\n\r\n"</span>;
</span></span><span class='shcb-loc'><span>	<span class="hljs-comment">/* translators: %s: Site name. */</span>
</span></span><span class='shcb-loc'><span>	$message .= sprintf( __( <span class="hljs-string">'Site Name: %s'</span>, <span class="hljs-string">'morrics-magical-cauldron'</span> ), $site_name ) . <span class="hljs-string">"\r\n\r\n"</span>;
</span></span><span class='shcb-loc'><span>	<span class="hljs-comment">/* translators: %s: User login. */</span>
</span></span><span class='shcb-loc'><span>	$message .= sprintf( __( <span class="hljs-string">'Username: %s'</span>, <span class="hljs-string">'morrics-magical-cauldron'</span> ), $user_login ) . <span class="hljs-string">"\r\n\r\n"</span>;
</span></span><span class='shcb-loc'><span>	$message .= __( <span class="hljs-string">'If this was a mistake, ignore this email and nothing will happen.'</span> ) . <span class="hljs-string">"\r\n\r\n"</span>;
</span></span><span class='shcb-loc'><span>	$message .= __( <span class="hljs-string">'To reset your password, visit the following address:'</span> ) . <span class="hljs-string">"\r\n\r\n"</span>;
</span></span><mark class='shcb-loc'><span>	$message .= HEADLESS_FRONTEND_URL . <span class="hljs-string">'/account/reset/'</span> . rawurlencode( $user_login ) . <span class="hljs-string">"/$key\r\n\r\n"</span>;
</span></mark><span class='shcb-loc'><span>	$requester_ip = $_SERVER&#91;<span class="hljs-string">'REMOTE_ADDR'</span>];
</span></span><span class='shcb-loc'><span>	<span class="hljs-keyword">if</span> ( $requester_ip ) {
</span></span><span class='shcb-loc'><span>		$message .= sprintf(
</span></span><span class='shcb-loc'><span>		<span class="hljs-comment">/* translators: %s: IP address of password reset requester. */</span>
</span></span><span class='shcb-loc'><span>			__( <span class="hljs-string">'This password reset request originated from the IP address %s.'</span> ),
</span></span><span class='shcb-loc'><span>			$requester_ip
</span></span><span class='shcb-loc'><span>		) . <span class="hljs-string">"\r\n"</span>;
</span></span><span class='shcb-loc'><span>	}
</span></span><span class='shcb-loc'><span>	<span class="hljs-keyword">return</span> $message;
</span></span><span class='shcb-loc'><span>}, <span class="hljs-number">10</span>, <span class="hljs-number">4</span> );
</span></span></code></span></pre>


<p class="wp-block-paragraph">With the highlighted change in place, the password reset email will link to whatever URL you provide.  In my example I am linking through to <code>morrics-magical-cauldron.netlify.app/account/reset/&lt;login&gt;/&lt;key&gt;</code>. </p>



<p class="wp-block-paragraph">On the app side, I am mapping this URL to my reset password form component using <a href="https://www.npmjs.com/package/@reach/router">Reach Router</a>, but you could also use <a href="https://github.com/ReactTraining/react-router">React Router</a> or any other routing library. You just need to ensure your app can read the user <code>login</code> and <code>key</code> from the current URL, or query-string.</p>



<h2 class="wp-block-heading">Forms</h2>



<p class="wp-block-paragraph">Now we have <strong>GraphQL setup</strong> on the server, some <strong>hooks to handle queries</strong> to the GraphQL API from the app, and we&#8217;ve <strong>fixed the password reset URL</strong>, we can build some <em>form components</em> to collect user data and trigger the events!</p>



<p class="wp-block-paragraph">The following two form examples are just basic examples and are not complete, but should give you an idea of how the custom hooks work. You&#8217;ll still need to build in error handling and status messages for the final version!</p>



<h3 class="wp-block-heading">Send Password Reset Email Form</h3>


<pre class="wp-block-code"><span><code class="hljs language-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> ResetForm = <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
	<span class="hljs-keyword">const</span> &#91; username, setUsername ] = React.useState( <span class="hljs-string">''</span> );
	<span class="hljs-keyword">const</span> { sendPasswordResetEmail } = sendPasswordResetEmail();

	<span class="hljs-keyword">const</span> onReset = <span class="hljs-function">(<span class="hljs-params"> e </span>) =&gt;</span> {
		e.preventDefault();
		sendResetPasswordEmail( username );
	};

	<span class="hljs-keyword">return</span> (
		<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{</span> <span class="hljs-attr">onReset</span> }&gt;</span>
			<span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span>Username or Email Address<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
			<span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{</span> <span class="hljs-attr">username</span> } <span class="hljs-attr">onChange</span>=<span class="hljs-string">{</span> ( <span class="hljs-attr">value</span> ) =&gt;</span> setUsername( value ) } /&gt;
			<span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{</span> <span class="hljs-attr">onReset</span> }&gt;</span>Reset<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
		<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span></span>
	);
};</code></span></pre>


<h3 class="wp-block-heading">Reset Password Form</h3>


<pre class="wp-block-code"><span><code class="hljs language-javascript"><span class="hljs-comment">// resetLogin and resetKey come from the querystring/page URL and are passed to this component as props.</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> ResetPasswordForm = <span class="hljs-function">(<span class="hljs-params"> { resetLogin, resetKey } </span>) =&gt;</span> {
	<span class="hljs-keyword">const</span> &#91; password, setPassword ] = useState( <span class="hljs-string">''</span> );
	<span class="hljs-keyword">const</span> { resetUserPassword } = useResetUserPasswordMutation();

	<span class="hljs-keyword">const</span> onReset = <span class="hljs-function">(<span class="hljs-params"> e </span>) =&gt;</span> {
		e.preventDefault();
		resetUserPassword( resetLogin, resetKey, password );
	};

	<span class="hljs-keyword">return</span> (
		<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{</span> <span class="hljs-attr">onReset</span> }&gt;</span>
			<span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span>Enter new password<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
			<span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{</span> <span class="hljs-attr">password</span> } <span class="hljs-attr">onChange</span>=<span class="hljs-string">{</span> ( <span class="hljs-attr">value</span> ) =&gt;</span> setPassword( value ) } /&gt;
			<span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{</span> <span class="hljs-attr">onReset</span> }&gt;</span>Set New Password<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
		<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span></span>
	);
};</code></span></pre>


<p class="wp-block-paragraph">You can view my <a href="https://github.com/mikejolley/morrics-magical-cauldron/blob/main/src/pages/account/reset/reset-password-form.js">Reset Password Form component here</a>, and my <a href="https://github.com/mikejolley/morrics-magical-cauldron/blob/main/src/pages/account/reset/reset-form.js">lost password form here</a>. My final versions handle errors from the API, and show the user the status of their request.</p>



<h2 class="wp-block-heading">Wrapping up</h2>



<p class="wp-block-paragraph">You can see a working example of the password reset flow in <a href="https://morrics-magical-cauldron.netlify.app/account/reset">my D&amp;D app here</a>, and the <a href="https://github.com/mikejolley/morrics-magical-cauldron/tree/main/src">source code is also public on GitHub.</a></p>



<p class="wp-block-paragraph">It&#8217;s great that <a href="https://www.wpgraphql.com/">WP GraphQL</a> handles password reset functionality by default. As I said in my last post, <a href="https://github.com/apollographql/apollo-client">Apollo Client</a> really makes the process of interacting with GraphQL simple, and (although I didn&#8217;t show examples) <a href="https://reach.tech/router/">Reach Router</a> is a fantastic routing library for sending users to the correct page of your app and accessing the query string. I highly recommend both!</p>


<div class="wp-post-series-box series-headless-wordpress wp-post-series-box--expandable">
			<input id="collapsible-series-headless-wordpress69c1fb686018a" class="wp-post-series-box__toggle_checkbox" type="checkbox">
	
	<label
		class="wp-post-series-box__label"
					for="collapsible-series-headless-wordpress69c1fb686018a"
			tabindex="0"
				>
		<p class="wp-post-series-box__name wp-post-series-name">
			This is post 3 of 4 in the series <em>&ldquo;Headless WordPress&rdquo;</em>		</p>
			</label>

			<div class="wp-post-series-box__posts">
			<ol>
									<li><a href="https://mikejolley.com/2021/03/02/headless-wordpress-cookie-based-login-using-graphql/">Headless WordPress: Cookie Based Login using GraphQL</a></li>
									<li><a href="https://mikejolley.com/2021/03/08/headless-wordpress-log-out-using-graphql-reactjs/">Headless WordPress: Log-out using GraphQL &#038; ReactJS</a></li>
									<li><span class="wp-post-series-box__current">Headless WordPress: Password Reset with ReactJS &#038; WPGraphQL</span></li>
									<li><a href="https://mikejolley.com/2021/03/26/headless-wordpress-user-registration-using-reactjs-wpgraphql/">Headless WordPress: User Registration with ReactJS &#038; WPGraphQL</a></li>
							</ol>
		</div>
	</div>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">54064</post-id>	</item>
		<item>
		<title>Headless WordPress: Log-out using GraphQL &#038; ReactJS</title>
		<link>https://mikejolley.com/2021/03/08/headless-wordpress-log-out-using-graphql-reactjs/</link>
		
		<dc:creator><![CDATA[Mike Jolley]]></dc:creator>
		<pubDate>Mon, 08 Mar 2021 18:40:19 +0000</pubDate>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[GraphQL]]></category>
		<category><![CDATA[Headless]]></category>
		<category><![CDATA[React]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://mikejolley.com/?p=54112</guid>

					<description><![CDATA[Creating a logout system for a ReactJS app using WordPress and WP GraphQL]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">In <a href="https://mikejolley.com/2021/03/02/headless-wordpress-cookie-based-login-using-graphql/">my last post</a> I showed how we <a href="https://mikejolley.com/2021/03/02/headless-wordpress-cookie-based-login-using-graphql/">can log into a WordPress site from a headless app using GraphQL and cookies</a>. </p>



<p class="wp-block-paragraph">In this post we&#8217;ll create a log-out mutation in GraphQL, and a custom React Hook to utilise it. I&#8217;ll be using <a href="https://github.com/apollographql/apollo-client">Apollo Client</a> to make the actual API calls.</p>



<span id="more-54112"></span>



<h2 class="wp-block-heading">Creating the GraphQL Mutation</h2>



<p class="wp-block-paragraph">We need to register a logout mutation in <a href="https://www.wpgraphql.com/">WP GraphQL</a> first which logs the user out and unsets their authentication cookies. </p>



<p class="has-black-color has-pale-cyan-blue-background-color has-text-color has-background wp-block-paragraph">Reminder<em>: In GraphQL, a<strong> mutation</strong>&nbsp;query is something that modifies data and returns a value. It&#8217;s the equivalent of a POST request in REST.</em></p>



<p class="wp-block-paragraph">This mutation doesn&#8217;t need any input; it just needs to unset the cookie (which in this case is a <em>HTTP Cookie</em> that React and JavaScript cannot see, nor access).</p>


<pre class="wp-block-code"><span><code class="hljs language-php">add_action( <span class="hljs-string">'graphql_register_types'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span> </span>{
    register_graphql_mutation(
        <span class="hljs-string">'logout'</span>,
        <span class="hljs-keyword">array</span>(
            <span class="hljs-string">'inputFields'</span>         =&gt; <span class="hljs-keyword">array</span>(),
            <span class="hljs-string">'outputFields'</span>        =&gt; <span class="hljs-keyword">array</span>(
                <span class="hljs-string">'status'</span> =&gt; <span class="hljs-keyword">array</span>(
                    <span class="hljs-string">'type'</span>        =&gt; <span class="hljs-string">'String'</span>,
                    <span class="hljs-string">'description'</span> =&gt; <span class="hljs-string">'Logout result'</span>,
                    <span class="hljs-string">'resolve'</span>     =&gt; <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">( $payload )</span> </span>{
                        <span class="hljs-keyword">return</span> $payload&#91;<span class="hljs-string">'status'</span>];
                    },
                ),
            ),
            <span class="hljs-string">'mutateAndGetPayload'</span> =&gt; <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span> </span>{
                wp_logout(); <span class="hljs-comment">// This destroys the WP Login cookie.</span>
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">array</span>( <span class="hljs-string">'status'</span> =&gt; <span class="hljs-string">'SUCCESS'</span> );
            },
        )
    )
} );</code></span></pre>


<p class="wp-block-paragraph">This code would live in a custom plugin or your theme, on the server running WordPress.</p>



<h2 class="wp-block-heading">Setting up Apollo Client</h2>



<p class="wp-block-paragraph">If you&#8217;ve not familiar with Apollo Client, it essentially provides us some React hooks with which we can make GraphQL requests to the server. For example, it provides a handy <a href="https://www.apollographql.com/docs/react/api/react/hooks/#usemutation"><code>useMutation</code> hook</a> that runs a mutation query and returns the result. The <a href="https://www.apollographql.com/docs/react/get-started/">getting started guide is here</a>. </p>



<p class="wp-block-paragraph">When using Apollo, you&#8217;ll likely want to wrap your App code in the Apollo Client context. For sake of completeness, here is a small example from my own app that sets up Apollo and ensures <code>credentials</code> are included with our requests (our cookies).</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript"><span class="hljs-keyword">import</span> { ApolloClient, InMemoryCache, ApolloProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">'@apollo/client'</span>;
<span class="hljs-keyword">import</span> { BatchHttpLink } <span class="hljs-keyword">from</span> <span class="hljs-string">'@apollo/client/link/batch-http'</span>;

<span class="hljs-keyword">const</span> cache = <span class="hljs-keyword">new</span> InMemoryCache( {} );
<span class="hljs-keyword">const</span> httpLink = <span class="hljs-keyword">new</span> BatchHttpLink( {
	<span class="hljs-attr">uri</span>: <span class="hljs-string">'https://morricsmagicalcauldron.wpcomstaging.com/index.php?graphql'</span>, <span class="hljs-comment">// My GraphQL Server</span>
	<span class="hljs-attr">credentials</span>: <span class="hljs-string">'include'</span>, <span class="hljs-comment">// This sends our logged in cookie with the requests!</span>
} );

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> useAppApolloClient = <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
	<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ApolloClient( {
		<span class="hljs-attr">link</span>: httpLink,
		cache,
	} );
};

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
	<span class="hljs-keyword">const</span> client = useAppApolloClient();
	<span class="hljs-keyword">return</span> (
		<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AuthContextProvider</span>&gt;</span>
			<span class="hljs-tag">&lt;<span class="hljs-name">ApolloProvider</span> <span class="hljs-attr">client</span>=<span class="hljs-string">{</span> <span class="hljs-attr">client</span> }&gt;</span>
				{ // Your App Code Here }
			<span class="hljs-tag">&lt;/<span class="hljs-name">ApolloProvider</span>&gt;</span>
		<span class="hljs-tag">&lt;/<span class="hljs-name">AuthContextProvider</span>&gt;</span></span>
	);
}</code></span></pre>


<p class="wp-block-paragraph">With Apollo client setup in this way, it can now be used from our other custom hooks.</p>



<h2 class="wp-block-heading">Create a custom hook in React JS</h2>



<p class="wp-block-paragraph">With Apollo Client setup, we can create a custom hook in React JS to make our logout functionality reusable across the app.</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript"><span class="hljs-keyword">import</span> { gql, useMutation, useApolloClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'@apollo/client'</span>;

<span class="hljs-keyword">const</span> LOGOUT = gql<span class="hljs-string">`
	mutation Logout {
		logout(input: {}) {
			status
		}
	}
`</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> useLogoutMutation = <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
	<span class="hljs-keyword">const</span> apolloClient = useApolloClient();
	<span class="hljs-keyword">const</span> &#91; mutation, mutationResults ] = useMutation( LOGOUT );

	<span class="hljs-keyword">const</span> logoutMutation = <span class="hljs-keyword">async</span> () =&gt; {
		<span class="hljs-comment">// Remove all data from the store since we are now logged out.</span>
		<span class="hljs-keyword">await</span> apolloClient.clearStore();
		<span class="hljs-keyword">return</span> mutation();
	};

	<span class="hljs-keyword">return</span> { logoutMutation, <span class="hljs-attr">results</span>: mutationResults };
};</code></span></pre>


<p class="wp-block-paragraph">With this in place, we can create a logout button like this:</p>


<pre class="wp-block-code"><span><code class="hljs language-javascript"><span class="hljs-keyword">const</span> { logoutMutation } = useLogoutMutation();

<span class="hljs-keyword">return</span> <span class="hljs-function">(<span class="hljs-params"> &lt;button onClick={ (</span>) =&gt;</span> logoutMutation() }&gt;
	Logout
&lt;<span class="hljs-regexp">/button&gt; );</span></code></span></pre>


<p class="wp-block-paragraph">You can see where I implemented this in my app <a href="https://github.com/mikejolley/morrics-magical-cauldron/blob/main/src/hooks/use-auth.js#L68-L71">here</a> and <a href="https://github.com/mikejolley/morrics-magical-cauldron/blob/b824273dab9b415727995608af9f37b7d4039179/src/components/account/profile/index.js#L22-L24">here</a>. You can see in my app I created another hooked named `useAuth` to house my login, logout, and status queries which are used across the app.</p>



<h2 class="wp-block-heading">Wrapping up</h2>



<p class="wp-block-paragraph">We now have the ability to logout from the app using a button! WP GraphQL will log the user out and destroy any cookies. You can see a working example this in <a href="https://morrics-magical-cauldron.netlify.app/account/reset">my D&amp;D app here</a>, and the <a href="https://github.com/mikejolley/morrics-magical-cauldron/tree/main/src">source code is also public on GitHub</a>.</p>



<p class="wp-block-paragraph"><a href="https://github.com/apollographql/apollo-client">Apollo Client</a> really makes the process of interacting with GraphQL simple, and takes care of all kinds of things like batching and cache for us. I highly recommend it if you&#8217;re using React!</p>


<div class="wp-post-series-box series-headless-wordpress wp-post-series-box--expandable">
			<input id="collapsible-series-headless-wordpress69c1fb68643e6" class="wp-post-series-box__toggle_checkbox" type="checkbox">
	
	<label
		class="wp-post-series-box__label"
					for="collapsible-series-headless-wordpress69c1fb68643e6"
			tabindex="0"
				>
		<p class="wp-post-series-box__name wp-post-series-name">
			This is post 2 of 4 in the series <em>&ldquo;Headless WordPress&rdquo;</em>		</p>
			</label>

			<div class="wp-post-series-box__posts">
			<ol>
									<li><a href="https://mikejolley.com/2021/03/02/headless-wordpress-cookie-based-login-using-graphql/">Headless WordPress: Cookie Based Login using GraphQL</a></li>
									<li><span class="wp-post-series-box__current">Headless WordPress: Log-out using GraphQL &#038; ReactJS</span></li>
									<li><a href="https://mikejolley.com/2021/03/15/headless-wordpress-password-reset-with-reactjs-wpgraphql/">Headless WordPress: Password Reset with ReactJS &#038; WPGraphQL</a></li>
									<li><a href="https://mikejolley.com/2021/03/26/headless-wordpress-user-registration-using-reactjs-wpgraphql/">Headless WordPress: User Registration with ReactJS &#038; WPGraphQL</a></li>
							</ol>
		</div>
	</div>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">54112</post-id>	</item>
		<item>
		<title>Flame Nebula</title>
		<link>https://mikejolley.com/2021/03/08/flame-nebula/</link>
		
		<dc:creator><![CDATA[Mike Jolley]]></dc:creator>
		<pubDate>Mon, 08 Mar 2021 11:31:56 +0000</pubDate>
				<category><![CDATA[Photography]]></category>
		<category><![CDATA[Instagram]]></category>
		<guid isPermaLink="false">https://mikejolley.com/?p=54108</guid>

					<description><![CDATA[Flame Nebula (NGC 2024) and Horsehead Nebula in the constellation Orion. This is an integration of 35x180s exposures at ISO 800 with a Nikon Z7ii and a Skywatcher Esprit 100 on a HEQ5 mount. Stacked in AstroPixelProcessor. via Instagram https://instagr.am/p/CMGQxclpm1N/]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Flame Nebula (NGC 2024) and Horsehead Nebula in the constellation Orion. This is an integration of 35x180s exposures at ISO 800 with a Nikon Z7ii and a Skywatcher Esprit 100 on a HEQ5 mount. Stacked in AstroPixelProcessor. </p>



<p class="wp-block-paragraph">via Instagram <a href="https://instagr.am/p/CMGQxclpm1N/" rel="nofollow">https://instagr.am/p/CMGQxclpm1N/</a></p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">54108</post-id>	</item>
		<item>
		<title>Headless WordPress: Cookie Based Login using GraphQL</title>
		<link>https://mikejolley.com/2021/03/02/headless-wordpress-cookie-based-login-using-graphql/</link>
		
		<dc:creator><![CDATA[Mike Jolley]]></dc:creator>
		<pubDate>Tue, 02 Mar 2021 00:46:42 +0000</pubDate>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[GraphQL]]></category>
		<category><![CDATA[Headless]]></category>
		<category><![CDATA[React]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://mikejolley.com/?p=53981</guid>

					<description><![CDATA[How I created a login system for a ReactJS app using WordPress and WP GraphQL]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">For a fun learning &amp; experimentation project, I&#8217;ve been building a <a href="https://morrics-magical-cauldron.netlify.app/">Dungeons and Dragons character generator</a>. The frontend is built using <a href="https://reactjs.org/">React JS</a> and deployed to Netlify, and the backend is powered by WordPress and <a href="https://www.wpgraphql.com/">WP GraphQL</a>.</p>



<p class="wp-block-paragraph">I could have tried using a serverless database such as FaunaDB, but I chose WordPress because of familiarity, what it gives you for free, and it&#8217;s flexibility when combined with <a href="https://www.wpgraphql.com/">WP GraphQL</a>. I did however need to build a login system. To do this I created a GraphQL login endpoint, and handled user sessions using Cookies.</p>



<span id="more-53981"></span>



<h2 class="wp-block-heading">Cookies vs JWT</h2>



<p class="wp-block-paragraph">JWT (JSON Web Tokens) can be used to authenticate requests between parties. There is a decent overview of how <a href="https://flaviocopes.com/jwt/">JWT works here</a>.</p>



<p class="wp-block-paragraph"><a href="https://github.com/wp-graphql/wp-graphql-jwt-authentication">A plugin is available for WP GraphQL</a> which handles authentication using JWT and adds a login endpoint. This looked promising, however, the most difficult thing about using JWT in the context of a web based React app is <em>storage of the token</em>. Whilst some articles recommend using localStorage, <a href="https://www.rdegges.com/2018/please-stop-using-local-storage/">doing so is not secure</a>—the token could be leaked using XSS. </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p><strong>Don’t store it in local storage (or session storage)</strong>. If any of the third-party scripts you include in your page gets compromised, it can access all your users’ tokens.</p><cite><a href="https://blog.logrocket.com/jwt-authentication-best-practices/">JWT Authentication: When and how to use it</a></cite></blockquote>



<p class="wp-block-paragraph">To keep our tokens secure our only option would be to store them in <em>memory</em>. The session would not persist if the browser tab was closed, making it less than ideal.</p>



<p class="wp-block-paragraph">A better option, and the solution I went with, is using good old fashioned cookies (specifically <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies">HTTP Cookies</a> which are inaccessible by JavaScript). I created a new GraphQL route that would set a HTTP cookie after successful login.</p>



<p class="wp-block-paragraph"><em>Side note: I did <a href="https://github.com/funkhaus/wp-graphql-cors">find a plugin for WP GraphQL which supports WP cookies</a>, but I encountered browser compatibility (CORS) issues, so ended up rolling my own based on that.</em></p>



<h2 class="wp-block-heading">Register a GraphQL Login Mutation</h2>



<p class="wp-block-paragraph">In order to login, we need to register a new endpoint with WP GraphQL. We do this by hooking in to <code>graphql_register_types</code> and adding our new mutation using the <code>register_graphql_mutation</code> function <a href="https://www.wpgraphql.com/functions/register_graphql_mutation/">documented here</a>.</p>



<p class="wp-block-paragraph">We&#8217;ll call our mutation <code>loginWithCookies</code> and we&#8217;ll use <code>inputFields</code> to define which fields are accepted (username and password), <code>outputFields</code> to define the response,  and <code>mutateAndGetPayload</code> to perform the login action:</p>


<pre class="wp-block-code"><span><code class="hljs language-php">add_action( <span class="hljs-string">'graphql_register_types'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span> </span>{
	register_graphql_mutation(
		<span class="hljs-string">'loginWithCookies'</span>,
		<span class="hljs-keyword">array</span>(
			<span class="hljs-string">'inputFields'</span> =&gt; <span class="hljs-keyword">array</span>(
				<span class="hljs-string">'login'</span>      =&gt; <span class="hljs-keyword">array</span>(
					<span class="hljs-string">'type'</span>        =&gt; <span class="hljs-keyword">array</span>( <span class="hljs-string">'non_null'</span> =&gt; <span class="hljs-string">'String'</span> ),
					<span class="hljs-string">'description'</span> =&gt; __( <span class="hljs-string">'Input your username/email.'</span> ),
				),
				<span class="hljs-string">'password'</span>   =&gt; <span class="hljs-keyword">array</span>(
					<span class="hljs-string">'type'</span>        =&gt; <span class="hljs-keyword">array</span>( <span class="hljs-string">'non_null'</span> =&gt; <span class="hljs-string">'String'</span> ),
					<span class="hljs-string">'description'</span> =&gt; __( <span class="hljs-string">'Input your password.'</span> ),
				),
			),
			<span class="hljs-string">'outputFields'</span>        =&gt; <span class="hljs-keyword">array</span>(
				<span class="hljs-string">'status'</span> =&gt; <span class="hljs-keyword">array</span>(
					<span class="hljs-string">'type'</span>        =&gt; <span class="hljs-string">'String'</span>,
					<span class="hljs-string">'description'</span> =&gt; <span class="hljs-string">'Login operation status'</span>,
					<span class="hljs-string">'resolve'</span>     =&gt; <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">( $payload )</span> </span>{
						<span class="hljs-keyword">return</span> $payload&#91;<span class="hljs-string">'status'</span>];
					},
				),
			),
			<span class="hljs-string">'mutateAndGetPayload'</span> =&gt; <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">( $input )</span> </span>{
				$user = wp_signon( 
					<span class="hljs-keyword">array</span>(
						<span class="hljs-string">'user_login'</span>    =&gt; wp_unslash( $input&#91;<span class="hljs-string">'login'</span>] ),
						<span class="hljs-string">'user_password'</span> =&gt; $input&#91;<span class="hljs-string">'password'</span>],
					), 
					<span class="hljs-keyword">true</span> 
				);

				<span class="hljs-keyword">if</span> ( is_wp_error( $user ) ) {
					<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> \GraphQL\Error\UserError\UserError( ! <span class="hljs-keyword">empty</span>( $user-&gt;get_error_code() ) ? $user-&gt;get_error_code() : <span class="hljs-string">'invalid login'</span> );
				}

				<span class="hljs-keyword">return</span> <span class="hljs-keyword">array</span>( <span class="hljs-string">'status'</span> =&gt; <span class="hljs-string">'SUCCESS'</span> );
			},
		)
	);
} );</code></span></pre>


<p class="wp-block-paragraph">We can use this mutation using a GraphQL query. You can view my usage of the <a href="https://github.com/mikejolley/morrics-magical-cauldron/blob/main/src/hooks/mutations/use-login-mutation.js">login mutation in the React app here</a>.</p>



<h2 class="wp-block-heading">Setting the logged-in cookie</h2>



<p class="wp-block-paragraph">When you log in, WordPress sets cookies to track your session. These cookies have a <code>httponly</code> parameter so it is not readable through JavaScript. </p>



<p class="wp-block-paragraph"><s>I found the easiest way to give the headless site access to cookies was to set an additional cookie.</s> <em>After reviewing this, it doesn&#8217;t appear to be needed—previously I had issues with Safari but this no longer seems to be a problem. The regular WordPress login cookie is enough. </em></p>



<p class="wp-block-paragraph">It doesn&#8217;t work yet though, if we try to login we&#8217;ll see a CORS error in the browser:</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><a href="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-01-at-23.32.31.png?ssl=1"><img data-recalc-dims="1" loading="lazy" decoding="async" width="557" height="92" data-attachment-id="54032" data-permalink="https://mikejolley.com/2021/03/02/headless-wordpress-cookie-based-login-using-graphql/screenshot-2021-03-01-at-23-32-31/" data-orig-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-01-at-23.32.31.png?fit=557%2C92&amp;ssl=1" data-orig-size="557,92" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Screenshot-2021-03-01-at-23.32.31" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-01-at-23.32.31.png?fit=300%2C50&amp;ssl=1" data-large-file="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-01-at-23.32.31.png?fit=557%2C92&amp;ssl=1" src="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-01-at-23.32.31.png?resize=557%2C92&#038;ssl=1" alt="A CORS error in the network inspector" class="wp-image-54032" srcset="https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-01-at-23.32.31.png?w=557&amp;ssl=1 557w, https://i0.wp.com/mikejolley.com/wp-content/uploads/2021/03/Screenshot-2021-03-01-at-23.32.31.png?resize=300%2C50&amp;ssl=1 300w" sizes="auto, (max-width: 557px) 100vw, 557px" /></a></figure></div>



<p class="wp-block-paragraph">We need to send some additional headers so the browser does not reject the cookie!</p>



<h2 class="wp-block-heading">Setting CORS Headers</h2>



<p class="wp-block-paragraph">The final piece of the puzzle is telling the browser it should accept the custom cookie when logging in from the headless site. We can define headers for our GraphQL routes using the <code>graphql_response_headers_to_send</code> hook.</p>


<pre class="wp-block-code"><span><code class="hljs language-php">add_filter( <span class="hljs-string">'graphql_response_headers_to_send'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">( $headers )</span> </span>{
	$http_origin     = get_http_origin();
	$allowed_origins = &#91;
		HEADLESS_FRONTEND_URL,
	];

	<span class="hljs-comment">// If the request is coming from an allowed origin (HEADLESS_FRONTEND_URL), tell the browser it can accept the response.</span>
	<span class="hljs-keyword">if</span> ( in_array( $http_origin, $allowed_origins, <span class="hljs-keyword">true</span> ) ) {
		$headers&#91;<span class="hljs-string">'Access-Control-Allow-Origin'</span>] = $http_origin;
	}

	<span class="hljs-comment">// Tells browsers to expose the response to frontend JavaScript code when the request credentials mode is "include".</span>
	$headers&#91;<span class="hljs-string">'Access-Control-Allow-Credentials'</span>] = <span class="hljs-string">'true'</span>;
	
	<span class="hljs-keyword">return</span> $headers;
}, <span class="hljs-number">20</span> );</code></span></pre>


<p class="wp-block-paragraph">A detailed explanation of CORS can be <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">found in MDN</a>, as well as docs for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials">allow credentials</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin">allow origin</a>.</p>



<h2 class="wp-block-heading">Wrapping up</h2>



<p class="wp-block-paragraph">We&#8217;ve now created a <em>GraphQL mutation</em> which accepts login credentials and logs users in using a <em>custom cookie</em>, and we&#8217;ve set C<em>ORS headers</em> so the browser allows the cookies to be used. Great! We can now hook this up to a <a href="https://github.com/mikejolley/morrics-magical-cauldron/blob/main/src/pages/account/login/login-form.js">login form</a> in the React app.</p>


<div class="wp-post-series-box series-headless-wordpress wp-post-series-box--expandable">
			<input id="collapsible-series-headless-wordpress69c1fb6867bf1" class="wp-post-series-box__toggle_checkbox" type="checkbox">
	
	<label
		class="wp-post-series-box__label"
					for="collapsible-series-headless-wordpress69c1fb6867bf1"
			tabindex="0"
				>
		<p class="wp-post-series-box__name wp-post-series-name">
			This is post 1 of 4 in the series <em>&ldquo;Headless WordPress&rdquo;</em>		</p>
			</label>

			<div class="wp-post-series-box__posts">
			<ol>
									<li><span class="wp-post-series-box__current">Headless WordPress: Cookie Based Login using GraphQL</span></li>
									<li><a href="https://mikejolley.com/2021/03/08/headless-wordpress-log-out-using-graphql-reactjs/">Headless WordPress: Log-out using GraphQL &#038; ReactJS</a></li>
									<li><a href="https://mikejolley.com/2021/03/15/headless-wordpress-password-reset-with-reactjs-wpgraphql/">Headless WordPress: Password Reset with ReactJS &#038; WPGraphQL</a></li>
									<li><a href="https://mikejolley.com/2021/03/26/headless-wordpress-user-registration-using-reactjs-wpgraphql/">Headless WordPress: User Registration with ReactJS &#038; WPGraphQL</a></li>
							</ol>
		</div>
	</div>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">53981</post-id>	</item>
	</channel>
</rss>