DEV Community: Rob Kendal {{☕}} The latest articles on DEV Community by Rob Kendal {{☕}} (@kendalmintcode). https://dev.to/kendalmintcode https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F170363%2Fff538c6e-5d0c-4fd3-b9cd-adaaaa7008ea.png DEV Community: Rob Kendal {{☕}} https://dev.to/kendalmintcode en Exciting news for remote dev job seekers Rob Kendal {{☕}} Tue, 22 Aug 2023 13:58:10 +0000 https://dev.to/kendalmintcode/exciting-news-for-remote-dev-job-seekers-4e51 https://dev.to/kendalmintcode/exciting-news-for-remote-dev-job-seekers-4e51 <p>This will be BIG news for tech recruiters and software/web dev job hunters.</p> <p>If you're keen to uncover the latest remote jobs in the UK then love my latest project: Remote Dev Jobs UK</p> <p>What's more, all roles will be fully, 100% remote!</p> <p>This simple, easy to use platform is ideal for:</p> <ul> <li>Devs of all levels looking to to search for and find their perfect dev role or other job in the tech industry.</li> <li>Recruiters and hiring managers looking to find new candidates for live roles</li> </ul> <p>It's currently in development, but you can check it out and join the waiting list today 👉 <a href="proxy.php?url=https://remotedevjobs.uk/">https://remotedevjobs.uk/</a></p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--z9NE8ljL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tckmoy81y786nqoi8wvn.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--z9NE8ljL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tckmoy81y786nqoi8wvn.png" alt="Image description" width="800" height="409"></a></p> webdev career news showdev Build a restful Node API server using JSON and TypeScript Rob Kendal {{☕}} Thu, 10 Aug 2023 23:00:00 +0000 https://dev.to/kendalmintcode/build-a-restful-node-api-server-using-json-and-typescript-a0e https://dev.to/kendalmintcode/build-a-restful-node-api-server-using-json-and-typescript-a0e <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--zyVIKIFf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://robkendal.co.uk/img/node-api-server-with-typescript.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--zyVIKIFf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://robkendal.co.uk/img/node-api-server-with-typescript.png" alt="Blog header image for Building a Node API server with TypeScript" title="Blog header image for React multi-image carousel" width="800" height="438"></a></p> <p>A while ago I wrote a popular article on <a href="proxy.php?url=https://robkendal.co.uk/blog/how-to-build-a-restful-node-js-api-server-using-json-files">how to build a RESTful Node API server.</a> This and it's <a href="proxy.php?url=https://github.com/bpk68/api-server-starter">accompanying GitHub repo</a>, went down well and even became part of the curriculum in a US university course!</p> <p>However, times and technology move on. After revisiting the repo and the code, I decided to revamp and rewrite from scratch, this time using not only the latest version of Express, but using a slightly different architecture and structure in the form of the controller and routes pattern. Also, for you TypeScript fans out there the new and updated version is fully type aware and is written entirely in TypeScript. Good times!</p> <p>You can check out the <a href="proxy.php?url=https://github.com/bpk68/api-server-starter-ts">Node API starter kit with TypeScript on GitHub</a>, but for now, let's dive in!</p> <h2> 1. Node API project setup </h2> <p>I like to start any project (especially smaller ones like this skeleton Node API server) by installing dependencies we'll need and defining a skeleton folder and files structure. With that in mind, let's create a folder on your machine where the project will live and kick things off with a package.json file:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="nx">pnpm</span> <span class="nx">init</span> </code></pre> </div> <blockquote> <p>We'll be using pnpm as the package manager here. If you've not installed or used pnpm before, then head over to <a href="proxy.php?url=//pnpm.io">pnpm.io </a>and get it installed on your machine. Alternatively, you can use npm or yarn just as easily if you prefer.</p> </blockquote> <p>Next, let's get a folder structure in place like this:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="o">/</span><span class="nx">projectfolder</span> <span class="o">--</span><span class="sr">/dat</span><span class="err">a </span> <span class="o">--</span><span class="nx">users</span><span class="p">.</span><span class="nx">json</span> <span class="o">--</span><span class="sr">/serve</span><span class="err">r </span><span class="o">--</span><span class="sr">/--/</span><span class="nx">controllers</span> <span class="o">--</span><span class="nx">types</span><span class="p">.</span><span class="nx">ts</span> <span class="o">--</span><span class="nx">users</span><span class="p">.</span><span class="nx">ts</span> <span class="o">--</span><span class="sr">/--/</span><span class="nx">routes</span> <span class="o">--</span><span class="nx">index</span><span class="p">.</span><span class="nx">ts</span> <span class="o">--</span><span class="nx">users</span><span class="p">.</span><span class="nx">ts</span> <span class="nx">server</span><span class="p">.</span><span class="nx">ts</span> <span class="kr">package</span><span class="p">.</span><span class="nx">json</span> <span class="nx">tsconfig</span><span class="p">.</span><span class="nx">json</span> </code></pre> </div> <h3> Installing dependencies and build scripts </h3> <p>With pnpm ready to go and our skeleton file structure in place, let's install some dependencies and add the build scripts to the package.json file.</p> <p>In your <code>package.json</code> file, add the following JSON:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight json"><code><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"start"</span><span class="p">:</span><span class="w"> </span><span class="s2">"nodemon server.ts"</span><span class="w"> </span><span class="p">}</span><span class="err">,</span><span class="w"> </span></code></pre> </div> <p>This is all we need for now, so let's move onto our dependencies. Run the following commands, separately, in your console:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="nx">pnpm</span> <span class="nx">add</span> <span class="o">-</span><span class="nx">D</span> <span class="p">@</span><span class="nd">types</span><span class="sr">/express @types/</span><span class="nx">node</span> <span class="nx">ts</span><span class="o">-</span><span class="nx">node</span> <span class="nx">typescript</span> </code></pre> </div> <p>This command will add some dev dependencies, namely some TypeScript types information for Express and Node, as well as TypeScript itself, and finally, ts-node, which is a library that allows us to run TypeScript in Node environments without having to precompile it first.</p> <p>Next, let's add some regular dependencies like this:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="nx">pnpm</span> <span class="nx">add</span> <span class="nx">express</span> <span class="nx">module</span><span class="o">-</span><span class="nx">alias</span> <span class="nx">nodemon</span> </code></pre> </div> <p>Here we're adding a couple of packages:</p> <ul> <li> <strong>Express</strong>, which is a web application framework that offers us lots of HTTP utility methods and is the defacto standard for creating node-based API's.</li> <li> <strong>Nodemon</strong>, (optional) a file-watcher that helps keep an eye on our files, reloading things if we change them during development.</li> <li> <strong>Module-alias</strong>, again optional but we'll use this handy package to save us from having to type some ugly file paths for our module imports. We'll cover this later, but it's not a requirement for this project.</li> </ul> <h3> Configuring TypeScript </h3> <p>Finally, as part of the project set up, we need to give TypeScript some solid defaults to work with. Open the <code>./tsconfig.json</code> file and add the following:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight json"><code><span class="p">{</span><span class="w"> </span><span class="nl">"compilerOptions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"forceConsistentCasingInFileNames"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="nl">"esModuleInterop"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="nl">"outDir"</span><span class="p">:</span><span class="w"> </span><span class="s2">"dist"</span><span class="p">,</span><span class="w"> </span><span class="nl">"rootDir"</span><span class="p">:</span><span class="w"> </span><span class="s2">"./"</span><span class="p">,</span><span class="w"> </span><span class="nl">"target"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ESNext"</span><span class="p">,</span><span class="w"> </span><span class="nl">"skipLibCheck"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="nl">"strict"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="nl">"sourceMap"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="nl">"lib"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"ESNext"</span><span class="p">],</span><span class="w"> </span><span class="nl">"baseUrl"</span><span class="p">:</span><span class="w"> </span><span class="s2">"./"</span><span class="p">,</span><span class="w"> </span><span class="nl">"paths"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"@controllers/*"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"./server/controllers/*"</span><span class="p">],</span><span class="w"> </span><span class="nl">"@routes/*"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"./server/routes/*"</span><span class="p">],</span><span class="w"> </span><span class="nl">"@data/*"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"./data/*"</span><span class="p">]</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="nl">"exclude"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"node_modules"</span><span class="p">]</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre> </div> <h3> Adding some basic user data </h3> <p>Our Node API server with TypeScript is founded on its ability to deal with data stored in JSON files. For us, we're going to start with some simple user data, so open up the <code>./data/users.json</code> file and flesh it out:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight json"><code><span class="p">{</span><span class="w"> </span><span class="nl">"users"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"king arthur"</span><span class="p">,</span><span class="w"> </span><span class="nl">"password"</span><span class="p">:</span><span class="w"> </span><span class="s2">"password1"</span><span class="p">,</span><span class="w"> </span><span class="nl">"profession"</span><span class="p">:</span><span class="w"> </span><span class="s2">"king"</span><span class="p">,</span><span class="w"> </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"rob kendal"</span><span class="p">,</span><span class="w"> </span><span class="nl">"password"</span><span class="p">:</span><span class="w"> </span><span class="s2">"password3"</span><span class="p">,</span><span class="w"> </span><span class="nl">"profession"</span><span class="p">:</span><span class="w"> </span><span class="s2">"code fiddler"</span><span class="p">,</span><span class="w"> </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ash ketchum"</span><span class="p">,</span><span class="w"> </span><span class="nl">"password"</span><span class="p">:</span><span class="w"> </span><span class="s2">"pikapika"</span><span class="p">,</span><span class="w"> </span><span class="nl">"profession"</span><span class="p">:</span><span class="w"> </span><span class="s2">"pokemon botherer"</span><span class="p">,</span><span class="w"> </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre> </div> <p>Nothing too fancy, just some simple JSON data about a typical user including their name and profession.</p> <h2> 2. Building the new server.ts file </h2> <p>With our project structure and dependencies in place, let's move on to building out our API server. Open up the <code>./server.ts</code> file and paste the following into it:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="k">import</span> <span class="dl">'</span><span class="s1">module-alias/register</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="nx">http</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">http</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="nx">express</span><span class="p">,</span> <span class="p">{</span> <span class="nx">Express</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">express</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="nx">routes</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@routes/index</span><span class="dl">'</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">router</span><span class="p">:</span> <span class="nx">Express</span> <span class="o">=</span> <span class="nx">express</span><span class="p">();</span> <span class="nx">router</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">express</span><span class="p">.</span><span class="nx">urlencoded</span><span class="p">({</span> <span class="na">extended</span><span class="p">:</span> <span class="kc">true</span> <span class="p">}));</span> <span class="nx">router</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">express</span><span class="p">.</span><span class="nx">json</span><span class="p">());</span> <span class="nx">router</span><span class="p">.</span><span class="nx">use</span><span class="p">((</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">,</span> <span class="nx">next</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="c1">// set the CORS policy</span> <span class="nx">res</span><span class="p">.</span><span class="nx">header</span><span class="p">(</span><span class="dl">'</span><span class="s1">Access-Control-Allow-Origin</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">*</span><span class="dl">'</span><span class="p">);</span> <span class="c1">// set the CORS headers</span> <span class="nx">res</span><span class="p">.</span><span class="nx">header</span><span class="p">(</span> <span class="dl">'</span><span class="s1">Access-Control-Allow-Headers</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">origin,X-Requested-With,Content-Type,Accept,Authorization</span><span class="dl">'</span> <span class="p">);</span> <span class="c1">// set the CORS method headers</span> <span class="k">if</span> <span class="p">(</span><span class="nx">req</span><span class="p">.</span><span class="nx">method</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">OPTIONS</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span> <span class="nx">res</span><span class="p">.</span><span class="nx">header</span><span class="p">(</span><span class="dl">'</span><span class="s1">Access-Control-Allow-Methods</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">GET, PUT, POST, DELETE</span><span class="dl">'</span><span class="p">);</span> <span class="k">return</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">200</span><span class="p">).</span><span class="nx">json</span><span class="p">({});</span> <span class="p">}</span> <span class="nx">next</span><span class="p">();</span> <span class="p">});</span> <span class="nx">router</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">,</span> <span class="nx">routes</span><span class="p">);</span> <span class="cm">/** Error handling */</span> <span class="nx">router</span><span class="p">.</span><span class="nx">use</span><span class="p">((</span><span class="nx">_</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">error</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">not found</span><span class="dl">'</span><span class="p">);</span> <span class="k">return</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">404</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span> <span class="na">message</span><span class="p">:</span> <span class="nx">error</span><span class="p">.</span><span class="nx">message</span><span class="p">,</span> <span class="p">});</span> <span class="p">});</span> <span class="c1">// Start that server</span> <span class="kd">const</span> <span class="nx">httpServer</span> <span class="o">=</span> <span class="nx">http</span><span class="p">.</span><span class="nx">createServer</span><span class="p">(</span><span class="nx">router</span><span class="p">);</span> <span class="kd">const</span> <span class="nx">PORT</span><span class="p">:</span> <span class="kr">string</span> <span class="o">|</span> <span class="kr">number</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">PORT</span> <span class="o">??</span> <span class="mi">8080</span><span class="p">;</span> <span class="nx">httpServer</span><span class="p">.</span><span class="nx">listen</span><span class="p">(</span><span class="nx">PORT</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`API server alive and kicking on port </span><span class="p">${</span><span class="nx">PORT</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span> <span class="p">);</span> </code></pre> </div> <p>Although this is a relatively small file, there's a lot going on in it, so let's break it down...</p> <h3> The imports </h3> <p>Right at the start, we bring in some dependencies:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="k">import</span> <span class="dl">'</span><span class="s1">module-alias/register</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="nx">http</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">http</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="nx">express</span><span class="p">,</span> <span class="p">{</span> <span class="nx">Express</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">express</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="nx">routes</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@routes/index</span><span class="dl">'</span><span class="p">;</span> </code></pre> </div> <p>The very first line where we <code>import module-alias/register</code> is an important line if you want to be able to use aliased paths for your module imports. There's an entire section coming up where we'll discuss this, but for now, just note that if you want to use imports like import thing from '@routes/myawesomefile' then you'll need this import right at the top of your project's entry point.</p> <p>We also bring in the http utility from the built-in http module, then Express (both the main package and the Express type for TypeScript) and finally our routing information from the <code>@routes/index</code> file — we'll create this later on so don't worry about it for now.</p> <h3> Setting up the Express router </h3> <p>With our imports done, we can create an instance of the Express framework, and then set up some encoding rules to make sure the data we receive is encoded in the same way as is described in the OPTIONS argument that's sent before the main API request.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="kd">const</span> <span class="nx">router</span><span class="p">:</span> <span class="nx">Express</span> <span class="o">=</span> <span class="nx">express</span><span class="p">();</span> <span class="nx">router</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">express</span><span class="p">.</span><span class="nx">urlencoded</span><span class="p">({</span> <span class="na">extended</span><span class="p">:</span> <span class="kc">true</span> <span class="p">}));</span> <span class="nx">router</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">express</span><span class="p">.</span><span class="nx">json</span><span class="p">());</span> </code></pre> </div> <p>We also make use of the <code>json()</code> method so that our API server will parse JSON requests.</p> <h3> Configuring CORS </h3> <p>CORS (<a href="proxy.php?url=https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">Cross-Origin Resource Sharing from MDN</a>) can be a horrendously frustrating part of development life. It helps to restrict the origins from which HTTP requests are made. However, in doing so, it can thwart many a local development environment by cutting off access to some legitimate requests.</p> <p>That said, it is a necessary and important part of any API set up as it helps prevent bad actors from making requests from unauthorised URLs or origins.</p> <p>Our little server will be no different so let's set up CORS for our API server now:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="nx">router</span><span class="p">.</span><span class="nx">use</span><span class="p">((</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">,</span> <span class="nx">next</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="c1">// set the CORS policy</span> <span class="nx">res</span><span class="p">.</span><span class="nx">header</span><span class="p">(</span><span class="dl">'</span><span class="s1">Access-Control-Allow-Origin</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">*</span><span class="dl">'</span><span class="p">);</span> <span class="c1">// set the CORS headers</span> <span class="nx">res</span><span class="p">.</span><span class="nx">header</span><span class="p">(</span> <span class="dl">'</span><span class="s1">Access-Control-Allow-Headers</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">origin,X-Requested-With,Content-Type,Accept,Authorization</span><span class="dl">'</span> <span class="p">);</span> <span class="c1">// set the CORS method headers</span> <span class="k">if</span> <span class="p">(</span><span class="nx">req</span><span class="p">.</span><span class="nx">method</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">OPTIONS</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span> <span class="nx">res</span><span class="p">.</span><span class="nx">header</span><span class="p">(</span><span class="dl">'</span><span class="s1">Access-Control-Allow-Methods</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">GET, PUT, POST, DELETE</span><span class="dl">'</span><span class="p">);</span> <span class="k">return</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">200</span><span class="p">).</span><span class="nx">json</span><span class="p">({});</span> <span class="p">}</span> <span class="nx">next</span><span class="p">();</span> <span class="p">});</span> </code></pre> </div> <p>This is a simple portion of code that basically sets which origins our API server will allow access from, which types of requests (e.g. what's been set in the header), and then what sorts of methods we'll allow — e.g. here we're allowing the four main CRUD methods, GET, PUT, POST, and DELETE.</p> <blockquote> <p>Note that in our example here, we're allowing all origins as denoted by the '*' symbol. In production, you'll definitely want to restrict calling of this API to perhaps just your app's url(s), depending on your needs!</p> </blockquote> <h3> Adding in the routes and handling errors </h3> <p>Of course, our API server will be of no use if we don't let it handle some routes. That's where the following line comes in:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="nx">router</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">,</span> <span class="nx">routes</span><span class="p">);</span> </code></pre> </div> <p>We've imported our routes object right at the start of the file and this will be further broken down (as we'll see later on) into sub-routes, each with their own controller and router handling logic. In our main server file, however, we just need to let the server know that anything starting with <code>/</code> will be handled by the imported routes object.</p> <p>Next up, we'll cater for any routes that aren't handled above:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="cm">/** Error handling */</span> <span class="nx">router</span><span class="p">.</span><span class="nx">use</span><span class="p">((</span><span class="nx">_</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">error</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">not found</span><span class="dl">'</span><span class="p">);</span> <span class="k">return</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">404</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span> <span class="na">message</span><span class="p">:</span> <span class="nx">error</span><span class="p">.</span><span class="nx">message</span><span class="p">,</span> <span class="p">});</span> <span class="p">});</span> </code></pre> </div> <p>Essentially, what we're saying here is 'any route that isn't already catered for, return an error message and a 404 status'.</p> <p>There's lots more you could do here to expand on it or handle different types of errors and that's probably quite a good idea to do in a larger app, or as your app begins to scale. For now, however, we can start simply and handle the most basic error where a request has tried to call a route that doesn't exist.</p> <h3> Starting the server </h3> <p>Finally, we're ready to kick off the server and get things running. This last bit of code creates an instance of a http server, creates a port for the server to run on, and finally starts the server:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="c1">// Start that server</span> <span class="kd">const</span> <span class="nx">httpServer</span> <span class="o">=</span> <span class="nx">http</span><span class="p">.</span><span class="nx">createServer</span><span class="p">(</span><span class="nx">router</span><span class="p">);</span> <span class="kd">const</span> <span class="nx">PORT</span><span class="p">:</span> <span class="kr">string</span> <span class="o">|</span> <span class="kr">number</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">PORT</span> <span class="o">??</span> <span class="mi">8080</span><span class="p">;</span> <span class="nx">httpServer</span><span class="p">.</span><span class="nx">listen</span><span class="p">(</span><span class="nx">PORT</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`API server alive and kicking on port </span><span class="p">${</span><span class="nx">PORT</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span> <span class="p">);</span> </code></pre> </div> <p>We try to use a PORT option that might be present in an environment variable, hence the process.env.PORT part, but if one isn't available, then the default port is simply '8080'.</p> <h2> 2a. Module aliases </h2> <p>Before we go ahead with the meat and potatoes of the API server, we need to quickly go over module path aliasing. By default, you'll always need to import a local module or package from another file using a relative path. This is fine when your files are close together.</p> <p>However, when they're separated by different levels or in a larger, more complex folder structure, then you can end up with ugly (and confusing) import statements like this:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">SomeThing</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../../../path/to/file</span><span class="dl">'</span><span class="p">;</span> </code></pre> </div> <p>Not the worst thing in the world, but it gets messier the more of these you have and also creates a headache when you want to migrate or move files!</p> <p>It's much nicer to be able to do something like this:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">SomeThing</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">module/file</span><span class="dl">'</span><span class="p">;</span> </code></pre> </div> <p>Some bundlers and code packagers will do this for you. If you've used Rollup, Vite or even Create React App, then you'll have lots of wonderful support from bundlers like Webpack that can offer some of this module path aliasing out of the box or with a simple config setting.</p> <p>TypeScript also has a nice idea of paths and in fact, we've already set this up earlier in our process. Check out the <code>./tsconfig.json</code> file towards the end of the file:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight json"><code><span class="nl">"paths"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"@controllers/*"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"./server/controllers/*"</span><span class="p">],</span><span class="w"> </span><span class="nl">"@routes/*"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"./server/routes/*"</span><span class="p">],</span><span class="w"> </span><span class="nl">"@data/*"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"./data/*"</span><span class="p">]</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre> </div> <p>Here, we've added a couple of helper routes so that wherever we have <code>@controllers/any_file_path_here</code> this will be replaced by TypeScript with <code>./server/controllers/any_file_path_here</code>. Neat!</p> <blockquote> <p>I've used little '@' symbols here because it looks pretty cool and I like it, but it's not part of the syntax, you could use whatever you like or just leave it as 'controllers/'.</p> </blockquote> <p>Unfortunately, whilst we can configure this from a TypeScript point of view, Node doesn't know what to do with this. Your options are to implement some sort of middle man transpiler to handle these paths for us and then run the server, or we can use the handle module-aliases package, which handily we installed earlier!</p> <p>The only downside (and it's a small downside) is that we have to duplicate our path aliases into our package.json file as outlined here:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight json"><code><span class="nl">"_moduleAliases"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"@controllers"</span><span class="p">:</span><span class="w"> </span><span class="s2">"./server/controllers"</span><span class="p">,</span><span class="w"> </span><span class="nl">"@routes"</span><span class="p">:</span><span class="w"> </span><span class="s2">"./server/routes"</span><span class="p">,</span><span class="w"> </span><span class="nl">"@data"</span><span class="p">:</span><span class="w"> </span><span class="s2">"./data"</span><span class="w"> </span><span class="p">}</span><span class="err">,</span><span class="w"> </span></code></pre> </div> <p>We add in the extra "_moduleAliases" property into the package.json file and then everything should work nicely and we can use our funky, shorter paths 👍.</p> <h2> 3. Mapping the routes </h2> <p>With our server prepped and ready to go, we need to feed it some routes to handle and serve. When someone requests an API endpoint such as <a href="proxy.php?url=https://api.domain.com/users/123">https://api.domain.com/users/123</a> we need to have a matching route within our server that can listen for and accept the route, then provide some sort of meaningful response to the requestee.</p> <p>In our case, we're going to be loading and saving JSON data about users, such as username, password, name, etc. For that, we need to define a series of user endpoint routes and we'll do that within the <code>./server/routes/users.ts</code> file:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="k">import</span> <span class="kd">type</span> <span class="p">{</span> <span class="nx">Router</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">express</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">getUser</span><span class="p">,</span> <span class="nx">getUsers</span><span class="p">,</span> <span class="nx">addUser</span><span class="p">,</span> <span class="nx">updateUser</span><span class="p">,</span> <span class="nx">deleteUser</span><span class="p">,</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@controllers/users</span><span class="dl">'</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">userRoutes</span> <span class="o">=</span> <span class="p">(</span><span class="nx">router</span><span class="p">:</span> <span class="nx">Router</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nx">router</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/users</span><span class="dl">'</span><span class="p">,</span> <span class="nx">getUsers</span><span class="p">);</span> <span class="nx">router</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/user/:id</span><span class="dl">'</span><span class="p">,</span> <span class="nx">getUser</span><span class="p">);</span> <span class="nx">router</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="dl">'</span><span class="s1">/users</span><span class="dl">'</span><span class="p">,</span> <span class="nx">addUser</span><span class="p">);</span> <span class="nx">router</span><span class="p">.</span><span class="nx">put</span><span class="p">(</span><span class="dl">'</span><span class="s1">/user/:id</span><span class="dl">'</span><span class="p">,</span> <span class="nx">updateUser</span><span class="p">);</span> <span class="nx">router</span><span class="p">.</span><span class="k">delete</span><span class="p">(</span><span class="dl">'</span><span class="s1">/user/:id</span><span class="dl">'</span><span class="p">,</span> <span class="nx">deleteUser</span><span class="p">);</span> <span class="k">return</span> <span class="nx">router</span><span class="p">;</span> <span class="p">};</span> <span class="k">export</span> <span class="k">default</span> <span class="nx">userRoutes</span><span class="p">;</span> </code></pre> </div> <p>First, we're bringing in the Router type from Express as we'll be passing in a router object to our user routes handling code in a moment. Next, we import some common CRUD-style handler functions from our controller, which we'll define in the next part.</p> <p>We've got a getUser function which will fetch and return a single user, and then <code>getUsers</code> which will, in turn, fetch us multiple users. After that, we handle adding a new user, updating a user, and deleting a user from our files.</p> <blockquote> <p>We haven't touched on the concept of a controller yet, but a controller's purpose is to deal with the data handling part of the request. The routes and router are designed to merely handle the incoming request and route it to the appropriate place. Once a route has been captured, the controller is called upon to do something meaningful whether that's fetching some data, or manipulating it in some way before saving it against a data store. In our case, this is a JSON file stored on disk.</p> </blockquote> <p>Next, we create a <code>userRoutes</code> function which accepts an instance of a Router object, which is an Express item that can be used to define routes. That's what we do next. We call <code>router.[method]</code> where 'method' is the request type (e.g. GET or POST), passing it a route we want to handle (e.g. '/users') and the function from our controller which will deal with the data business for that route.</p> <p>Where we have something like <code>:id</code> this is a route parameter. It will be substituted with a real world value when the API route is called. We can use this within our controller to access this substituted value and use it to access specific data. For example, in the route <code>/user/:id</code> the real API would be called like <code>/user/AFC34OI</code> where the value <code>AFC340I</code> is the 'id' value of a user and we can use it to search a database or similar for said user.</p> <p>After we add a bunch of supporting CRUD-like routes and their controller handlers, we return the updated router object from the function.</p> <h3> Adding user routes to our main router </h3> <p>Doing the above is not quite enough to have the API handle those routes for us. We need to connect the user route handlers to the main Express router.</p> <p>Open up the <code>./server/routes/index.ts</code> and add in the following:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="k">import</span> <span class="nx">express</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">express</span><span class="dl">'</span><span class="p">;</span> <span class="c1">// Import individual route profiles from controllers</span> <span class="k">import</span> <span class="nx">usersRoute</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@routes/users</span><span class="dl">'</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">router</span> <span class="o">=</span> <span class="nx">express</span><span class="p">.</span><span class="nx">Router</span><span class="p">();</span> <span class="c1">// Pass our router instance to controllers</span> <span class="nx">router</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="dl">'</span><span class="s1">/users</span><span class="dl">'</span><span class="p">,</span> <span class="nx">usersRoute</span><span class="p">(</span><span class="nx">router</span><span class="p">));</span> <span class="k">export</span> <span class="k">default</span> <span class="nx">router</span><span class="p">;</span> </code></pre> </div> <p>We pull in our usersRoute here and this is where you'd add in any other additional routes that you create later down the line.</p> <p>Next, we create an instance of the Express Router. In the next line down we fire off the <code>router.use()</code> function and supply it with our base users route, <code>/users</code> and pass it the handling function, usersRoute which itself is passed the express router.</p> <p>Any route that begins with <code>/users</code> will be handled by our <code>usersRoute</code> handler.</p> <p>Finally, we export the router that we consumed in our <code>server.ts</code> file earlier. All done, nice and simple.</p> <h2> 4. Creating our controller </h2> <p>The controller is where our data handling will occur. It doesn't know about what route it's serving, just that it has a specific job to fetch or update certain data and respond to the request with that data in a particular shape or format (e.g. JSON, text or XML).</p> <p>For us, we'll start simply by showing how handle fetching all the users from the store. Open up the <code>./server/controllers/users.ts</code> file and add in the following:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="k">import</span> <span class="kd">type</span> <span class="p">{</span> <span class="nx">Request</span><span class="p">,</span> <span class="nx">Response</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">express</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="kd">type</span> <span class="p">{</span> <span class="nx">UserList</span><span class="p">,</span> <span class="nx">User</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./types</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="nx">fs</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">fs/promises</span><span class="dl">'</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">dataPath</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">data/users.json</span><span class="dl">'</span><span class="p">;</span> <span class="k">export</span> <span class="kd">const</span> <span class="nx">getUsers</span> <span class="o">=</span> <span class="k">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">:</span> <span class="nx">Request</span><span class="p">,</span> <span class="nx">res</span><span class="p">:</span> <span class="nx">Response</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">try</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">readFile</span><span class="p">(</span><span class="nx">dataPath</span><span class="p">,</span> <span class="p">{</span> <span class="na">encoding</span><span class="p">:</span> <span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">,</span> <span class="p">});</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">200</span><span class="p">).</span><span class="nx">send</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">data</span><span class="p">));</span> <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">500</span><span class="p">).</span><span class="nx">send</span><span class="p">(</span><span class="dl">'</span><span class="s1">An error occurred when fetching the users</span><span class="dl">'</span><span class="p">);</span> <span class="p">}</span> <span class="p">};</span> <span class="k">export</span> <span class="k">default</span> <span class="p">{</span> <span class="nx">getUsers</span><span class="p">,</span> <span class="p">};</span> </code></pre> </div> <p>We import some types from Express and our locally defined ones (see the next section) and then the <code>fs</code> file handling package from Node. This will help us deal with reading from and writing to our local files.</p> <p>With getUsers we use the <code>fs</code> package to asynchronously read from the <code>users.json</code> file and send it straight back to the request via the res or 'response' object. If we're successful, we set the status to 200 (i.e. a successful response), and parse the users data into JSON for the response.</p> <p>Finally, we can export the getUsers function as part of a default object.</p> <blockquote> <p>Notice that the main body of code in getUsers is wrapped in a try catch block. If an error occurs, it's captured here and an appropriate status is returned (a 500 code) along with a simple string message. There's lot of different error handling and logging approaches, but this is fine for a simple app like ours.</p> </blockquote> <h2> User types for TypeScript </h2> <p>In the last section, you'll see we imported some local types, namely UserList and User. You can add these or change them to your needs, but open up <code>./server/controllers/types.ts</code> and add in the following:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="k">export</span> <span class="kr">interface</span> <span class="nx">User</span> <span class="p">{</span> <span class="nl">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">profession</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">id</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span> <span class="p">}</span> <span class="k">export</span> <span class="kr">interface</span> <span class="nx">UserList</span> <span class="p">{</span> <span class="nl">users</span><span class="p">:</span> <span class="nx">User</span><span class="p">[];</span> <span class="p">}</span> </code></pre> </div> <p>Nothing too fancy or complex, but notice how the structure of the data here maps to that of our users JSON data from the beginning of the article.</p> <h2> 5. Testing the API server </h2> <p>With our server all put together, the only sensible thing to do is to fire up the server and test it! Save all your files and then open up your nearest and favourite console or terminal and enter the command <code>pnpm start</code> and you should see the following:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="nx">API</span> <span class="nx">server</span> <span class="nx">alive</span> <span class="nx">and</span> <span class="nx">kicking</span> <span class="nx">on</span> <span class="nx">port</span> <span class="mi">8080</span> </code></pre> </div> <p>Nothing too exciting here. Instead, head into a simple browser and navigate to <code>http://localhost:8080/users/</code> and what you should see now is a list of our available users from our <code>./data/users.json</code> file. Alternatively you could use an app like Postman or Rapid API to check your own API too and they'll work just as well.</p> <h2> 6. Extending the API server with CRUD </h2> <p>We've missed a few parts out of the users.ts file for brevity, but here is the entire file for completeness. Whilst long, it should be fairly straightforward to scan and understand and a lot of the code is very similar, especially around updating and deleting users.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight typescript"><code><span class="k">import</span> <span class="kd">type</span> <span class="p">{</span> <span class="nx">Request</span><span class="p">,</span> <span class="nx">Response</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">express</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="kd">type</span> <span class="p">{</span> <span class="nx">UserList</span><span class="p">,</span> <span class="nx">User</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./types</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="nx">fs</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">fs/promises</span><span class="dl">'</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">dataPath</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">data/users.json</span><span class="dl">'</span><span class="p">;</span> <span class="k">export</span> <span class="kd">const</span> <span class="nx">getUsers</span> <span class="o">=</span> <span class="k">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">:</span> <span class="nx">Request</span><span class="p">,</span> <span class="nx">res</span><span class="p">:</span> <span class="nx">Response</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">try</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">readFile</span><span class="p">(</span><span class="nx">dataPath</span><span class="p">,</span> <span class="p">{</span> <span class="na">encoding</span><span class="p">:</span> <span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">,</span> <span class="p">});</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">200</span><span class="p">).</span><span class="nx">send</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">data</span><span class="p">));</span> <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">500</span><span class="p">).</span><span class="nx">send</span><span class="p">(</span><span class="dl">'</span><span class="s1">An error occurred when fetching the users</span><span class="dl">'</span><span class="p">);</span> <span class="p">}</span> <span class="p">};</span> <span class="k">export</span> <span class="kd">const</span> <span class="nx">getUser</span> <span class="o">=</span> <span class="k">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">:</span> <span class="nx">Request</span><span class="p">,</span> <span class="nx">res</span><span class="p">:</span> <span class="nx">Response</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">try</span> <span class="p">{</span> <span class="kd">let</span> <span class="nx">user</span> <span class="o">=</span> <span class="p">{};</span> <span class="kd">const</span> <span class="nx">userId</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">params</span><span class="p">.</span><span class="nx">id</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">readFile</span><span class="p">(</span><span class="nx">dataPath</span><span class="p">,</span> <span class="p">{</span> <span class="na">encoding</span><span class="p">:</span> <span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">,</span> <span class="p">});</span> <span class="k">if</span> <span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="kd">const</span> <span class="na">allUsers</span><span class="p">:</span> <span class="nx">UserList</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span> <span class="nx">user</span> <span class="o">=</span> <span class="p">{</span> <span class="p">...</span><span class="nx">allUsers</span><span class="p">.</span><span class="nx">users</span><span class="p">.</span><span class="nx">find</span><span class="p">((</span><span class="nx">user</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">user</span><span class="p">.</span><span class="nx">id</span> <span class="o">===</span> <span class="nb">Number</span><span class="p">(</span><span class="nx">userId</span><span class="p">)),</span> <span class="p">};</span> <span class="p">}</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">200</span><span class="p">).</span><span class="nx">send</span><span class="p">(</span><span class="nx">user</span><span class="p">);</span> <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span> <span class="nx">res</span> <span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">500</span><span class="p">)</span> <span class="p">.</span><span class="nx">send</span><span class="p">(</span> <span class="dl">'</span><span class="s1">An error occurred when fetching the user with id </span><span class="dl">'</span> <span class="o">+</span> <span class="nx">req</span><span class="p">.</span><span class="nx">params</span><span class="p">.</span><span class="nx">id</span> <span class="p">);</span> <span class="p">}</span> <span class="p">};</span> <span class="k">export</span> <span class="kd">const</span> <span class="nx">addUser</span> <span class="o">=</span> <span class="k">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">:</span> <span class="nx">Request</span><span class="p">,</span> <span class="nx">res</span><span class="p">:</span> <span class="nx">Response</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">try</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">readFile</span><span class="p">(</span><span class="nx">dataPath</span><span class="p">,</span> <span class="p">{</span> <span class="na">encoding</span><span class="p">:</span> <span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">,</span> <span class="p">});</span> <span class="kd">const</span> <span class="na">allUsers</span><span class="p">:</span> <span class="nx">UserList</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span> <span class="kd">const</span> <span class="na">newUser</span><span class="p">:</span> <span class="nx">User</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">body</span><span class="p">;</span> <span class="c1">// Note: this isn't ideal for production use.</span> <span class="c1">// ideally, use something like a UUID or other GUID for a unique ID value</span> <span class="kd">const</span> <span class="nx">newUserId</span> <span class="o">=</span> <span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">();</span> <span class="nx">newUser</span><span class="p">.</span><span class="nx">id</span> <span class="o">=</span> <span class="nx">newUserId</span><span class="p">;</span> <span class="nx">allUsers</span><span class="p">.</span><span class="nx">users</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">newUser</span><span class="p">);</span> <span class="k">await</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">writeFile</span><span class="p">(</span><span class="nx">dataPath</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">allUsers</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span> <span class="p">{</span> <span class="na">encoding</span><span class="p">:</span> <span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">,</span> <span class="p">});</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">200</span><span class="p">).</span><span class="nx">send</span><span class="p">(</span><span class="nx">newUser</span><span class="p">);</span> <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">500</span><span class="p">).</span><span class="nx">send</span><span class="p">(</span><span class="dl">'</span><span class="s1">An error occurred when adding the new user</span><span class="dl">'</span><span class="p">);</span> <span class="p">}</span> <span class="p">};</span> <span class="k">export</span> <span class="kd">const</span> <span class="nx">updateUser</span> <span class="o">=</span> <span class="k">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">:</span> <span class="nx">Request</span><span class="p">,</span> <span class="nx">res</span><span class="p">:</span> <span class="nx">Response</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">try</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">readFile</span><span class="p">(</span><span class="nx">dataPath</span><span class="p">,</span> <span class="p">{</span> <span class="na">encoding</span><span class="p">:</span> <span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">,</span> <span class="p">});</span> <span class="kd">const</span> <span class="na">allUsers</span><span class="p">:</span> <span class="nx">UserList</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span> <span class="kd">const</span> <span class="na">userId</span><span class="p">:</span> <span class="kr">number</span> <span class="o">=</span> <span class="nb">Number</span><span class="p">(</span><span class="nx">req</span><span class="p">.</span><span class="nx">params</span><span class="p">.</span><span class="nx">id</span><span class="p">);</span> <span class="kd">const</span> <span class="na">userToUpdate</span><span class="p">:</span> <span class="nx">User</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">body</span><span class="p">;</span> <span class="nx">allUsers</span><span class="p">.</span><span class="nx">users</span> <span class="o">=</span> <span class="nx">allUsers</span><span class="p">.</span><span class="nx">users</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">user</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">user</span><span class="p">.</span><span class="nx">id</span> <span class="o">===</span> <span class="nx">userId</span> <span class="p">?</span> <span class="p">{</span> <span class="p">...</span><span class="nx">user</span><span class="p">,</span> <span class="p">...</span><span class="nx">userToUpdate</span> <span class="p">}</span> <span class="p">:</span> <span class="nx">user</span> <span class="p">);</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">allUsers</span><span class="p">);</span> <span class="k">await</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">writeFile</span><span class="p">(</span><span class="nx">dataPath</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">allUsers</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span> <span class="p">{</span> <span class="na">encoding</span><span class="p">:</span> <span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">,</span> <span class="p">});</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">200</span><span class="p">).</span><span class="nx">send</span><span class="p">(</span><span class="nx">allUsers</span><span class="p">);</span> <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span> <span class="nx">res</span> <span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">500</span><span class="p">)</span> <span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="dl">'</span><span class="s1">An error occurred when updating the user with id</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">req</span><span class="p">.</span><span class="nx">params</span><span class="p">.</span><span class="nx">id</span><span class="p">);</span> <span class="p">}</span> <span class="p">};</span> <span class="k">export</span> <span class="kd">const</span> <span class="nx">deleteUser</span> <span class="o">=</span> <span class="k">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">:</span> <span class="nx">Request</span><span class="p">,</span> <span class="nx">res</span><span class="p">:</span> <span class="nx">Response</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">try</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">readFile</span><span class="p">(</span><span class="nx">dataPath</span><span class="p">,</span> <span class="p">{</span> <span class="na">encoding</span><span class="p">:</span> <span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">,</span> <span class="p">});</span> <span class="kd">const</span> <span class="na">allUsers</span><span class="p">:</span> <span class="nx">UserList</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span> <span class="kd">const</span> <span class="na">userId</span><span class="p">:</span> <span class="kr">number</span> <span class="o">=</span> <span class="nb">Number</span><span class="p">(</span><span class="nx">req</span><span class="p">.</span><span class="nx">params</span><span class="p">.</span><span class="nx">id</span><span class="p">);</span> <span class="nx">allUsers</span><span class="p">.</span><span class="nx">users</span> <span class="o">=</span> <span class="nx">allUsers</span><span class="p">.</span><span class="nx">users</span><span class="p">.</span><span class="nx">filter</span><span class="p">((</span><span class="nx">user</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">user</span><span class="p">.</span><span class="nx">id</span> <span class="o">!==</span> <span class="nx">userId</span><span class="p">);</span> <span class="k">await</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">writeFile</span><span class="p">(</span><span class="nx">dataPath</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">allUsers</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span> <span class="p">{</span> <span class="na">encoding</span><span class="p">:</span> <span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">,</span> <span class="p">});</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">200</span><span class="p">).</span><span class="nx">send</span><span class="p">(</span><span class="nx">allUsers</span><span class="p">);</span> <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span> <span class="nx">res</span> <span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">500</span><span class="p">)</span> <span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="dl">'</span><span class="s1">An error occurred when deleting the user with id</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">req</span><span class="p">.</span><span class="nx">params</span><span class="p">.</span><span class="nx">id</span><span class="p">);</span> <span class="p">}</span> <span class="p">};</span> <span class="k">export</span> <span class="k">default</span> <span class="p">{</span> <span class="nx">getUsers</span><span class="p">,</span> <span class="nx">getUser</span><span class="p">,</span> <span class="nx">addUser</span><span class="p">,</span> <span class="nx">updateUser</span><span class="p">,</span> <span class="nx">deleteUser</span><span class="p">,</span> <span class="p">};</span> </code></pre> </div> <blockquote> <p>As a challenge you could look to extend this to handle some other user editing options, or refactor it to reduce some of the similar code across the different functions.</p> </blockquote> <h2> Enjoy your fully functional Node-based API server </h2> <p>There's lots more you could add to extend this, such as authentication handling, talking to a database, different routes, and so on. As it stands, we've got a fully functional API server base that will happily serve you some information from a JSON file store.</p> <p>You can <a href="proxy.php?url=https://github.com/bpk68/api-server-starter-ts">visit the GitHub repository for the starter API server</a> to download, checkout or fork to your heart's content.</p> <p>If there's anything I've missed or that you'd like to know more about, let me know in the comments or shoot me an email to me[at]robkendal.co.uk.</p> javascript webdev tutorial typescript How to fix 'Property does not exist on type Window in TypeScript' error Rob Kendal {{☕}} Wed, 23 Mar 2022 09:08:41 +0000 https://dev.to/kendalmintcode/how-to-fix-property-does-not-exist-on-type-window-in-typescript-error-2o9i https://dev.to/kendalmintcode/how-to-fix-property-does-not-exist-on-type-window-in-typescript-error-2o9i <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--zNSknMy3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://robkendal.co.uk/img/property-does-not-exist-on-window-blog-header.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--zNSknMy3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://robkendal.co.uk/img/property-does-not-exist-on-window-blog-header.png" alt="Blog header image for how to fix property does not exist on Window article" title="Blog header image for how to fix property does not exist on Window article" width="800" height="438"></a></p> <p>If you've been doing something with an external library, global variable or anything that involved the native <code>Window</code> object in TypeScript, you may have come across this error:</p> <blockquote> <p>Property does not exist on type 'Window &amp; typeof globalThis'.ts (123)</p> </blockquote> <p>It's a pain, but really simple to fix, so let's get to it!</p> <h2> Discovering the 'Property does not exist on type Window in TypeScript' error </h2> <p>I was recently working on a client site and it involved using the excellent <a href="proxy.php?url=https://cloudinary.com">Cloudinary</a> service. They have some equally excellent <a href="proxy.php?url=https://cloudinary.com/documentation/upload_widget">embeddable widgets</a> which only require a bit of simple JS to drop in. Here's a snippet of the code:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code>const myWidget = window.cloudinary.createUploadWidget(options, processResults); // ...rest of the file </code></pre> </div> <p>The problem is, this was a React app in Next.js and a TypeScript codebase at that! The code worked fine, but the classic 'Property does not exist on type Window in TypeScript' error flagged up on the build command and TypeScript complained about it endlessly.</p> <h2> Identifying the problem </h2> <p>The <code>Window</code> type is defined in the <code>lib.dom</code> TypeScript module (as per the <a href="proxy.php?url=https://microsoft.github.io/PowerBI-JavaScript/interfaces/_node_modules_typedoc_node_modules_typescript_lib_lib_dom_d_.window.html">following documentation on TSDoc</a>. You can see we have the global <code>Window</code> object definition here with a ton of properties.</p> <p>If you need anything not defined in this list, however, then that's when you'll hit the <code>Property does not exist on type 'window &amp; typeof globalthis'.ts</code> style error. Common examples of this are when you add external libraries from Google for their analytics or Tag Manager, or, in my case, Cloudinary.</p> <p>Cloudinary added it's own <code>cloudinary</code> object to the global <code>window</code> object provided by the browser, but TypeScript has no idea what it is or what <em>types</em> it has because they're not defined.</p> <p>This is one of those cases where the error is actually pointing us in the right direction.</p> <p><a href="proxy.php?url=http://twitter.com/kendalmintcode"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--ZRVnu6_T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://robkendal.co.uk/img/twitter_cta.png" alt="twitter banner call to action" width="800" height="126"></a></p> <h2> Fixing the 'Property does not exist on type Window in TypeScript' error </h2> <p>Fortunately for us, the fix is quite simple and involves three steps:</p> <ol> <li>Creating an <code>index.d.ts</code> file somewhere in our project.</li> <li>Editing the file to define the types on the <code>Window</code> object.</li> <li>(optional) adding a reference to the file in the <code>tsconfig.json</code> file.</li> </ol> <p>Starting with number 1, you'll need to add the following into your <code>index.d.ts</code> file:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code>export {}; declare global { interface Window { somePropertyHere: any; } } </code></pre> </div> <blockquote> <p>If you don't have such a file, then create a folder in your project root or <code>src</code> folder called <code>types</code>. In here, add a new file called <code>index.d.ts</code> and then add the previous code snippet in there.</p> </blockquote> <p>If you already have an <code>index.d.ts</code> file or a <code>global.d.ts</code> (or any other declared project types file) in your project with other type definitions in it, then you may not need the <code>export {}</code> line. You can omit that part and add the rest.</p> <p>You can define whatever other properties you'd like in here as per your project needs, e.g.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code>export {}; declare global { interface Window { cloudinary: any; gtag: (...args: any[]) =&gt; void; } } </code></pre> </div> <p>In the above snippet, you'll see I added the <code>cloudinary</code> global object with a type of <code>any</code> as, unfortunately Cloudinary don't seem to offer a TypeScript option for their widgets so we're unsure what the <code>cloudinary</code> object will contain. However, with the <code>gtag</code> one, we know that it is a function that has a <code>void</code> return type.</p> <p><strong>Where possible, always try to strongly type your properties if you can, rather than opting for <code>any</code></strong>.</p> <p>Finally, if you're still experiencing issues, you might need to do one last step to wire things up. Take a note of your <code>index.d.ts</code> file's location and head into your <code>tsconfig.json</code> file, which should be in the root of your project:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code>{ "compilerOptions": { // ... other settings "typeRoots": ["./src/types", "./types"] } } </code></pre> </div> <p>Under the <code>typeRoots</code> property (an array), make sure you have a path to the containing folder for your <code>index.d.ts</code> file. In my case it was in <code>./src/types</code> so I added that in my <code>tsconfig.json</code> file.</p> <h2> Wrapping up </h2> <p>And it's a simple as that. If you've been plagued by TypeScript's complaints about a 'Property does not exist on type Window in TypeScript' error, you should have the knowledge and really simple steps to fix that once and for all.</p> javascript typescript tutorial How to build a multi-image carousel in React and Tailwind Rob Kendal {{☕}} Thu, 10 Mar 2022 09:13:41 +0000 https://dev.to/kendalmintcode/how-to-build-a-multi-image-carousel-in-react-and-tailwind-5cci https://dev.to/kendalmintcode/how-to-build-a-multi-image-carousel-in-react-and-tailwind-5cci <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frobkendal.co.uk%2Fimg%2Freact-multi-item-carousel-blog-header.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frobkendal.co.uk%2Fimg%2Freact-multi-item-carousel-blog-header.png" title="Blog header image for React multi-image carousel" alt="Blog header image for React multi-image carousel"></a></p> <p>I had a client project recently that required some nifty coding to produce <strong>a multi-item carousel in React</strong>. Really, it was a <strong>multi-image carousel</strong> with a few extra bits, but as we'll soon see, what we'll build in this article can be easily modified to suit whatever multi-items you'd like to stuff into your carousel.</p> <p>Let's get building.</p> <h2> What we'll be building </h2> <p>To be honest, it's difficult to find a solid definition of what exactly a 'multi-item carousel' or 'multi-image carousel' is, let alone finding a good example of one built in React. Sometimes it seems the terms 'carousel', 'slider', and others get all interchanged and mingled around to the point where it can be confusing...</p> <p>My definition or requirements looked like this:</p> <blockquote> <p>I wanted to create a fixed-width container that housed several child items (mainly images with overlays for my purposes) arranged horizontally with an equal gap between them. Any child items that overflowed the container's bounds would be hidden offscreen, yet scrollable to bring them into view along the horizontal axis.</p> </blockquote> <p>See the following image to illustrate what I mean:</p> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frobkendal.co.uk%2Fimg%2Fmuti-item-carousel-example.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frobkendal.co.uk%2Fimg%2Fmuti-item-carousel-example.png" title="Example of how a multi-item carousel can work" alt="Example of how a multi-item carousel can work"></a></p> <p>There are some existing packages in the wild, such as this one <a href="proxy.php?url=https://www.npmjs.com/package/react-multi-carousel" rel="noopener noreferrer">react multi carousel</a>, which is worth a look, but often they're too complex, or just not what we need.</p> <p>What we're building here is a simple, minimal (as possible), example that fits the definition above, and to my mind embodies the wider definition of a multi-item carousel built in React.</p> <p>You can view the finished multi-item carousel here <a href="proxy.php?url=https://codesandbox.io/s/react-multi-item-carousel-uvmchp" rel="noopener noreferrer">https://codesandbox.io/s/react-multi-item-carousel-uvmchp</a>.</p> <p>Also, there's a repo with the code in here, <a href="proxy.php?url=https://github.com/bpk68/react-carousel" rel="noopener noreferrer">react-carousel on GitHub</a>.</p> <h3> Tools used </h3> <p>For this React multi-item carousel, I've chosen to build it using the <a href="proxy.php?url=https://vitejs.dev/" rel="noopener noreferrer">really popular Vite.js</a> and <a href="proxy.php?url=https://tailwindcss.com/" rel="noopener noreferrer">Tailwind CSS</a>. Tailwind just allows for rapid building of websites and apps by removing all the fluff of starting with a blank CSS slate and gives us the utility-based CSS building blocks to quickly put things like this together.</p> <p>Vite is just used to bundle and build our JavaScript so React works, but you can use whatever you like. Same with Tailwind -- use any CSS you like, but obviously you'll have to code the matching styles that Tailwind gives us into your version.</p> <p>The key point in the demo is the <code>carousel.jsx</code> component that we'll see in a minute.</p> <h2> Building the multi-image carousel in React </h2> <p>Enough preamble; let's build our multi-image carousel in React and Tailwind, starting with the scaffolding parts.</p> <p>The exact set-up with Vite and Tailwind is outside the scope of this article, so I'm assuming you have some sort of React project set up and ready to go that also has Tailwind installed and configured.</p> <h3> Carousel data </h3> <p>In the finished demo you'll see that each carousel item looks like this:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code><span class="p">&lt;</span><span class="nt">div</span> <span class="na">key</span><span class="p">=</span><span class="si">{</span><span class="nx">index</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"..."</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="p">=</span><span class="si">{</span><span class="nx">resource</span><span class="p">.</span><span class="nx">link</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"..."</span> <span class="na">style</span><span class="p">=</span><span class="si">{</span><span class="p">{</span> <span class="na">backgroundImage</span><span class="p">:</span> <span class="s2">`url(</span><span class="p">${</span><span class="nx">resource</span><span class="p">.</span><span class="nx">imageUrl</span> <span class="o">||</span> <span class="dl">''</span><span class="p">}</span><span class="s2">)`</span> <span class="p">}</span><span class="si">}</span> <span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">img</span> <span class="na">src</span><span class="p">=</span><span class="si">{</span><span class="nx">resource</span><span class="p">.</span><span class="nx">imageUrl</span> <span class="o">||</span> <span class="dl">''</span><span class="si">}</span> <span class="na">alt</span><span class="p">=</span><span class="si">{</span><span class="nx">resource</span><span class="p">.</span><span class="nx">title</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"..."</span> <span class="p">/&gt;</span> <span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="p">=</span><span class="si">{</span><span class="nx">resource</span><span class="p">.</span><span class="nx">link</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"..."</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">h3</span> <span class="na">className</span><span class="p">=</span><span class="s">"..."</span><span class="p">&gt;</span><span class="si">{</span><span class="nx">resource</span><span class="p">.</span><span class="nx">title</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">h3</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> </code></pre> </div> <p>And that's it. That's a single carousel item. I've omitted the Tailwind classes from this to keep it a little cleaner, but you can see that this could be whatever you like to show in the carousel, I've just gone with images inside a clickable link, then a heading level 3 that will be displayed on hover.</p> <p>The main point here is that we're pulling data in to use where we have things like <code>href={resource.link}</code> from a file <code>data.json</code>, which looks like this:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight json"><code><span class="p">{</span><span class="w"> </span><span class="nl">"resources"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Find me on Twitter"</span><span class="p">,</span><span class="w"> </span><span class="nl">"link"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://twitter.com/kendalmintcode"</span><span class="p">,</span><span class="w"> </span><span class="nl">"imageUrl"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://placeimg.com/300/300/any"</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Welcome to Ark Labs"</span><span class="p">,</span><span class="w"> </span><span class="nl">"link"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://ark-labs.co.uk"</span><span class="p">,</span><span class="w"> </span><span class="nl">"imageUrl"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://placeimg.com/300/300/animals"</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Some sort of third title"</span><span class="p">,</span><span class="w"> </span><span class="nl">"link"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://twitter.com/kendalmintcode"</span><span class="p">,</span><span class="w"> </span><span class="nl">"imageUrl"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://placeimg.com/300/300/architecture"</span><span class="w"> </span><span class="p">},</span><span class="w"> </span><span class="err">...other</span><span class="w"> </span><span class="err">entries</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Super item number the last"</span><span class="p">,</span><span class="w"> </span><span class="nl">"link"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://twitter.com/kendalmintcode"</span><span class="p">,</span><span class="w"> </span><span class="nl">"imageUrl"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://placeimg.com/300/300/tech"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre> </div> <p>You can see we have a title, link URL and image source URL in an array of objects called resources. When this <code>data.json</code> file is imported into the carousel component we can loop through each resource item, which will become a single carousel item.</p> <p>Again, you could bend this to your needs and this data might even come from an API (the real project I use this in does just that), but it'll keep things cleaner inside our carousel component for now.</p> <h3> Basic styles </h3> <p>The only other thing to highlight from the demo point of view is the starting styles. In the main App component, <code>app.jsx</code> we have this:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code><span class="kd">function</span> <span class="nf">App</span><span class="p">()</span> <span class="p">{</span> <span class="k">return </span><span class="p">(</span> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"2xl:container 2xl:mx-auto 2xl:px-0 py-3 px-10"</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nc">Carousel</span> <span class="p">/&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">);</span> <span class="p">}</span> </code></pre> </div> <p>Really simple and all it's doing is pulling in the carousel component and wrapping it in a div with some basic tailwind classes on it, to fix the width on very large screens and add some nice padding around the carousel for nicer display purposes.</p> <p>Next, in the <code>styles.css</code> file, some basic CSS:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight css"><code><span class="k">@tailwind</span> <span class="n">base</span><span class="p">;</span> <span class="k">@tailwind</span> <span class="n">components</span><span class="p">;</span> <span class="k">@tailwind</span> <span class="n">utilities</span><span class="p">;</span> <span class="nt">html</span><span class="o">,</span> <span class="nt">body</span> <span class="p">{</span> <span class="nl">padding</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span> <span class="nl">margin</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span> <span class="p">}</span> <span class="o">*</span> <span class="p">{</span> <span class="nl">box-sizing</span><span class="p">:</span> <span class="n">border-box</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <p>Up top we have the necessary Tailwind imports, then we just strip off the padding and margin from body and HTML, and set all box-sizing to <code>border-box</code>.</p> <p>Again, these styles aren't super important for your purposes, but I want to be clear up-front about where some minor little display styles are and what they're doing.</p> <h2> The multi-item carousel component </h2> <p>And now, the part you've been waiting for, the main carousel component itself. In the <code>carousel.jsx</code> component you'll see the following imports:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">useState</span><span class="p">,</span> <span class="nx">useRef</span><span class="p">,</span> <span class="nx">useEffect</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span><span class="p">;</span> <span class="c1">// Data</span> <span class="k">import</span> <span class="nx">data</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./data.json</span><span class="dl">'</span><span class="p">;</span> </code></pre> </div> <p>We've already mentioned the data that we're pulling in from our JSON file, but yours might be coming from your own JSON data, an API, a database, wherever. The key point here is that we're going to be using three hooks from React, <code>useState</code>, <code>useRef</code> and <code>useEffect</code>.</p> <h3> The carousel JSX </h3> <p>Perhaps counterintuitively we'll start with the output JSX from the component. It looks like this:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code><span class="k">return </span><span class="p">(</span> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"carousel my-12 mx-auto"</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">h2</span> <span class="na">className</span><span class="p">=</span><span class="s">"text-4xl leading-8 font-semibold mb-12 text-slate-700"</span><span class="p">&gt;</span> Our epic carousel <span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"relative overflow-hidden"</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"flex justify-between absolute top left w-full h-full"</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">button</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="nx">movePrev</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"hover:bg-blue-900/75 text-white w-10 h-full text-center opacity-75 hover:opacity-100 disabled:opacity-25 disabled:cursor-not-allowed z-10 p-0 m-0 transition-all ease-in-out duration-300"</span> <span class="na">disabled</span><span class="p">=</span><span class="si">{</span><span class="nf">isDisabled</span><span class="p">(</span><span class="dl">'</span><span class="s1">prev</span><span class="dl">'</span><span class="p">)</span><span class="si">}</span> <span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">svg</span> <span class="na">xmlns</span><span class="p">=</span><span class="s">"http://www.w3.org/2000/svg"</span> <span class="na">className</span><span class="p">=</span><span class="s">"h-12 w-20 -ml-5"</span> <span class="na">fill</span><span class="p">=</span><span class="s">"none"</span> <span class="na">viewBox</span><span class="p">=</span><span class="s">"0 0 24 24"</span> <span class="na">stroke</span><span class="p">=</span><span class="s">"currentColor"</span> <span class="na">strokeWidth</span><span class="p">=</span><span class="si">{</span><span class="mi">2</span><span class="si">}</span> <span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">path</span> <span class="na">strokeLinecap</span><span class="p">=</span><span class="s">"round"</span> <span class="na">strokeLinejoin</span><span class="p">=</span><span class="s">"round"</span> <span class="na">d</span><span class="p">=</span><span class="s">"M15 19l-7-7 7-7"</span> <span class="p">/&gt;</span> <span class="p">&lt;/</span><span class="nt">svg</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">span</span> <span class="na">className</span><span class="p">=</span><span class="s">"sr-only"</span><span class="p">&gt;</span>Prev<span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">button</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="nx">moveNext</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"hover:bg-blue-900/75 text-white w-10 h-full text-center opacity-75 hover:opacity-100 disabled:opacity-25 disabled:cursor-not-allowed z-10 p-0 m-0 transition-all ease-in-out duration-300"</span> <span class="na">disabled</span><span class="p">=</span><span class="si">{</span><span class="nf">isDisabled</span><span class="p">(</span><span class="dl">'</span><span class="s1">next</span><span class="dl">'</span><span class="p">)</span><span class="si">}</span> <span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">svg</span> <span class="na">xmlns</span><span class="p">=</span><span class="s">"http://www.w3.org/2000/svg"</span> <span class="na">className</span><span class="p">=</span><span class="s">"h-12 w-20 -ml-5"</span> <span class="na">fill</span><span class="p">=</span><span class="s">"none"</span> <span class="na">viewBox</span><span class="p">=</span><span class="s">"0 0 24 24"</span> <span class="na">stroke</span><span class="p">=</span><span class="s">"currentColor"</span> <span class="na">strokeWidth</span><span class="p">=</span><span class="si">{</span><span class="mi">2</span><span class="si">}</span> <span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">path</span> <span class="na">strokeLinecap</span><span class="p">=</span><span class="s">"round"</span> <span class="na">strokeLinejoin</span><span class="p">=</span><span class="s">"round"</span> <span class="na">d</span><span class="p">=</span><span class="s">"M9 5l7 7-7 7"</span> <span class="p">/&gt;</span> <span class="p">&lt;/</span><span class="nt">svg</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">span</span> <span class="na">className</span><span class="p">=</span><span class="s">"sr-only"</span><span class="p">&gt;</span>Next<span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">ref</span><span class="p">=</span><span class="si">{</span><span class="nx">carousel</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"carousel-container relative flex gap-1 overflow-hidden scroll-smooth snap-x snap-mandatory touch-pan-x z-0"</span> <span class="p">&gt;</span> <span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">resources</span><span class="p">.</span><span class="nf">map</span><span class="p">((</span><span class="nx">resource</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">return </span><span class="p">(</span> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">key</span><span class="p">=</span><span class="si">{</span><span class="nx">index</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"carousel-item text-center relative w-64 h-64 snap-start"</span> <span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="p">=</span><span class="si">{</span><span class="nx">resource</span><span class="p">.</span><span class="nx">link</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"h-full w-full aspect-square block bg-origin-padding bg-left-top bg-cover bg-no-repeat z-0"</span> <span class="na">style</span><span class="p">=</span><span class="si">{</span><span class="p">{</span> <span class="na">backgroundImage</span><span class="p">:</span> <span class="s2">`url(</span><span class="p">${</span><span class="nx">resource</span><span class="p">.</span><span class="nx">imageUrl</span> <span class="o">||</span> <span class="dl">''</span><span class="p">}</span><span class="s2">)`</span> <span class="p">}</span><span class="si">}</span> <span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">img</span> <span class="na">src</span><span class="p">=</span><span class="si">{</span><span class="nx">resource</span><span class="p">.</span><span class="nx">imageUrl</span> <span class="o">||</span> <span class="dl">''</span><span class="si">}</span> <span class="na">alt</span><span class="p">=</span><span class="si">{</span><span class="nx">resource</span><span class="p">.</span><span class="nx">title</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"w-full aspect-square hidden"</span> <span class="p">/&gt;</span> <span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="p">=</span><span class="si">{</span><span class="nx">resource</span><span class="p">.</span><span class="nx">link</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"h-full w-full aspect-square block absolute top-0 left-0 transition-opacity duration-300 opacity-0 hover:opacity-100 bg-blue-800/75 z-10"</span> <span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">h3</span> <span class="na">className</span><span class="p">=</span><span class="s">"text-white py-6 px-3 mx-auto text-xl"</span><span class="p">&gt;</span> <span class="si">{</span><span class="nx">resource</span><span class="p">.</span><span class="nx">title</span><span class="si">}</span> <span class="p">&lt;/</span><span class="nt">h3</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">);</span> <span class="p">})</span><span class="si">}</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">);</span> </code></pre> </div> <p>Breaking that down a little, we start with a simple container and heading level 2:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code><span class="p">&lt;</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"my-12 mx-auto"</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">h2</span> <span class="na">className</span><span class="p">=</span><span class="s">"text-4xl leading-8 font-semibold mb-12 text-slate-700"</span><span class="p">&gt;</span> Our epic carousel <span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"relative overflow-hidden"</span><span class="p">&gt;</span>... rest of carousel jsx<span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> </code></pre> </div> <p>Nothing too fancy thus far, we're just adding some vertical margins and displaying it centrally on the screen. With the heading, we're adjusting the size to suit our needs.</p> <p>The <code>div</code> that features the <code>relative overflow-hidden</code> classes will house our images or items and the left and right controls. We hide the overflow so we can scroll it into view later, and the <code>relative</code> class allows us to absolutely position the scroll buttons.</p> <p>Next up, we have a block that houses our left and right scroll buttons:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code><span class="p">&lt;</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"flex justify-between absolute top left w-full h-full"</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">button</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="nx">movePrev</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"hover:bg-blue-900/75 text-white w-10 h-full text-center opacity-75 hover:opacity-100 disabled:opacity-25 disabled:cursor-not-allowed z-10 p-0 m-0 transition-all ease-in-out duration-300"</span> <span class="na">disabled</span><span class="p">=</span><span class="si">{</span><span class="nf">isDisabled</span><span class="p">(</span><span class="dl">'</span><span class="s1">prev</span><span class="dl">'</span><span class="p">)</span><span class="si">}</span> <span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">svg</span> <span class="na">xmlns</span><span class="p">=</span><span class="s">"http://www.w3.org/2000/svg"</span> <span class="na">className</span><span class="p">=</span><span class="s">"h-12 w-20 -ml-5"</span> <span class="na">fill</span><span class="p">=</span><span class="s">"none"</span> <span class="na">viewBox</span><span class="p">=</span><span class="s">"0 0 24 24"</span> <span class="na">stroke</span><span class="p">=</span><span class="s">"currentColor"</span> <span class="na">strokeWidth</span><span class="p">=</span><span class="si">{</span><span class="mi">2</span><span class="si">}</span> <span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">path</span> <span class="na">strokeLinecap</span><span class="p">=</span><span class="s">"round"</span> <span class="na">strokeLinejoin</span><span class="p">=</span><span class="s">"round"</span> <span class="na">d</span><span class="p">=</span><span class="s">"M15 19l-7-7 7-7"</span> <span class="p">/&gt;</span> <span class="p">&lt;/</span><span class="nt">svg</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">span</span> <span class="na">className</span><span class="p">=</span><span class="s">"sr-only"</span><span class="p">&gt;</span>Prev<span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">button</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="nx">moveNext</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"hover:bg-blue-900/75 text-white w-10 h-full text-center opacity-75 hover:opacity-100 disabled:opacity-25 disabled:cursor-not-allowed z-10 p-0 m-0 transition-all ease-in-out duration-300"</span> <span class="na">disabled</span><span class="p">=</span><span class="si">{</span><span class="nf">isDisabled</span><span class="p">(</span><span class="dl">'</span><span class="s1">next</span><span class="dl">'</span><span class="p">)</span><span class="si">}</span> <span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">svg</span> <span class="na">xmlns</span><span class="p">=</span><span class="s">"http://www.w3.org/2000/svg"</span> <span class="na">className</span><span class="p">=</span><span class="s">"h-12 w-20 -ml-5"</span> <span class="na">fill</span><span class="p">=</span><span class="s">"none"</span> <span class="na">viewBox</span><span class="p">=</span><span class="s">"0 0 24 24"</span> <span class="na">stroke</span><span class="p">=</span><span class="s">"currentColor"</span> <span class="na">strokeWidth</span><span class="p">=</span><span class="si">{</span><span class="mi">2</span><span class="si">}</span> <span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">path</span> <span class="na">strokeLinecap</span><span class="p">=</span><span class="s">"round"</span> <span class="na">strokeLinejoin</span><span class="p">=</span><span class="s">"round"</span> <span class="na">d</span><span class="p">=</span><span class="s">"M9 5l7 7-7 7"</span> <span class="p">/&gt;</span> <span class="p">&lt;/</span><span class="nt">svg</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">span</span> <span class="na">className</span><span class="p">=</span><span class="s">"sr-only"</span><span class="p">&gt;</span>Next<span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> </code></pre> </div> <p>One of the downsides to Tailwind is that the class lists get quite verbose and subsequent JSX gets a little longer, but we'll break each part down here.</p> <p>Starting with the scroll left/right button container:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code><span class="p">&lt;</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"flex justify-between absolute top left w-full h-full"</span><span class="p">&gt;</span> ...buttons <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> </code></pre> </div> <p>We position the block absolutely, add flexbox to the display type, then push the child items (i.e. left/right buttons) to the far left and right edges using <code>justify-between</code>. Finally, we force the container to have full width and height.</p> <p>Next up, the buttons:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code><span class="p">&lt;</span><span class="nt">button</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="nx">movePrev</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"hover:bg-blue-900/75 text-white w-10 h-full text-center opacity-75 hover:opacity-100 disabled:opacity-25 disabled:cursor-not-allowed z-10 p-0 m-0 transition-all ease-in-out duration-300"</span> <span class="na">disabled</span><span class="p">=</span><span class="si">{</span><span class="nf">isDisabled</span><span class="p">(</span><span class="dl">'</span><span class="s1">prev</span><span class="dl">'</span><span class="p">)</span><span class="si">}</span> <span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">svg</span> <span class="na">xmlns</span><span class="p">=</span><span class="s">"http://www.w3.org/2000/svg"</span> <span class="na">className</span><span class="p">=</span><span class="s">"h-12 w-20 -ml-5"</span> <span class="na">fill</span><span class="p">=</span><span class="s">"none"</span> <span class="na">viewBox</span><span class="p">=</span><span class="s">"0 0 24 24"</span> <span class="na">stroke</span><span class="p">=</span><span class="s">"currentColor"</span> <span class="na">strokeWidth</span><span class="p">=</span><span class="si">{</span><span class="mi">2</span><span class="si">}</span> <span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">path</span> <span class="na">strokeLinecap</span><span class="p">=</span><span class="s">"round"</span> <span class="na">strokeLinejoin</span><span class="p">=</span><span class="s">"round"</span> <span class="na">d</span><span class="p">=</span><span class="s">"M15 19l-7-7 7-7"</span> <span class="p">/&gt;</span> <span class="p">&lt;/</span><span class="nt">svg</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">span</span> <span class="na">className</span><span class="p">=</span><span class="s">"sr-only"</span><span class="p">&gt;</span>Prev<span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span> </code></pre> </div> <p>I'm just showing the 'prev' button for now as both buttons are the same, just the SVG icon differs between left and right chevron. We're assigning the function <code>movePrev</code> to the button's click handler. The other button has a matching <code>moveNext</code> click handler and we'll define these click handlers in the logic section coming up.</p> <p>Both buttons have a <code>disabled</code> property that's calculated using the <code>isDisabled()</code> function that we'll also cover next in the logic section of the article.</p> <p>And each button has a butt load of Tailwind classes on it, but they essentially do the following:</p> <ul> <li>Add base background colours and opacity</li> <li>Add hover colors with less opactiy</li> <li>Add disabled styles (i.e. when you can't move left or right any further)</li> <li>Set the height and width</li> <li>Set some base transitions just for nice look and feel when you hover over them</li> </ul> <p>The other thing of note here is that we've included a simple span element with the <code>sr-only</code> class so that screen readers can still understand what they're dealing with. If we just have images or SVGs on there it'll be harder for less abled or visually impaired users to understand what the button is and does.</p> <p>We're using SVG icons from the excellent (and free!) heroicons, which is another Tailwind CSS product, but you could use your own icons, no icons, whatever you like here.</p> <p>And finally, we'll look at the main carousel item loop:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code><span class="p">&lt;</span><span class="nt">div</span> <span class="na">ref</span><span class="p">=</span><span class="si">{</span><span class="nx">carousel</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"carousel-container relative flex gap-1 overflow-hidden scroll-smooth snap-x snap-mandatory touch-pan-x z-0"</span> <span class="p">&gt;</span> <span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">resources</span><span class="p">.</span><span class="nf">map</span><span class="p">((</span><span class="nx">resource</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">return </span><span class="p">(</span> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">key</span><span class="p">=</span><span class="si">{</span><span class="nx">index</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"carousel-item text-center relative w-64 h-64 snap-start"</span> <span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="p">=</span><span class="si">{</span><span class="nx">resource</span><span class="p">.</span><span class="nx">link</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"h-full w-full aspect-square block bg-origin-padding bg-left-top bg-cover bg-no-repeat z-0"</span> <span class="na">style</span><span class="p">=</span><span class="si">{</span><span class="p">{</span> <span class="na">backgroundImage</span><span class="p">:</span> <span class="s2">`url(</span><span class="p">${</span><span class="nx">resource</span><span class="p">.</span><span class="nx">imageUrl</span> <span class="o">||</span> <span class="dl">''</span><span class="p">}</span><span class="s2">)`</span> <span class="p">}</span><span class="si">}</span> <span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">img</span> <span class="na">src</span><span class="p">=</span><span class="si">{</span><span class="nx">resource</span><span class="p">.</span><span class="nx">imageUrl</span> <span class="o">||</span> <span class="dl">''</span><span class="si">}</span> <span class="na">alt</span><span class="p">=</span><span class="si">{</span><span class="nx">resource</span><span class="p">.</span><span class="nx">title</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"w-full aspect-square hidden"</span> <span class="p">/&gt;</span> <span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="p">=</span><span class="si">{</span><span class="nx">resource</span><span class="p">.</span><span class="nx">link</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"h-full w-full aspect-square block absolute top-0 left-0 transition-opacity duration-300 opacity-0 hover:opacity-100 bg-blue-800/75 z-10"</span> <span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">h3</span> <span class="na">className</span><span class="p">=</span><span class="s">"text-white py-6 px-3 mx-auto text-xl"</span><span class="p">&gt;</span> <span class="si">{</span><span class="nx">resource</span><span class="p">.</span><span class="nx">title</span><span class="si">}</span> <span class="p">&lt;/</span><span class="nt">h3</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">);</span> <span class="p">})</span><span class="si">}</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> </code></pre> </div> <p>With our final JSX, we start with some classes that hide the overflow of any child items, display child items using flexbox, provide a basic gap between carousel items, and then add a bunch of scroll snap styles using <a href="proxy.php?url=https://tailwindcss.com/docs/scroll-snap-type" rel="noopener noreferrer">Tailwind's handy scroll-snap</a> facilities.</p> <p>The scroll snap stuff isn't 100% necessary but it's recommended as it adds a nice little feel to how each item <em>snaps</em> into place when scrolled left to right, and helps prevent the scroll ending up weird half-way place between image items.</p> <p>Next up we have a classic React pattern of looping through some sort of array with the <code>.map()</code> function and spitting out some repeated JSX for each iteration.</p> <p>For each resource item we produce the following JSX:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code><span class="p">&lt;</span><span class="nt">div</span> <span class="na">key</span><span class="p">=</span><span class="si">{</span><span class="nx">index</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"carousel-item text-center relative w-64 h-64 snap-start"</span> <span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="p">=</span><span class="si">{</span><span class="nx">resource</span><span class="p">.</span><span class="nx">link</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"h-full w-full aspect-square block bg-origin-padding bg-left-top bg-cover bg-no-repeat z-0"</span> <span class="na">style</span><span class="p">=</span><span class="si">{</span><span class="p">{</span> <span class="na">backgroundImage</span><span class="p">:</span> <span class="s2">`url(</span><span class="p">${</span><span class="nx">resource</span><span class="p">.</span><span class="nx">imageUrl</span> <span class="o">||</span> <span class="dl">''</span><span class="p">}</span><span class="s2">)`</span> <span class="p">}</span><span class="si">}</span> <span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">img</span> <span class="na">src</span><span class="p">=</span><span class="si">{</span><span class="nx">resource</span><span class="p">.</span><span class="nx">imageUrl</span> <span class="o">||</span> <span class="dl">''</span><span class="si">}</span> <span class="na">alt</span><span class="p">=</span><span class="si">{</span><span class="nx">resource</span><span class="p">.</span><span class="nx">title</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"w-full aspect-square hidden"</span> <span class="p">/&gt;</span> <span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="p">=</span><span class="si">{</span><span class="nx">resource</span><span class="p">.</span><span class="nx">link</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"h-full w-full aspect-square block absolute top-0 left-0 transition-opacity duration-300 opacity-0 hover:opacity-100 bg-blue-800/75 z-10"</span> <span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">h3</span> <span class="na">className</span><span class="p">=</span><span class="s">"text-white py-6 px-3 mx-auto text-xl"</span><span class="p">&gt;</span><span class="si">{</span><span class="nx">resource</span><span class="p">.</span><span class="nx">title</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">h3</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> </code></pre> </div> <p>This is what we saw in the early part of the article but with the Tailwind classes back in. What's happening here is that we have two blocks for each resource item.</p> <p>The first block has a forced square width and height as well as centring any text. Inside of this, we have a link and an image. We're using an image-hiding pattern here that aids accessibility whilst giving us a tip-top UI. The image is given a <code>src</code> property and an alt tag, but is visually hidden from display. This allows screen readers to <em>see</em> the image but handles situations where the image is a wonky or irregular shape.</p> <p>We attach the same image URL to the background property of the item and then set background styles via Tailwind to centralise and cover the full height and width of the item with the image.</p> <p>The second block is another link that contains a heading level 3 element with the resource's title. Like its image block friend, it's given a full height and width, but 0% opacity so it's effectively 'hidden' from view (hiding in plain sight 😆).</p> <p>When hovered on it's given a full opacity with a translucent background colour and contrasting white text. It's also positioned absolutely so we can display it on top of the image; the z-index value of 10 really helps here too.</p> <p>This combination pattern of having some sort of image with some sort of hovered content appearing is very common. Although it's worth bearing in mind that <strong>for mobile purposes you'd likely want an alternative approach</strong> as the hover stuff won't work.</p> <h3> The carousel logic </h3> <p>Now for the fun part: making the carousel be more, well, carouselly...</p> <p>Let's start with the component definition and initial variables:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code><span class="kd">const</span> <span class="nx">Carousel</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">maxScrollWidth</span> <span class="o">=</span> <span class="nf">useRef</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">currentIndex</span><span class="p">,</span> <span class="nx">setCurrentIndex</span><span class="p">]</span> <span class="o">=</span> <span class="nf">useState</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span> <span class="kd">const</span> <span class="nx">carousel</span> <span class="o">=</span> <span class="nf">useRef</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span> </code></pre> </div> <p>We define the component and then set up some variables:</p> <ul> <li> <code>maxScrollWidth</code> - we're going to store the carousel's total scrollable width in this variable once we finish rendering the component. We're using the <code>useRef</code> Hook from React here because it allows us to create a fixed or static value that won't change between renders. Important because the component is likely to rerender by clicking the prev/next buttons.</li> <li> <code>currentIndex</code> - this is a simple state value that will keep track of what 'page' we're on. It'll help us later on to determine if we can move forward or backwards.</li> <li> <code>carousel</code> - we're using the <code>useRef</code> Hook again, but this time to create a static reference to the underlying DOM element that is a <code>div</code> which houses the carousel contents. We'll need this to help work out when and how to scroll and get values relating to the carousel's width.</li> </ul> <p>With the variables in place, let's look at the scrolling handlers...<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code><span class="kd">const</span> <span class="nx">movePrev</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">if </span><span class="p">(</span><span class="nx">currentIndex</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="nf">setCurrentIndex</span><span class="p">((</span><span class="nx">prevState</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">prevState</span> <span class="o">-</span> <span class="mi">1</span><span class="p">);</span> <span class="p">}</span> <span class="p">};</span> </code></pre> </div> <p>For moving backwards, the <code>movePrev</code> function handles button clicks on the 'prev' button. We check to see if the <code>currentIndex</code> value is greater than zero and if it <em>is</em>, then we simply update the value in state to one <em>less</em> than the current value.</p> <p>If we're already at zero then it doesn't make sense to go back anymore so the function short circuits out and doesn't do anything.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code><span class="kd">const</span> <span class="nx">moveNext</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">if </span><span class="p">(</span> <span class="nx">carousel</span><span class="p">.</span><span class="nx">current</span> <span class="o">!==</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="nx">carousel</span><span class="p">.</span><span class="nx">current</span><span class="p">.</span><span class="nx">offsetWidth</span> <span class="o">*</span> <span class="nx">currentIndex</span> <span class="o">&lt;=</span> <span class="nx">maxScrollWidth</span><span class="p">.</span><span class="nx">current</span> <span class="p">)</span> <span class="p">{</span> <span class="nf">setCurrentIndex</span><span class="p">((</span><span class="nx">prevState</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">prevState</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span> <span class="p">}</span> <span class="p">};</span> </code></pre> </div> <p>When the 'next' button is clicked it's <code>moveNext</code>'s time to shine. We're essentially doing the exact opposite of the <code>movePrev</code> function but things are a bit trickier. When moving backwards we just need to know when we hit zero. But when scrolling <em>forwards</em> we don't know how many times we can do that, it's not a hard limit defined by a single number.</p> <p>Instead, we need to work out if the currently visible slice (i.e. width) of the carousel, times the current <em>page</em>, is going to be <em>less than</em> the maximum scrollable width of the carousel's content -- i.e. the carousel's total width, even that which isn't visible.</p> <p>If it's going to be <em>more</em> than the max-width, it doesn't make sense to allow users to scroll anymore, so we don't do anything.</p> <p>However, if our conditional statement passes, we do the opposite of <code>movePrev</code> and update the <code>currentIndex</code> value in state to one higher than its current value.</p> <blockquote> <p>On their own, these button click handlers don't physically scroll the carousel contents, but we'll see in a moment how we can watch the value of <code>currentIndex</code> using the <code>useEffect</code> Hook to make that happen.</p> </blockquote> <p>Next up, our <code>isDisabled</code> helper function:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code><span class="kd">const</span> <span class="nx">isDisabled</span> <span class="o">=</span> <span class="p">(</span><span class="nx">direction</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">if </span><span class="p">(</span><span class="nx">direction</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">prev</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">currentIndex</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span> <span class="k">if </span><span class="p">(</span><span class="nx">direction</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">next</span><span class="dl">'</span> <span class="o">&amp;&amp;</span> <span class="nx">carousel</span><span class="p">.</span><span class="nx">current</span> <span class="o">!==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span> <span class="k">return </span><span class="p">(</span> <span class="nx">carousel</span><span class="p">.</span><span class="nx">current</span><span class="p">.</span><span class="nx">offsetWidth</span> <span class="o">*</span> <span class="nx">currentIndex</span> <span class="o">&gt;=</span> <span class="nx">maxScrollWidth</span><span class="p">.</span><span class="nx">current</span> <span class="p">);</span> <span class="p">}</span> <span class="k">return</span> <span class="kc">false</span><span class="p">;</span> <span class="p">};</span> </code></pre> </div> <p>Whilst the <code>movePrev</code> and <code>moveNext</code> click handlers will take care of actually triggering a scroll (or not), our users won't get any visual cues that they can or can't actually scroll. That's where our <code>isDisabled</code> function comes in.</p> <p>On each render and rerender of the component, the buttons call out to the <code>isDisabled</code> function to see if their <code>disabled</code> attribute should be true, or false.</p> <p>It accepts a <code>direction</code> argument and checks that first. You'll see that the conditional statements are very similar to the <code>movePrev</code> and <code>moveNext</code> ones. If we can't scroll left (previous) anymore, then it'll return <em>true</em> so that the button is disabled. Likewise, if we can't scroll right (next) anymore we'll also return <em>true</em> so the next button is disabled.</p> <p>Failing all else, we'll just return <em>false</em> so that the buttons aren't disabled should the execution fall past our 'if' statements.</p> <p>If a button is disabled, then Tailwind's <code>disabled:</code> styles will kick in and the user will find it much more obvious as to what they can and can't do.</p> <p>Onto the part that makes the magic happen, the first <code>useEffect</code> Hook:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code><span class="nf">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">if </span><span class="p">(</span><span class="nx">carousel</span> <span class="o">!==</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="nx">carousel</span><span class="p">.</span><span class="nx">current</span> <span class="o">!==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span> <span class="nx">carousel</span><span class="p">.</span><span class="nx">current</span><span class="p">.</span><span class="nx">scrollLeft</span> <span class="o">=</span> <span class="nx">carousel</span><span class="p">.</span><span class="nx">current</span><span class="p">.</span><span class="nx">offsetWidth</span> <span class="o">*</span> <span class="nx">currentIndex</span><span class="p">;</span> <span class="p">}</span> <span class="p">},</span> <span class="p">[</span><span class="nx">currentIndex</span><span class="p">]);</span> </code></pre> </div> <p>It's a deceptively simple little function that powers the scrolling of the carousel. The Hook accepts an array of dependencies that cause the code inside the Hook to fire when any of their values change.</p> <p>In our case, we've added the <code>currentIndex</code> value as a dependency. So, when this value changes, say when we press the next or prev buttons, the code inside will run.</p> <p><a href="proxy.php?url=https://www.newline.co/courses/beginners-guide-to-real-world-react" rel="noopener noreferrer"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frobkendal.co.uk%2Fimg%2Freact-course-cta.png" alt="The Beginner's Guide to Real World React"></a></p> <p>The first thing that happens is a null check to make sure that we've actually got a reference to the underlying carousel <code>div</code> element from our <code>useRef</code> Hook.</p> <p>If we do, then we simply update the carousel's <code>scrollLeft</code> value to the carousel's currently visible width multiplied by the current index or page or <em>slice</em> of the content that we want to see.</p> <blockquote> <p>As a simplified example of the maths involved here think of it like this...</p> <p>If we have 10 items in our carousel each being 100 pixels wide, then we have a total scrollable width of 1000 pixels (10 items x 100 px).</p> <p>However, because of the size of screen, the <em>visible</em> width of the carousel is only 250 pixels (remember, all the overflow is hidden by the CSS). This means we'll only be able to see two and a half items at any one time.</p> <p>If we start from the initial view, 0 scroll left position, when we click 'next', the current index will be bumped up to '1'.</p> <p>Now, we need to scroll the visible content <code>currentIndex</code> times the currently visible content width (1 x 250 px). Our carousel's new <code>scrollLeft</code> value will become 250 px and the carousel's contents will scroll over.</p> </blockquote> <p>This will cause the contents of the carousel to scroll to the left and because of the smooth scroll and snap classes provided us by Tailwind, this happens nice and smoothly with a satisfying little 'snap' animation. Pretty neat!</p> <p>There's just one last thing to take care of and that's a <code>useEffect</code> that fires on component render:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code><span class="nf">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nx">maxScrollWidth</span><span class="p">.</span><span class="nx">current</span> <span class="o">=</span> <span class="nx">carousel</span><span class="p">.</span><span class="nx">current</span> <span class="p">?</span> <span class="nx">carousel</span><span class="p">.</span><span class="nx">current</span><span class="p">.</span><span class="nx">scrollWidth</span> <span class="o">-</span> <span class="nx">carousel</span><span class="p">.</span><span class="nx">current</span><span class="p">.</span><span class="nx">offsetWidth</span> <span class="p">:</span> <span class="mi">0</span><span class="p">;</span> <span class="p">},</span> <span class="p">[]);</span> </code></pre> </div> <p>We're passing in an empty array here, so this Hook only fires once, on the first component render. Its sole purpose is to get the carousel element's total scrollable content width <em>minus</em> the currently visible offset width value, and store this value in the <code>maxScrollWidth</code> ref value.</p> <p>This will give us the bounding boxes that allow us to work out how much to scroll, how many times we can scroll before we run out of road, and help make the magic happen.</p> <h2> The final multi-item carousel component </h2> <p>The full <code>carousel.jsx</code> component looks like this:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight jsx"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">useState</span><span class="p">,</span> <span class="nx">useRef</span><span class="p">,</span> <span class="nx">useEffect</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span><span class="p">;</span> <span class="c1">// Data</span> <span class="k">import</span> <span class="nx">data</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./data.json</span><span class="dl">'</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">Carousel</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">maxScrollWidth</span> <span class="o">=</span> <span class="nf">useRef</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">currentIndex</span><span class="p">,</span> <span class="nx">setCurrentIndex</span><span class="p">]</span> <span class="o">=</span> <span class="nf">useState</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span> <span class="kd">const</span> <span class="nx">carousel</span> <span class="o">=</span> <span class="nf">useRef</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span> <span class="kd">const</span> <span class="nx">movePrev</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">if </span><span class="p">(</span><span class="nx">currentIndex</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="nf">setCurrentIndex</span><span class="p">((</span><span class="nx">prevState</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">prevState</span> <span class="o">-</span> <span class="mi">1</span><span class="p">);</span> <span class="p">}</span> <span class="p">};</span> <span class="kd">const</span> <span class="nx">moveNext</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">if </span><span class="p">(</span> <span class="nx">carousel</span><span class="p">.</span><span class="nx">current</span> <span class="o">!==</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="nx">carousel</span><span class="p">.</span><span class="nx">current</span><span class="p">.</span><span class="nx">offsetWidth</span> <span class="o">*</span> <span class="nx">currentIndex</span> <span class="o">&lt;=</span> <span class="nx">maxScrollWidth</span><span class="p">.</span><span class="nx">current</span> <span class="p">)</span> <span class="p">{</span> <span class="nf">setCurrentIndex</span><span class="p">((</span><span class="nx">prevState</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">prevState</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span> <span class="p">}</span> <span class="p">};</span> <span class="kd">const</span> <span class="nx">isDisabled</span> <span class="o">=</span> <span class="p">(</span><span class="nx">direction</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">if </span><span class="p">(</span><span class="nx">direction</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">prev</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">currentIndex</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span> <span class="k">if </span><span class="p">(</span><span class="nx">direction</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">next</span><span class="dl">'</span> <span class="o">&amp;&amp;</span> <span class="nx">carousel</span><span class="p">.</span><span class="nx">current</span> <span class="o">!==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span> <span class="k">return </span><span class="p">(</span> <span class="nx">carousel</span><span class="p">.</span><span class="nx">current</span><span class="p">.</span><span class="nx">offsetWidth</span> <span class="o">*</span> <span class="nx">currentIndex</span> <span class="o">&gt;=</span> <span class="nx">maxScrollWidth</span><span class="p">.</span><span class="nx">current</span> <span class="p">);</span> <span class="p">}</span> <span class="k">return</span> <span class="kc">false</span><span class="p">;</span> <span class="p">};</span> <span class="nf">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">if </span><span class="p">(</span><span class="nx">carousel</span> <span class="o">!==</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="nx">carousel</span><span class="p">.</span><span class="nx">current</span> <span class="o">!==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span> <span class="nx">carousel</span><span class="p">.</span><span class="nx">current</span><span class="p">.</span><span class="nx">scrollLeft</span> <span class="o">=</span> <span class="nx">carousel</span><span class="p">.</span><span class="nx">current</span><span class="p">.</span><span class="nx">offsetWidth</span> <span class="o">*</span> <span class="nx">currentIndex</span><span class="p">;</span> <span class="p">}</span> <span class="p">},</span> <span class="p">[</span><span class="nx">currentIndex</span><span class="p">]);</span> <span class="nf">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nx">maxScrollWidth</span><span class="p">.</span><span class="nx">current</span> <span class="o">=</span> <span class="nx">carousel</span><span class="p">.</span><span class="nx">current</span> <span class="p">?</span> <span class="nx">carousel</span><span class="p">.</span><span class="nx">current</span><span class="p">.</span><span class="nx">scrollWidth</span> <span class="o">-</span> <span class="nx">carousel</span><span class="p">.</span><span class="nx">current</span><span class="p">.</span><span class="nx">offsetWidth</span> <span class="p">:</span> <span class="mi">0</span><span class="p">;</span> <span class="p">},</span> <span class="p">[]);</span> <span class="k">return </span><span class="p">(</span> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"carousel my-12 mx-auto"</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">h2</span> <span class="na">className</span><span class="p">=</span><span class="s">"text-4xl leading-8 font-semibold mb-12 text-slate-700"</span><span class="p">&gt;</span> Our epic carousel <span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"relative overflow-hidden"</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"flex justify-between absolute top left w-full h-full"</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">button</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="nx">movePrev</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"hover:bg-blue-900/75 text-white w-10 h-full text-center opacity-75 hover:opacity-100 disabled:opacity-25 disabled:cursor-not-allowed z-10 p-0 m-0 transition-all ease-in-out duration-300"</span> <span class="na">disabled</span><span class="p">=</span><span class="si">{</span><span class="nf">isDisabled</span><span class="p">(</span><span class="dl">'</span><span class="s1">prev</span><span class="dl">'</span><span class="p">)</span><span class="si">}</span> <span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">svg</span> <span class="na">xmlns</span><span class="p">=</span><span class="s">"http://www.w3.org/2000/svg"</span> <span class="na">className</span><span class="p">=</span><span class="s">"h-12 w-20 -ml-5"</span> <span class="na">fill</span><span class="p">=</span><span class="s">"none"</span> <span class="na">viewBox</span><span class="p">=</span><span class="s">"0 0 24 24"</span> <span class="na">stroke</span><span class="p">=</span><span class="s">"currentColor"</span> <span class="na">strokeWidth</span><span class="p">=</span><span class="si">{</span><span class="mi">2</span><span class="si">}</span> <span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">path</span> <span class="na">strokeLinecap</span><span class="p">=</span><span class="s">"round"</span> <span class="na">strokeLinejoin</span><span class="p">=</span><span class="s">"round"</span> <span class="na">d</span><span class="p">=</span><span class="s">"M15 19l-7-7 7-7"</span> <span class="p">/&gt;</span> <span class="p">&lt;/</span><span class="nt">svg</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">span</span> <span class="na">className</span><span class="p">=</span><span class="s">"sr-only"</span><span class="p">&gt;</span>Prev<span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">button</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="nx">moveNext</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"hover:bg-blue-900/75 text-white w-10 h-full text-center opacity-75 hover:opacity-100 disabled:opacity-25 disabled:cursor-not-allowed z-10 p-0 m-0 transition-all ease-in-out duration-300"</span> <span class="na">disabled</span><span class="p">=</span><span class="si">{</span><span class="nf">isDisabled</span><span class="p">(</span><span class="dl">'</span><span class="s1">next</span><span class="dl">'</span><span class="p">)</span><span class="si">}</span> <span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">svg</span> <span class="na">xmlns</span><span class="p">=</span><span class="s">"http://www.w3.org/2000/svg"</span> <span class="na">className</span><span class="p">=</span><span class="s">"h-12 w-20 -ml-5"</span> <span class="na">fill</span><span class="p">=</span><span class="s">"none"</span> <span class="na">viewBox</span><span class="p">=</span><span class="s">"0 0 24 24"</span> <span class="na">stroke</span><span class="p">=</span><span class="s">"currentColor"</span> <span class="na">strokeWidth</span><span class="p">=</span><span class="si">{</span><span class="mi">2</span><span class="si">}</span> <span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">path</span> <span class="na">strokeLinecap</span><span class="p">=</span><span class="s">"round"</span> <span class="na">strokeLinejoin</span><span class="p">=</span><span class="s">"round"</span> <span class="na">d</span><span class="p">=</span><span class="s">"M9 5l7 7-7 7"</span> <span class="p">/&gt;</span> <span class="p">&lt;/</span><span class="nt">svg</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">span</span> <span class="na">className</span><span class="p">=</span><span class="s">"sr-only"</span><span class="p">&gt;</span>Next<span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">ref</span><span class="p">=</span><span class="si">{</span><span class="nx">carousel</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"carousel-container relative flex gap-1 overflow-hidden scroll-smooth snap-x snap-mandatory touch-pan-x z-0"</span> <span class="p">&gt;</span> <span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">resources</span><span class="p">.</span><span class="nf">map</span><span class="p">((</span><span class="nx">resource</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">return </span><span class="p">(</span> <span class="p">&lt;</span><span class="nt">div</span> <span class="na">key</span><span class="p">=</span><span class="si">{</span><span class="nx">index</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"carousel-item text-center relative w-64 h-64 snap-start"</span> <span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="p">=</span><span class="si">{</span><span class="nx">resource</span><span class="p">.</span><span class="nx">link</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"h-full w-full aspect-square block bg-origin-padding bg-left-top bg-cover bg-no-repeat z-0"</span> <span class="na">style</span><span class="p">=</span><span class="si">{</span><span class="p">{</span> <span class="na">backgroundImage</span><span class="p">:</span> <span class="s2">`url(</span><span class="p">${</span><span class="nx">resource</span><span class="p">.</span><span class="nx">imageUrl</span> <span class="o">||</span> <span class="dl">''</span><span class="p">}</span><span class="s2">)`</span> <span class="p">}</span><span class="si">}</span> <span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">img</span> <span class="na">src</span><span class="p">=</span><span class="si">{</span><span class="nx">resource</span><span class="p">.</span><span class="nx">imageUrl</span> <span class="o">||</span> <span class="dl">''</span><span class="si">}</span> <span class="na">alt</span><span class="p">=</span><span class="si">{</span><span class="nx">resource</span><span class="p">.</span><span class="nx">title</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"w-full aspect-square hidden"</span> <span class="p">/&gt;</span> <span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="p">=</span><span class="si">{</span><span class="nx">resource</span><span class="p">.</span><span class="nx">link</span><span class="si">}</span> <span class="na">className</span><span class="p">=</span><span class="s">"h-full w-full aspect-square block absolute top-0 left-0 transition-opacity duration-300 opacity-0 hover:opacity-100 bg-blue-800/75 z-10"</span> <span class="p">&gt;</span> <span class="p">&lt;</span><span class="nt">h3</span> <span class="na">className</span><span class="p">=</span><span class="s">"text-white py-6 px-3 mx-auto text-xl"</span><span class="p">&gt;</span> <span class="si">{</span><span class="nx">resource</span><span class="p">.</span><span class="nx">title</span><span class="si">}</span> <span class="p">&lt;/</span><span class="nt">h3</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">);</span> <span class="p">})</span><span class="si">}</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> <span class="p">);</span> <span class="p">};</span> <span class="k">export</span> <span class="k">default</span> <span class="nx">Carousel</span><span class="p">;</span> </code></pre> </div> <h2> Viewing the final demo </h2> <p>Here's the finished carousel code embedded via CodeSandbox and you can <a href="proxy.php?url=https://codesandbox.io/s/react-multi-item-carousel-uvmchp" rel="noopener noreferrer">find a link to the sandbox here too</a>:</p> <p><iframe src="proxy.php?url=https://codesandbox.io/embed/react-multi-item-carousel-uvmchp"> </iframe> </p> javascript webdev tutorial react How to use contact forms with headless WordPress and Next.js Rob Kendal {{☕}} Fri, 03 Sep 2021 20:08:20 +0000 https://dev.to/kendalmintcode/how-to-use-contact-forms-with-headless-wordpress-and-next-js-4060 https://dev.to/kendalmintcode/how-to-use-contact-forms-with-headless-wordpress-and-next-js-4060 <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frobkendal.co.uk%2Fimg%2Fnext-js-with-wordpress-part-4-blog-post.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frobkendal.co.uk%2Fimg%2Fnext-js-with-wordpress-part-4-blog-post.png" title="How to use contact forms with headless WordPress and Next.js" alt="Blog header for How to use contact forms with headless WordPress and Next.js"></a></p> <p>If you’ve been following along with the series, you’ll have come across the previous posts:</p> <ul> <li><a href="proxy.php?url=https://dev.to/kendalmintcode/configuring-wordpress-as-a-headless-cms-with-next-js-3p1o">Configuring WordPress for use as a headless CMS and setting up a Next.js project</a></li> <li><a href="proxy.php?url=https://dev.to/kendalmintcode/using-wordpress-as-a-headless-cms-with-next-js-2h5p">Using WordPress as a headless CMS with Next.js</a></li> <li><a href="proxy.php?url=https://dev.to/kendalmintcode/create-a-next-js-rss-feed-for-your-static-website-210p">Create a Next.js RSS feed for your static website</a></li> </ul> <p>In this article, part 4, we’re going to cover a key part of and good website: handling contact forms within a static website.</p> <p><em>If you like this article, you’ll love the other helpful content I post on Twitter.</em><a href="proxy.php?url=https://twitter.com/kendalmintcode" rel="noopener noreferrer">Find me on Twitter @kendalmintcode</a><em>and say hi.</em></p> <h2> Contact forms and headless WordPress with Next.js </h2> <p>When it comes to allowing your visitors to send information to you via a contact form on your headless WordPress backend from a statically-generated front end, there are a few options, and I’m sure more are being added all the time.</p> <p>However, in my experience, there are two solid, reliable, stand-out options to choose from:</p> <ul> <li> <a href="proxy.php?url=https://www.netlify.com/products/forms/" rel="noopener noreferrer">Netlify Forms</a>.</li> <li> <a href="proxy.php?url=https://wpgraphqldocs.gatsbyjs.io/extenstion-plugins/wpgraphql-send-email/" rel="noopener noreferrer">The WPGraphQL WordPress plugin</a>.</li> </ul> <p>Let’s take a look at these options in more detail.</p> <h2> Netlify Forms </h2> <p><a href="proxy.php?url=https://www.netlify.com/products/forms/" rel="noopener noreferrer">Netlify Forms</a> is yet another super powerful extension to the ever-popular <a href="proxy.php?url=https://dev.toNetlify">Netlify</a> platform. It works so easily and simply, using the familiar magic that only Netlify has.</p> <p>It’s a cinch to set up with very minimal changes to your form HTML (or JSX in our case as we <em>are</em> dealing with React after all 😊), so let’s start with an example.</p> <blockquote> <p><strong>Wait!</strong> What if I don’t want to host things on Netlify? Good point. Netlify is awesome and I highly recommend it, but Vercel (the makers of Next) do the best job of hosting Next sites. That’s why we’re looking at the powerful Forms feature from Netlify <em>first</em> and then I’ll explain about the next best option.</p> </blockquote> <p>Here’s a typical HTML form that you might have in your React app:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">const</span> <span class="nx">MyContactForm</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">(</span> <span class="o">&lt;</span><span class="nx">form</span> <span class="nx">name</span><span class="o">=</span><span class="dl">"</span><span class="s2">contact</span><span class="dl">"</span> <span class="nx">method</span><span class="o">=</span><span class="dl">"</span><span class="s2">post</span><span class="dl">"</span><span class="o">&gt;</span> <span class="o">&lt;</span><span class="nx">p</span><span class="o">&gt;</span> <span class="o">&lt;</span><span class="nx">label</span><span class="o">&gt;</span><span class="nx">Your</span> <span class="nx">Name</span><span class="p">:</span> <span class="o">&lt;</span><span class="nx">input</span> <span class="nx">type</span><span class="o">=</span><span class="dl">"</span><span class="s2">text</span><span class="dl">"</span> <span class="nx">name</span><span class="o">=</span><span class="dl">"</span><span class="s2">name</span><span class="dl">"</span><span class="o">/&gt;&lt;</span><span class="sr">/label</span><span class="err">&gt; </span> <span class="o">&lt;</span><span class="sr">/p</span><span class="err">&gt; </span> <span class="o">&lt;</span><span class="nx">p</span><span class="o">&gt;</span> <span class="o">&lt;</span><span class="nx">label</span><span class="o">&gt;</span><span class="nx">Your</span> <span class="nx">Email</span><span class="p">:</span> <span class="o">&lt;</span><span class="nx">input</span> <span class="nx">type</span><span class="o">=</span><span class="dl">"</span><span class="s2">email</span><span class="dl">"</span> <span class="nx">name</span><span class="o">=</span><span class="dl">"</span><span class="s2">email</span><span class="dl">"</span><span class="o">/&gt;&lt;</span><span class="sr">/label</span><span class="err">&gt; </span> <span class="o">&lt;</span><span class="sr">/p</span><span class="err">&gt; </span> <span class="o">&lt;</span><span class="nx">p</span><span class="o">&gt;</span> <span class="o">&lt;</span><span class="nx">label</span><span class="o">&gt;</span><span class="nx">Message</span><span class="p">:</span> <span class="o">&lt;</span><span class="nx">textarea</span> <span class="nx">name</span><span class="o">=</span><span class="dl">"</span><span class="s2">message</span><span class="dl">"</span><span class="o">&gt;&lt;</span><span class="sr">/textarea&gt;&lt;/</span><span class="nx">label</span><span class="o">&gt;</span> <span class="o">&lt;</span><span class="sr">/p</span><span class="err">&gt; </span> <span class="o">&lt;</span><span class="nx">p</span><span class="o">&gt;</span> <span class="o">&lt;</span><span class="nx">button</span> <span class="nx">type</span><span class="o">=</span><span class="dl">"</span><span class="s2">submit</span><span class="dl">"</span><span class="o">&gt;</span><span class="nx">Send</span><span class="o">&lt;</span><span class="sr">/button</span><span class="err">&gt; </span> <span class="o">&lt;</span><span class="sr">/p</span><span class="err">&gt; </span> <span class="o">&lt;</span><span class="sr">/form</span><span class="err">&gt; </span><span class="p">);</span> </code></pre> </div> <p>Nothing too fancy there. To add Netlify’s form-handling powers to this then you need to do a few things:</p> <ol> <li>Add in a hidden input with a <code>form-name</code> attribute and provide the name of your form.</li> <li>Add in a <code>netlify</code> or <code>data-netlify</code> attribute to help Netlify identify the form.</li> <li>Add in a <code>data-netlify-honeypot</code> attribute to help avoid unnecessary captchas for your visitors.</li> </ol> <p>With these parts in place, the form now looks like this:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">const</span> <span class="nx">MyContactForm</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">(</span> <span class="o">&lt;</span><span class="nx">form</span> <span class="nx">name</span><span class="o">=</span><span class="dl">"</span><span class="s2">contact</span><span class="dl">"</span> <span class="nx">method</span><span class="o">=</span><span class="dl">"</span><span class="s2">post</span><span class="dl">"</span> <span class="nx">data</span><span class="o">-</span><span class="nx">netlify</span><span class="o">=</span><span class="dl">"</span><span class="s2">true</span><span class="dl">"</span> <span class="nx">data</span><span class="o">-</span><span class="nx">netlify</span><span class="o">-</span><span class="nx">honeypot</span><span class="o">=</span><span class="dl">"</span><span class="s2">bot-field</span><span class="dl">"</span> <span class="o">&gt;</span> <span class="o">&lt;</span><span class="nx">input</span> <span class="nx">type</span><span class="o">=</span><span class="dl">"</span><span class="s2">hidden</span><span class="dl">"</span> <span class="nx">name</span><span class="o">=</span><span class="dl">"</span><span class="s2">form-name</span><span class="dl">"</span> <span class="nx">value</span><span class="o">=</span><span class="dl">"</span><span class="s2">contact form</span><span class="dl">"</span> <span class="o">/&gt;</span> <span class="p">{</span><span class="cm">/* ...Rest of the form*/</span><span class="p">}</span> <span class="o">&lt;</span><span class="sr">/form</span><span class="err">&gt; </span><span class="p">);</span> </code></pre> </div> <p>Yeah, I know, it really is <em>that</em> simple. Depending on what React flavour your using (Next, Gatsby, etc.), you might need to add in a couple of additional small steps to make sure the form is wired up with Netlify. In this case you can read all the details on their blog post about <a href="proxy.php?url=https://www.netlify.com/blog/2017/07/20/how-to-integrate-netlifys-form-handling-in-a-react-app/#form-handling-with-static-site-generators" rel="noopener noreferrer">integrating Netlify Forms in a React App</a>.</p> <p>You can also read more in the <a href="proxy.php?url=https://docs.netlify.com/forms/setup/#javascript-forms" rel="noopener noreferrer">official Netlify Forms documentation</a>.</p> <h2> WPGraphQL Send Email </h2> <p>Our next option is to use the popular WordPress plugin <a href="proxy.php?url=https://en-gb.wordpress.org/plugins/add-wpgraphql-send-mail/" rel="noopener noreferrer">WPGraphQL Send Email</a>. Before we dive into the setup and implementation, head over to the plugin’s page on WordPress.org and download and install it, or search for it directly in your WordPress site and add it there.</p> <p>The WPGraphQL Send Email plugin wires up email sending capability into WordPress and exposes a GraphQL mutation inside of the WPGraphQL schema. This new <code>sendEmail</code> mutation enables you to send emails from your static front-end <em>via</em> WordPress.</p> <p><a href="proxy.php?url=https://www.newline.co/courses/beginners-guide-to-real-world-react/" rel="noopener noreferrer"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frobkendal.co.uk%2Fimg%2Freact-course-cta.png" title="Beginner's Guide to Real-World React" alt="course banner for beginners React course"></a></p> <p>You can integrate the sending capability with different providers too, such as SendGrid, Gmail, and Outlook/Office365. That part of the setup is a little beyond the scope of this article, but you’re free to use a simple SMTP server if you’d prefer — basically any service that can fire emails to other people.</p> <h2> Updating our Next.js site to use the WPGraphQL Send Email plugin </h2> <p>It’s not too difficult to plug all this into our Next site, but it does require a little more work than the Netlify Forms configuration.</p> <p>We need to do two things here:</p> <ol> <li>Add a new <code>async</code> API function to our <code>/lib/api.js</code> file.</li> <li>Add a new contact page complete with a new contact form.</li> </ol> <h3> Add a new API handling function </h3> <p>OK, so first things first we need to add a new function to our <code>/lib/api.js</code> file. This new function will be a GraphQL <em>mutation</em>. Its sole purpose will be to pass our website visitor’s form data to our WordPress backend. Here, the Send Email plugin (now wired into the WPGraphQL system) will handle the physical sending of the email to whomever we’ve set up in there.</p> <p>If you’re following on from the previous article on <a href="proxy.php?url=https://dev.to/kendalmintcode/using-wordpress-as-a-headless-cms-with-next-js-2h5p">Using WordPress as a Headless CMS with Next.js</a> then you can go ahead and open up the <code>/lib/api.js</code> file.</p> <blockquote> <p>If you’re reading this article fresh with no existing codebase, then you can download a great starter kit project to wire up WordPress with Next.js on my GitHub profile. Check out the <a href="proxy.php?url=https://github.com/bpk68/wordpress-next-starter" rel="noopener noreferrer">wordpress-next-starter repo</a> to get started.</p> </blockquote> <p>With the <code>api.js</code> file open and ready, add in the following new function:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="k">export</span> <span class="k">async</span> <span class="kd">function</span> <span class="nf">sendMail</span><span class="p">(</span><span class="nx">subject</span><span class="p">,</span> <span class="nx">body</span><span class="p">,</span> <span class="nx">mutationId</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">contact</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">fromAddress</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">[email protected]</span><span class="dl">'</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">toAddress</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">[email protected]</span><span class="dl">'</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">fetchAPI</span><span class="p">(</span> <span class="s2">` mutation SendEmail($input: SendEmailInput!) { sendEmail(input: $input) { message origin sent } } `</span><span class="p">,</span> <span class="p">{</span> <span class="na">variables</span><span class="p">:</span> <span class="p">{</span> <span class="na">input</span><span class="p">:</span> <span class="p">{</span> <span class="na">clientMutationId</span><span class="p">:</span> <span class="nx">mutationId</span><span class="p">,</span> <span class="na">from</span><span class="p">:</span> <span class="nx">fromAddress</span><span class="p">,</span> <span class="na">to</span><span class="p">:</span> <span class="nx">toAddress</span><span class="p">,</span> <span class="na">body</span><span class="p">:</span> <span class="nx">body</span><span class="p">,</span> <span class="na">subject</span><span class="p">:</span> <span class="nx">subject</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> <span class="p">);</span> <span class="k">return</span> <span class="nx">data</span><span class="p">?.</span><span class="nx">sendEmail</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <p>What’s going on here in this new <code>sendMail</code> API function? Well, the <a href="proxy.php?url=https://en-gb.wordpress.org/plugins/add-wpgraphql-send-mail/#description" rel="noopener noreferrer">official Send Mail plugin</a> isn’t hugely helpful in explaining this on the plugin info page, but let’s go over what’s what.</p> <p>First up, we have a function that accepts a <code>subject</code>, a <code>body</code>, and a <code>mutationId</code> which defaults to <code>contact</code>. The <code>subject</code> and <code>body</code> arguments are going to represent the subject line of the email we wish to send, and the body (HTML or otherwise) of the very same email. The <code>mutationId</code> field helps us to identify the name of the form we wish to send an email about.</p> <p>We have a few variables at the start of the function that outline a ‘from’ address and ‘to’ address’, which will be attached to the email to identify who it’s <em>from</em> and where it’s going <em>to</em>. With the <code>data</code> variable, this is the main GraphQL mutation.</p> <p>The mutation calls the <code>sendEmail</code> endpoint (this is the new endpoint exposed by the Send Email plugin) and is passed a <code>SendEmailInput</code> object. What it gives us back is a message, origin, and sent values. These are useful, particularly the sent value, for our front end to be sure that the form has been submitted successfully.</p> <p>Further down the mutation in the supplied <code>variables</code> we just connect up all the incoming data and arguments we have so far, such as the body, from address and subject.</p> <p>With that taken care of, let’s create a new page to house our contact form.</p> <h3> Add a new contact page and form </h3> <p>You could create a contact form anyway, or even create a reusable component that does the same trick, but for our purposes we’re going to create a good ol’ contact page with a form directly upon it.</p> <p>We want this to live at the <code>/contact</code> route in our website, so in your Next.js project, under the <code>/pages</code> directory, create a new folder <code>contact</code> and a new file <code>index.js</code>.</p> <p>With that done, populate the new <code>index.js</code> file with the following:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="k">import</span> <span class="nx">Head</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">next/head</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">useState</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">useRouter</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">next/router</span><span class="dl">'</span><span class="p">;</span> <span class="k">import</span> <span class="nx">styles</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../styles/Home.module.css</span><span class="dl">'</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">Contact</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">menuItems</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">name</span><span class="p">,</span> <span class="nx">setName</span><span class="p">]</span> <span class="o">=</span> <span class="nf">useState</span><span class="p">(</span><span class="dl">''</span><span class="p">);</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">email</span><span class="p">,</span> <span class="nx">setEmail</span><span class="p">]</span> <span class="o">=</span> <span class="nf">useState</span><span class="p">(</span><span class="dl">''</span><span class="p">);</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">message</span><span class="p">,</span> <span class="nx">setMessage</span><span class="p">]</span> <span class="o">=</span> <span class="nf">useState</span><span class="p">(</span><span class="dl">''</span><span class="p">);</span> <span class="kd">const</span> <span class="nx">router</span> <span class="o">=</span> <span class="nf">useRouter</span><span class="p">();</span> <span class="kd">const</span> <span class="nx">handleSubmit</span> <span class="o">=</span> <span class="k">async</span> <span class="nx">evt</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="c1">// we'll fill this in in a moment</span> <span class="p">};</span> <span class="k">return </span><span class="p">(</span> <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="p">{</span><span class="nx">styles</span><span class="p">.</span><span class="nx">container</span><span class="p">}</span><span class="o">&gt;</span> <span class="o">&lt;</span><span class="nx">Head</span><span class="o">&gt;</span> <span class="o">&lt;</span><span class="nx">title</span><span class="o">&gt;</span><span class="nx">Contact</span> <span class="nx">us</span> <span class="nx">page</span><span class="o">&lt;</span><span class="sr">/title</span><span class="err">&gt; </span> <span class="o">&lt;</span><span class="sr">/Head</span><span class="err">&gt; </span> <span class="o">&lt;</span><span class="nx">main</span> <span class="nx">className</span><span class="o">=</span><span class="p">{</span><span class="nx">styles</span><span class="p">.</span><span class="nx">main</span><span class="p">}</span><span class="o">&gt;</span> <span class="o">&lt;</span><span class="nx">h1</span> <span class="nx">className</span><span class="o">=</span><span class="p">{</span><span class="nx">styles</span><span class="p">.</span><span class="nx">title</span><span class="p">}</span><span class="o">&gt;</span><span class="nx">Contact</span> <span class="nx">us</span><span class="o">&lt;</span><span class="sr">/h1</span><span class="err">&gt; </span> <span class="o">&lt;</span><span class="nx">hr</span> <span class="o">/&gt;</span> <span class="o">&lt;</span><span class="nx">form</span> <span class="nx">onSubmit</span><span class="o">=</span><span class="p">{</span><span class="nx">handleSubmit</span><span class="p">}</span><span class="o">&gt;</span> <span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span> <span class="o">&lt;</span><span class="nx">label</span> <span class="nx">className</span><span class="o">=</span><span class="dl">'</span><span class="s1">label</span><span class="dl">'</span><span class="o">&gt;</span><span class="nx">Your</span> <span class="nx">name</span><span class="o">&lt;</span><span class="sr">/label</span><span class="err">&gt; </span> <span class="o">&lt;</span><span class="nx">input</span> <span class="nx">className</span><span class="o">=</span><span class="dl">'</span><span class="s1">input</span><span class="dl">'</span> <span class="nx">type</span><span class="o">=</span><span class="dl">'</span><span class="s1">text</span><span class="dl">'</span> <span class="nx">value</span><span class="o">=</span><span class="p">{</span><span class="nx">name</span><span class="p">}</span> <span class="nx">onChange</span><span class="o">=</span><span class="p">{</span><span class="nx">e</span> <span class="o">=&gt;</span> <span class="nf">setName</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">)}</span> <span class="nx">required</span> <span class="o">/&gt;</span> <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt; </span> <span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span> <span class="o">&lt;</span><span class="nx">label</span> <span class="nx">className</span><span class="o">=</span><span class="dl">'</span><span class="s1">label</span><span class="dl">'</span><span class="o">&gt;</span><span class="nx">Your</span> <span class="nx">email</span><span class="o">&lt;</span><span class="sr">/label</span><span class="err">&gt; </span> <span class="o">&lt;</span><span class="nx">input</span> <span class="kd">class</span><span class="o">=</span><span class="dl">'</span><span class="s1">input</span><span class="dl">'</span> <span class="nx">type</span><span class="o">=</span><span class="dl">'</span><span class="s1">email</span><span class="dl">'</span> <span class="nx">value</span><span class="o">=</span><span class="p">{</span><span class="nx">email</span><span class="p">}</span> <span class="nx">onChange</span><span class="o">=</span><span class="p">{</span><span class="nx">e</span> <span class="o">=&gt;</span> <span class="nf">setEmail</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">)}</span> <span class="nx">required</span> <span class="o">/&gt;</span> <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt; </span> <span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span> <span class="o">&lt;</span><span class="nx">label</span> <span class="nx">className</span><span class="o">=</span><span class="dl">'</span><span class="s1">label</span><span class="dl">'</span><span class="o">&gt;</span><span class="nx">Your</span> <span class="nx">message</span><span class="o">&lt;</span><span class="sr">/label</span><span class="err">&gt; </span> <span class="o">&lt;</span><span class="nx">textarea</span> <span class="nx">className</span><span class="o">=</span><span class="dl">'</span><span class="s1">textarea</span><span class="dl">'</span> <span class="nx">value</span><span class="o">=</span><span class="p">{</span><span class="nx">message</span><span class="p">}</span> <span class="nx">onChange</span><span class="o">=</span><span class="p">{</span><span class="nx">e</span> <span class="o">=&gt;</span> <span class="nf">setMessage</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">)}</span> <span class="o">&gt;&lt;</span><span class="sr">/textarea</span><span class="err">&gt; </span> <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt; </span> <span class="o">&lt;</span><span class="nx">button</span><span class="o">&gt;</span><span class="nx">Send</span><span class="o">&lt;</span><span class="sr">/button</span><span class="err">&gt; </span> <span class="o">&lt;</span><span class="sr">/form</span><span class="err">&gt; </span> <span class="o">&lt;</span><span class="sr">/main</span><span class="err">&gt; </span> <span class="o">&lt;</span><span class="sr">/div&gt; </span><span class="err"> </span> <span class="p">);</span> <span class="p">};</span> <span class="k">export</span> <span class="k">default</span> <span class="nx">Contact</span><span class="p">;</span> </code></pre> </div> <p>Looks quite long, but I always feel anything involving forms tends to look like this. Let’s break it down.</p> <p>At the top, in the imports section, we’re bringing in <code>useState</code> from React, which we’ll use to store the entered values in our upcoming form fields. We also bring in <code>useRouter</code> and <code>Head</code> from Next which we’ll respectively use to route the user to another page when they’ve submitted the form, and to inject some SEO values in the page meta area.</p> <p>Next we bring in the <code>styles</code> CSS module for the same generic styling we used before in the previous articles.</p> <p>Now we’re onto the component itself. We set up a few <code>useState</code> variables, one each for our name, email, and message form fields we’ll be defining shortly:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">const</span> <span class="p">[</span><span class="nx">name</span><span class="p">,</span> <span class="nx">setName</span><span class="p">]</span> <span class="o">=</span> <span class="nf">useState</span><span class="p">(</span><span class="dl">''</span><span class="p">);</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">email</span><span class="p">,</span> <span class="nx">setEmail</span><span class="p">]</span> <span class="o">=</span> <span class="nf">useState</span><span class="p">(</span><span class="dl">''</span><span class="p">);</span> <span class="kd">const</span> <span class="p">[</span><span class="nx">message</span><span class="p">,</span> <span class="nx">setMessage</span><span class="p">]</span> <span class="o">=</span> <span class="nf">useState</span><span class="p">(</span><span class="dl">''</span><span class="p">);</span> <span class="kd">const</span> <span class="nx">router</span> <span class="o">=</span> <span class="nf">useRouter</span><span class="p">();</span> </code></pre> </div> <p>We’re also initialising Next’s built-in <code>useRouter()</code> function so that we can redirect our visitors to a ‘thank you’ page when they’ve submitted the form.</p> <p>We’ve created a <code>handleSubmit()</code> function that you won’t be surprised to hear will handle the submission of our form, which we’ll define next.</p> <p>In the component’s JSX, after we add a bit of page scaffolding, title, SEO meta data, etc. you can see we return a pretty standard HTML form. We’re attaching the <code>handleSubmit</code> function to the form’s <code>onSubmit</code> event, and then everything else is straightforward HTML form business. The only thing of note here is that we connect each of the <code>useState</code> getters and setters to their corresponding form fields’ <code>value</code> attributes and <code>onChange</code> events.</p> <p>So now, when people update the form fields’ values, their input values will be stored in our component’s <code>state</code>. Pretty neat!</p> <p>With that out of the way, let’s fill out the <code>handleSubmit</code> function:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="c1">// ...other imports</span> <span class="c1">// bring in the new sendMail API function</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">sendMail</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../lib/api</span><span class="dl">'</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">Contact</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">menuItems</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="c1">// ...variables as before</span> <span class="kd">const</span> <span class="nx">handleSubmit</span> <span class="o">=</span> <span class="k">async</span> <span class="nx">evt</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nx">evt</span><span class="p">.</span><span class="nf">preventDefault</span><span class="p">();</span> <span class="kd">const</span> <span class="nx">emailContent</span> <span class="o">=</span> <span class="s2">` Message received from &lt;strong&gt;</span><span class="p">${</span><span class="nx">name</span><span class="p">}</span><span class="s2">&lt;/strong&gt;. Their email address is &lt;strong&gt;</span><span class="p">${</span><span class="nx">email</span><span class="p">}</span><span class="s2">&lt;/strong&gt;. &lt;br /&gt; They'd like to know about... </span><span class="p">${</span><span class="nx">message</span><span class="p">}</span><span class="s2"> `</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">sendMail</span><span class="p">(</span> <span class="dl">'</span><span class="s1">New message from website contact form</span><span class="dl">'</span><span class="p">,</span> <span class="nx">emailContent</span> <span class="p">);</span> <span class="k">if </span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">sent</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// email was sent successfully!</span> <span class="nx">router</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="dl">'</span><span class="s1">/contact/thanks</span><span class="dl">'</span><span class="p">);</span> <span class="p">}</span> <span class="p">};</span> <span class="k">return </span><span class="p">(</span> <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="p">{</span><span class="nx">styles</span><span class="p">.</span><span class="nx">container</span><span class="p">}</span><span class="o">&gt;</span> <span class="p">{</span><span class="cm">/* ...rest of page and contact form */</span><span class="p">}</span> <span class="o">&lt;</span><span class="sr">/div&gt; </span><span class="err"> </span> <span class="p">);</span> <span class="p">};</span> <span class="k">export</span> <span class="k">default</span> <span class="nx">Contact</span><span class="p">;</span> </code></pre> </div> <p>We’ve brought in out <code>sendMail</code> function from the API file, and filled out the <code>handleSubmit</code> function. Walking through the function, the process looks like this:</p> <ul> <li>We call the <code>evt.preventDefault()</code> to stop the form from refreshing the page.</li> <li>Next, we construct a string message, <code>emailContent</code> which will represent the body of the email we’re going to send from WordPress.</li> <li>After this, we call our <code>sendMail</code> API function, passing in an email subject and our <code>emailContent</code> email body, and <code>await</code> the return.</li> <li>Once the function returns, we check the <code>sent</code> property and if it’s successfully sent it’ll be true, which means we can redirect our visitor to the <code>/contact/thanks</code> page via Next’s built-in router.</li> </ul> <p><strong>We haven’t actually created the <code>/contact/thanks</code> route</strong> as it’s outside the aim of this article, but you could redirect your visitor’s anywhere at this point, an external URL, another internal page, or even not route them anywhere and just display a simple ‘thank you’ message.</p> <blockquote> <p><strong>Note</strong> : you could abstract some of the email body creation into a separate function in a utility/helper library to build up a suite of rich HTML email templates if you wanted. We’ve just opted for a plain ‘someone sent you a message on your website’ situation here for simplicity.</p> </blockquote> <h2> Sending contact forms with WordPress and Next.js </h2> <p>And that’s it! With very little effort, we’ve managed to wire up our WordPress backend with our detached, Next-powered front-end to facilitate sending contact forms from our static sites via the handy WPGraphQL Send Email plugin.</p> <h2> Helpful links </h2> <p>Here’s a reminder of the links used in this article:</p> <ul> <li><a href="proxy.php?url=https://www.netlify.com/" rel="noopener noreferrer">Netlify</a></li> <li> <a href="proxy.php?url=https://www.netlify.com/products/forms/" rel="noopener noreferrer">Netlify Forms</a> and the <a href="proxy.php?url=https://docs.netlify.com/forms/setup/" rel="noopener noreferrer">official Forms documentation</a> </li> <li><a href="proxy.php?url=https://en-gb.wordpress.org/plugins/add-wpgraphql-send-mail/" rel="noopener noreferrer">WPGraphQL Send Mail</a></li> <li>Part one of the series: <a href="proxy.php?url=https://dev.to/kendalmintcode/configuring-wordpress-as-a-headless-cms-with-next-js-3p1o">Configuring WordPress as a Headless CMS with Next</a> </li> <li>Part two of the series: <a href="proxy.php?url=https://dev.to/kendalmintcode/using-wordpress-as-a-headless-cms-with-next-js-2h5p">Using WordPress as a Headless CMS with Next</a> </li> <li>Part three of the series: <a href="proxy.php?url=https://dev.to/kendalmintcode/create-a-next-js-rss-feed-for-your-static-website-210p">Create a Next.js RSS feed for your static website</a> </li> </ul> javascript webdev tutorial react React, Angular and Vue compared, which should you learn in 2021? Rob Kendal {{☕}} Mon, 07 Jun 2021 08:08:20 +0000 https://dev.to/kendalmintcode/react-angular-and-vue-compared-which-should-you-learn-in-2021-1f6h https://dev.to/kendalmintcode/react-angular-and-vue-compared-which-should-you-learn-in-2021-1f6h <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frobkendal.co.uk%2Fimg%2Fwhich-frameworks-to-learn-2021-post.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frobkendal.co.uk%2Fimg%2Fwhich-frameworks-to-learn-2021-post.png" title="Which of the most popular JavaScript frameworks should you learn in 2021" alt="Blog header for which JavaScript framework should you learn in 2021"></a></p> <p>One of the most common questions I get as a <a href="proxy.php?url=https://dev.to/mentorship">coding mentor</a> when it comes to moving on from learning the basics of HTML/CSS and JavaScript is:</p> <blockquote> <p><strong>which is the best JavaScript framework to learn, right now, in 2021? Should I learn React, Angular or Vue?</strong></p> </blockquote> <p>I recently released a brand-new course for React students, <a href="proxy.php?url=https://www.newline.co/courses/beginners-guide-to-real-world-react" rel="noopener noreferrer">The Beginner's Guide to Real-World React</a>, where you can learn how to go from complete React beginner to building realistic UI applications using the React framework. Hot on the heals of the course release, I thought it would be good time to answer this very important question.</p> <p>Let's dive in!</p> <h2> Similar frameworks and their comparisons </h2> <p>There are many JavaScript libraries, frameworks, tools, projects, and platforms out there that help solve developers’ problems, help them build user interfaces, and ship excellent products faster. When it comes to the building user interfaces part, arguably the most popular libraries out there are Angular, VueJS, and ReactJS.</p> <p>Much like React (which I'm a huge fan of) both Vue and Angular are concerned with empowering developers to build complex user interfaces in a modular way, breaking down parts of those interfaces into component parts.</p> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frobkendal.co.uk%2Fimg%2Freact-vue-angular-search-trends.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frobkendal.co.uk%2Fimg%2Freact-vue-angular-search-trends.png" alt="Search trends for React, Vue, and Angular between 2019 to 2021"></a></p> <p>Looking at the search data from Google Trends, you can see that Vue has been steadily gaining interest over the past few years, only dipping over the course of late 2020. React has increased dramatically, whilst Angular has declined over the same time frame. Both React and Angular have dropped off in search interest over the course of late 2020, with React gaining more popularity once more towards present day (June 2021)</p> <h2> Comparing Angular, React and Vue frameworks </h2> <p>Let's take a quick tour of each of these three big players in the front-end web application building space and see if we can help you determine which of the popular JavaScript frameworks you should be learning in 2021.</p> <h2> React </h2> <p>React came onto life in 2013 and has gone from strength to strength. React is more of a library than a complete framework and it has many missing pieces of the overall development puzzle (this is one of the things <a href="proxy.php?url=https://www.newline.co/courses/beginners-guide-to-real-world-react" rel="noopener noreferrer">The Beginner's Guide to Real-World React</a> aims to address!).</p> <p>React is <em>declarative</em>, meaning it allows us to break down our complex user interfaces into smaller, bite-sized pieces of functionality that operate on their own; managing their state, given some sort of input data referred to as props. These smaller components can be brought together as building blocks for larger, more complex user interfaces that interact via passed in data and events.</p> <p><a href="proxy.php?url=https://www.newline.co/courses/beginners-guide-to-real-world-react/" rel="noopener noreferrer"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frobkendal.co.uk%2Fimg%2Freact-course-cta.png" title="Beginner's Guide to Real-World React" alt="course banner for beginners React course"></a></p> <h3> Benefits </h3> <ul> <li>Small and relatively lightweight as libraries go.</li> <li>Declarative in nature; you can write your UI as you want it to be rendered.</li> <li>Shallow learning curve, especially when it comes to learning the essentials.</li> <li>Works a lot on an 'opt-in' fashion where you can adopt as much or as little as you need.</li> <li>React is very unopinionated, it gets out of your way.</li> </ul> <h3> Drawbacks </h3> <ul> <li>Some library differences can cause confusion and add to the learning curve (e.g. classes vs. function components).</li> <li>React is very unopinionated, you're going to have to fill in the blanks to add things like navigation to your app.</li> <li>It's made by Facebook -- this could be a deal-breaker for some developers.</li> <li>Because of its free and easy approach, there aren't a lot of best-practices or clear guidelines around developing with React.</li> </ul> <h2> Vue </h2> <p>VueJS came onto the scene around 2014 and was developed by a former Google employee Evan Yu. Vue is a progressive JavaScript framework, similar to React, and has a rich ecosystem of additional libraries and plug-ins to enable additional functionality.</p> <h3> Benefits </h3> <ul> <li>Empowered HTML: Vue has a lot of similarities with Angular in the way that it decorates HTML blocks within its components.</li> <li>Detailed documentation: one of the best features of Vue is the hugely detailed and informative documentation that helps developers at either end of the experience scale.</li> <li>Ease of integration: whether you’re building single-page applications (SPA’s) or more complex applications, Vue’s smaller, interactive parts mean it can be integrated into existing infrastructure without affecting the wider * system.</li> <li>Large scaling: Vue can be used to develop pretty large, reusable templates to power very complex apps.</li> <li>Small code footprint: Vue weighs in at around 20KB keeping its size small, but also aiding in increasing its speed and performance.</li> <li>Short learning curve</li> </ul> <h3> Drawbacks </h3> <ul> <li>Lack of support and resources: whilst rising in interest and boasting a loyal and growing community, Vue has the smallest market share and so naturally has limited resources and solutions out there for budding developers or those seeking answers.</li> </ul> <h2> Angular </h2> <p>Angular (formerly AngularJS, which is a very different beast), is a fully-fledged framework released back in 2009. It boasts a very mature framework that handles everything you need to build rich, data-driven user interfaces right out of the box. It offers an MVVM (Model, View, View Model) approach to development structure that separates the working parts into their respective areas of responsibility.</p> <h3> Benefits </h3> <ul> <li>MVVM structure: this allows developers to work on the same area of the app without stepping on colleagues’ toes. However, the MVVM approach is not as intuitive to those starting out or those unfamiliar with this particular project structure.</li> <li>Two-way data-binding: this enables singular behaviour within the app, reducing the risk for errors.</li> <li>Dependency injection of the required features that the current components depend on.</li> <li>Deep coupling with TypeScript: including excellent TypeScript support.</li> <li>Very detailed documentation and guides</li> <li>Mature ecosystem: similar to the documentation, Angular is widely used and has a large ecosystem of resources, guides, help, and advice for just about any situation you’ll come across.</li> <li>Opinionated approach: even down to the structure and architecture to make sure your project can scale well.</li> </ul> <h3> Drawbacks </h3> <ul> <li>Steep learning curve: Angular is very complex and has an equally steep learning curve which is a big barrier to entry, especially for new developers</li> <li>Migration: is often a problem when moving from older versions to new</li> <li>Deep coupling with TypeScript: yes, this is also a benefit, but TypeScript is not widely adopted by new developers and adds yet another few degrees to an already steep learning curve</li> </ul> <h2> When it comes to React, Angular and Vue, which framework is best? </h2> <p>This is really a deceitful question as it pits each framework against one another and it really boils down to opinions and preference. There are many solid reasons to choose any of these three frameworks, or indeed, none at all.</p> <p>Remember, that any library, framework, platform, language, design, pattern, whatever, they’re all just tools.</p> <p>In the same way that you wouldn’t use a hammer to unscrew a bolt, sometimes it’s about finding the right tool for the job. When you have a choice of hammers, then it can be a simple as ‘I prefer this one over that one’. And it’s just as easy to write bad code in a good library.</p> <h2> Should I learn React, Angular or Vue to get a job? </h2> <p>If you're coming here with the angle of looking for a job then the best advice is to take a look at the local job market where you are. For example, here in Yorkshire, we have the two cities of York and Leeds, the latter being a huge tech-hub for the North of England.</p> <p>It’s very diverse as tech goes with a wide variety of technologies, languages and sectors that you can choose to apply for roles within. In terms of tech stack, the job market in this part of the world is very React and Angular dense at the front-end, with PHP and .NET forming the largest part of the server-side technologies.</p> <p>So, if I were in the market for a job, I’d be focusing on those languages and tech-stack.</p> <h2> What about other frameworks, which should I learn? </h2> <p>There are, of course, about as many user interface building libraries, frameworks, and platforms as there are hairs on your head, with more popping up all the time. The big question you should be asking is "which one is right for me, my team, my project(s)?".</p> <p>You might not have much of a choice in this, depending on where you choose to work and what projects you choose to work on. This article is really about comparing the good and bad parts of the three heavy hitters in the market today, midway through 2021.</p> <p>If you’re interested in looking at other front-end, UI-building libraries and frameworks then some other popular choices include:</p> <ul> <li> <a href="proxy.php?url=https://preactjs.com/" rel="noopener noreferrer">Preact</a>, a tiny alternative to React that operates in a very similar way.</li> <li> <a href="proxy.php?url=https://svelte.dev/" rel="noopener noreferrer">Svelte</a>, a very popular alternative to other interactive UI building languages that shifts to a compiled step (as opposed to React and Vue that do work in the browser).</li> <li> <a href="proxy.php?url=https://emberjs.com/" rel="noopener noreferrer">Ember</a>, a very robust and battle-tested JS framework for building modern web applications.</li> </ul> <h2> Further reading and other resources </h2> <p>Whilst I have a lot of love for the other JavaScript frameworks on show here, React is my first love and where I spend most of my time building funky things. If you're interested in learning React then I recommend the following:</p> <ul> <li>Check out my recently launched novice guide to those just starting out learning the library, <a href="proxy.php?url=https://www.newline.co/courses/beginners-guide-to-real-world-react" rel="noopener noreferrer">The Beginner's Guide to Real-World React</a>.</li> <li>Talk to me about my <a href="proxy.php?url=https://dev.to/mentorship">mentorship</a> options, even if it's just a one-off email to ask questions or pick my brains.</li> </ul> react beginners javascript frontend The Beginner's Guide to Real-World React Rob Kendal {{☕}} Thu, 13 May 2021 07:02:05 +0000 https://dev.to/kendalmintcode/the-beginner-s-guide-to-real-world-react-28mc https://dev.to/kendalmintcode/the-beginner-s-guide-to-real-world-react-28mc <p>🚨 HUGE ANNOUNCEMENT ALERT 🚨</p> <p>My shiny new course, <a href="proxy.php?url=https://www.newline.co/courses/beginners-guide-to-real-world-react/" rel="noopener noreferrer">The Beginner's Guide to Real-World React</a> is live 🥳</p> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm35vck3uz6cmr232s5sm.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm35vck3uz6cmr232s5sm.png" alt="Beginners Guide to Real-World React course cover"></a></p> <p>If you've been wanting to learn React or struggling to get going then this course is a perfect place to start!</p> <p>In the course you'll take a deep dive into core React concepts as you learn more about how React works, how you can use it to build complex web applications, and how to combine it with third-party libraries and frameworks to 'join the dots' and gain a detailed understanding of how you might use React in a real world setting.</p> <p>You'll get:</p> <ul> <li>8+ hours of video</li> <li>50 lessons</li> <li>loads of practical React advice</li> <li>...and more</li> </ul> <p>Great for beginners, the link's below with 50% off for a limited time</p> <p>Check it out here, on the <a href="proxy.php?url=https://www.newline.co/courses/beginners-guide-to-real-world-react/" rel="noopener noreferrer">Newline course page</a>.</p> react webdev javascript news The Front End: S3-E2 - Matt Studdert Rob Kendal {{☕}} Sun, 21 Mar 2021 15:01:21 +0000 https://dev.to/kendalmintcode/the-front-end-s3-e2-matt-studdert-5a8i https://dev.to/kendalmintcode/the-front-end-s3-e2-matt-studdert-5a8i <p>In a very exciting second episode we have the one and only <a href="proxy.php?url=https://twitter.com/_mattstuddert">Matt Studdert</a>, the founder of <a href="proxy.php?url=https://www.frontendmentor.io/">Frontend Mentor</a>, a learning resource used by over 100,000 budding developers to sharpen their skills.</p> <p>Listen to the brand new episode here:</p> <ul> <li>Apple Podcasts &gt; <a href="proxy.php?url=https://podcasts.apple.com/gb/podcast/the-front-end/id1499349107">https://podcasts.apple.com/gb/podcast/the-front-end/id1499349107</a> </li> <li>Pocket casts &gt; <a href="proxy.php?url=https://pca.st/osul2zsa">https://pca.st/osul2zsa</a> </li> <li>Spotify &gt; <a href="proxy.php?url=https://open.spotify.com/show/4GuAxptrtY8Es5iHwp09dT">https://open.spotify.com/show/4GuAxptrtY8Es5iHwp09dT</a> </li> <li>Anchor &gt; <a href="proxy.php?url=https://anchor.fm/the-front-end">https://anchor.fm/the-front-end</a> </li> </ul> <h2> In this episode </h2> <p>After being recommended as a guest to the show by several twitter peeps, we’re so pleased to welcome Matt Studdert. Matt is a fellow UK developer and founder of the hugely popular Frontend Mentor, a coding skills levelling up platform used by close to 100,000 people to hone their skills in front end development.</p> <p>Matt hails from a background in fitness and personal training and made the leap to dev life, and shares a passion for helping others get into development. He’s going to share his story and give us some tips on how and where to start with learning to be a developer.</p> <h2> Listening direct </h2> <p>Want to listen directly from here?</p> <p><iframe src="proxy.php?url=https://open.spotify.com/embed/episode/4MDK9OjTBRDZnWCtUKn5nQ" width="100%" height="232px"> </iframe> </p> <h2> Want to be a guest or sponsor the show? </h2> <p>We're always happy to take requests for guests to talk about a range of different development topics from careers to origin stories, learning and professional growth, frameworks and more.</p> <p>Similarly, we're looking for sponsors to fill promotional slots within the show. </p> <p>You can find out more here:</p> <ul> <li><a href="proxy.php?url=https://thefrontendpodcast.site/guests">Being a guest</a></li> <li><a href="proxy.php?url=https://thefrontendpodcast.site/sponsorship">Sponsorship information</a></li> </ul> <p>Thanks for listening!</p> podcast frontend career The Front End: S3-E1 - Brian Rinaldi & Raymond Camden Rob Kendal {{☕}} Mon, 08 Mar 2021 07:14:51 +0000 https://dev.to/kendalmintcode/the-front-end-s3-e1-brian-rinaldi-raymond-camden-2fnp https://dev.to/kendalmintcode/the-front-end-s3-e1-brian-rinaldi-raymond-camden-2fnp <p>🚨 NEW SEASON ALERT 🚨</p> <p>In this very first episode of the brand new season 3, we talk to <a href="proxy.php?url=https://twitter.com/remotesynth">Brian Rinaldi</a>, a dev advocate from <a href="proxy.php?url=https://twitter.com/stepzen_dev">StepZen</a>, and his partner in crime, <a href="proxy.php?url=https://twitter.com/raymondcamden">Raymond Camden</a>, a lead developer evangelist for HERE. </p> <p>Listen to the brand new episode here:</p> <ul> <li>Apple Podcasts &gt; <a href="proxy.php?url=https://podcasts.apple.com/gb/podcast/the-front-end/id1499349107">https://podcasts.apple.com/gb/podcast/the-front-end/id1499349107</a> </li> <li>Pocket casts &gt; <a href="proxy.php?url=https://pca.st/osul2zsa">https://pca.st/osul2zsa</a> </li> <li>Spotify &gt; <a href="proxy.php?url=https://open.spotify.com/show/4GuAxptrtY8Es5iHwp09dT">https://open.spotify.com/show/4GuAxptrtY8Es5iHwp09dT</a> </li> <li>Anchor &gt; <a href="proxy.php?url=https://anchor.fm/the-front-end">https://anchor.fm/the-front-end</a> </li> </ul> <h2> In this episode </h2> <p>This Jamstack-loving pair have co-authored a book called The Jamstack Book, which is published by Manning and dives deep into the Jamstack and helps you build up a portfolio of Jamstack-architectured sites including a full-blown e-commerce store.</p> <p>Now, as a very special reward for you lovely listeners of the show, Manning have been kind enough to offer some free copies of the book and some permanent discount codes so you can snap up a copy for yourself and learn all about the Jamstack.</p> <h2> Grabbing your copy of The Jamstack Book (with discounts) </h2> <p>Pop along to this link to grab your own copy of the book, </p> <p><a href="proxy.php?url=http://mng.bz/MXMQ">The Jamstack Book</a></p> <p>and use the code <strong>podfrontend21</strong> to get a <strong>MASSIVE 35% off!</strong></p> <h2> Listening direct </h2> <p>Want to listen directly from here?</p> <p><iframe src="proxy.php?url=https://open.spotify.com/embed/episode/1ttYD5ueZz0Vq0AuGt4wuv" width="100%" height="232px"> </iframe> </p> <h2> Want to be a guest or sponsor the show? </h2> <p>We're always happy to take requests for guests to talk about a range of different development topics from careers to origin stories, learning and professional growth, frameworks and more.</p> <p>Similarly, we're looking for sponsors to fill promotional slots within the show. </p> <p>You can find out more here:</p> <ul> <li><a href="proxy.php?url=https://thefrontendpodcast.site/guests">Being a guest</a></li> <li><a href="proxy.php?url=https://thefrontendpodcast.site/sponsorship">Sponsorship information</a></li> </ul> <p>Thanks for listening!</p> podcast frontend career The Front End: S2-E10 - Hassan El Mghari Rob Kendal {{☕}} Mon, 14 Dec 2020 07:18:26 +0000 https://dev.to/kendalmintcode/the-front-end-s2-e10-hassan-el-mghari-nb6 https://dev.to/kendalmintcode/the-front-end-s2-e10-hassan-el-mghari-nb6 <p>🚨 SEASON FINALÉ 🚨</p> <p>For this final episode in the season <a href="proxy.php?url=https://twitter.com/nutlope">Hassan El Mghari</a> joins us to share his incredible journey from high-school student running multiple gaming communities totalling almost a million users, founding a gaming marketing company, to pursuing a CS degree and learning full stack development. </p> <p>Listen to the brand new episode here:</p> <ul> <li>Apple Podcasts &gt; <a href="proxy.php?url=https://podcasts.apple.com/gb/podcast/the-front-end/id1499349107">https://podcasts.apple.com/gb/podcast/the-front-end/id1499349107</a> </li> <li>Pocket casts &gt; <a href="proxy.php?url=https://pca.st/osul2zsa">https://pca.st/osul2zsa</a> </li> <li>Spotify &gt; <a href="proxy.php?url=https://open.spotify.com/show/4GuAxptrtY8Es5iHwp09dT">https://open.spotify.com/show/4GuAxptrtY8Es5iHwp09dT</a> </li> <li>Anchor &gt; <a href="proxy.php?url=https://anchor.fm/the-front-end">https://anchor.fm/the-front-end</a> </li> </ul> <h2> In this episode </h2> <p>Hassan El Mghari is currently studying for a CS degree at Drexel. However, whilst in high-school he successfully started a games marketing business, UltraShock Gaming. </p> <p>Hassan talks lessons learned, how to learn in public and the power of long-game effort.</p> <h2> Listening direct </h2> <p>Want to listen directly from here?</p> <p><iframe src="proxy.php?url=https://open.spotify.com/embed/episode/2X9JNLUlS2LI3LJZIAse9D" width="100%" height="232px"> </iframe> </p> <h2> Want to be a guest or sponsor the show? </h2> <p>We're always happy to take requests for guests to talk about a range of different development topics from careers to origin stories, learning and professional growth, frameworks and more.</p> <p>Similarly, we're looking for sponsors to fill promotional slots within the show. </p> <p>You can find out more here:</p> <ul> <li><a href="proxy.php?url=https://thefrontendpodcast.site/guests">Being a guest</a></li> <li><a href="proxy.php?url=https://thefrontendpodcast.site/sponsorship">Sponsorship information</a></li> </ul> <p>Thanks for listening!</p> podcast frontend career The Front End: S2-E9 - Rob Kendal Q&A Rob Kendal {{☕}} Mon, 30 Nov 2020 08:33:30 +0000 https://dev.to/kendalmintcode/the-front-end-s2-e9-rob-kendal-q-a-59h4 https://dev.to/kendalmintcode/the-front-end-s2-e9-rob-kendal-q-a-59h4 <p>🚨 NEW EPISODE ALERT 🚨</p> <p>For the penultimate episode (9) of season 2, I'm hosting a special 1-2-1 show where I answer some of your burning questions on front end development, careers, and more.</p> <p>Listen to the brand new episode here:</p> <ul> <li>Apple Podcasts &gt; <a href="proxy.php?url=https://podcasts.apple.com/gb/podcast/the-front-end/id1499349107">https://podcasts.apple.com/gb/podcast/the-front-end/id1499349107</a> </li> <li>Pocket casts &gt; <a href="proxy.php?url=https://pca.st/osul2zsa">https://pca.st/osul2zsa</a> </li> <li>Spotify &gt; <a href="proxy.php?url=https://open.spotify.com/show/4GuAxptrtY8Es5iHwp09dT">https://open.spotify.com/show/4GuAxptrtY8Es5iHwp09dT</a> </li> <li>Anchor &gt; <a href="proxy.php?url=https://anchor.fm/the-front-end">https://anchor.fm/the-front-end</a> </li> </ul> <h2> In this episode </h2> <p>I'm Rob, a freelance front end developer, Shopify Partner, and lover of WordPress, the Jamstack, and helping people learn to code. </p> <p>I'm going to answer burning developer questions such as:</p> <ul> <li>Should I learn React or Vue?</li> <li>How long before I start looking for a job?</li> <li>How do I start blogging and creating content?</li> <li>How can I get into freelancing?</li> <li>...and many more!</li> </ul> <h2> Listening direct </h2> <p>Want to listen directly from here?</p> <p><iframe src="proxy.php?url=https://open.spotify.com/embed/episode/7pPFw49ukXjY18mDElc8Vt" width="100%" height="232px"> </iframe> </p> <h2> Want to be a guest or sponsor the show? </h2> <p>We're always happy to take requests for guests to talk about a range of different development topics from careers to origin stories, learning and professional growth, frameworks and more.</p> <p>Similarly, we're looking for sponsors to fill promotional slots within the show. </p> <p>You can find out more here:</p> <ul> <li><a href="proxy.php?url=https://thefrontendpodcast.site/guests">Being a guest</a></li> <li><a href="proxy.php?url=https://thefrontendpodcast.site/sponsorship">Sponsorship information</a></li> </ul> <p>Thanks for listening!</p> podcast frontend career Things I wish I'd known as a junior developer Rob Kendal {{☕}} Fri, 20 Nov 2020 07:44:55 +0000 https://dev.to/kendalmintcode/things-i-wish-i-d-known-as-a-junior-developer-127n https://dev.to/kendalmintcode/things-i-wish-i-d-known-as-a-junior-developer-127n <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--hh2qmjw7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://robkendal.co.uk/img/what-i-wish-i-knew-as-a-junior-dev.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--hh2qmjw7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://robkendal.co.uk/img/what-i-wish-i-knew-as-a-junior-dev.png" alt="" width="800" height="438"></a></p> <p>I've been a developer for over fifteen years and I've loved almost every minute of it. I can't imagine doing anything different but there are moments when I think back to starting out and how different the development landscape was, especially when it comes to learning how to code.</p> <p>I'm really <em>not</em> that old, but things in the tech world move fast and there weren't as many resources around to help people get into frontend development and learn how to build something from HTML, CSS, JavaScript, etc. There wasn't as many great communitys like Dev or Hashnode or WeStryve, not learning platforms such as Free Code Camp or The Odin Project.</p> <p>So a lot of my early career felt like fumbling around for me, picking up everything I could from senior developers, hoping that they knew what they were doing, reading as many books as a I could and practicing my craft.</p> <p>It's fair to say there were quite a few things I wish I'd known a lot earlier in my career that could have helped me progress faster or just develop both my coding skills, as well as my more complimentary skills, such as dealing with people or learning <em>how</em> to learn.</p> <p>Hopefully, with this article I can distill what I've learned to date and share some things to watch out for and take on board to help you with your career as a frontend developer.</p> <p><em>(Pssst -- If you like this article, come</em> <a href="proxy.php?url=https://twitter.com/kendalmintcode"><em>find me on Twitter</em></a> <em>where I post regular helpful content every day)</em></p> <h2> Start writing a blog </h2> <p>This very blog you're reading now (whether it's on my own website or my Dev or Hashnode accounts) has grown quite popular and that's great. But I really wish I'd started much earlier and written more frequently.</p> <p>Honestly, blogging is such a great way to cement what you've learned <em>as you're learning it</em>. You absolutely do not have to be a developer with X years in the business, nor do you have to be an expert.</p> <p>All you need is a curios mind and to think about writing for your past self: write the article you wish you'd had on the subject.</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--CMak6OQd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://robkendal.co.uk/img/workplace-2303851_640.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--CMak6OQd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://robkendal.co.uk/img/workplace-2303851_640.jpg" alt="" width="640" height="426"></a></p> <p>As a bonus, if you fix something particularly tricky, you'll be able to refer back to your own article to help you solve it in the future!</p> <p>My friend Catlin Pit has a couple of recommended articles on blogging too:</p> <ul> <li><a href="proxy.php?url=https://catalins.tech/should-you-start-a-blog-as-a-developer">Should you start a blog as a developer</a></li> <li> <a href="proxy.php?url=https://catalins.tech/how-to-start-your-blog-as-a-developer">How to start a blog</a> - a common question after 'what do I write about?'</li> </ul> <h2> Ask more questions! </h2> <p>This seems obvious, but in my experience it's really not. A lot of developers, especially those early on in their careers are easily intimidated by more experienced devs or just those that have been around longer, and this can prevent them from speaking up.</p> <p>But you know what? That's how you learn: you ask questions, you get answers (and, back to the previous point, you now have content for a blog article!).</p> <p>Never be afraid of asking questions, especially if you're stuck on something. There are no stupid questions, but I'm not going to lie, you will at some point come across arrogant or unhelpful people who will make you feel otherwise. My advice, ignore them and find more open and helpful people.</p> <h2> Be honest on what you're passionate about </h2> <p>If you love what you do and are fortunate enough to get to do that regularly, especially as a full time career then you've hit the jackpot.</p> <p>However, I see a lot of people stuck in places and in roles that they just don't have the passion or drive for. I'm well aware that not everyone is fortunate enough to be able to pick and choose, but as developers we have long held a valuable position in the job market where there are more jobs than there are developers.</p> <p>But this is really about having a truthful discussion with your inner dev and being both honest about what you <em>really</em> enjoy doing/not doing, and realistic about how to start a role doing this.</p> <p>For example, if you really really really don't like PHP, then take care when interviewing or seeking out roles in development agencies where WordPress is heavily featured.</p> <p>This advice is especially true for those seeking, perhaps, their first development roles. Be honest and candid about what's right for you and (if you're able to), don't just jump at the first opportunity that comes your way.</p> <p>The more you can discover what direction you want to travel, the better aligned you'll be down the line in your career.</p> <p>With all of the above said, please don't put pressure on yourself to decide too early. Sometimes you have to experience what you <em>don't</em> want to know what <em>is</em> right for you.</p> <h2> Get a coding mentor </h2> <p>I was supremely fortunate to have worked with some amazing developers back when I started. They were experienced, knowledgable, helpful and humble and really took me under their wing. I'll never be able to thank them enough.</p> <p>Therefore, I'd highly recommend getting yourself a mentor to help with your development journey.</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--c4kkt58I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://robkendal.co.uk/img/people-2569234_640.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--c4kkt58I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://robkendal.co.uk/img/people-2569234_640.jpg" alt="" width="640" height="436"></a></p> <p>This is different from a teacher or tutor (although there is overlap at the edges of the roles): a mentor is a guiding light to help give you focus and direction, to answer questions and point you along the right path for your particular journey.</p> <p>I'd recommend taking a look at <a href="proxy.php?url=https://codingcoach.io">Coding Coach</a> or my very own <a href="proxy.php?url=https://robkendal.co.uk/mentorship">mentorship programmes</a> to find a mentor that's right for you.</p> <h2> Mentor others </h2> <p>Following on from the last point, you too can be a mentor and again, you don't have to have coded for decades or be an absolute expert.</p> <p>Think about where you are on your journey and how far you've come. Even if you're 6-8 months into learning to code, there are people only 1-2 <em>weeks</em> into theirs; these are the people you can definitely help out.</p> <p>That said, if you <em>are</em> quite experienced, then all the more reason to help someone else who's just starting out.</p> <blockquote> <p>Become the guide you wish you'd had when you started</p> </blockquote> <p>It's rewarding beyond measure, and a win win situation: someone getting started is helped and guided, and you learn so much in the process, believe me.</p> <h2> You don't get what you don't ask for </h2> <p>I'm from Yorkshire in the UK and there's a saying in this North East part of the world: Shy bairns get nowt.</p> <p>In essence we're really saying that 'you get nothing by keeping quiet'.</p> <p>It's easy to look at seemingly successful developers and popular Twitter personas and assume that everyone has lucky breaks and opportunity just falls from the sky.</p> <p>To be honest, this does happen to a small number of people, but most of the successful people you'll know are a combination of trying (a lot), keeping their eyes open for opportunities and only a little sprinkle of luck.</p> <p>Most of the time it's about asking for what you want. Now I'm not suggesting you just go kicking your boss' door in a demanding a raise, but in all seriousness, if you think you deserve a pay rise, prove it and then <em>ask for it</em>!</p> <p>Want to switch up your career? Ask about training programs at your current workplace. Want to talk to someone on LinkedIn about their career? Ask them.</p> <p>The worst that will happen is you get a 'no'. In which case, time to move on and try something else.</p> <h2> Keep track of all your wins </h2> <p>This is a big one for me. It's very easy as a human to skew towards the negative. Even if you've just launched five features on a product used by millions, I would bet that it's that single tiny error that happened that you'll be thinking about, right?!</p> <p>I keep a notebook of little victories and compliments I've received and look through them from time to time, especially when the <a href="proxy.php?url=https://robkendal.co.uk/blog/2019-05-10-fighting-imposter-syndrome-as-a-developer">imposter syndrome</a> kicks in from time to time.</p> <p>It's not about ego or vanity, but it's about celebrating your victories and remembering how far you've come.</p> <h2> You don't have to know everything </h2> <p>The world of deveopment is HUGE. I mean seriously almost infinite. Between languages, frameworks, patterns, platforms, frontend, backend, the list goes on.</p> <p>When you're starting out there can be an overwhelming urge to have to know everything all at once. But I'm here to tell you that you don't need to know everything and, in reality, you can't or won't ever know it all q-- the landscape is just too vast.</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--Aku9OR4K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://robkendal.co.uk/img/magic-cube-1976725_640.jpg" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--Aku9OR4K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://robkendal.co.uk/img/magic-cube-1976725_640.jpg" alt="" width="640" height="331"></a></p> <p>Of course, as a frontend developer starting out, I'd recommend focussing on the three core pillars of HTML, CSS, and JavaScript, but once you're getting into the swing of things, start to add bits and pieces to your knowledge depending on what takes your fancy.</p> <h2> Senior doesn't mean encylopedia </h2> <p>Just because someone is a senior developer or an architect or principle, or has been programming for 100's of years, does not automatically make them the all knowing beasts you think they may be.</p> <p>I'll let you into a secret: I've been developing in a fullstack fashion for over fifteen years and I still have to look up some of the most trivial snippets of code; I still miss semicolons out of things and I still make mistakes.</p> <p>Sure, I know a lot about a lot, but there's oceans of knowledge I haven't even sailed upon. Just because people have been around the block doesn't directly equate to an infallible, all-knowing knowledge. Neither does it mean they're always automatically right or know better.</p> <p>The point I'm driving at here is that just because you're less experienced or starting out doesn't mean your ideas are less valuable or that you have less to contribute.</p> <p>It's also why adopting a life long approach to learning is so important as a developer.</p> <h2> Titles don't really mean much in the long run </h2> <p>Another thing to bear in mind is that some people get caught up in a title race and it's easy to think that becoming a 'principle developer' or 'lead architect' is the end goal. Yes, titles and recognition are nice and there's money and rewards that come with more senior titles, but they don't always reflect the abilities of the holder nor</p> <p>I'm not saying that it's not OK to have 'become a lead developer' as a goal, but don't set your standards by the titles that you, or others, hold. It's your attitude, actions, and what you contribute to the development community that will ultimately shape your legacy.</p> <h2> Networking is essential!! </h2> <p>It really is <em>who you know</em> not always <em>what you know</em>.</p> <p>I wish I'd spent more time making connections with other developers, recruiters, leaders, and makers on social media, in real life. I think I've made up for it over the years, but had I started sooner, who knows what doors it could have opened up.</p> <p>Make yourself a Twitter profile and register on LinkedIn. These are both excellent platforms for connecting with your fellow devs and growing your network. It's not about a popularity contest, but it'll help you with a number of areas in your career:</p> <ul> <li>Finding a new job.</li> <li>Starting a freelance business.</li> <li>Launching a product or service.</li> <li>Selling content, ebooks, courses, and more.</li> <li>Make new friends.</li> <li>Helping other to learn.</li> <li>Contributing to open source.</li> </ul> <p>And it doesn't have to involve hours and hours of work, but just regular little interactions with people, making connections, being helpful and honest, being yourself. Over time, your network will grow and you'll get as much out of it as you give.</p> <p><a href="proxy.php?url=https://twitter.com/kendalmintcode"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--ZRVnu6_T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://robkendal.co.uk/img/twitter_cta.png" alt="Call to action banner pointing people to my twitter account" title="follow me on twitter for more great content" width="800" height="126"></a></p> <h2> Ignore the gatekeepers </h2> <p>As with all life, you're going to come across people who, for whatever reason, just aren't great. There's a lot of gatekeeping in a lot of industries, but it's especially rife in the tech world.</p> <p>You can avoid a lot of it by just not engaging, and I'd strongly suggest that you do avoid as much as possible. I come across a lot of developers who have been put off before they've even started because of trolls, haters, negative attitudes, condescension, and don't get me started on how women in tech can be treated.</p> <p>Development is one of those few things in life that's truly open for <em>everyone</em>. It's about your mental attitude and looking at problems, breaking them down, solving for others. Everything else is just tools, time, practice and approach.</p> <p>I don't like CSS-in-JS, but you know what, it's a valid approach and if it helps some people build something awesome for others then that's OK by me.</p> <p>Sure, there's better ways of doing things and some tried and tested approaches that work smoother than others, but a lot of development is opinions, nothing more.</p> <p>So don't let others tell you you can't or let them intimidate you of course. Similarly, don't shame people when they share something of theirs.</p> <h2> Recruiters are both the devil and angels in disguise </h2> <p>I love recruiters. Well, <em>some</em>, recruiters. In fact, I owe a lot of my success to the good ones.</p> <p>If you want to get your foot in the doors of places you might not ordinarily be able to, get in touch with some recruiters and share with them your career aspirations. This is an essential part of networking for me.</p> <p>It's increasingly important as a junior developer starting out to grow a connection base that includes at least a handful of trusted recruiters that take a genuine interest in you. They good ones will put the work in, take the time to get to know you and your skills and really talk you up to their clients (the employers).</p> <p>Don't be put off by the chancers, the glorified mobile phone salesmen, and the ones that don't have your interests at heart.</p> <h2> Everyone feels like imposter </h2> <p>I have <a href="proxy.php?url=https://robkendal.co.uk/blog/2019-05-10-fighting-imposter-syndrome-as-a-developer">an article on fighting imposter syndrome</a> as a developer which is worth a read, but there's bound to be a few points in your career where you'll feel down, feel a little worthless like your contributions don't matter, and wonder what the hell you're doing with your career.</p> <p>That's OK. That's normal and it's more common than you think. In fact, it's very common in high-functioning, intelligent people. That feeling of not belonging, where you're simply blagging it and faking it and people will eventually find you out.</p> <p>This is where little tips like talking about it helps, as does keeping that accomplishments journal we talked about earlier. Believe me, everyone suffers from it here and there and I'm no exception.</p> <p>Just remember that you can combat it and you should push ahead with your goals regardless.</p> <h2> Other helpful career articles </h2> <p>I have a few other articles and helpful recourses if you're looking for a leg up in your development career or to get started as a developer:</p> <ul> <li>Article on <a href="proxy.php?url=https://robkendal.co.uk/blog/2020-01-07-what-should-you-know-as-a-frontend-developer">things you should know as a frontend developer</a>.</li> <li>Discover my <a href="proxy.php?url=https://robkendal.co.uk/mentorship">coding mentioship services</a>.</li> <li>How to <a href="proxy.php?url=https://robkendal.co.uk/blog/2019-05-04-getting-hired-as-a-developer-in-the-tech-industry">get hired as a developer in the tech industry</a>.</li> <li>Free ebook, <a href="proxy.php?url=https://robkendal.co.uk/publications">From Aspire to Hired</a> - start coding and land your first dev job.</li> </ul> career beginners frontend codenewbie