-
Notifications
You must be signed in to change notification settings - Fork 7.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Added Route for 30secondsofcode #18045
Merged
Merged
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
github-actions
bot
added
Route
Auto: Route Test Complete
Auto route test has finished on given PR
labels
Jan 4, 2025
Successfully generated as following: http://localhost:1200/30secondsofcode - Success ✔️<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
<channel>
<title>RSSHub</title>
<link>https://www.30secondsofcode.org</link>
<atom:link href="http://localhost:1200/30secondsofcode" rel="self" type="application/rss+xml"></atom:link>
<description>Browse articles by collection or check out the top picks and latest content below. - Powered by RSSHub</description>
<generator>RSSHub</generator>
<webMaster>contact@rsshub.app (RSSHub)</webMaster>
<language>en</language>
<image>
<url>https://www.30secondsofcode.org/assets/splash/work-sunrise-600.webp</url>
<title>RSSHub</title>
<link>https://www.30secondsofcode.org</link>
</image>
<lastBuildDate>Sat, 04 Jan 2025 13:00:01 GMT</lastBuildDate>
<ttl>5</ttl>
<item>
<title>Modeling complex JavaScript object factories</title>
<description><h1>Modeling complex JavaScript object factories</h1> <img src="https://www.30secondsofcode.org/assets/cover/tranquil-desktop-800.webp" srcset="/assets/cover/tranquil-desktop-800.webp 800w,/assets/cover/tranquil-desktop-400.webp 400w,/assets/cover/tranquil-desktop-1200.webp 1200w" alt="" height="180" width="360" fetchpriority="high" referrerpolicy="no-referrer">
<figure class="admonition" data-admonition-type="important">
<figcaption>ℹ Important</figcaption>
<p>This article is part of a series, following <a href="https://www.30secondsofcode.org/js/s/complex-object-serialization">Modeling complex JavaScript object serialization</a>. It may still make sense on its own, but it's highly recommended to read the previous articles first. This series is more of a <strong>show &amp; tell</strong> with the aim to <strong class="sparkles">inspire</strong> you to build more advanced JavaScript projects.</p>
</figure>
<p>In the past four installments, we've created <strong>models</strong>, <strong>queries</strong>, <strong>scopes</strong> and <strong>serialization</strong> for our ActiveRecord-inspired project. As the project grows larger, we might find a need to <strong>test our code</strong>. However, mocking things is hard, especially the more complex our objects get. This is where <strong>factories</strong> come in.</p>
<h2><a href="https://www.30secondsofcode.org/js/s/complex-object-factories#directory-structure" id="directory-structure">Directory structure</a></h2>
<p>On top of what we previously built, we'll add a <strong>new class</strong> in the <code class="notranslate" translate="no">core</code> directory, called <code class="notranslate" translate="no">Factory</code>. We'll also start populating the <code class="notranslate" translate="no">spec</code> directory with <code class="notranslate" translate="no">factories</code> for our models.</p>
<pre class="language-plaintext notranslate" translate="no">src/
├── core/
│ ├── model.js
│ ├── recordSet.js
│ ├── serializer.js
│ └── factory.js
├── models/
│ ├── author.js
│ └── post.js
└── serializers/
├── postSerializer.js
└── postPreviewSerializer.js
spec/
└── factories/
├── authorFactory.js
└── postFactory.js</pre>
<figure class="admonition" data-admonition-type="tip">
<figcaption>💡 Tip</figcaption>
<p>As usual, you can find a <strong>refresher</strong> of the entire implementation so far in the <a href="https://www.30secondsofcode.org/js/s/complex-object-serialization#addendum-code-summary">code summary</a> from the previous article.</p>
</figure>
<h2><a href="https://www.30secondsofcode.org/js/s/complex-object-factories#fixtures-factories" id="fixtures-factories">Fixtures &amp; Factories</a></h2>
<p>In the world of testing, we often need to create <strong>mock data</strong> to test our code. This is often done with <strong>fixtures</strong>, static pieces of data that we can use to test our code. However, fixtures can be cumbersome to maintain, especially as our objects grow complex. They also often break or get outdated, making seemingly unrelated tests fail.</p>
<p>This is where <strong>factories</strong> come in. Factories are <strong>dynamic</strong> pieces of code that can generate <strong>mock data</strong> for our tests. They can be as simple or as complex as we need them to be, and they can be updated easily when our objects change. As far as I can tell, they are based on the <strong>Factory pattern</strong> from the <a href="https://en.wikipedia.org/wiki/Design_Patterns" rel="noopener noreferrer" target="_blank">Gang of Four book</a>.</p>
<p>For this particular implementation, I'm going to loosely base my factories on the <a href="https://thoughtbot.github.io/factory_bot/" rel="noopener noreferrer" target="_blank">Factory Bot</a> gem for Ruby. The reason is that I'm rather familiar with this library and I like using it, so that's where I'm drawing my inspiration from.</p>
<h2><a href="https://www.30secondsofcode.org/js/s/complex-object-factories#the-factory-class" id="the-factory-class">The Factory class</a></h2>
<p>To create our factories, we'll start by creating a <code class="notranslate" translate="no">Factory</code> class in the <code class="notranslate" translate="no">core</code> directory. As we usually do, we'll start simple and build from there.</p>
<h3><a href="https://www.30secondsofcode.org/js/s/complex-object-factories#preparing-the-factory" id="preparing-the-factory">Preparing the factory</a></h3>
<p>First off, we will follow the by-now familiar pattern of using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Static_initialization_blocks" rel="noopener noreferrer" target="_blank"><strong>static initialization blocks</strong></a> to create a <strong>registry</strong> of factories. This will allow us to easily access our factories from anywhere in our code.</p>
<p><em>But what will the registry hold?</em> I hear you asking. If we look to our models, each model needs some initial <code class="notranslate" translate="no">data</code> to pass to its <code class="notranslate" translate="no">constructor</code>. That's what we'll call the <strong>base</strong> of the factory. Then, to allow for more complex objects, we'll also add <strong>traits</strong> to our factories. Traits are essentially modifiers that can be applied to the base data to create customized objects.</p>
<p>Finishing up the setup, we'll also need to pass the <strong>model</strong> to the factory, so it knows what kind of object it's supposed to create. Putting everything together, we arrive at our first draft of the <code class="notranslate" translate="no">prepare</code> static method.</p>
<pre class="language-js notranslate" translate="no" data-code-language="JavaScript" data-code-title="src/core/factory.js"><span class="token keyword module">export</span> <span class="token keyword module">default</span> <span class="token keyword">class</span> <span class="token class-name">Factory</span> <span class="token punctuation">{</span>
<span class="token keyword">static</span> factoryMap <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">static</span> <span class="token function">prepare</span><span class="token punctuation">(</span><span class="token parameter">factory<span class="token punctuation">,</span> model<span class="token punctuation">,</span> base <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span> traits <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> factoryName <span class="token operator">=</span> factory<span class="token punctuation">.</span><span class="token property-access">name</span><span class="token punctuation">;</span>
<span class="token comment">// Store the factory in the factory map</span>
<span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token maybe-class-name">Factory</span><span class="token punctuation">.</span><span class="token property-access">factoryMap</span><span class="token punctuation">[</span>factoryName<span class="token punctuation">]</span><span class="token punctuation">)</span>
<span class="token maybe-class-name">Factory</span><span class="token punctuation">.</span><span class="token property-access">factoryMap</span><span class="token punctuation">.</span><span class="token method function property-access">set</span><span class="token punctuation">(</span>factoryName<span class="token punctuation">,</span> <span class="token punctuation">{</span>
model<span class="token punctuation">,</span>
base<span class="token punctuation">,</span>
traits<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span></pre>
<figure class="admonition" data-admonition-type="note">
<figcaption>💬 Note</figcaption>
<p>In some cases, you may want <strong>customizable traits</strong> with parameters. For example, you might want a few interdependent fields combined in a trait, but you may want to pass some parameter to create them. While this isn't exactly covered in this article, the next few sections cover a similar pattern, via the use of functions.</p>
</figure>
<h3><a href="https://www.30secondsofcode.org/js/s/complex-object-factories#creating-a-factory" id="creating-a-factory">Creating a factory</a></h3>
<p>Given our setup, we can create factories for our models. They won't do much yet, but let's get them set up anyway.</p>
<pre class="language-js notranslate" translate="no" data-code-language="JavaScript" data-code-title="spec/factories/authorFactory.js"><span class="token keyword module">import</span> <span class="token imports"><span class="token maybe-class-name">Factory</span></span> <span class="token keyword module">from</span> <span class="token string">'#src/core/factory.js'</span><span class="token punctuation">;</span>
<span class="token keyword module">import</span> <span class="token imports"><span class="token maybe-class-name">Author</span></span> <span class="token keyword module">from</span> <span class="token string">'#src/models/author.js'</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> base <span class="token operator">=</span> <span class="token punctuation">{</span>
<span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span>
<span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'Author'</span><span class="token punctuation">,</span>
<span class="token literal-property property">surname</span><span class="token operator">:</span> <span class="token string">'Authorson'</span><span class="token punctuation">,</span>
<span class="token literal-property property">email</span><span class="token operator">:</span> <span class="token string">'author@authornet.io'</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword module">export</span> <span class="token keyword module">default</span> <span class="token keyword">class</span> <span class="token class-name">AuthorFactory</span> <span class="token keyword">extends</span> <span class="token class-name">Factory</span> <span class="token punctuation">{</span>
<span class="token keyword">static</span> <span class="token punctuation">{</span>
<span class="token maybe-class-name">Factory</span><span class="token punctuation">.</span><span class="token method function property-access">prepare</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> <span class="token maybe-class-name">Author</span><span class="token punctuation">,</span> base<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span></pre>
<pre class="language-js notranslate" translate="no" data-code-language="JavaScript" data-code-title="spec/factories/postFactory.js"><span class="token keyword module">import</span> <span class="token imports"><span class="token maybe-class-name">Factory</span></span> <span class="token keyword module">from</span> <span class="token string">'#src/core/factory.js'</span><span class="token punctuation">;</span>
<span class="token keyword module">import</span> <span class="token imports"><span class="token maybe-class-name">Post</span></span> <span class="token keyword module">from</span> <span class="token string">'#src/models/post.js'</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> base <span class="token operator">=</span> <span class="token punctuation">{</span>
<span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span>
<span class="token literal-property property">title</span><span class="token operator">:</span> <span class="token string">'Post title'</span><span class="token punctuation">,</span>
<span class="token literal-property property">content</span><span class="token operator">:</span> <span class="token string">'Post content'</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> traits <span class="token operator">=</span> <span class="token punctuation">{</span>
<span class="token literal-property property">published</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token literal-property property">publishedAt</span><span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token literal-property property">unpublished</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token literal-property property">publishedAt</span><span class="token operator">:</span> <span class="token keyword null nil">null</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword module">export</span> <span class="token keyword module">default</span> <span class="token keyword">class</span> <span class="token class-name">PostFactory</span> <span class="token keyword">extends</span> <span class="token class-name">Factory</span> <span class="token punctuation">{</span>
<span class="token keyword">static</span> <span class="token punctuation">{</span>
<span class="token maybe-class-name">Factory</span><span class="token punctuation">.</span><span class="token method function property-access">prepare</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> <span class="token maybe-class-name">Post</span><span class="token punctuation">,</span> base<span class="token punctuation">,</span> traits<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span></pre>
<p>Notice how we're using the <code class="notranslate" translate="no">prepare</code> method to set up our factories. We're passing the factory itself, the model it's supposed to create, the base data for the model, and any traits we want to apply to the base data. As <code class="notranslate" translate="no">Author</code> is a little simpler for the time being, we only need to pass the base data, skipping the traits entirely.</p>
<h3><a href="https://www.30secondsofcode.org/js/s/complex-object-factories#building-objects" id="building-objects">Building objects</a></h3>
<p>Now that we have our factories set up, we can start building objects with them. We'll add a static <code class="notranslate" translate="no">build</code> method to our <code class="notranslate" translate="no">Factory</code> class that will take any number of traits and apply them to the base data.</p>
<pre class="language-js notranslate" translate="no" data-code-language="JavaScript" data-code-title="src/core/factory.js"><span class="token keyword module">export</span> <span class="token keyword module">default</span> <span class="token keyword">class</span> <span class="token class-name">Factory</span> <span class="token punctuation">{</span>
<span class="token comment">// ...</span>
<span class="token keyword">static</span> <span class="token function">build</span><span class="token punctuation">(</span><span class="token parameter"><span class="token spread operator">...</span>desiredTraits</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> factoryName <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">name</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> model<span class="token punctuation">,</span> base<span class="token punctuation">,</span> traits <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token maybe-class-name">Factory</span><span class="token punctuation">.</span><span class="token property-access">factoryMap</span><span class="token punctuation">.</span><span class="token method function property-access">get</span><span class="token punctuation">(</span>factoryName<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// Merge the base and traits</span>
<span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token known-class-name class-name">Object</span><span class="token punctuation">.</span><span class="token method function property-access">assign</span><span class="token punctuation">(</span>
<span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
base<span class="token punctuation">,</span>
<span class="token spread operator">...</span>desiredTraits<span class="token punctuation">.</span><span class="token method function property-access">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">trait</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> traits<span class="token punctuation">[</span>trait<span class="token punctuation">]</span><span class="token punctuation">)</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">return</span> <span class="token keyword">new</span> <span class="token class-name">model</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span></pre>
<p>What we've done is fairly simple, as you can probably tell. As the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">this</code></a> context of the function refers to the factory subclass, we can use <code class="notranslate" translate="no">this.name</code> to get the factory name, look up the factory in the registry, and merge the base data with any desired traits. We then pass the resulting data to the model's constructor and return the new object.</p>
<pre class="language-js notranslate" translate="no" data-code-language="JavaScript"><span class="token keyword">const</span> post <span class="token operator">=</span> <span class="token maybe-class-name">PostFactory</span><span class="token punctuation">.</span><span class="token method function property-access">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// Post { id: 1, title: 'Post title', content: 'Post content' }</span>
<span class="token keyword">const</span> publishedPost <span class="token operator">=</span> <span class="token maybe-class-name">PostFactory</span><span class="token punctuation">.</span><span class="token method function property-access">build</span><span class="token punctuation">(</span><span class="token string">'published'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// Post {</span>
<span class="token comment">// id: 1,</span>
<span class="token comment">// title: 'Post title',</span>
<span class="token comment">// content: 'Post content',</span>
<span class="token comment">// publishedAt: 2025-01-02T00:00:00.000Z</span>
<span class="token comment">//}</span></pre>
<h3><a href="https://www.30secondsofcode.org/js/s/complex-object-factories#customizing-objects" id="customizing-objects">Customizing objects</a></h3>
<p>Our traits are nice and all, but it's inefficient to have to define a trait for every special case in our codebase. While they provide the benefit of <strong>composition</strong>, they can still end up becoming a bit unwieldy.</p>
<p>To solve this, we can allow the <code class="notranslate" translate="no">build</code> method to accept <strong>objects and functions</strong>. Given an object, the method will merge it with the base data. Given a function, it will call the function with the base data and return the result.</p>
<pre class="language-js notranslate" translate="no" data-code-language="JavaScript" data-code-title="src/core/factory.js"><span class="token keyword module">export</span> <span class="token keyword module">default</span> <span class="token keyword">class</span> <span class="token class-name">Factory</span> <span class="token punctuation">{</span>
<span class="token comment">// ...</span>
<span class="token keyword">static</span> <span class="token function">build</span><span class="token punctuation">(</span><span class="token parameter"><span class="token spread operator">...</span>desiredTraits</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> factoryName <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">name</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> model<span class="token punctuation">,</span> base<span class="token punctuation">,</span> traits <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token maybe-class-name">Factory</span><span class="token punctuation">.</span><span class="token property-access">factoryMap</span><span class="token punctuation">.</span><span class="token method function property-access">get</span><span class="token punctuation">(</span>factoryName<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token spread operator">...</span>base <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token comment">// Merge the base and traits</span>
desiredTraits<span class="token punctuation">.</span><span class="token method function property-access">forEach</span><span class="token punctuation">(</span><span class="token parameter">trait</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> trait <span class="token operator">===</span> <span class="token string">'string'</span><span class="token punctuation">)</span>
<span class="token known-class-name class-name">Object</span><span class="token punctuation">.</span><span class="token method function property-access">assign</span><span class="token punctuation">(</span>data<span class="token punctuation">,</span> traits<span class="token punctuation">[</span>trait<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">else</span> <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> trait <span class="token operator">===</span> <span class="token string">'object'</span><span class="token punctuation">)</span>
<span class="token known-class-name class-name">Object</span><span class="token punctuation">.</span><span class="token method function property-access">assign</span><span class="token punctuation">(</span>data<span class="token punctuation">,</span> trait<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">else</span> <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> trait <span class="token operator">===</span> <span class="token string">'function'</span><span class="token punctuation">)</span>
<span class="token known-class-name class-name">Object</span><span class="token punctuation">.</span><span class="token method function property-access">assign</span><span class="token punctuation">(</span>data<span class="token punctuation">,</span> <span class="token function">trait</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">return</span> <span class="token keyword">new</span> <span class="token class-name">model</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span></pre>
<p><em>What can we do with it?</em> Well, we can create very specific objects, as needed. For example, let's pass a custom title to our post and a function to generate a random email for our author.</p>
<pre class="language-js notranslate" translate="no" data-code-language="JavaScript"><span class="token keyword">const</span> post <span class="token operator">=</span> <span class="token maybe-class-name">PostFactory</span><span class="token punctuation">.</span><span class="token method function property-access">build</span><span class="token punctuation">(</span><span class="token string">'unpublished'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">title</span><span class="token operator">:</span> <span class="token string">'Custom title'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// Post {</span>
<span class="token comment">// id: 1,</span>
<span class="token comment">// title: 'Custom title',</span>
<span class="token comment">// content: 'Post content',</span>
<span class="token comment">// publishedAt: null</span>
<span class="token comment">// }</span>
<span class="token keyword">const</span> author <span class="token operator">=</span> <span class="token maybe-class-name">AuthorFactory</span><span class="token punctuation">.</span><span class="token method function property-access">build</span><span class="token punctuation">(</span>
<span class="token punctuation">{</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'John'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token parameter">data</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">email</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token punctuation">.</span><span class="token property-access">name</span><span class="token punctuation">.</span><span class="token method function property-access">toLowerCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">@authornet.io</span><span class="token template-punctuation string">`</span></span> <span class="token punctuation">}</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// Author {</span>
<span class="token comment">// id: 1,</span>
<span class="token comment">// name: 'John',</span>
<span class="token comment">// surname: 'Authorson',</span>
<span class="token comment">// email: 'john@authornet.io'</span>
<span class="token comment">// }</span></pre>
<figure class="admonition" data-admonition-type="note">
<figcaption>💬 Note</figcaption>
<p>Some of you might be wondering why I didn't choose to make the customized object contain functions, so that <code class="notranslate" translate="no">email</code> can be specified as a function, instead of passing two parameters. This is a <strong>design choice</strong> I stand by, as the cost of calling a function for each property can easily pile up. Instead, most of the time, we can get away with passing a function that generates multiple properties at once. At most, we'll end up with an object and a function for each factory call.</p>
</figure>
<h3><a href="https://www.30secondsofcode.org/js/s/complex-object-factories#clearing-objects" id="clearing-objects">Clearing objects</a></h3>
<p>In some cases, we might want to <strong>clear out objects</strong>, to make sure they don't interfere with our tests. This is especially useful when we're counting records or checking relationships, for example.</p>
<p>We can add two new methods, <code class="notranslate" translate="no">clear</code> and <code class="notranslate" translate="no">clearAll</code>, to our <code class="notranslate" translate="no">Factory</code> class to handle this. These methods will simply access the <strong>static variables</strong> (<code class="notranslate" translate="no">instances</code>, <code class="notranslate" translate="no">indexedInstances</code> and <code class="notranslate" translate="no">getterCache</code>) of the <code class="notranslate" translate="no">Model</code> class and reset them.</p>
<pre class="language-js notranslate" translate="no" data-code-language="JavaScript" data-code-title="src/core/factory.js"><span class="token keyword module">import</span> <span class="token imports"><span class="token maybe-class-name">Model</span></span> <span class="token keyword module">from</span> <span class="token string">'#src/core/model.js'</span><span class="token punctuation">;</span>
<span class="token keyword module">export</span> <span class="token keyword module">default</span> <span class="token keyword">class</span> <span class="token class-name">Factory</span> <span class="token punctuation">{</span>
<span class="token comment">// ...</span>
<span class="token keyword">static</span> <span class="token function">clear</span><span class="token punctuation">(</span><span class="token parameter">model</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token maybe-class-name">Model</span><span class="token punctuation">.</span><span class="token property-access">instances</span><span class="token punctuation">[</span>model<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token maybe-class-name">Model</span><span class="token punctuation">.</span><span class="token property-access">indexedInstances</span><span class="token punctuation">[</span>model<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token maybe-class-name">Model</span><span class="token punctuation">.</span><span class="token property-access">getterCache</span><span class="token punctuation">[</span>model<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">static</span> <span class="token function">clearAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token maybe-class-name">Model</span><span class="token punctuation">.</span><span class="token property-access">instances</span> <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token maybe-class-name">Model</span><span class="token punctuation">.</span><span class="token property-access">indexedInstances</span> <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token maybe-class-name">Model</span><span class="token punctuation">.</span><span class="token property-access">getterCache</span> <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span></pre>
<p>And here they are in action, clearing instances created by the <code class="notranslate" translate="no">PostFactory</code>.</p>
<pre class="language-js notranslate" translate="no" data-code-language="JavaScript"><span class="token maybe-class-name">PostFactory</span><span class="token punctuation">.</span><span class="token method function property-access">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token maybe-class-name">Post</span><span class="token punctuation">.</span><span class="token property-access">all</span><span class="token punctuation">.</span><span class="token property-access">length</span><span class="token punctuation">;</span> <span class="token comment">// 1</span>
<span class="token maybe-class-name">Factory</span><span class="token punctuation">.</span><span class="token method function property-access">clear</span><span class="token punctuation">(</span><span class="token string">'Post'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token maybe-class-name">Post</span><span class="token punctuation">.</span><span class="token property-access">all</span><span class="token punctuation">.</span><span class="token property-access">length</span><span class="token punctuation">;</span> <span class="token comment">// 0</span></pre>
<h3><a href="https://www.30secondsofcode.org/js/s/complex-object-factories#convenience-methods" id="convenience-methods">Convenience methods</a></h3>
<p>As you're well aware by this point, convenience methods are a staple of my coding style. I definitely dislike having to find the appropriate factory to call every time I need to create an object. I'd much rather call a method on the <code class="notranslate" translate="no">Factory</code> class itself, specifying the model I want to create.</p>
<p>This requires a little bit of setup first. We'll have to add a <code class="notranslate" translate="no">modelMap</code> to our <code class="notranslate" translate="no">Factory</code> class, which will allow us to look up the factory for a given model. And, instead of having a static <code class="notranslate" translate="no">build</code> method on the Factory class, we'll make sure to define it per factory subclass.</p>
<pre class="language-js notranslate" translate="no" data-code-language="JavaScript" data-code-title="src/core/factory.js"><span class="token keyword module">export</span> <span class="token keyword module">default</span> <span class="token keyword">class</span> <span class="token class-name">Factory</span> <span class="token punctuation">{</span>
<span class="token keyword">static</span> modelMap <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">static</span> <span class="token function">prepare</span><span class="token punctuation">(</span><span class="token parameter">factory<span class="token punctuation">,</span> model<span class="token punctuation">,</span> base <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span> traits <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> modelName <span class="token operator">=</span> model<span class="token punctuation">.</span><span class="token property-access">name</span><span class="token punctuation">;</span>
<span class="token comment">// Store the factory in the model map</span>
<span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token maybe-class-name">Factory</span><span class="token punctuation">.</span><span class="token property-access">modelMap</span><span class="token punctuation">.</span><span class="token method function property-access">has</span><span class="token punctuation">(</span>modelName<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token maybe-class-name">Factory</span><span class="token punctuation">.</span><span class="token property-access">modelMap</span><span class="token punctuation">.</span><span class="token method function property-access">set</span><span class="token punctuation">(</span>modelName<span class="token punctuation">,</span> factory<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token known-class-name class-name">Object</span><span class="token punctuation">.</span><span class="token method function property-access">defineProperty</span><span class="token punctuation">(</span>factory<span class="token punctuation">,</span> <span class="token string">'build'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
<span class="token function-variable function">value</span><span class="token operator">:</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token spread operator">...</span>desiredTraits</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token spread operator">...</span>base <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token comment">// Merge the base and traits</span>
desiredTraits<span class="token punctuation">.</span><span class="token method function property-access">forEach</span><span class="token punctuation">(</span><span class="token parameter">trait</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> trait <span class="token operator">===</span> <span class="token string">'string'</span><span class="token punctuation">)</span>
<span class="token known-class-name class-name">Object</span><span class="token punctuation">.</span><span class="token method function property-access">assign</span><span class="token punctuation">(</span>data<span class="token punctuation">,</span> traits<span class="token punctuation">[</span>trait<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">else</span> <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> trait <span class="token operator">===</span> <span class="token string">'object'</span><span class="token punctuation">)</span>
<span class="token known-class-name class-name">Object</span><span class="token punctuation">.</span><span class="token method function property-access">assign</span><span class="token punctuation">(</span>data<span class="token punctuation">,</span> trait<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">else</span> <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> trait <span class="token operator">===</span> <span class="token string">'function'</span><span class="token punctuation">)</span>
<span class="token known-class-name class-name">Object</span><span class="token punctuation">.</span><span class="token method function property-access">assign</span><span class="token punctuation">(</span>data<span class="token punctuation">,</span> <span class="token function">trait</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">return</span> <span class="token keyword">new</span> <span class="token class-name">model</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">// Remove the static `build` method</span>
<span class="token punctuation">}</span></pre>
<p>So far, we've ended up exactly where we were before. Only difference is that we've moved the <code class="notranslate" translate="no">build</code> method to the factory subclass, using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference ... |
http://localhost:1200/30secondsofcode/react - Success ✔️<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
<channel>
<title>React Articles</title>
<link>https://www.30secondsofcode.org</link>
<atom:link href="http://localhost:1200/30secondsofcode/react" rel="self" type="application/rss+xml"></atom:link>
<description>The React article collection contains function components and reusable hooks for React 18. - Powered by RSSHub</description>
<generator>RSSHub</generator>
<webMaster>contact@rsshub.app (RSSHub)</webMaster>
<language>en</language>
<image>
<url>https://www.30secondsofcode.org/assets/splash/succulent-cluster-600.webp</url>
<title>React Articles</title>
<link>https://www.30secondsofcode.org</link>
</image>
<lastBuildDate>Sat, 04 Jan 2025 13:00:05 GMT</lastBuildDate>
<ttl>5</ttl>
<item>
<title>React useClickOutside and useClickInside hooks</title>
<description><h1>React useClickOutside and useClickInside hooks</h1> <img src="https://www.30secondsofcode.org/assets/cover/clutter-800.webp" srcset="/assets/cover/clutter-800.webp 800w,/assets/cover/clutter-400.webp 400w,/assets/cover/clutter-1200.webp 1200w" alt="" height="180" width="360" fetchpriority="high" referrerpolicy="no-referrer"> <p>Handling <strong>click events</strong> in React is usually as simple as adding an <code class="notranslate" translate="no">onClick</code> handler to a component. However, there are cases where you need to handle clicks outside or inside a component. This takes some extra effort and, most often than not, requires the use of custom hooks.</p>
<figure class="admonition" data-admonition-type="note">
<figcaption>💬 Note</figcaption>
<p>It's highly recommended that you learn more about <a href="https://www.30secondsofcode.org/js/s/listen-click-outside-event">detection of click events outside an element with JavaScript</a>.</p>
</figure>
<h2><a href="https://www.30secondsofcode.org/react/s/use-click-outside-inside#use-click-outside-hook" id="use-click-outside-hook"><code class="notranslate" translate="no">useClickOutside</code> hook</a></h2>
<p>In order to handle clicks <strong>outside of a component</strong>, you'll first need to have a <strong>reference</strong> to the component. This is where the <a href="https://react.dev/reference/react/useRef" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">useRef()</code></a> hook comes in handy. You can then use the <a href="https://react.dev/reference/react/useEffect" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">useEffect()</code></a> hook to add an <strong>event listener</strong> to the <code class="notranslate" translate="no">document</code> and check if the click event occurred outside the component.</p>
<pre class="language-jsx notranslate" translate="no" data-code-language="React"><span class="token keyword">const</span> <span class="token function-variable function">useClickOutside</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">ref<span class="token punctuation">,</span> callback</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> <span class="token function-variable function">handleClick</span> <span class="token operator">=</span> <span class="token parameter">e</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>ref<span class="token punctuation">.</span><span class="token property-access">current</span> <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>ref<span class="token punctuation">.</span><span class="token property-access">current</span><span class="token punctuation">.</span><span class="token method function property-access">contains</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span><span class="token property-access">target</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">callback</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token maybe-class-name">React</span><span class="token punctuation">.</span><span class="token method function property-access">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">addEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> handleClick<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">removeEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> handleClick<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></pre>
<p>Using the hook is fairly simple. You just need to pass a <code class="notranslate" translate="no">ref</code> and a <code class="notranslate" translate="no">callback</code> function to handle the click event. The <code class="notranslate" translate="no">callback</code> function will be called when the click event occurs outside the component.</p>
<pre class="language-jsx notranslate" translate="no" data-code-language="React"><span class="token keyword">const</span> <span class="token function-variable function"><span class="token maybe-class-name">ClickBox</span></span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> onClickOutside <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> clickRef <span class="token operator">=</span> <span class="token maybe-class-name">React</span><span class="token punctuation">.</span><span class="token method function property-access">useRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">useClickOutside</span><span class="token punctuation">(</span>clickRef<span class="token punctuation">,</span> onClickOutside<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">return</span> <span class="token punctuation">(</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span>
<span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>click-box<span class="token punctuation">"</span></span>
<span class="token attr-name">ref</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>clickRef<span class="token punctuation">}</span></span>
<span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span>
<span class="token literal-property property">border</span><span class="token operator">:</span> <span class="token string">'2px dashed orangered'</span><span class="token punctuation">,</span>
<span class="token literal-property property">height</span><span class="token operator">:</span> <span class="token number">200</span><span class="token punctuation">,</span>
<span class="token literal-property property">width</span><span class="token operator">:</span> <span class="token number">400</span><span class="token punctuation">,</span>
<span class="token literal-property property">display</span><span class="token operator">:</span> <span class="token string">'flex'</span><span class="token punctuation">,</span>
<span class="token literal-property property">justifyContent</span><span class="token operator">:</span> <span class="token string">'center'</span><span class="token punctuation">,</span>
<span class="token literal-property property">alignItems</span><span class="token operator">:</span> <span class="token string">'center'</span>
<span class="token punctuation">}</span><span class="token punctuation">}</span></span>
<span class="token punctuation">&gt;</span></span><span class="token plain-text">
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span><span class="token plain-text">Click out of this element</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token maybe-class-name">ReactDOM</span><span class="token punctuation">.</span><span class="token method function property-access">createRoot</span><span class="token punctuation">(</span><span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">getElementById</span><span class="token punctuation">(</span><span class="token string">'root'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">render</span><span class="token punctuation">(</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">ClickBox</span></span> <span class="token attr-name">onClickOutside</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token function">alert</span><span class="token punctuation">(</span><span class="token string">'click outside'</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span> <span class="token punctuation">/&gt;</span></span>
<span class="token punctuation">)</span><span class="token punctuation">;</span></pre>
<h2><a href="https://www.30secondsofcode.org/react/s/use-click-outside-inside#use-click-inside-hook" id="use-click-inside-hook"><code class="notranslate" translate="no">useClickInside</code> hook</a></h2>
<p>Similarly, you can create a hook to handle clicks <strong>inside a component</strong>. The logic is almost the same, but you need to check if the click event occurred inside the component. This hook can come in handy when you need to handle clicks on multiple children and you don't want to add an <code class="notranslate" translate="no">onClick</code> handler to each of them.</p>
<pre class="language-jsx notranslate" translate="no" data-code-language="React"><span class="token keyword">const</span> <span class="token function-variable function">useClickInside</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">ref<span class="token punctuation">,</span> callback</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> <span class="token function-variable function">handleClick</span> <span class="token operator">=</span> <span class="token parameter">e</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>ref<span class="token punctuation">.</span><span class="token property-access">current</span> <span class="token operator">&amp;&amp;</span> ref<span class="token punctuation">.</span><span class="token property-access">current</span><span class="token punctuation">.</span><span class="token method function property-access">contains</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span><span class="token property-access">target</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">callback</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token maybe-class-name">React</span><span class="token punctuation">.</span><span class="token method function property-access">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">addEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> handleClick<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">removeEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> handleClick<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></pre>
<p>Again, you will need to pass the <code class="notranslate" translate="no">ref</code> and a <code class="notranslate" translate="no">callback</code> function to handle the click event inside the component, same as before.</p>
<pre class="language-jsx notranslate" translate="no" data-code-language="React"><span class="token keyword">const</span> <span class="token function-variable function"><span class="token maybe-class-name">ClickBox</span></span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> onClickInside <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> clickRef <span class="token operator">=</span> <span class="token maybe-class-name">React</span><span class="token punctuation">.</span><span class="token method function property-access">useRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">useClickInside</span><span class="token punctuation">(</span>clickRef<span class="token punctuation">,</span> onClickInside<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">return</span> <span class="token punctuation">(</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span>
<span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>click-box<span class="token punctuation">"</span></span>
<span class="token attr-name">ref</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>clickRef<span class="token punctuation">}</span></span>
<span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span>
<span class="token literal-property property">border</span><span class="token operator">:</span> <span class="token string">'2px dashed orangered'</span><span class="token punctuation">,</span>
<span class="token literal-property property">height</span><span class="token operator">:</span> <span class="token number">200</span><span class="token punctuation">,</span>
<span class="token literal-property property">width</span><span class="token operator">:</span> <span class="token number">400</span><span class="token punctuation">,</span>
<span class="token literal-property property">display</span><span class="token operator">:</span> <span class="token string">'flex'</span><span class="token punctuation">,</span>
<span class="token literal-property property">justifyContent</span><span class="token operator">:</span> <span class="token string">'center'</span><span class="token punctuation">,</span>
<span class="token literal-property property">alignItems</span><span class="token operator">:</span> <span class="token string">'center'</span>
<span class="token punctuation">}</span><span class="token punctuation">}</span></span>
<span class="token punctuation">&gt;</span></span><span class="token plain-text">
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span><span class="token plain-text">Click inside this element</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token maybe-class-name">ReactDOM</span><span class="token punctuation">.</span><span class="token method function property-access">createRoot</span><span class="token punctuation">(</span><span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">getElementById</span><span class="token punctuation">(</span><span class="token string">'root'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">render</span><span class="token punctuation">(</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">ClickBox</span></span> <span class="token attr-name">onClickInside</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token function">alert</span><span class="token punctuation">(</span><span class="token string">'click inside'</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span> <span class="token punctuation">/&gt;</span></span>
<span class="token punctuation">)</span><span class="token punctuation">;</span></pre> <footer> <p>Last updated: <time datetime="2024-07-05">July 5, 2024</time> <a href="https://github.com/Chalarangelo/30-seconds-of-code/blob/master/content/snippets/react/s/use-click-outside-inside.md" target="_blank" rel="noopener noreferrer">View on GitHub</a> </p> </footer> </description>
<link>https://www.30secondsofcode.org/react/s/use-click-outside-inside</link>
<guid isPermaLink="false">https://www.30secondsofcode.org/react/s/use-click-outside-inside</guid>
<pubDate>Thu, 04 Jul 2024 16:00:00 GMT</pubDate>
<author>30 Seconds of Code</author>
<enclosure url="https://www.30secondsofcode.orghttps://www.30secondsofcode.org/assets/cover/clutter-800.webp" type="image/jpeg"></enclosure>
<category> React </category>
<category> Hooks </category>
</item>
<item>
<title>Advanced React state hooks</title>
<description><h1>Advanced React state hooks</h1> <img src="https://www.30secondsofcode.org/assets/cover/engine-800.webp" srcset="/assets/cover/engine-800.webp 800w,/assets/cover/engine-400.webp 400w,/assets/cover/engine-1200.webp 1200w" alt="" height="180" width="360" fetchpriority="high" referrerpolicy="no-referrer"> <p>React's toolbox is intentionally quite limited, providing you some very versatile building blocks to create your own abstractions. But, if you find <a href="https://react.dev/reference/react/useState" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">useState()</code></a> too limited for your needs, and <a href="https://react.dev/reference/react/useReducer" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">useReducer()</code></a> doesn't quite cut it either, how do you go about creating more advanced state management hooks? Let's deep dive into some advanced state management hooks.</p>
<h2><a href="https://www.30secondsofcode.org/react/s/advanced-react-state-hooks#use-toggler-hook" id="use-toggler-hook"><code class="notranslate" translate="no">useToggler</code> hook</a></h2>
<p>Starting off with a simple one, the <code class="notranslate" translate="no">useToggler</code> hook provides a <strong>boolean state variable</strong> that can be toggled between its two states. Instead of managing the state manually, you can simply call the <code class="notranslate" translate="no">toggleValue</code> function to toggle the state.</p>
<p>The implementation is rather simple, as well. You use the <a href="https://react.dev/reference/react/useState" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">useState()</code></a> hook to create the <code class="notranslate" translate="no">value</code> state variable and its setter. Then, you create a function that toggles the value of the state variable and memoize it, using the <a href="https://react.dev/reference/react/useCallback" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">useCallback()</code></a> hook. Finally, you return the <code class="notranslate" translate="no">value</code> state variable and the memoized function.</p>
<pre class="language-jsx notranslate" translate="no" data-code-language="React"><span class="token keyword">const</span> <span class="token function-variable function">useToggler</span> <span class="token operator">=</span> <span class="token parameter">initialState</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> <span class="token punctuation">[</span>value<span class="token punctuation">,</span> setValue<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token maybe-class-name">React</span><span class="token punctuation">.</span><span class="token method function property-access">useState</span><span class="token punctuation">(</span>initialState<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> toggleValue <span class="token operator">=</span> <span class="token maybe-class-name">React</span><span class="token punctuation">.</span><span class="token method function property-access">useCallback</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token function">setValue</span><span class="token punctuation">(</span><span class="token parameter">prev</span> <span class="token arrow operator">=&gt;</span> <span class="token operator">!</span>prev<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">return</span> <span class="token punctuation">[</span>value<span class="token punctuation">,</span> toggleValue<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token function-variable function"><span class="token maybe-class-name">Switch</span></span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> <span class="token punctuation">[</span>val<span class="token punctuation">,</span> toggleVal<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useToggler</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>toggleVal<span class="token punctuation">}</span></span><span class="token punctuation">&gt;</span></span><span class="token punctuation">{</span>val <span class="token operator">?</span> <span class="token string">'ON'</span> <span class="token operator">:</span> <span class="token string">'OFF'</span><span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token maybe-class-name">ReactDOM</span><span class="token punctuation">.</span><span class="token method function property-access">createRoot</span><span class="token punctuation">(</span><span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">getElementById</span><span class="token punctuation">(</span><span class="token string">'root'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">render</span><span class="token punctuation">(</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Switch</span></span> <span class="token punctuation">/&gt;</span></span>
<span class="token punctuation">)</span><span class="token punctuation">;</span></pre>
<h2><a href="https://www.30secondsofcode.org/react/s/advanced-react-state-hooks#use-map-hook" id="use-map-hook"><code class="notranslate" translate="no">useMap</code> hook</a></h2>
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">Map</code></a> object is a very versatile <strong>data structure</strong> in JavaScript, but it's not directly supported by React's state management hooks. The <code class="notranslate" translate="no">useMap</code> hook creates a stateful <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">Map</code></a> object and a set of functions to manipulate it.</p>
<p>Using the <a href="https://react.dev/reference/react/useState" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">useState()</code></a> hook and the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">Map()</code></a> constructor, you create a new <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">Map</code></a> from the <code class="notranslate" translate="no">initialValue</code>. Then, you use the <a href="https://react.dev/reference/react/useMemo" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">useMemo()</code></a> hook to create a set of non-mutating actions that manipulate the state variable, using the state setter to create a new <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">Map</code></a> every time. Finally, you return the <code class="notranslate" translate="no">map</code> state variable and the created <code class="notranslate" translate="no">actions</code>.</p>
<pre class="language-jsx notranslate" translate="no" data-code-language="React"><span class="token keyword">const</span> <span class="token function-variable function">useMap</span> <span class="token operator">=</span> <span class="token parameter">initialValue</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> <span class="token punctuation">[</span>map<span class="token punctuation">,</span> setMap<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token maybe-class-name">React</span><span class="token punctuation">.</span><span class="token method function property-access">useState</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span>initialValue<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> actions <span class="token operator">=</span> <span class="token maybe-class-name">React</span><span class="token punctuation">.</span><span class="token method function property-access">useMemo</span><span class="token punctuation">(</span>
<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
<span class="token function-variable function">set</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">key<span class="token punctuation">,</span> value</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span>
<span class="token function">setMap</span><span class="token punctuation">(</span><span class="token parameter">prevMap</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> nextMap <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span>prevMap<span class="token punctuation">)</span><span class="token punctuation">;</span>
nextMap<span class="token punctuation">.</span><span class="token method function property-access">set</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">return</span> nextMap<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token function-variable function">remove</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">key<span class="token punctuation">,</span> value</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span>
<span class="token function">setMap</span><span class="token punctuation">(</span><span class="token parameter">prevMap</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> nextMap <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span>prevMap<span class="token punctuation">)</span><span class="token punctuation">;</span>
nextMap<span class="token punctuation">.</span><span class="token method function property-access">delete</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">return</span> nextMap<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token function-variable function">clear</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token function">setMap</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">[</span>setMap<span class="token punctuation">]</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">return</span> <span class="token punctuation">[</span>map<span class="token punctuation">,</span> actions<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token function-variable function"><span class="token maybe-class-name">MyApp</span></span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> <span class="token punctuation">[</span>map<span class="token punctuation">,</span> <span class="token punctuation">{</span> set<span class="token punctuation">,</span> remove<span class="token punctuation">,</span> clear <span class="token punctuation">}</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useMap</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">[</span><span class="token string">'apples'</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">return</span> <span class="token punctuation">(</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token function">set</span><span class="token punctuation">(</span><span class="token known-class-name class-name">Date</span><span class="token punctuation">.</span><span class="token method function property-access">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">toJSON</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">Add</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token function">clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">Reset</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token function">remove</span><span class="token punctuation">(</span><span class="token string">'apples'</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span> <span class="token attr-name">disabled</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token operator">!</span>map<span class="token punctuation">.</span><span class="token method function property-access">has</span><span class="token punctuation">(</span><span class="token string">'apples'</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
Remove apples
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>pre</span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
</span><span class="token punctuation">{</span><span class="token known-class-name class-name">JSON</span><span class="token punctuation">.</span><span class="token method function property-access">stringify</span><span class="token punctuation">(</span>
<span class="token punctuation">[</span><span class="token spread operator">...</span>map<span class="token punctuation">.</span><span class="token method function property-access">entries</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token method function property-access">reduce</span><span class="token punctuation">(</span>
<span class="token punctuation">(</span><span class="token parameter">acc<span class="token punctuation">,</span> <span class="token punctuation">[</span>key<span class="token punctuation">,</span> value<span class="token punctuation">]</span></span><span class="token punctuation">)</span> &l ... |
http://localhost:1200/30secondsofcode/css/interactivity - Success ✔️<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
<channel>
<title>CSS Interactivity Articles</title>
<link>https://www.30secondsofcode.org</link>
<atom:link href="http://localhost:1200/30secondsofcode/css/interactivity" rel="self" type="application/rss+xml"></atom:link>
<description>The CSS article collection contains utilities and interactive examples for CSS3. It includes modern techniques for creating commonly-used layouts, styling and animating elements, as well as articles for handling user interactions. - Powered by RSSHub</description>
<generator>RSSHub</generator>
<webMaster>contact@rsshub.app (RSSHub)</webMaster>
<language>en</language>
<image>
<url>https://www.30secondsofcode.org/assets/splash/camera-600.webp</url>
<title>CSS Interactivity Articles</title>
<link>https://www.30secondsofcode.org</link>
</image>
<lastBuildDate>Sat, 04 Jan 2025 13:00:06 GMT</lastBuildDate>
<ttl>5</ttl>
<item>
<title>Image gallery with horizontal or vertical scroll</title>
<description><h1>Image gallery with horizontal or vertical scroll</h1> <img src="https://www.30secondsofcode.org/assets/cover/flower-portrait-5-800.webp" srcset="/assets/cover/flower-portrait-5-800.webp 800w,/assets/cover/flower-portrait-5-400.webp 400w,/assets/cover/flower-portrait-5-1200.webp 1200w" alt="" height="180" width="360" fetchpriority="high" referrerpolicy="no-referrer"> <style>
/* Style CodePen embeds to fit the demos */
.codepen-wrapper > div { height: 820px; }
</style>
<p>Image galleries are useful in various contexts, from showcasing products to displaying a collection of images. Depending on your needs, you may want to create a horizontally or vertically scrollable gallery. While CSS has come a long way, unfortunately, you'll have to get your hands dirty with JavaScript to achieve this effect.</p>
<figure class="admonition" data-admonition-type="note">
<figcaption>💬 Note</figcaption>
<p>You may first want to get caught up with <a href="https://www.30secondsofcode.org/css/s/scroll-snap">scroll snapping</a> to understand how it works, if you haven't already.</p>
</figure>
<h2><a href="https://www.30secondsofcode.org/css/s/horizontal-vertical-gallery#image-gallery-with-vertical-scroll" id="image-gallery-with-vertical-scroll">Image gallery with vertical scroll</a></h2>
<p>To create a <strong>vertically scrollable image gallery</strong>, you will need to create a container with <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/display#flex" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">display: flex</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content#center" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">justify-content: center</code></a> and a set of slides with <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/display#flex" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">display: flex</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/flex-direction#column" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">flex-direction: column</code></a>.</p>
<p>You will also need to use <code class="notranslate" translate="no">scroll-snap-type: y mandatory</code> and <code class="notranslate" translate="no">overscroll-behavior-y: contain</code> to create a snap effect on vertical scroll. Snap elements to the start of the container using <code class="notranslate" translate="no">scroll-snap-align: start</code>. In order to hide the scrollbars, you can use <code class="notranslate" translate="no">scrollbar-width: none</code> and style the pseudo-element <code class="notranslate" translate="no">::-webkit-scrollbar</code> to <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/display#none" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">display: none</code></a>.</p>
<p>Then, you can use <code class="notranslate" translate="no">Element.scrollTo()</code> to define a <code class="notranslate" translate="no">scrollToElement</code> function that scrolls the gallery to the given item. You can use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">Array.prototype.map()</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">Array.prototype.join()</code></a> to populate the <code class="notranslate" translate="no">.thumbnails</code> element. Give each thumbnail a <code class="notranslate" translate="no">data-id</code> attribute with the index of the image.</p>
<p>Using the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">Document.querySelectorAll()</code></a> method, you can get all the thumbnail elements. Use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">Array.prototype.forEach()</code></a> to register a handler for the <code class="notranslate" translate="no">'click'</code> event on each thumbnail, using <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">EventTarget.addEventListener()</code></a> and the <code class="notranslate" translate="no">scrollToElement</code> function.</p>
<p>Finally, use <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">Document.querySelector()</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">EventTarget.addEventListener()</code></a> to register a handler for the <code class="notranslate" translate="no">'scroll'</code> event. Update the <code class="notranslate" translate="no">.thumbnails</code> and <code class="notranslate" translate="no">.scrollbar</code> elements to match the current scroll position, using the <code class="notranslate" translate="no">scrollThumb</code> function.</p>
<figure class="codepen-wrapper">
<p class="codepen" data-height="100%" data-theme-id="dark" data-default-tab="result" data-border-color="#07071c" data-border="none" data-tab-bar-color="#161632" data-tab-link-color="#e3e3e8" data-active-link-color="#8bb7fe" data-active-tab-accent-color="#5394fd" data-active-tab-color="#18203a" data-tab-color="#18203a" data-slug-hash="ExqVMzW" data-user="chalarangelo">
<span>See the <a href="https://codepen.io/chalarangelo/pen/ExqVMzW" target="_blank" rel="noopener noreferrer">embedded CodePen</a></span>
</p>
</figure>
<pre class="language-html notranslate" translate="no" data-code-language="HTML"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>gallery-container<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>thumbnails<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>scrollbar<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>thumb<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>slides<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/1067/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/122/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/188/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/249/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/257/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/259/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/283/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/288/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/299/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span></pre>
<pre class="language-css notranslate" translate="no" data-code-language="CSS"><span class="token selector"><span class="token class">.gallery-container</span></span> <span class="token punctuation">{</span>
<span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
<span class="token property">justify-content</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector"><span class="token class">.thumbnails</span></span> <span class="token punctuation">{</span>
<span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
<span class="token property">flex-direction</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span>
<span class="token property">gap</span><span class="token punctuation">:</span> <span class="token number">8</span><span class="token unit">px</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector"><span class="token class">.thumbnails</span> img</span> <span class="token punctuation">{</span>
<span class="token property">width</span><span class="token punctuation">:</span> <span class="token number">40</span><span class="token unit">px</span><span class="token punctuation">;</span>
<span class="token property">height</span><span class="token punctuation">:</span> <span class="token number">40</span><span class="token unit">px</span><span class="token punctuation">;</span>
<span class="token property">cursor</span><span class="token punctuation">:</span> pointer<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector"><span class="token class">.scrollbar</span></span> <span class="token punctuation">{</span>
<span class="token property">width</span><span class="token punctuation">:</span> <span class="token number">1</span><span class="token unit">px</span><span class="token punctuation">;</span>
<span class="token property">height</span><span class="token punctuation">:</span> <span class="token number">720</span><span class="token unit">px</span><span class="token punctuation">;</span>
<span class="token property">background</span><span class="token punctuation">:</span> <span class="token hexcode color" style="--hex-color:#ccc">#ccc</span><span class="token punctuation">;</span>
<span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span>
<span class="token property">margin</span><span class="token punctuation">:</span> <span class="token number">0</span> <span class="token number">0</span> <span class="token number">0</span> <span class="token number">8</span><span class="token unit">px</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector"><span class="token class">.thumb</span></span> <span class="token punctuation">{</span>
<span class="token property">width</span><span class="token punctuation">:</span> <span class="token number">1</span><span class="token unit">px</span><span class="token punctuation">;</span>
<span class="token property">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span>
<span class="token property">height</span><span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="token property">background</span><span class="token punctuation">:</span> <span class="token hexcode color" style="--hex-color:#000">#000</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector"><span class="token class">.slides</span></span> <span class="token punctuation">{</span>
<span class="token property">margin</span><span class="token punctuation">:</span> <span class="token number">0</span> <span class="token number">16</span><span class="token unit">px</span><span class="token punctuation">;</span>
<span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span>
<span class="token property">grid-auto-flow</span><span class="token punctuation">:</span> row<span class="token punctuation">;</span>
<span class="token property">gap</span><span class="token punctuation">:</span> <span class="token number">1</span><span class="token unit">rem</span><span class="token punctuation">;</span>
<span class="token property">width</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token number">540</span><span class="token unit">px</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token unit">rem</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token property">padding</span><span class="token punctuation">:</span> <span class="token number">0</span> <span class="token number">0.25</span><span class="token unit">rem</span><span class="token punctuation">;</span>
<span class="token property">height</span><span class="token punctuation">:</span> <span class="token number">720</span><span class="token unit">px</span><span class="token punctuation">;</span>
<span class="token property">overflow-y</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span>
<span class="token property">overscroll-behavior-y</span><span class="token punctuation">:</span> contain<span class="token punctuation">;</span>
<span class="token property">scroll-snap-type</span><span class="token punctuation">:</span> y mandatory<span class="token punctuation">;</span>
<span class="token property">scrollbar-width</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector"><span class="token class">.slides</span> <span class="token combinator">&gt;</span> div</span> <span class="token punctuation">{</span>
<span class="token property">scroll-snap-align</span><span class="token punctuation">:</span> start<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector"><span class="token class">.slides</span> img</span> <span class="token punctuation">{</span>
<span class="token property">width</span><span class="token punctuation">:</span> <span class="token number">540</span><span class="token unit">px</span><span class="token punctuation">;</span>
<span class="token property">object-fit</span><span class="token punctuation">:</span> contain<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector"><span class="token class">.slides</span><span class="token pseudo-element">::-webkit-scrollbar</span></span> <span class="token punctuation">{</span>
<span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
<span class="token punctuation">}</span></pre>
<pre class="language-js notranslate" translate="no" data-code-language="JavaScript"><span class="token keyword">const</span> slideGallery <span class="token operator">=</span> <span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">querySelector</span><span class="token punctuation">(</span><span class="token string">'.slides'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> slides <span class="token operator">=</span> slideGallery<span class="token punctuation">.</span><span class="token method function property-access">querySelectorAll</span><span class="token punctuation">(</span><span class="token string">'div'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> scrollbarThumb <span class="token operator">=</span> <span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">querySelector</span><span class="token punctuation">(</span><span class="token string">'.thumb'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> slideCount <span class="token operator">=</span> slides<span class="token punctuation">.</span><span class="token property-access">length</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> slideHeight <span class="token operator">=</span> <span class="token number">720</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> marginTop <span class="token operator">=</span> <span class="token number">16</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token function-variable function">scrollThumb</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> index <span class="token operator">=</span> <span class="token known-class-name class-name">Math</span><span class="token punctuation">.</span><span class="token method function property-access">floor</span><span class="token punctuation">(</span>slideGallery<span class="token punctuation">.</span><span class="token property-access">scrollTop</span> <span class="token operator">/</span> slideHeight<span class="token punctuation">)</span><span class="token punctuation">;</span>
scrollbarThumb<span class="token punctuation">.</span><span class="token property-access">style</span><span class="token punctuation">.</span><span class="token property-access">height</span> <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token punctuation">(</span><span class="token punctuation">(</span>index <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">/</span> slideCount<span class="token punctuation">)</span> <span class="token operator">*</span> slideHeight<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">px</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token function-variable function">scrollToElement</span> <span class="token operator">=</span> <span class="token parameter">el</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> index <span class="token operator">=</span> <span class="token function">parseInt</span><span class="token punctuation">(</span>el<span class="token punctuation">.</span><span class="token property-access">dataset</span><span class="token punctuation">.</span><span class="token property-access">id</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
slideGallery<span class="token punctuation">.</span><span class="token method function property-access">scrollTo</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> index <span class="token operator">*</span> slideHeight <span class="token operator">+</span> marginTop<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">querySelector</span><span class="token punctuation">(</span><span class="token string">'.thumbnails'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token property-access">innerHTML</span> <span class="token operator">+=</span> <span class="token punctuation">[</span><span class="token spread operator">...</span>slides<span class="token punctuation">]</span>
<span class="token punctuation">.</span><span class="token method function property-access">map</span><span class="token punctuation">(</span>
<span class="token punctuation">(</span><span class="token parameter">slide<span class="token punctuation">,</span> i</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;img src="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>slide<span class="token punctuation">.</span><span class="token method function property-access">querySelector</span><span class="token punctuation">(</span><span class="token string">'img'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token property-access">src</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" data-id="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>i<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"&gt;</span><span class="token template-punctuation string">`</span></span>
<span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token method function property-access">join</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">querySelectorAll</span><span class="token punctuation">(</span><span class="token string">'.thumbnails img'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">forEach</span><span class="token punctuation">(</span><span class="token parameter">el</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
el<span class="token punctuation">.</span><span class="token method function property-access">addEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token function">scrollToElement</span><span class="token punctuation">(</span>el<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
slideGallery<span class="token punctuation">.</span><span class="token method function property-access">addEventListener</span><span class="token punctuation">(</span><span class="token string">'scroll'</span><span class="token punctuation">,</span> <span class="token parameter">e</span> <span class="token arrow operator">=&gt;</span> <span class="token function">scrollThumb</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">scrollThumb</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre>
<h2><a href="https://www.30secondsofcode.org/css/s/horizontal-vertical-gallery#image-gallery-with-horizontal-scroll" id="image-gallery-with-horizontal-scroll">Image gallery with horizontal scroll</a></h2>
<p>To create a <strong>horizontally scrollable image gallery</strong>, you will need to position its <code class="notranslate" translate="no">.thumbnails</code> container at the bottom of the gallery, using <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/position#absolute" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">position: absolute</code></a>. Then, use <code class="notranslate" translate="no">scroll-snap-type: x mandatory</code> and <code class="notranslate" translate="no">overscroll-behavior-x: contain</code> to create a snap effect on horizontal scroll. Snap elements to the start of the container using <code class="notranslate" translate="no">scroll-snap-align: start</code>.</p>
<p>Hide the scrollbars the same way as before. Use <code class="notranslate" translate="no">Element.scrollTo()</code> to define a <code class="notranslate" translate="no">scrollToElement</code> function that scrolls the gallery to the given item. Populate the <code class="notranslate" translate="no">.thumbnails</code> element using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">Array.prototype.map()</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">Array.prototype.join()</code></a>, giving each thumbnail a <code class="notranslate" translate="no">data-id</code> attribute with the index of the image.</p>
<p>Use <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">Document.querySelectorAll()</code></a> to get all the thumbnail elements and register <code class="notranslate" translate="no">'click'</code> event handlers on each thumbnail, using the <code class="notranslate" translate="no">highlightThumbnail</code> function. Finally, register a handler for the <code class="notranslate" translate="no">'scroll'</code> event the same as before and update the <code class="notranslate" translate="no">.thumbnails</code> element to match the current scroll position using the <code class="notranslate" translate="no">highlightThumbnail</code> function.</p>
<figure class="codepen-wrapper">
<p class="codepen" data-height="100%" data-theme-id="dark" data-default-tab="result" data-border-color="#07071c" data-border="none" data-tab-bar-color="#161632" data-tab-link-color="#e3e3e8" data-active-link-color="#8bb7fe" data-active-tab-accent-color="#5394fd" data-active-tab-color="#18203a" data-tab-color="#18203a" data-slug-hash="RwXWObY" data-user="chalarangelo">
<span>See the <a href="https://codepen.io/chalarangelo/pen/RwXWObY" target="_blank" rel="noopener noreferrer">embedded CodePen</a></span>
</p>
</figure>
<pre class="language-html notranslate" translate="no" data-code-language="HTML"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>gallery-container<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>thumbnails<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>slides<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/1067/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/122/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/188/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/249/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/257/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/259/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/283/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/288/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt |
TonyRL
reviewed
Jan 5, 2025
Successfully generated as following: http://localhost:1200/30secondsofcode/latest - Success ✔️<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
<channel>
<title>New & Popular Snippets</title>
<link>https://www.30secondsofcode.org</link>
<atom:link href="http://localhost:1200/30secondsofcode/latest" rel="self" type="application/rss+xml"></atom:link>
<description>Discover short code snippets for all your development needs. - Powered by RSSHub</description>
<generator>RSSHub</generator>
<webMaster>contact@rsshub.app (RSSHub)</webMaster>
<language>en</language>
<lastBuildDate>Sun, 05 Jan 2025 16:34:17 GMT</lastBuildDate>
<ttl>5</ttl>
<item>
<title>Modeling complex JavaScript object factories</title>
<description><img src="https://www.30secondsofcode.org/assets/cover/tranquil-desktop-800.webp" srcset="/assets/cover/tranquil-desktop-800.webp 800w,/assets/cover/tranquil-desktop-400.webp 400w,/assets/cover/tranquil-desktop-1200.webp 1200w" alt="" height="180" width="360" fetchpriority="high" referrerpolicy="no-referrer">
<figure class="admonition" data-admonition-type="important">
<figcaption>ℹ Important</figcaption>
<p>This article is part of a series, following <a href="https://www.30secondsofcode.org/js/s/complex-object-serialization">Modeling complex JavaScript object serialization</a>. It may still make sense on its own, but it's highly recommended to read the previous articles first. This series is more of a <strong>show &amp; tell</strong> with the aim to <strong class="sparkles">inspire</strong> you to build more advanced JavaScript projects.</p>
</figure>
<p>In the past four installments, we've created <strong>models</strong>, <strong>queries</strong>, <strong>scopes</strong> and <strong>serialization</strong> for our ActiveRecord-inspired project. As the project grows larger, we might find a need to <strong>test our code</strong>. However, mocking things is hard, especially the more complex our objects get. This is where <strong>factories</strong> come in.</p>
<h2><a href="https://www.30secondsofcode.org/js/s/complex-object-factories#directory-structure" id="directory-structure">Directory structure</a></h2>
<p>On top of what we previously built, we'll add a <strong>new class</strong> in the <code class="notranslate" translate="no">core</code> directory, called <code class="notranslate" translate="no">Factory</code>. We'll also start populating the <code class="notranslate" translate="no">spec</code> directory with <code class="notranslate" translate="no">factories</code> for our models.</p>
<pre class="language-plaintext notranslate" translate="no">src/
├── core/
│ ├── model.js
│ ├── recordSet.js
│ ├── serializer.js
│ └── factory.js
├── models/
│ ├── author.js
│ └── post.js
└── serializers/
├── postSerializer.js
└── postPreviewSerializer.js
spec/
└── factories/
├── authorFactory.js
└── postFactory.js</pre>
<figure class="admonition" data-admonition-type="tip">
<figcaption>💡 Tip</figcaption>
<p>As usual, you can find a <strong>refresher</strong> of the entire implementation so far in the <a href="https://www.30secondsofcode.org/js/s/complex-object-serialization#addendum-code-summary">code summary</a> from the previous article.</p>
</figure>
<h2><a href="https://www.30secondsofcode.org/js/s/complex-object-factories#fixtures-factories" id="fixtures-factories">Fixtures &amp; Factories</a></h2>
<p>In the world of testing, we often need to create <strong>mock data</strong> to test our code. This is often done with <strong>fixtures</strong>, static pieces of data that we can use to test our code. However, fixtures can be cumbersome to maintain, especially as our objects grow complex. They also often break or get outdated, making seemingly unrelated tests fail.</p>
<p>This is where <strong>factories</strong> come in. Factories are <strong>dynamic</strong> pieces of code that can generate <strong>mock data</strong> for our tests. They can be as simple or as complex as we need them to be, and they can be updated easily when our objects change. As far as I can tell, they are based on the <strong>Factory pattern</strong> from the <a href="https://en.wikipedia.org/wiki/Design_Patterns" rel="noopener noreferrer" target="_blank">Gang of Four book</a>.</p>
<p>For this particular implementation, I'm going to loosely base my factories on the <a href="https://thoughtbot.github.io/factory_bot/" rel="noopener noreferrer" target="_blank">Factory Bot</a> gem for Ruby. The reason is that I'm rather familiar with this library and I like using it, so that's where I'm drawing my inspiration from.</p>
<h2><a href="https://www.30secondsofcode.org/js/s/complex-object-factories#the-factory-class" id="the-factory-class">The Factory class</a></h2>
<p>To create our factories, we'll start by creating a <code class="notranslate" translate="no">Factory</code> class in the <code class="notranslate" translate="no">core</code> directory. As we usually do, we'll start simple and build from there.</p>
<h3><a href="https://www.30secondsofcode.org/js/s/complex-object-factories#preparing-the-factory" id="preparing-the-factory">Preparing the factory</a></h3>
<p>First off, we will follow the by-now familiar pattern of using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Static_initialization_blocks" rel="noopener noreferrer" target="_blank"><strong>static initialization blocks</strong></a> to create a <strong>registry</strong> of factories. This will allow us to easily access our factories from anywhere in our code.</p>
<p><em>But what will the registry hold?</em> I hear you asking. If we look to our models, each model needs some initial <code class="notranslate" translate="no">data</code> to pass to its <code class="notranslate" translate="no">constructor</code>. That's what we'll call the <strong>base</strong> of the factory. Then, to allow for more complex objects, we'll also add <strong>traits</strong> to our factories. Traits are essentially modifiers that can be applied to the base data to create customized objects.</p>
<p>Finishing up the setup, we'll also need to pass the <strong>model</strong> to the factory, so it knows what kind of object it's supposed to create. Putting everything together, we arrive at our first draft of the <code class="notranslate" translate="no">prepare</code> static method.</p>
<pre class="language-js notranslate" translate="no" data-code-language="JavaScript" data-code-title="src/core/factory.js"><span class="token keyword module">export</span> <span class="token keyword module">default</span> <span class="token keyword">class</span> <span class="token class-name">Factory</span> <span class="token punctuation">{</span>
<span class="token keyword">static</span> factoryMap <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">static</span> <span class="token function">prepare</span><span class="token punctuation">(</span><span class="token parameter">factory<span class="token punctuation">,</span> model<span class="token punctuation">,</span> base <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span> traits <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> factoryName <span class="token operator">=</span> factory<span class="token punctuation">.</span><span class="token property-access">name</span><span class="token punctuation">;</span>
<span class="token comment">// Store the factory in the factory map</span>
<span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token maybe-class-name">Factory</span><span class="token punctuation">.</span><span class="token property-access">factoryMap</span><span class="token punctuation">[</span>factoryName<span class="token punctuation">]</span><span class="token punctuation">)</span>
<span class="token maybe-class-name">Factory</span><span class="token punctuation">.</span><span class="token property-access">factoryMap</span><span class="token punctuation">.</span><span class="token method function property-access">set</span><span class="token punctuation">(</span>factoryName<span class="token punctuation">,</span> <span class="token punctuation">{</span>
model<span class="token punctuation">,</span>
base<span class="token punctuation">,</span>
traits<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span></pre>
<figure class="admonition" data-admonition-type="note">
<figcaption>💬 Note</figcaption>
<p>In some cases, you may want <strong>customizable traits</strong> with parameters. For example, you might want a few interdependent fields combined in a trait, but you may want to pass some parameter to create them. While this isn't exactly covered in this article, the next few sections cover a similar pattern, via the use of functions.</p>
</figure>
<h3><a href="https://www.30secondsofcode.org/js/s/complex-object-factories#creating-a-factory" id="creating-a-factory">Creating a factory</a></h3>
<p>Given our setup, we can create factories for our models. They won't do much yet, but let's get them set up anyway.</p>
<pre class="language-js notranslate" translate="no" data-code-language="JavaScript" data-code-title="spec/factories/authorFactory.js"><span class="token keyword module">import</span> <span class="token imports"><span class="token maybe-class-name">Factory</span></span> <span class="token keyword module">from</span> <span class="token string">'#src/core/factory.js'</span><span class="token punctuation">;</span>
<span class="token keyword module">import</span> <span class="token imports"><span class="token maybe-class-name">Author</span></span> <span class="token keyword module">from</span> <span class="token string">'#src/models/author.js'</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> base <span class="token operator">=</span> <span class="token punctuation">{</span>
<span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span>
<span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'Author'</span><span class="token punctuation">,</span>
<span class="token literal-property property">surname</span><span class="token operator">:</span> <span class="token string">'Authorson'</span><span class="token punctuation">,</span>
<span class="token literal-property property">email</span><span class="token operator">:</span> <span class="token string">'author@authornet.io'</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword module">export</span> <span class="token keyword module">default</span> <span class="token keyword">class</span> <span class="token class-name">AuthorFactory</span> <span class="token keyword">extends</span> <span class="token class-name">Factory</span> <span class="token punctuation">{</span>
<span class="token keyword">static</span> <span class="token punctuation">{</span>
<span class="token maybe-class-name">Factory</span><span class="token punctuation">.</span><span class="token method function property-access">prepare</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> <span class="token maybe-class-name">Author</span><span class="token punctuation">,</span> base<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span></pre>
<pre class="language-js notranslate" translate="no" data-code-language="JavaScript" data-code-title="spec/factories/postFactory.js"><span class="token keyword module">import</span> <span class="token imports"><span class="token maybe-class-name">Factory</span></span> <span class="token keyword module">from</span> <span class="token string">'#src/core/factory.js'</span><span class="token punctuation">;</span>
<span class="token keyword module">import</span> <span class="token imports"><span class="token maybe-class-name">Post</span></span> <span class="token keyword module">from</span> <span class="token string">'#src/models/post.js'</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> base <span class="token operator">=</span> <span class="token punctuation">{</span>
<span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span>
<span class="token literal-property property">title</span><span class="token operator">:</span> <span class="token string">'Post title'</span><span class="token punctuation">,</span>
<span class="token literal-property property">content</span><span class="token operator">:</span> <span class="token string">'Post content'</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> traits <span class="token operator">=</span> <span class="token punctuation">{</span>
<span class="token literal-property property">published</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token literal-property property">publishedAt</span><span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token literal-property property">unpublished</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token literal-property property">publishedAt</span><span class="token operator">:</span> <span class="token keyword null nil">null</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword module">export</span> <span class="token keyword module">default</span> <span class="token keyword">class</span> <span class="token class-name">PostFactory</span> <span class="token keyword">extends</span> <span class="token class-name">Factory</span> <span class="token punctuation">{</span>
<span class="token keyword">static</span> <span class="token punctuation">{</span>
<span class="token maybe-class-name">Factory</span><span class="token punctuation">.</span><span class="token method function property-access">prepare</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> <span class="token maybe-class-name">Post</span><span class="token punctuation">,</span> base<span class="token punctuation">,</span> traits<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span></pre>
<p>Notice how we're using the <code class="notranslate" translate="no">prepare</code> method to set up our factories. We're passing the factory itself, the model it's supposed to create, the base data for the model, and any traits we want to apply to the base data. As <code class="notranslate" translate="no">Author</code> is a little simpler for the time being, we only need to pass the base data, skipping the traits entirely.</p>
<h3><a href="https://www.30secondsofcode.org/js/s/complex-object-factories#building-objects" id="building-objects">Building objects</a></h3>
<p>Now that we have our factories set up, we can start building objects with them. We'll add a static <code class="notranslate" translate="no">build</code> method to our <code class="notranslate" translate="no">Factory</code> class that will take any number of traits and apply them to the base data.</p>
<pre class="language-js notranslate" translate="no" data-code-language="JavaScript" data-code-title="src/core/factory.js"><span class="token keyword module">export</span> <span class="token keyword module">default</span> <span class="token keyword">class</span> <span class="token class-name">Factory</span> <span class="token punctuation">{</span>
<span class="token comment">// ...</span>
<span class="token keyword">static</span> <span class="token function">build</span><span class="token punctuation">(</span><span class="token parameter"><span class="token spread operator">...</span>desiredTraits</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> factoryName <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">name</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> model<span class="token punctuation">,</span> base<span class="token punctuation">,</span> traits <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token maybe-class-name">Factory</span><span class="token punctuation">.</span><span class="token property-access">factoryMap</span><span class="token punctuation">.</span><span class="token method function property-access">get</span><span class="token punctuation">(</span>factoryName<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// Merge the base and traits</span>
<span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token known-class-name class-name">Object</span><span class="token punctuation">.</span><span class="token method function property-access">assign</span><span class="token punctuation">(</span>
<span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
base<span class="token punctuation">,</span>
<span class="token spread operator">...</span>desiredTraits<span class="token punctuation">.</span><span class="token method function property-access">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">trait</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> traits<span class="token punctuation">[</span>trait<span class="token punctuation">]</span><span class="token punctuation">)</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">return</span> <span class="token keyword">new</span> <span class="token class-name">model</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span></pre>
<p>What we've done is fairly simple, as you can probably tell. As the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">this</code></a> context of the function refers to the factory subclass, we can use <code class="notranslate" translate="no">this.name</code> to get the factory name, look up the factory in the registry, and merge the base data with any desired traits. We then pass the resulting data to the model's constructor and return the new object.</p>
<pre class="language-js notranslate" translate="no" data-code-language="JavaScript"><span class="token keyword">const</span> post <span class="token operator">=</span> <span class="token maybe-class-name">PostFactory</span><span class="token punctuation">.</span><span class="token method function property-access">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// Post { id: 1, title: 'Post title', content: 'Post content' }</span>
<span class="token keyword">const</span> publishedPost <span class="token operator">=</span> <span class="token maybe-class-name">PostFactory</span><span class="token punctuation">.</span><span class="token method function property-access">build</span><span class="token punctuation">(</span><span class="token string">'published'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// Post {</span>
<span class="token comment">// id: 1,</span>
<span class="token comment">// title: 'Post title',</span>
<span class="token comment">// content: 'Post content',</span>
<span class="token comment">// publishedAt: 2025-01-02T00:00:00.000Z</span>
<span class="token comment">//}</span></pre>
<h3><a href="https://www.30secondsofcode.org/js/s/complex-object-factories#customizing-objects" id="customizing-objects">Customizing objects</a></h3>
<p>Our traits are nice and all, but it's inefficient to have to define a trait for every special case in our codebase. While they provide the benefit of <strong>composition</strong>, they can still end up becoming a bit unwieldy.</p>
<p>To solve this, we can allow the <code class="notranslate" translate="no">build</code> method to accept <strong>objects and functions</strong>. Given an object, the method will merge it with the base data. Given a function, it will call the function with the base data and return the result.</p>
<pre class="language-js notranslate" translate="no" data-code-language="JavaScript" data-code-title="src/core/factory.js"><span class="token keyword module">export</span> <span class="token keyword module">default</span> <span class="token keyword">class</span> <span class="token class-name">Factory</span> <span class="token punctuation">{</span>
<span class="token comment">// ...</span>
<span class="token keyword">static</span> <span class="token function">build</span><span class="token punctuation">(</span><span class="token parameter"><span class="token spread operator">...</span>desiredTraits</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> factoryName <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">name</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> model<span class="token punctuation">,</span> base<span class="token punctuation">,</span> traits <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token maybe-class-name">Factory</span><span class="token punctuation">.</span><span class="token property-access">factoryMap</span><span class="token punctuation">.</span><span class="token method function property-access">get</span><span class="token punctuation">(</span>factoryName<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token spread operator">...</span>base <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token comment">// Merge the base and traits</span>
desiredTraits<span class="token punctuation">.</span><span class="token method function property-access">forEach</span><span class="token punctuation">(</span><span class="token parameter">trait</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> trait <span class="token operator">===</span> <span class="token string">'string'</span><span class="token punctuation">)</span>
<span class="token known-class-name class-name">Object</span><span class="token punctuation">.</span><span class="token method function property-access">assign</span><span class="token punctuation">(</span>data<span class="token punctuation">,</span> traits<span class="token punctuation">[</span>trait<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">else</span> <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> trait <span class="token operator">===</span> <span class="token string">'object'</span><span class="token punctuation">)</span>
<span class="token known-class-name class-name">Object</span><span class="token punctuation">.</span><span class="token method function property-access">assign</span><span class="token punctuation">(</span>data<span class="token punctuation">,</span> trait<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">else</span> <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> trait <span class="token operator">===</span> <span class="token string">'function'</span><span class="token punctuation">)</span>
<span class="token known-class-name class-name">Object</span><span class="token punctuation">.</span><span class="token method function property-access">assign</span><span class="token punctuation">(</span>data<span class="token punctuation">,</span> <span class="token function">trait</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">return</span> <span class="token keyword">new</span> <span class="token class-name">model</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span></pre>
<p><em>What can we do with it?</em> Well, we can create very specific objects, as needed. For example, let's pass a custom title to our post and a function to generate a random email for our author.</p>
<pre class="language-js notranslate" translate="no" data-code-language="JavaScript"><span class="token keyword">const</span> post <span class="token operator">=</span> <span class="token maybe-class-name">PostFactory</span><span class="token punctuation">.</span><span class="token method function property-access">build</span><span class="token punctuation">(</span><span class="token string">'unpublished'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">title</span><span class="token operator">:</span> <span class="token string">'Custom title'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// Post {</span>
<span class="token comment">// id: 1,</span>
<span class="token comment">// title: 'Custom title',</span>
<span class="token comment">// content: 'Post content',</span>
<span class="token comment">// publishedAt: null</span>
<span class="token comment">// }</span>
<span class="token keyword">const</span> author <span class="token operator">=</span> <span class="token maybe-class-name">AuthorFactory</span><span class="token punctuation">.</span><span class="token method function property-access">build</span><span class="token punctuation">(</span>
<span class="token punctuation">{</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'John'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token parameter">data</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">email</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>data<span class="token punctuation">.</span><span class="token property-access">name</span><span class="token punctuation">.</span><span class="token method function property-access">toLowerCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">@authornet.io</span><span class="token template-punctuation string">`</span></span> <span class="token punctuation">}</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// Author {</span>
<span class="token comment">// id: 1,</span>
<span class="token comment">// name: 'John',</span>
<span class="token comment">// surname: 'Authorson',</span>
<span class="token comment">// email: 'john@authornet.io'</span>
<span class="token comment">// }</span></pre>
<figure class="admonition" data-admonition-type="note">
<figcaption>💬 Note</figcaption>
<p>Some of you might be wondering why I didn't choose to make the customized object contain functions, so that <code class="notranslate" translate="no">email</code> can be specified as a function, instead of passing two parameters. This is a <strong>design choice</strong> I stand by, as the cost of calling a function for each property can easily pile up. Instead, most of the time, we can get away with passing a function that generates multiple properties at once. At most, we'll end up with an object and a function for each factory call.</p>
</figure>
<h3><a href="https://www.30secondsofcode.org/js/s/complex-object-factories#clearing-objects" id="clearing-objects">Clearing objects</a></h3>
<p>In some cases, we might want to <strong>clear out objects</strong>, to make sure they don't interfere with our tests. This is especially useful when we're counting records or checking relationships, for example.</p>
<p>We can add two new methods, <code class="notranslate" translate="no">clear</code> and <code class="notranslate" translate="no">clearAll</code>, to our <code class="notranslate" translate="no">Factory</code> class to handle this. These methods will simply access the <strong>static variables</strong> (<code class="notranslate" translate="no">instances</code>, <code class="notranslate" translate="no">indexedInstances</code> and <code class="notranslate" translate="no">getterCache</code>) of the <code class="notranslate" translate="no">Model</code> class and reset them.</p>
<pre class="language-js notranslate" translate="no" data-code-language="JavaScript" data-code-title="src/core/factory.js"><span class="token keyword module">import</span> <span class="token imports"><span class="token maybe-class-name">Model</span></span> <span class="token keyword module">from</span> <span class="token string">'#src/core/model.js'</span><span class="token punctuation">;</span>
<span class="token keyword module">export</span> <span class="token keyword module">default</span> <span class="token keyword">class</span> <span class="token class-name">Factory</span> <span class="token punctuation">{</span>
<span class="token comment">// ...</span>
<span class="token keyword">static</span> <span class="token function">clear</span><span class="token punctuation">(</span><span class="token parameter">model</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token maybe-class-name">Model</span><span class="token punctuation">.</span><span class="token property-access">instances</span><span class="token punctuation">[</span>model<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token maybe-class-name">Model</span><span class="token punctuation">.</span><span class="token property-access">indexedInstances</span><span class="token punctuation">[</span>model<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token maybe-class-name">Model</span><span class="token punctuation">.</span><span class="token property-access">getterCache</span><span class="token punctuation">[</span>model<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">static</span> <span class="token function">clearAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token maybe-class-name">Model</span><span class="token punctuation">.</span><span class="token property-access">instances</span> <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token maybe-class-name">Model</span><span class="token punctuation">.</span><span class="token property-access">indexedInstances</span> <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token maybe-class-name">Model</span><span class="token punctuation">.</span><span class="token property-access">getterCache</span> <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span></pre>
<p>And here they are in action, clearing instances created by the <code class="notranslate" translate="no">PostFactory</code>.</p>
<pre class="language-js notranslate" translate="no" data-code-language="JavaScript"><span class="token maybe-class-name">PostFactory</span><span class="token punctuation">.</span><span class="token method function property-access">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token maybe-class-name">Post</span><span class="token punctuation">.</span><span class="token property-access">all</span><span class="token punctuation">.</span><span class="token property-access">length</span><span class="token punctuation">;</span> <span class="token comment">// 1</span>
<span class="token maybe-class-name">Factory</span><span class="token punctuation">.</span><span class="token method function property-access">clear</span><span class="token punctuation">(</span><span class="token string">'Post'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token maybe-class-name">Post</span><span class="token punctuation">.</span><span class="token property-access">all</span><span class="token punctuation">.</span><span class="token property-access">length</span><span class="token punctuation">;</span> <span class="token comment">// 0</span></pre>
<h3><a href="https://www.30secondsofcode.org/js/s/complex-object-factories#convenience-methods" id="convenience-methods">Convenience methods</a></h3>
<p>As you're well aware by this point, convenience methods are a staple of my coding style. I definitely dislike having to find the appropriate factory to call every time I need to create an object. I'd much rather call a method on the <code class="notranslate" translate="no">Factory</code> class itself, specifying the model I want to create.</p>
<p>This requires a little bit of setup first. We'll have to add a <code class="notranslate" translate="no">modelMap</code> to our <code class="notranslate" translate="no">Factory</code> class, which will allow us to look up the factory for a given model. And, instead of having a static <code class="notranslate" translate="no">build</code> method on the Factory class, we'll make sure to define it per factory subclass.</p>
<pre class="language-js notranslate" translate="no" data-code-language="JavaScript" data-code-title="src/core/factory.js"><span class="token keyword module">export</span> <span class="token keyword module">default</span> <span class="token keyword">class</span> <span class="token class-name">Factory</span> <span class="token punctuation">{</span>
<span class="token keyword">static</span> modelMap <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">static</span> <span class="token function">prepare</span><span class="token punctuation">(</span><span class="token parameter">factory<span class="token punctuation">,</span> model<span class="token punctuation">,</span> base <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span> traits <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> modelName <span class="token operator">=</span> model<span class="token punctuation">.</span><span class="token property-access">name</span><span class="token punctuation">;</span>
<span class="token comment">// Store the factory in the model map</span>
<span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token maybe-class-name">Factory</span><span class="token punctuation">.</span><span class="token property-access">modelMap</span><span class="token punctuation">.</span><span class="token method function property-access">has</span><span class="token punctuation">(</span>modelName<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token maybe-class-name">Factory</span><span class="token punctuation">.</span><span class="token property-access">modelMap</span><span class="token punctuation">.</span><span class="token method function property-access">set</span><span class="token punctuation">(</span>modelName<span class="token punctuation">,</span> factory<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token known-class-name class-name">Object</span><span class="token punctuation">.</span><span class="token method function property-access">defineProperty</span><span class="token punctuation">(</span>factory<span class="token punctuation">,</span> <span class="token string">'build'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
<span class="token function-variable function">value</span><span class="token operator">:</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token spread operator">...</span>desiredTraits</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token spread operator">...</span>base <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token comment">// Merge the base and traits</span>
desiredTraits<span class="token punctuation">.</span><span class="token method function property-access">forEach</span><span class="token punctuation">(</span><span class="token parameter">trait</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> trait <span class="token operator">===</span> <span class="token string">'string'</span><span class="token punctuation">)</span>
<span class="token known-class-name class-name">Object</span><span class="token punctuation">.</span><span class="token method function property-access">assign</span><span class="token punctuation">(</span>data<span class="token punctuation">,</span> traits<span class="token punctuation">[</span>trait<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">else</span> <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> trait <span class="token operator">===</span> <span class="token string">'object'</span><span class="token punctuation">)</span>
<span class="token known-class-name class-name">Object</span><span class="token punctuation">.</span><span class="token method function property-access">assign</span><span class="token punctuation">(</span>data<span class="token punctuation">,</span> trait<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">else</span> <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> trait <span class="token operator">===</span> <span class="token string">'function'</span><span class="token punctuation">)</span>
<span class="token known-class-name class-name">Object</span><span class="token punctuation">.</span><span class="token method function property-access">assign</span><span class="token punctuation">(</span>data<span class="token punctuation">,</span> <span class="token function">trait</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">return</span> <span class="token keyword">new</span> <span class="token class-name">model</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">// Remove the static `build` method</span>
<span class="token punctuation">}</span></pre>
<p>So far, we've ended up exactly where we were before. Only difference is that we've moved the <code class="notranslate" translate="no">build</code> method to the factory subclass, using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">Object.defineProperty() ... |
http://localhost:1200/30secondsofcode/category/react - Success ✔️<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
<channel>
<title>React Articles</title>
<link>https://www.30secondsofcode.org</link>
<atom:link href="http://localhost:1200/30secondsofcode/category/react" rel="self" type="application/rss+xml"></atom:link>
<description>The React article collection contains function components and reusable hooks for React 18. - Powered by RSSHub</description>
<generator>RSSHub</generator>
<webMaster>contact@rsshub.app (RSSHub)</webMaster>
<language>en</language>
<image>
<url>https://www.30secondsofcode.org/assets/splash/succulent-cluster-600.webp</url>
<title>React Articles</title>
<link>https://www.30secondsofcode.org</link>
</image>
<lastBuildDate>Sun, 05 Jan 2025 16:34:21 GMT</lastBuildDate>
<ttl>5</ttl>
<item>
<title>React useClickOutside and useClickInside hooks</title>
<description><img src="https://www.30secondsofcode.org/assets/cover/clutter-800.webp" srcset="/assets/cover/clutter-800.webp 800w,/assets/cover/clutter-400.webp 400w,/assets/cover/clutter-1200.webp 1200w" alt="" height="180" width="360" fetchpriority="high" referrerpolicy="no-referrer"> <p>Handling <strong>click events</strong> in React is usually as simple as adding an <code class="notranslate" translate="no">onClick</code> handler to a component. However, there are cases where you need to handle clicks outside or inside a component. This takes some extra effort and, most often than not, requires the use of custom hooks.</p>
<figure class="admonition" data-admonition-type="note">
<figcaption>💬 Note</figcaption>
<p>It's highly recommended that you learn more about <a href="https://www.30secondsofcode.org/js/s/listen-click-outside-event">detection of click events outside an element with JavaScript</a>.</p>
</figure>
<h2><a href="https://www.30secondsofcode.org/react/s/use-click-outside-inside#use-click-outside-hook" id="use-click-outside-hook"><code class="notranslate" translate="no">useClickOutside</code> hook</a></h2>
<p>In order to handle clicks <strong>outside of a component</strong>, you'll first need to have a <strong>reference</strong> to the component. This is where the <a href="https://react.dev/reference/react/useRef" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">useRef()</code></a> hook comes in handy. You can then use the <a href="https://react.dev/reference/react/useEffect" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">useEffect()</code></a> hook to add an <strong>event listener</strong> to the <code class="notranslate" translate="no">document</code> and check if the click event occurred outside the component.</p>
<pre class="language-jsx notranslate" translate="no" data-code-language="React"><span class="token keyword">const</span> <span class="token function-variable function">useClickOutside</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">ref<span class="token punctuation">,</span> callback</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> <span class="token function-variable function">handleClick</span> <span class="token operator">=</span> <span class="token parameter">e</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>ref<span class="token punctuation">.</span><span class="token property-access">current</span> <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>ref<span class="token punctuation">.</span><span class="token property-access">current</span><span class="token punctuation">.</span><span class="token method function property-access">contains</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span><span class="token property-access">target</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">callback</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token maybe-class-name">React</span><span class="token punctuation">.</span><span class="token method function property-access">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">addEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> handleClick<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">removeEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> handleClick<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></pre>
<p>Using the hook is fairly simple. You just need to pass a <code class="notranslate" translate="no">ref</code> and a <code class="notranslate" translate="no">callback</code> function to handle the click event. The <code class="notranslate" translate="no">callback</code> function will be called when the click event occurs outside the component.</p>
<pre class="language-jsx notranslate" translate="no" data-code-language="React"><span class="token keyword">const</span> <span class="token function-variable function"><span class="token maybe-class-name">ClickBox</span></span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> onClickOutside <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> clickRef <span class="token operator">=</span> <span class="token maybe-class-name">React</span><span class="token punctuation">.</span><span class="token method function property-access">useRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">useClickOutside</span><span class="token punctuation">(</span>clickRef<span class="token punctuation">,</span> onClickOutside<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">return</span> <span class="token punctuation">(</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span>
<span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>click-box<span class="token punctuation">"</span></span>
<span class="token attr-name">ref</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>clickRef<span class="token punctuation">}</span></span>
<span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span>
<span class="token literal-property property">border</span><span class="token operator">:</span> <span class="token string">'2px dashed orangered'</span><span class="token punctuation">,</span>
<span class="token literal-property property">height</span><span class="token operator">:</span> <span class="token number">200</span><span class="token punctuation">,</span>
<span class="token literal-property property">width</span><span class="token operator">:</span> <span class="token number">400</span><span class="token punctuation">,</span>
<span class="token literal-property property">display</span><span class="token operator">:</span> <span class="token string">'flex'</span><span class="token punctuation">,</span>
<span class="token literal-property property">justifyContent</span><span class="token operator">:</span> <span class="token string">'center'</span><span class="token punctuation">,</span>
<span class="token literal-property property">alignItems</span><span class="token operator">:</span> <span class="token string">'center'</span>
<span class="token punctuation">}</span><span class="token punctuation">}</span></span>
<span class="token punctuation">&gt;</span></span><span class="token plain-text">
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span><span class="token plain-text">Click out of this element</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token maybe-class-name">ReactDOM</span><span class="token punctuation">.</span><span class="token method function property-access">createRoot</span><span class="token punctuation">(</span><span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">getElementById</span><span class="token punctuation">(</span><span class="token string">'root'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">render</span><span class="token punctuation">(</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">ClickBox</span></span> <span class="token attr-name">onClickOutside</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token function">alert</span><span class="token punctuation">(</span><span class="token string">'click outside'</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span> <span class="token punctuation">/&gt;</span></span>
<span class="token punctuation">)</span><span class="token punctuation">;</span></pre>
<h2><a href="https://www.30secondsofcode.org/react/s/use-click-outside-inside#use-click-inside-hook" id="use-click-inside-hook"><code class="notranslate" translate="no">useClickInside</code> hook</a></h2>
<p>Similarly, you can create a hook to handle clicks <strong>inside a component</strong>. The logic is almost the same, but you need to check if the click event occurred inside the component. This hook can come in handy when you need to handle clicks on multiple children and you don't want to add an <code class="notranslate" translate="no">onClick</code> handler to each of them.</p>
<pre class="language-jsx notranslate" translate="no" data-code-language="React"><span class="token keyword">const</span> <span class="token function-variable function">useClickInside</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">ref<span class="token punctuation">,</span> callback</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> <span class="token function-variable function">handleClick</span> <span class="token operator">=</span> <span class="token parameter">e</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>ref<span class="token punctuation">.</span><span class="token property-access">current</span> <span class="token operator">&amp;&amp;</span> ref<span class="token punctuation">.</span><span class="token property-access">current</span><span class="token punctuation">.</span><span class="token method function property-access">contains</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span><span class="token property-access">target</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">callback</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token maybe-class-name">React</span><span class="token punctuation">.</span><span class="token method function property-access">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">addEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> handleClick<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">removeEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> handleClick<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></pre>
<p>Again, you will need to pass the <code class="notranslate" translate="no">ref</code> and a <code class="notranslate" translate="no">callback</code> function to handle the click event inside the component, same as before.</p>
<pre class="language-jsx notranslate" translate="no" data-code-language="React"><span class="token keyword">const</span> <span class="token function-variable function"><span class="token maybe-class-name">ClickBox</span></span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> onClickInside <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> clickRef <span class="token operator">=</span> <span class="token maybe-class-name">React</span><span class="token punctuation">.</span><span class="token method function property-access">useRef</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">useClickInside</span><span class="token punctuation">(</span>clickRef<span class="token punctuation">,</span> onClickInside<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">return</span> <span class="token punctuation">(</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span>
<span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>click-box<span class="token punctuation">"</span></span>
<span class="token attr-name">ref</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>clickRef<span class="token punctuation">}</span></span>
<span class="token attr-name">style</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span>
<span class="token literal-property property">border</span><span class="token operator">:</span> <span class="token string">'2px dashed orangered'</span><span class="token punctuation">,</span>
<span class="token literal-property property">height</span><span class="token operator">:</span> <span class="token number">200</span><span class="token punctuation">,</span>
<span class="token literal-property property">width</span><span class="token operator">:</span> <span class="token number">400</span><span class="token punctuation">,</span>
<span class="token literal-property property">display</span><span class="token operator">:</span> <span class="token string">'flex'</span><span class="token punctuation">,</span>
<span class="token literal-property property">justifyContent</span><span class="token operator">:</span> <span class="token string">'center'</span><span class="token punctuation">,</span>
<span class="token literal-property property">alignItems</span><span class="token operator">:</span> <span class="token string">'center'</span>
<span class="token punctuation">}</span><span class="token punctuation">}</span></span>
<span class="token punctuation">&gt;</span></span><span class="token plain-text">
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span><span class="token plain-text">Click inside this element</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token maybe-class-name">ReactDOM</span><span class="token punctuation">.</span><span class="token method function property-access">createRoot</span><span class="token punctuation">(</span><span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">getElementById</span><span class="token punctuation">(</span><span class="token string">'root'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">render</span><span class="token punctuation">(</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">ClickBox</span></span> <span class="token attr-name">onClickInside</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token function">alert</span><span class="token punctuation">(</span><span class="token string">'click inside'</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span> <span class="token punctuation">/&gt;</span></span>
<span class="token punctuation">)</span><span class="token punctuation">;</span></pre> <footer> <p>Last updated: <time datetime="2024-07-05">July 5, 2024</time> <a href="https://github.com/Chalarangelo/30-seconds-of-code/blob/master/content/snippets/react/s/use-click-outside-inside.md" target="_blank" rel="noopener noreferrer">View on GitHub</a> </p> </footer> </description>
<link>https://www.30secondsofcode.org/react/s/use-click-outside-inside</link>
<guid isPermaLink="false">https://www.30secondsofcode.org/react/s/use-click-outside-inside</guid>
<pubDate>Thu, 04 Jul 2024 16:00:00 GMT</pubDate>
<author>30 Seconds of Code</author>
<enclosure url="https://www.30secondsofcode.orghttps://www.30secondsofcode.org/assets/cover/clutter-800.webp" type="image/jpeg"></enclosure>
<category> React </category>
<category> Hooks </category>
</item>
<item>
<title>Advanced React state hooks</title>
<description><img src="https://www.30secondsofcode.org/assets/cover/engine-800.webp" srcset="/assets/cover/engine-800.webp 800w,/assets/cover/engine-400.webp 400w,/assets/cover/engine-1200.webp 1200w" alt="" height="180" width="360" fetchpriority="high" referrerpolicy="no-referrer"> <p>React's toolbox is intentionally quite limited, providing you some very versatile building blocks to create your own abstractions. But, if you find <a href="https://react.dev/reference/react/useState" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">useState()</code></a> too limited for your needs, and <a href="https://react.dev/reference/react/useReducer" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">useReducer()</code></a> doesn't quite cut it either, how do you go about creating more advanced state management hooks? Let's deep dive into some advanced state management hooks.</p>
<h2><a href="https://www.30secondsofcode.org/react/s/advanced-react-state-hooks#use-toggler-hook" id="use-toggler-hook"><code class="notranslate" translate="no">useToggler</code> hook</a></h2>
<p>Starting off with a simple one, the <code class="notranslate" translate="no">useToggler</code> hook provides a <strong>boolean state variable</strong> that can be toggled between its two states. Instead of managing the state manually, you can simply call the <code class="notranslate" translate="no">toggleValue</code> function to toggle the state.</p>
<p>The implementation is rather simple, as well. You use the <a href="https://react.dev/reference/react/useState" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">useState()</code></a> hook to create the <code class="notranslate" translate="no">value</code> state variable and its setter. Then, you create a function that toggles the value of the state variable and memoize it, using the <a href="https://react.dev/reference/react/useCallback" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">useCallback()</code></a> hook. Finally, you return the <code class="notranslate" translate="no">value</code> state variable and the memoized function.</p>
<pre class="language-jsx notranslate" translate="no" data-code-language="React"><span class="token keyword">const</span> <span class="token function-variable function">useToggler</span> <span class="token operator">=</span> <span class="token parameter">initialState</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> <span class="token punctuation">[</span>value<span class="token punctuation">,</span> setValue<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token maybe-class-name">React</span><span class="token punctuation">.</span><span class="token method function property-access">useState</span><span class="token punctuation">(</span>initialState<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> toggleValue <span class="token operator">=</span> <span class="token maybe-class-name">React</span><span class="token punctuation">.</span><span class="token method function property-access">useCallback</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token function">setValue</span><span class="token punctuation">(</span><span class="token parameter">prev</span> <span class="token arrow operator">=&gt;</span> <span class="token operator">!</span>prev<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">return</span> <span class="token punctuation">[</span>value<span class="token punctuation">,</span> toggleValue<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token function-variable function"><span class="token maybe-class-name">Switch</span></span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> <span class="token punctuation">[</span>val<span class="token punctuation">,</span> toggleVal<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useToggler</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>toggleVal<span class="token punctuation">}</span></span><span class="token punctuation">&gt;</span></span><span class="token punctuation">{</span>val <span class="token operator">?</span> <span class="token string">'ON'</span> <span class="token operator">:</span> <span class="token string">'OFF'</span><span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token maybe-class-name">ReactDOM</span><span class="token punctuation">.</span><span class="token method function property-access">createRoot</span><span class="token punctuation">(</span><span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">getElementById</span><span class="token punctuation">(</span><span class="token string">'root'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">render</span><span class="token punctuation">(</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Switch</span></span> <span class="token punctuation">/&gt;</span></span>
<span class="token punctuation">)</span><span class="token punctuation">;</span></pre>
<h2><a href="https://www.30secondsofcode.org/react/s/advanced-react-state-hooks#use-map-hook" id="use-map-hook"><code class="notranslate" translate="no">useMap</code> hook</a></h2>
<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">Map</code></a> object is a very versatile <strong>data structure</strong> in JavaScript, but it's not directly supported by React's state management hooks. The <code class="notranslate" translate="no">useMap</code> hook creates a stateful <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">Map</code></a> object and a set of functions to manipulate it.</p>
<p>Using the <a href="https://react.dev/reference/react/useState" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">useState()</code></a> hook and the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">Map()</code></a> constructor, you create a new <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">Map</code></a> from the <code class="notranslate" translate="no">initialValue</code>. Then, you use the <a href="https://react.dev/reference/react/useMemo" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">useMemo()</code></a> hook to create a set of non-mutating actions that manipulate the state variable, using the state setter to create a new <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">Map</code></a> every time. Finally, you return the <code class="notranslate" translate="no">map</code> state variable and the created <code class="notranslate" translate="no">actions</code>.</p>
<pre class="language-jsx notranslate" translate="no" data-code-language="React"><span class="token keyword">const</span> <span class="token function-variable function">useMap</span> <span class="token operator">=</span> <span class="token parameter">initialValue</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> <span class="token punctuation">[</span>map<span class="token punctuation">,</span> setMap<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token maybe-class-name">React</span><span class="token punctuation">.</span><span class="token method function property-access">useState</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span>initialValue<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> actions <span class="token operator">=</span> <span class="token maybe-class-name">React</span><span class="token punctuation">.</span><span class="token method function property-access">useMemo</span><span class="token punctuation">(</span>
<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
<span class="token function-variable function">set</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">key<span class="token punctuation">,</span> value</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span>
<span class="token function">setMap</span><span class="token punctuation">(</span><span class="token parameter">prevMap</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> nextMap <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span>prevMap<span class="token punctuation">)</span><span class="token punctuation">;</span>
nextMap<span class="token punctuation">.</span><span class="token method function property-access">set</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">return</span> nextMap<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token function-variable function">remove</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">key<span class="token punctuation">,</span> value</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span>
<span class="token function">setMap</span><span class="token punctuation">(</span><span class="token parameter">prevMap</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> nextMap <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span>prevMap<span class="token punctuation">)</span><span class="token punctuation">;</span>
nextMap<span class="token punctuation">.</span><span class="token method function property-access">delete</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">return</span> nextMap<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token function-variable function">clear</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token function">setMap</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">[</span>setMap<span class="token punctuation">]</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">return</span> <span class="token punctuation">[</span>map<span class="token punctuation">,</span> actions<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token function-variable function"><span class="token maybe-class-name">MyApp</span></span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> <span class="token punctuation">[</span>map<span class="token punctuation">,</span> <span class="token punctuation">{</span> set<span class="token punctuation">,</span> remove<span class="token punctuation">,</span> clear <span class="token punctuation">}</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useMap</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">[</span><span class="token string">'apples'</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword control-flow">return</span> <span class="token punctuation">(</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token function">set</span><span class="token punctuation">(</span><span class="token known-class-name class-name">Date</span><span class="token punctuation">.</span><span class="token method function property-access">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">toJSON</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">Add</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token function">clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">Reset</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">onClick</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token function">remove</span><span class="token punctuation">(</span><span class="token string">'apples'</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span> <span class="token attr-name">disabled</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token operator">!</span>map<span class="token punctuation">.</span><span class="token method function property-access">has</span><span class="token punctuation">(</span><span class="token string">'apples'</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
Remove apples
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>pre</span><span class="token punctuation">&gt;</span></span><span class="token plain-text">
</span><span class="token punctuation">{</span><span class="token known-class-name class-name">JSON</span><span class="token punctuation">.</span><span class="token method function property-access">stringify</span><span class="token punctuation">(</span>
<span class="token punctuation">[</span><span class="token spread operator">...</span>map<span class="token punctuation">.</span><span class="token method function property-access">entries</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token method function property-access">reduce</span><span class="token punctuation">(</span>
<span class="token punctuation">(</span><span class="token parameter">acc<span class="token punctuation">,</span> <span class="token punctuation">[</span>key<span class="token punctuation">,</span> value<span class="token punctuation">]</span></span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class=&q ... |
http://localhost:1200/30secondsofcode/category/css/interactivity - Success ✔️<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
<channel>
<title>CSS Interactivity Articles</title>
<link>https://www.30secondsofcode.org</link>
<atom:link href="http://localhost:1200/30secondsofcode/category/css/interactivity" rel="self" type="application/rss+xml"></atom:link>
<description>The CSS article collection contains utilities and interactive examples for CSS3. It includes modern techniques for creating commonly-used layouts, styling and animating elements, as well as articles for handling user interactions. - Powered by RSSHub</description>
<generator>RSSHub</generator>
<webMaster>contact@rsshub.app (RSSHub)</webMaster>
<language>en</language>
<image>
<url>https://www.30secondsofcode.org/assets/splash/camera-600.webp</url>
<title>CSS Interactivity Articles</title>
<link>https://www.30secondsofcode.org</link>
</image>
<lastBuildDate>Sun, 05 Jan 2025 16:34:22 GMT</lastBuildDate>
<ttl>5</ttl>
<item>
<title>Image gallery with horizontal or vertical scroll</title>
<description><img src="https://www.30secondsofcode.org/assets/cover/flower-portrait-5-800.webp" srcset="/assets/cover/flower-portrait-5-800.webp 800w,/assets/cover/flower-portrait-5-400.webp 400w,/assets/cover/flower-portrait-5-1200.webp 1200w" alt="" height="180" width="360" fetchpriority="high" referrerpolicy="no-referrer"> <style>
/* Style CodePen embeds to fit the demos */
.codepen-wrapper > div { height: 820px; }
</style>
<p>Image galleries are useful in various contexts, from showcasing products to displaying a collection of images. Depending on your needs, you may want to create a horizontally or vertically scrollable gallery. While CSS has come a long way, unfortunately, you'll have to get your hands dirty with JavaScript to achieve this effect.</p>
<figure class="admonition" data-admonition-type="note">
<figcaption>💬 Note</figcaption>
<p>You may first want to get caught up with <a href="https://www.30secondsofcode.org/css/s/scroll-snap">scroll snapping</a> to understand how it works, if you haven't already.</p>
</figure>
<h2><a href="https://www.30secondsofcode.org/css/s/horizontal-vertical-gallery#image-gallery-with-vertical-scroll" id="image-gallery-with-vertical-scroll">Image gallery with vertical scroll</a></h2>
<p>To create a <strong>vertically scrollable image gallery</strong>, you will need to create a container with <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/display#flex" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">display: flex</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content#center" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">justify-content: center</code></a> and a set of slides with <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/display#flex" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">display: flex</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/flex-direction#column" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">flex-direction: column</code></a>.</p>
<p>You will also need to use <code class="notranslate" translate="no">scroll-snap-type: y mandatory</code> and <code class="notranslate" translate="no">overscroll-behavior-y: contain</code> to create a snap effect on vertical scroll. Snap elements to the start of the container using <code class="notranslate" translate="no">scroll-snap-align: start</code>. In order to hide the scrollbars, you can use <code class="notranslate" translate="no">scrollbar-width: none</code> and style the pseudo-element <code class="notranslate" translate="no">::-webkit-scrollbar</code> to <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/display#none" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">display: none</code></a>.</p>
<p>Then, you can use <code class="notranslate" translate="no">Element.scrollTo()</code> to define a <code class="notranslate" translate="no">scrollToElement</code> function that scrolls the gallery to the given item. You can use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">Array.prototype.map()</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">Array.prototype.join()</code></a> to populate the <code class="notranslate" translate="no">.thumbnails</code> element. Give each thumbnail a <code class="notranslate" translate="no">data-id</code> attribute with the index of the image.</p>
<p>Using the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">Document.querySelectorAll()</code></a> method, you can get all the thumbnail elements. Use <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">Array.prototype.forEach()</code></a> to register a handler for the <code class="notranslate" translate="no">'click'</code> event on each thumbnail, using <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">EventTarget.addEventListener()</code></a> and the <code class="notranslate" translate="no">scrollToElement</code> function.</p>
<p>Finally, use <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">Document.querySelector()</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">EventTarget.addEventListener()</code></a> to register a handler for the <code class="notranslate" translate="no">'scroll'</code> event. Update the <code class="notranslate" translate="no">.thumbnails</code> and <code class="notranslate" translate="no">.scrollbar</code> elements to match the current scroll position, using the <code class="notranslate" translate="no">scrollThumb</code> function.</p>
<figure class="codepen-wrapper">
<p class="codepen" data-height="100%" data-theme-id="dark" data-default-tab="result" data-border-color="#07071c" data-border="none" data-tab-bar-color="#161632" data-tab-link-color="#e3e3e8" data-active-link-color="#8bb7fe" data-active-tab-accent-color="#5394fd" data-active-tab-color="#18203a" data-tab-color="#18203a" data-slug-hash="ExqVMzW" data-user="chalarangelo">
<span>See the <a href="https://codepen.io/chalarangelo/pen/ExqVMzW" target="_blank" rel="noopener noreferrer">embedded CodePen</a></span>
</p>
</figure>
<pre class="language-html notranslate" translate="no" data-code-language="HTML"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>gallery-container<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>thumbnails<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>scrollbar<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>thumb<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>slides<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/1067/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/122/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/188/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/249/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/257/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/259/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/283/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/288/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/299/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span></pre>
<pre class="language-css notranslate" translate="no" data-code-language="CSS"><span class="token selector"><span class="token class">.gallery-container</span></span> <span class="token punctuation">{</span>
<span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
<span class="token property">justify-content</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector"><span class="token class">.thumbnails</span></span> <span class="token punctuation">{</span>
<span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
<span class="token property">flex-direction</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span>
<span class="token property">gap</span><span class="token punctuation">:</span> <span class="token number">8</span><span class="token unit">px</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector"><span class="token class">.thumbnails</span> img</span> <span class="token punctuation">{</span>
<span class="token property">width</span><span class="token punctuation">:</span> <span class="token number">40</span><span class="token unit">px</span><span class="token punctuation">;</span>
<span class="token property">height</span><span class="token punctuation">:</span> <span class="token number">40</span><span class="token unit">px</span><span class="token punctuation">;</span>
<span class="token property">cursor</span><span class="token punctuation">:</span> pointer<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector"><span class="token class">.scrollbar</span></span> <span class="token punctuation">{</span>
<span class="token property">width</span><span class="token punctuation">:</span> <span class="token number">1</span><span class="token unit">px</span><span class="token punctuation">;</span>
<span class="token property">height</span><span class="token punctuation">:</span> <span class="token number">720</span><span class="token unit">px</span><span class="token punctuation">;</span>
<span class="token property">background</span><span class="token punctuation">:</span> <span class="token hexcode color" style="--hex-color:#ccc">#ccc</span><span class="token punctuation">;</span>
<span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span>
<span class="token property">margin</span><span class="token punctuation">:</span> <span class="token number">0</span> <span class="token number">0</span> <span class="token number">0</span> <span class="token number">8</span><span class="token unit">px</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector"><span class="token class">.thumb</span></span> <span class="token punctuation">{</span>
<span class="token property">width</span><span class="token punctuation">:</span> <span class="token number">1</span><span class="token unit">px</span><span class="token punctuation">;</span>
<span class="token property">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span>
<span class="token property">height</span><span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="token property">background</span><span class="token punctuation">:</span> <span class="token hexcode color" style="--hex-color:#000">#000</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector"><span class="token class">.slides</span></span> <span class="token punctuation">{</span>
<span class="token property">margin</span><span class="token punctuation">:</span> <span class="token number">0</span> <span class="token number">16</span><span class="token unit">px</span><span class="token punctuation">;</span>
<span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span>
<span class="token property">grid-auto-flow</span><span class="token punctuation">:</span> row<span class="token punctuation">;</span>
<span class="token property">gap</span><span class="token punctuation">:</span> <span class="token number">1</span><span class="token unit">rem</span><span class="token punctuation">;</span>
<span class="token property">width</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token number">540</span><span class="token unit">px</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token unit">rem</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token property">padding</span><span class="token punctuation">:</span> <span class="token number">0</span> <span class="token number">0.25</span><span class="token unit">rem</span><span class="token punctuation">;</span>
<span class="token property">height</span><span class="token punctuation">:</span> <span class="token number">720</span><span class="token unit">px</span><span class="token punctuation">;</span>
<span class="token property">overflow-y</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span>
<span class="token property">overscroll-behavior-y</span><span class="token punctuation">:</span> contain<span class="token punctuation">;</span>
<span class="token property">scroll-snap-type</span><span class="token punctuation">:</span> y mandatory<span class="token punctuation">;</span>
<span class="token property">scrollbar-width</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector"><span class="token class">.slides</span> <span class="token combinator">&gt;</span> div</span> <span class="token punctuation">{</span>
<span class="token property">scroll-snap-align</span><span class="token punctuation">:</span> start<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector"><span class="token class">.slides</span> img</span> <span class="token punctuation">{</span>
<span class="token property">width</span><span class="token punctuation">:</span> <span class="token number">540</span><span class="token unit">px</span><span class="token punctuation">;</span>
<span class="token property">object-fit</span><span class="token punctuation">:</span> contain<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector"><span class="token class">.slides</span><span class="token pseudo-element">::-webkit-scrollbar</span></span> <span class="token punctuation">{</span>
<span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span>
<span class="token punctuation">}</span></pre>
<pre class="language-js notranslate" translate="no" data-code-language="JavaScript"><span class="token keyword">const</span> slideGallery <span class="token operator">=</span> <span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">querySelector</span><span class="token punctuation">(</span><span class="token string">'.slides'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> slides <span class="token operator">=</span> slideGallery<span class="token punctuation">.</span><span class="token method function property-access">querySelectorAll</span><span class="token punctuation">(</span><span class="token string">'div'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> scrollbarThumb <span class="token operator">=</span> <span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">querySelector</span><span class="token punctuation">(</span><span class="token string">'.thumb'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> slideCount <span class="token operator">=</span> slides<span class="token punctuation">.</span><span class="token property-access">length</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> slideHeight <span class="token operator">=</span> <span class="token number">720</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> marginTop <span class="token operator">=</span> <span class="token number">16</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token function-variable function">scrollThumb</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> index <span class="token operator">=</span> <span class="token known-class-name class-name">Math</span><span class="token punctuation">.</span><span class="token method function property-access">floor</span><span class="token punctuation">(</span>slideGallery<span class="token punctuation">.</span><span class="token property-access">scrollTop</span> <span class="token operator">/</span> slideHeight<span class="token punctuation">)</span><span class="token punctuation">;</span>
scrollbarThumb<span class="token punctuation">.</span><span class="token property-access">style</span><span class="token punctuation">.</span><span class="token property-access">height</span> <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token punctuation">(</span><span class="token punctuation">(</span>index <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">/</span> slideCount<span class="token punctuation">)</span> <span class="token operator">*</span> slideHeight<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">px</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token function-variable function">scrollToElement</span> <span class="token operator">=</span> <span class="token parameter">el</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> index <span class="token operator">=</span> <span class="token function">parseInt</span><span class="token punctuation">(</span>el<span class="token punctuation">.</span><span class="token property-access">dataset</span><span class="token punctuation">.</span><span class="token property-access">id</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
slideGallery<span class="token punctuation">.</span><span class="token method function property-access">scrollTo</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> index <span class="token operator">*</span> slideHeight <span class="token operator">+</span> marginTop<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">querySelector</span><span class="token punctuation">(</span><span class="token string">'.thumbnails'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token property-access">innerHTML</span> <span class="token operator">+=</span> <span class="token punctuation">[</span><span class="token spread operator">...</span>slides<span class="token punctuation">]</span>
<span class="token punctuation">.</span><span class="token method function property-access">map</span><span class="token punctuation">(</span>
<span class="token punctuation">(</span><span class="token parameter">slide<span class="token punctuation">,</span> i</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;img src="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>slide<span class="token punctuation">.</span><span class="token method function property-access">querySelector</span><span class="token punctuation">(</span><span class="token string">'img'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token property-access">src</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" data-id="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>i<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"&gt;</span><span class="token template-punctuation string">`</span></span>
<span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token method function property-access">join</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">querySelectorAll</span><span class="token punctuation">(</span><span class="token string">'.thumbnails img'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">forEach</span><span class="token punctuation">(</span><span class="token parameter">el</span> <span class="token arrow operator">=&gt;</span> <span class="token punctuation">{</span>
el<span class="token punctuation">.</span><span class="token method function property-access">addEventListener</span><span class="token punctuation">(</span><span class="token string">'click'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=&gt;</span> <span class="token function">scrollToElement</span><span class="token punctuation">(</span>el<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
slideGallery<span class="token punctuation">.</span><span class="token method function property-access">addEventListener</span><span class="token punctuation">(</span><span class="token string">'scroll'</span><span class="token punctuation">,</span> <span class="token parameter">e</span> <span class="token arrow operator">=&gt;</span> <span class="token function">scrollThumb</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">scrollThumb</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></pre>
<h2><a href="https://www.30secondsofcode.org/css/s/horizontal-vertical-gallery#image-gallery-with-horizontal-scroll" id="image-gallery-with-horizontal-scroll">Image gallery with horizontal scroll</a></h2>
<p>To create a <strong>horizontally scrollable image gallery</strong>, you will need to position its <code class="notranslate" translate="no">.thumbnails</code> container at the bottom of the gallery, using <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/position#absolute" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">position: absolute</code></a>. Then, use <code class="notranslate" translate="no">scroll-snap-type: x mandatory</code> and <code class="notranslate" translate="no">overscroll-behavior-x: contain</code> to create a snap effect on horizontal scroll. Snap elements to the start of the container using <code class="notranslate" translate="no">scroll-snap-align: start</code>.</p>
<p>Hide the scrollbars the same way as before. Use <code class="notranslate" translate="no">Element.scrollTo()</code> to define a <code class="notranslate" translate="no">scrollToElement</code> function that scrolls the gallery to the given item. Populate the <code class="notranslate" translate="no">.thumbnails</code> element using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">Array.prototype.map()</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">Array.prototype.join()</code></a>, giving each thumbnail a <code class="notranslate" translate="no">data-id</code> attribute with the index of the image.</p>
<p>Use <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll" data-code-reference="true" rel="noopener noreferrer" target="_blank"><code class="notranslate" translate="no">Document.querySelectorAll()</code></a> to get all the thumbnail elements and register <code class="notranslate" translate="no">'click'</code> event handlers on each thumbnail, using the <code class="notranslate" translate="no">highlightThumbnail</code> function. Finally, register a handler for the <code class="notranslate" translate="no">'scroll'</code> event the same as before and update the <code class="notranslate" translate="no">.thumbnails</code> element to match the current scroll position using the <code class="notranslate" translate="no">highlightThumbnail</code> function.</p>
<figure class="codepen-wrapper">
<p class="codepen" data-height="100%" data-theme-id="dark" data-default-tab="result" data-border-color="#07071c" data-border="none" data-tab-bar-color="#161632" data-tab-link-color="#e3e3e8" data-active-link-color="#8bb7fe" data-active-tab-accent-color="#5394fd" data-active-tab-color="#18203a" data-tab-color="#18203a" data-slug-hash="RwXWObY" data-user="chalarangelo">
<span>See the <a href="https://codepen.io/chalarangelo/pen/RwXWObY" target="_blank" rel="noopener noreferrer">embedded CodePen</a></span>
</p>
</figure>
<pre class="language-html notranslate" translate="no" data-code-language="HTML"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>gallery-container<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>thumbnails<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>slides<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/1067/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/122/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/188/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/249/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/257/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/259/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/283/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/id/288/540/720<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span cla |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Involved Issue / 该 PR 相关 Issue
Close #
Example for the Proposed Route(s) / 路由地址示例
New RSS Route Checklist / 新 RSS 路由检查表
Puppeteer
Note / 说明