Tag: Components

Grid for layout, flexbox for components

When should we reach for CSS grid and when should we use flexbox? Rachel Andrew wrote about this very conundrum way back in 2016:

Flexbox is essentially for laying out items in a single dimension – in a row OR a column. Grid is for layout of items in two dimensions – rows AND columns.

Ahmad Shadeed wrote a post where he gives the same advice, but from a different angle. He argues we should use grid for layout and flexbox for components:

Remember that old layout method might be perfect for the job. Overusing flexbox or grid can increase the complexity of your CSS by time. I don’t mean they are complex, but using them correctly and in the right context as explained from the examples in this article is much better.

Speaking of which, there’s so many great layout examples in this post, too.

Direct Link to ArticlePermalink

The post Grid for layout, flexbox for components appeared first on CSS-Tricks.

CSS-Tricks

, , ,

React Single File Components Are Here

Shawn Wang is talking about RedwoodJS here:

…  it is the first time React components are being expressed in a single file format with explicit conventions.

Which is the RedwoodJS idea of Cells. To me, it feels like a slightly cleaner version of how Apollo wants you to do it with useQuery. Shawn makes that same connection and I know RedwoodJS uses Apollo, so I’m thinking it’s some nice semantic sugar.

There is a lot of cool stuff going on in RedwoodJS. “A highly opinionated stack” if its helpful to think of it that way, but Tom made clear in our last episode of ShopTalk that it’s not like Rails. Not that Rails is bad (it isn’t), but that this new world can do things in new and better ways that make for long-term healthy software.

The post React Single File Components Are Here appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

Dealing With Stale Props and States in React’s Functional Components

There’s one aspect of JavaScript that always has me pulling my hair: closures. I work with React a lot, and the overlap there is that they can sometimes be the cause of stale props and state. We’ll get into exactly what that means, but the trouble is that the data we use to build our UI can be totally wrong in unexpected ways, which is, you know, bad.

Stale props and states

Long story short: it’s when code that is executed asynchronously has a reference to a prop or state that is no longer fresh, and thus, the value it returns is not the latest one.

To be even more clear, let’s play around with the same stale reference example React has in its documentation.

function Counter() {   const [count, setCount] = useState(0);    function handleAlertClick() {     setTimeout(() => {       alert("You clicked on: " + count);     }, 3000);   }    return (     <div>       <p>You clicked {count} times</p>       <button onClick={() => setCount(count + 1)}>Click me</button>       <button onClick={handleAlertClick}>Show alert</button>     </div>   ); }

(Live demo)

Nothing fancy here. We have a functional component named Counter. It keeps track of how many times the user has clicked one button and shows an alert that displays how many times that button was clicked when clicking another button. Try this:

  1. Click the “Click me” button. You are going to see the click counter go up.
  2. Now click the “Show alert”button. Three seconds should go by and then trigger an alert telling you how many times you clicked the “Click me” button.
  3. Now, click the “Show alert” button again and quickly click the “Click me” button before it triggers the alert in three seconds.

See what happens? The count shown on the page and the count shown in the alert do not match. The number in the alert is not just some random number, though. That number is the value the count variable had in the moment the asynchronous function inside the setTimeout was defined, which is the moment the “Show alert” button is clicked.

That’s just how closures work. We’re not going to get into the specifics of them in this post, but here are some docs that cover them in greater detail.

Let’s focus on how we can avoid these stale references with our states and props.

React offers a tip on how to deal with stale dates and props in the same documentation where the example was pulled.

If you intentionally want to read the latest state from some asynchronous callback, you could keep it in a ref, mutate it, and read from it.

By keeping the value  asynchronously in a ref, we can bypass stale references. If you need to know more about ref in functional components, React’s documentation has a lot more information.

So, that begs the question: How can we keep our props or state in a ref?

Let’s do it the dirty way first.

The dirty way to store props and state in a ref

We can easily create a ref using useRef() and use count as its initial value. Then, wherever the state is being updated, we set the ref.current property to the new value. Lastly, use ref.current instead of count in the asynchronous part of our code.

function Counter() {   const [count, setCount] = useState(0);   const ref = useRef(count); // Make a ref and give it the count    function handleAlertClick() {     setTimeout(() => {       alert("You clicked on: " + ref.current); // Use ref instead of count     }, 3000);   }    return (     <div>       <p>You clicked {count} times</p>       <button         onClick={() => {           setCount(count + 1);           ref.current = count + 1; // Update ref whenever the count changes         }}       >         Click me       </button>       <button         onClick={() => {           handleAlertClick();         }}       >         Show alert       </button>     </div>   ); }

(Live demo)

Go ahead and do the same as last time. Click “Show alert” and then click “Click me” before the alert is triggered in three seconds.

Now we have the latest value!

Here’s why it works. When the asynchronous callback function is defined inside setTimeout, it saves a reference to the variables it uses, which is count in this case. This way, when the state updates, React not only changes the value but the variable reference in memory is completely different as well.

This means that — even if the state’s value is non-primitive — the variable you are working with in your asynchronous callback is not the same in memory. An object that would typically keep its reference throughout different functions now has a different value.

How does using a ref solve this? If we take a quick look at React’s docs again, we find an interesting, but easy-to-miss, bit of information:

[…] useRef will give you the same ref object on every render.

It doesn’t matter what we do. Throughout the lifetime of your component, React will give us the exact same ref object in memory. Any callback, no matter when it’s defined or executed, is working with the same object. No more stale reference.

The cleaner way to store props and state in a ref

Let’s be honest… using a ref like that is an ugly fix. What happens if your state is being updated in a thousand different places? Now you have to change your code and manually update the ref in all those places. That’s a no-no.

We are going to make this more scalable by giving ref the value of the state automatically when the state changes.

Let’s start by getting rid of the manual change to the ref in the “Click me”button.

Next, we make a function called updateState that is called whenever we need to change the state. This function takes the new state as an argument and it sets the ref.current property to the new state and updates the state as well with that same value.

Finally, let’s substitute the original setCount function React gives us with the new updateState function where the state is being updated.

function Counter() {   const [count, setCount] = useState(0);   const ref = useRef(count);    // Keeps the state and ref equal   function updateState(newState) {     ref.current = newState;     setCount(newState);   }    function handleAlertClick() { ... }    return (     <div>       <p>You clicked {count} times</p>       <button         onClick={() => {           // Use the created function instead of the manual update           updateState(count + 1);         }}       >         Click me       </button>       <button onClick={handleAlertClick}>Show alert</button>     </div>   ); }

(Live demo)

Using a custom hook

The cleaner solution works just fine. It gets the job done just like the dirty solution, but only calls a single function to update the state and ref.

But guess what? We can do better. What if we need to add more states? What if we want to do this in other components too? Let’s take the state, ref and updateState function and make them truly portable. Custom hooks to the rescue!

Outside the Counter component, we are going to define a new function. Let’s name it useAsyncReference. (It can be named anything, really, but note that it’s common practice to name custom hooks with “use” as a prefix.) Our new hook will have a single parameter for now. We’ll call it value.

Our previous solution had the same information stored twice: once in the state and once in the ref. We are going to optimize that by keeping the value just in ref this time. In other words, we will create a ref and give it the value parameter as its initial value.

Right after the ref, we will make an updateState function that takes the new state and sets it to the ref.current property.

Lastly, we return an array with ref and the updateState function, very similar to what React does with useState.

function useAsyncReference(value) {   const ref = useRef(value);    function updateState(newState) {     ref.current = newState;   }    return [ref, updateState]; }  function Counter() { ... }

We are forgetting something! If we check the useRef documentation, we learn that updating a ref does not trigger a re-render. So, while ref has the updated value, we wouldn’t see the changes on screen. We need to force a re-render every time ref gets updated.

What we need is a fake state. The value doesn’t matter. It’s only going to be there to provoke the re-render. We can even ignore the state and only keep its update function. We are calling that update function forceRender and giving it an initial value of false.

Now, inside updateState, we force the re-render by calling forceRender and passing it a state different to the current one after setting ref.current to newState.

function useAsyncReference(value) {   const ref = useRef(value);   const [, forceRender] = useState(false);    function updateState(newState) {     ref.current = newState;     forceRender(s => !s);   }    return [ref, updateState]; }  function Counter() { ... }

Take whatever value it has and return the opposite. The state doesn’t really matter. We are merely changing it so React detects a change in state and re-renders the component.

Next, we can clean the Count component and remove the previously used useState, ref and updateState function, then implement the new hook. The first value of the returned array is the state in the form of a ref. We’ll keep calling it count, where the second value is the function to update the state/ref. We’ll continue calling it setCount.

We also have to change the references to the count since now that they all must be count.current. And we must call setCount instead of calling updateState.

function useAsyncReference(value) { ... }  function Counter() {   const [count, setCount] = useAsyncReference(0);    function handleAlertClick() {     setTimeout(() => {       alert("You clicked on: " + count.current);     }, 3000);   }    return (     <div>       <p>You clicked {count.current} times</p>       <button         onClick={() => {           setCount(count.current + 1);         }}       >         Click me       </button>       <button onClick={handleAlertClick}>Show alert</button>     </div>   ); }

Making this work with props

We have a truly portable solution for our problem. But guess what… there’s still a little more to do. Specifically, we need to make the solution compatible with props.

Let’s take the “Show alert” button and handleAlertClick function to a new component outside the Counter component. We are gonna call it Alert and it’s going to take a single prop called count. This new component is going to show the count prop value we are passing it in an alert after a three second delay.

function useAsyncReference(value) { ... }  function Alert({ count }) {   function handleAlertClick() {     setTimeout(() => {       alert("You clicked on: " + count);     }, 3000);   }    return <button onClick={handleAlertClick}>Show alert</button>; }  function Counter() { ... }

In Counter, we’re swapping the “Show alert” button for the Alert component. We’ll pass count.current to the count prop.

function useAsyncReference(value) { ... }  function Alert({ count }) { ... }  function Counter() {   const [count, setCount] = useAsyncReference(0);    return (     <div>       <p>You clicked {count.current} times</p>       <button         onClick={() => {           setCount(count.current + 1);         }}       >         Click me       </button>       <Alert count={count.current} />     </div>   ); }

(Live demo)

Alright, time to run through the testing steps again. See? Even though we are using a safe reference to the count in Counter, the reference to the count prop in the Alert component is not asynchronously safe and our custom hook is not suitable to use with props… yet.

Lucky for us, the solution is fairly simple.

All we have to do is add a second parameter to our useAsyncReference hook named isProp, with false as the initial value. Just before we return the array with ref and updateState, we set up a condition. If isProp is true, we set the ref.current property to value and only return ref.

function useAsyncReference(value, isProp = false) {   const ref = useRef(value);   const [, forceRender] = useState(false);    function updateState(newState) {     ref.current = newState;     forceRender(s => !s);   }    if (isProp) {     ref.current = value;     return ref;   }    return [ref, updateState]; }  function Alert({ count }) { ... }  function Counter() { ... }

Now let’s update Alert so that is uses the hook. Remember to pass true as a second argument to useAsyncReference since we are passing a prop and not a state.

function useAsyncReference(value) { ... }  function Alert({ count }) {   const asyncCount = useAsyncReference(count, true);    function handleAlertClick() {     setTimeout(() => {       alert("You clicked on: " + asyncCount.current);     }, 3000);   }    return <button onClick={handleAlertClick}>Show alert</button>; }  function Counter() { ... }

(Live demo)

Give it another try. Now it works perfectly whether you use states or props.

One last thing…

There’s one last change I’d like to make. React’s useState docs tell us that React will bail out of a re-render if the new state is identical to the previous one. Our solution doesn’t do that. If we pass the current state again to the hook’s updateState function, we will force a re-render no matter what. Let’s change that.

Let’s put the body of updateState inside an if statement and execute it when ref.current is different than the new state. The comparison must be done with Object.is(), just like React does.

function useAsyncReference(value, isProp = false) {   const ref = useRef(value);   const [, forceRender] = useState(false);    function updateState(newState) {     if (!Object.is(ref.current, newState)) {       ref.current = newState;       forceRender(s => !s);     }   }    if (isProp) {     ref.current = value;     return ref;   }    return [ref, updateState]; }  function Alert({ count }) { ... }  function Counter() { ... }

Now we are finally done!


React can sometimes seem like a black box that is full of little quirks. Those quirks might be daunting to deal with, like the one we just tackled. But if you are patient and enjoy being challenged, you’ll soon realize it’s an awesome framework and a pleasure to work with.

The post Dealing With Stale Props and States in React’s Functional Components appeared first on CSS-Tricks.

CSS-Tricks

, , , , , ,
[Top]

Creating a Modal Image Gallery With Bootstrap Components

Have you ever clicked on an image on a webpage that opens up a larger version of the image with navigation to view other photos?

Some folks call it a pop-up. Others call it a lightbox. Bootstrap calls it a modal. I mention Bootstrap because I want to use it to make the same sort of thing. So, let’s call it a modal from here on out.

Why Bootstrap? you might ask. Well, a few reasons:

  • I’m already using Bootstrap on the site where I want this effect, so there’s no additional overhead in terms of loading resources.
  • I want something where I have complete and easy control over aesthetics. Bootstrap is a clean slate compared to most modal plugins I’ve come across.
  • The functionality I need is fairly simple. There isn’t much to be gained by coding everything from scratch. I consider the time I save using the Bootstrap framework to be more beneficial than any potential drawbacks.

Here’s where we’ll end up:

Let’s go through that, bit by bit.

Step 1: Create the image gallery grid

Let’s start with the markup for a grid layout of images. We can use Bootstrap’s grid system for that.

<div class="row" id="gallery">   <div class="col-12 col-sm-6 col-lg-3">     <img class="w-100" src="/image-1">   </div>   <div class="col-12 col-sm-6 col-lg-3">     <img class="w-100" src="/image-2">   </div>   <div class="col-12 col-sm-6 col-lg-3">     <img class="w-100" src="/image-3">   </div>   <div class="col-12 col-sm-6 col-lg-3">     <img class="w-100" src="/image-4">   </div> </div>

Now we need data attributes to make those images interactive. Bootstrap looks at data attributes to figure out which elements should be interactive and what they should do. In this case, we’ll be creating interactions that open the modal component and allow scrolling through the images using the carousel component.

About those data attributes:

  1. We’ll add data-toggle="modal"  and data-target="#exampleModal" to the parent element (#gallery). This makes it so clicking anything in the gallery opens the modal. We should also add the data-target value (#exampleModal) as the ID of the modal itself, but we’ll do that once we get to the modal markup.
  2. Let’s add data-target="#carouselExample"  and a data-slide-to attribute to each image. We could add those to the image wrappers instead, but we’ll go with the images in this post. Later on, we’ll want to use the data-target value (#carouselExample) as the ID for the carousel, so note that for when we get there. The values for data-slide-to are based on the order of the images.

Here’s what we get when we put that together:

<div class="row" id="gallery" data-toggle="modal" data-target="#exampleModal">   <div class="col-12 col-sm-6 col-lg-3">     <img class="w-100" src="/image-1.jpg" data-target="#carouselExample" data-slide-to="0">   </div>   <div class="col-12 col-sm-6 col-lg-3">     <img class="w-100" src="/image-2.jpg" data-target="#carouselExample" data-slide-to="1">   </div>   <div class="col-12 col-sm-6 col-lg-3">     <img class="w-100" src="/image-3.jpg" data-target="#carouselExample" data-slide-to="2">   </div>   <div class="col-12 col-sm-6 col-lg-3">     <img class="w-100" src="/image-4.jpg" data-target="#carouselExample" data-slide-to="3">   </div> </div>

Interested in knowing more about data attributes? Check out the CSS-Tricks guide to them.

Step 2: Make the modal work

This is a carousel inside a modal, both of which are standard Bootstrap components. We’re just nesting one inside the other here. Pretty much a straight copy-and-paste job from the Bootstrap documentation.

Here’s some important parts to watch for though:

  1. The modal ID should match the data-target of the gallery element.
  2. The carousel ID should match the data-target of the images in the gallery.
  3. The carousel slides should match the gallery images and must be in the same order.

Here’s the markup for the modal with our attributes in place:

<!-- Modal markup: https://getbootstrap.com/docs/4.4/components/modal/ --> <div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-hidden="true">   <div class="modal-dialog" role="document">     <div class="modal-content">       <div class="modal-header">         <button type="button" class="close" data-dismiss="modal" aria-label="Close">           <span aria-hidden="true">×</span>         </button>       </div>       <div class="modal-body">                <!-- Carousel markup goes here --> 
       <div class="modal-footer">         <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>       </div>     </div>   </div> </div>

We can drop the carousel markup right in there, Voltron style!

<!-- Modal markup: https://getbootstrap.com/docs/4.4/components/modal/ --> <div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-hidden="true">   <div class="modal-dialog" role="document">     <div class="modal-content">       <div class="modal-header">         <button type="button" class="close" data-dismiss="modal" aria-label="Close">           <span aria-hidden="true">×</span>         </button>       </div>       <div class="modal-body">                <!-- Carousel markup: https://getbootstrap.com/docs/4.4/components/carousel/ -->       <div id="carouselExample" class="carousel slide" data-ride="carousel">           <div class="carousel-inner">             <div class="carousel-item active">               <img class="d-block w-100" src="/image-1.jpg">             </div>             <div class="carousel-item">               <img class="d-block w-100" src="/image-2.jpg">             </div>             <div class="carousel-item">               <img class="d-block w-100" src="/image-3.jpg">             </div>             <div class="carousel-item">               <img class="d-block w-100" src="/image-4.jpg">             </div>           </div>           <a class="carousel-control-prev" href="#carouselExample" role="button" data-slide="prev">             <span class="carousel-control-prev-icon" aria-hidden="true"></span>             <span class="sr-only">Previous</span>           </a>           <a class="carousel-control-next" href="#carouselExample" role="button" data-slide="next">             <span class="carousel-control-next-icon" aria-hidden="true"></span>             <span class="sr-only">Next</span>           </a>         </div>       </div>        <div class="modal-footer">         <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>       </div>     </div>   </div> </div>

Looks like a lot of code, right? Again, it’s basically straight from the Bootstrap docs, only with our attributes and images.

Step 3: Deal with image sizes

This isn’t necessary, but if the images in the carousel have different dimensions, we can crop them with CSS to keep things consistent. Note that we’re using Sass here.

// Use Bootstrap breakpoints for consistency. $  bootstrap-sm: 576px; $  bootstrap-md: 768px; $  bootstrap-lg: 992px; $  bootstrap-xl: 1200px; 
 // Crop thumbnail images. #gallery {   img {     height: 75vw;     object-fit: cover;          @media (min-width: $  bootstrap-sm) {       height: 35vw;     }          @media (min-width: $  bootstrap-lg) {       height: 18vw;     }   } } 
 // Crop images in the coursel .carousel-item {   img {     height: 60vw;     object-fit: cover;          @media (min-width: $  bootstrap-sm) {       height: 350px;     }   } }

Step 4: Optimize the images

You may have noticed that the markup uses the same image files in the gallery as we do in the modal. That doesn’t need to be the case. In fact, it’s a better idea to use smaller, more performant versions of the images for the gallery. We’re going to be blowing up the images to their full size version anyway in the modal, so there’s no need to have the best quality up front.

The good thing about Bootstrap’s approach here is that we can use different images in the gallery than we do in the modal. They’re not mutually exclusive where they have to point to the same file.

So, for that, I’d suggest updating the gallery markup with lower-quality images:

<div class="row" id="gallery" data-toggle="modal" data-target="#exampleModal">   <div class="col-12 col-sm-6 col-lg-3">     <img class="w-100" src="/image-1-small.jpg" data-target="#carouselExample" data-slide-to="0">      <!-- and so on... --> </div>

That’s it!

The site where I’m using this has already themed Bootstrap. That means everything is already styled to spec. That said, even if you haven’t themed Bootstrap you can still easily add custom styles! With this approach (Bootstrap vs. plugins), customization is painless because you have complete control over the markup and Bootstrap styling is relatively sparse.

Here’s the final demo:

The post Creating a Modal Image Gallery With Bootstrap Components appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]

How to Build Vue Components in a WordPress Theme

Intrigued by the title and just wanna see some code? Skip ahead.

A few months ago, I was building a WordPress website that required a form with a bunch of fancy conditional fields. Different options and info were required for different choices you could make on the form, and our client needed complete control over all fields 1. In addition, the form needed to appear in multiple places in each page, with slightly different configs.

And the header instance of the form needed to be mutually exclusive with the hamburger menu, so that opening one closes the other.

And the form had text content that was relevant to SEO.

And we wanted the server response to present some cute animated feedback.

(Phew.)

The whole thing felt complex enough that I didn’t want to handle all that state manually. I remembered reading Sarah Drasner’s article “Replacing jQuery With Vue.js: No Build Step Necessary” which shows how to replace classic jQuery patterns with simple Vue micro-apps. That seemed like a good place to start, but I quickly realized that things would get messy on the PHP side of WordPress.

What I really needed were reusable components

PHP → JavaScript

I love the static-first approach of Jamstack tools, like Nuxt, and was looking to do something similar here — send the full content from the server, and progressively enhance on the client side.

But PHP doesn’t have a built-in way to work with components. It does, however, support require-ing files inside other files 2. WordPress has an abstraction of require called get_template_part, that runs relative to the theme folder and is easier to work with. Dividing code into template parts is about the closest thing to components that WordPress provides 3.

Vue, on the other hand, is all about components — but it can only do its thing after the page has loaded and JavaScript is running.

The secret to this marriage of paradigms turns out to be the lesser-known Vue directive inline-template. Its great and wonderful powers allow us to define a Vue component using the markup we already have. It’s the perfect middle ground between getting static HTML from the server, and mounting dynamic DOM elements in the client.

First, the browser gets the HTML, then Vue makes it do stuff. Since the markup is built by WordPress, rather than by Vue in the browser, components can easily use any information that site administrators can edit. And, as opposed to .vue files (which are great for building more app-y things), we can keep the same separation of concerns we use for the whole site — structure and content in PHP, style in CSS, and functionality in JavaScript.

To show how this all fits together, we’re going to build a few features for a recipe blog. First, we’ll add a way for users to rate recipes. Then we’ll build a feedback form based on that rating. Finally, we’ll allow users to filter recipes, based on tags and rating.

We’ll build a few components that share state and live on the same page. To get them to play nicely together — and to make it easy to add additional components in the future — we’ll make the whole page our Vue app, and register components inside it.

Each component will live in its own PHP file and be included in the theme using get_template_part.

Laying the groundwork

There are a few special considerations to take into account when applying Vue to existing pages. The first is that Vue doesn’t want you loading scripts inside it — it will send ominous errors to the console if you do. The easiest way to avoid this is to add a wrapper element around the content for every page, then load scripts outside of it (which is already a common pattern for all kinds of reasons). Something like this:

<?php /* header.php */ ?>  <body <?php body_class(); ?>> <div id="site-wrapper">
<?php /* footer.php */ ?>   </div> <!-- #site-wrapper --> <?php wp_footer(); ?>

The second consideration is that Vue has to be called at the end of body element so that it will load after the rest of the DOM is available to parse. We’ll pass true as the fifth argument  (in_footer) for the wp_enqueue_script  function. Also, to make sure Vue is loaded first, we’ll register it as a dependency of the main script.

<?php // functions.php  add_action( 'wp_enqueue_scripts', function() {   wp_enqueue_script('vue', get_template_directory_uri() . '/assets/js/lib/vue.js', null, null, true); // change to vue.min.js for production   wp_enqueue_script('main', get_template_directory_uri() . '/assets/js/main.js', 'vue', null, true);

Finally, in the main script, we’ll initialize Vue on the site-wrapper element.

// main.js  new Vue({   el: document.getElementById('site-wrapper') })

The star rating component

Our single post template currently looks like this:

<?php /* single-post.php */ ?>  <article class="recipe">   <?php /* ... post content */ ?>    <!-- star rating component goes here --> </article>

We’ll register the star rating component and add some logic to manage it:

// main.js  Vue.component('star-rating', {   data () {     return {       rating: 0     }   },   methods: {     rate (i) { this.rating = i }   },   watch: {     rating (val) {       // prevent rating from going out of bounds by checking it to on every change       if (val < 0)          this.rating = 0       else if (val > 5)          this.rating = 5        // ... some logic to save to localStorage or somewhere else     }   } })  // make sure to initialize Vue after registering all components new Vue({   el: document.getElementById('site-wrapper') })

We’ll write the component template in a separate PHP file. The component will comprise six buttons (one for unrated, 5 with stars). Each button will contain an SVG with either a black or transparent fill.

<?php /* components/star-rating.php */ ?>  <star-rating inline-template>   <div class="star-rating">     <p>Rate recipe:</p>     <button @click="rate(0)">       <svg><path d="..." :fill="rating === 0 ? 'black' : 'transparent'"></svg>     </button>     <button v-for="(i in 5) @click="rate(i)">       <svg><path d="..." :fill="rating >= i ? 'black' : 'transparent'"></svg>     </button>   </div> </star-rating>

As a rule of thumb, I like to give a component’s top element a class name that is identical to that of the component itself. This makes it easy to reason between markup and CSS (e.g. <star-rating> can be thought of as .star-rating).

And now we’ll include it in our page template.

<?php /* single-post.php */ ?>  <article class="recipe">   <?php /* post content */ ?>    <?php get_template_part('components/star-rating'); ?> </article>

All the HTML inside the template is valid and understood by the browser, except for <star-rating>. We can go the extra mile to fix that by using Vue’s is directive:

<div is="star-rating" inline-template>...</div>

Now let’s say that the maximum rating isn’t necessarily 5, but is controllable by the website’s editor using Advanced Custom Fields, a popular WordPress plugin that adds custom fields for pages, posts and other WordPress content. All we need to do is inject that value as a prop of the component that we’ll call maxRating:

<?php // components/star-rating.php  // max_rating is the name of the ACF field $  max_rating = get_field('max_rating'); ?> <div is="star-rating" inline-template :max-rating="<?= $  max_rating ?>">   <div class="star-rating">     <p>Rate recipe:</p>     <button @click="rate(0)">       <svg><path d="..." :fill="rating === 0 ? 'black' : 'transparent'"></svg>     </button>     <button v-for="(i in maxRating) @click="rate(i)">       <svg><path d="..." :fill="rating >= i ? 'black' : 'transparent'"></svg>     </button>   </div> </div>

And in our script, let’s register the prop and replace the magic number 5:

// main.js  Vue.component('star-rating', {   props: {     maxRating: {       type: Number,       default: 5 // highlight     }   },   data () {     return {       rating: 0     }   },   methods: {     rate (i) { this.rating = i }   },   watch: {     rating (val) {       // prevent rating from going out of bounds by checking it to on every change       if (val < 0)          this.rating = 0       else if (val > maxRating)          this.rating = maxRating        // ... some logic to save to localStorage or somewhere else     }   } })

In order to save the rating of the specific recipe, we’ll need to pass in the ID of the post. Again, same idea:

<?php // components/star-rating.php  $  max_rating = get_field('max_rating'); $  recipe_id = get_the_ID(); ?> <div is="star-rating" inline-template :max-rating="<?= $  max_rating ?>" recipe-id="<?= $  recipe_id ?>">   <div class="star-rating">     <p>Rate recipe:</p>     <button @click="rate(0)">       <svg><path d="..." :fill="rating === 0 ? 'black' : 'transparent'"></svg>     </button>     <button v-for="(i in maxRating) @click="rate(i)">       <svg><path d="..." :fill="rating >= i ? 'black' : 'transparent'"></svg>     </button>   </div> </div>
// main.js  Vue.component('star-rating', {   props: {     maxRating: {        // Same as before     },     recipeId: {       type: String,       required: true     }   },   // ...   watch: {     rating (val) {       // Same as before        // on every change, save to some storage       // e.g. localStorage or posting to a WP comments endpoint       someKindOfStorageDefinedElsewhere.save(this.recipeId, this.rating)     }   },   mounted () {     this.rating = someKindOfStorageDefinedElsewhere.load(this.recipeId)       } })

Now we can include the same component file in the archive page (a loop of posts), without any additional setup:

<?php // archive.php  if (have_posts()): while ( have_posts()): the_post(); ?> <article class="recipe">   <?php // Excerpt, featured image, etc. then:   get_template_part('components/star-rating'); ?> </article> <?php endwhile; endif; ?>

The feedback form

The moment a user rates a recipe is a great opportunity to ask for more feedback, so let’s add a little form that appears right after the rating is set.

// main.js  Vue.component('feedback-form', {   props: {     recipeId: {       type: String,       required: true     },     show: { type: Boolean, default: false }   },   data () {     return {       name: '',       subject: ''       // ... other form fields     }   } })
<?php // components/feedback-form.php  $  recipe_id = get_the_ID(); ?> <div is="feedback-form" inline-template recipe-id="<?= $  recipe_id ?>" v-if="showForm(recipe-id)">   <form class="recipe-feedback-form" id="feedback-form-<?= $  recipe_id ?>">     <input type="text" :id="first-name-<?= $  recipe_id ?>" v-model="name">     <label for="first-name-<?= $  recipe_id ?>">Your name</label>     <?php /* ... */ ?>   </form> </div>

Notice that we’re appending a unique string (in this case, recipe-id) to each form element’s ID. This is to make sure they all have unique IDs, even if there are multiple copies of the form on the page.

So, where do we want this form to live? It needs to know the recipe’s rating so it knows it needs to open. We’re just building good ol’ components, so let’s use composition to place the form inside the <star-rating>:

<?php // components/star-rating.php  $  max_rating = get_field('max_rating'); $  recipe_id = get_the_ID(); ?> <div is="star-rating" inline-template :max-rating="<?= $  max_rating ?>" recipe-id="<?= $  recipe_id ?>">   <div class="star-rating">     <p>Rate recipe:</p>     <button @click="rate(0)">       <svg><path d="..." :fill="rating === 0 ? 'black' : 'transparent'"></svg>     </button>     <button v-for="(i in maxRating) @click="rate(i)">       <svg><path d="..." :fill="rating >= i ? 'black' : 'transparent'"></svg>     </button>     <?php get_template_part('components/feedback-form'); ?>   </div> </div>

If at this point you’re thinking, “We really should be composing both components into a single parent component that handles the rating state,” then please give yourself 10 points and wait patiently.

A small progressive enhancement we can add to make the form usable without JavaScript, is to give it the traditional PHP action and then override it in Vue. We’ll use @submit.prevent to prevent the original action, then run a submit method to send the form data in JavaScript.

<?php // components/feedback-form.php  $  recipe_id = get_the_ID(); ?> <div is="feedback-form" inline-template recipe-id="<?= $  recipe_id ?>">   <form action="path/to/feedback-form-handler.php"        @submit.prevent="submit"       class="recipe-feedback-form"        id="feedback-form-<?= $  recipe_id ?>">     <input type="text" :id="first-name-<?= $  recipe_id ?>" v-model="name">     <label for="first-name-<?= $  recipe_id ?>">Your name</label>    <!-- ... -->   </form> </div>

Then, assuming we want to use fetch, our submit method can be something like this:

// main.js  Vue.component('feedback-form', {   // Same as before    methods: {     submit () {       const form = this.$  el.querySelector('form')       const URL = form.action       const formData = new FormData(form)       fetch(URL, {method: 'POST', body: formData})         .then(result => { ... })         .catch(error => { ... })     }   } })

OK, so what do we want to do in .then and .catch? Let’s add a component that will show real-time feedback for the form’s submit status. First let’s add the state to track sending, success, and failure, and a computed property telling us if we’re pending results.

// main.js  Vue.component('feedback-form', {   // Same as before    data () {     return {       name: '',       subject: ''       // ... other form fields       sent: false,       success: false, ​​      error: null     }   },   methods: {     submit () {       const form = this.$  el.querySelector('form')       const URL = form.action       const formData = new FormData(form)       fetch(URL, {method: 'POST', body: formData})         .then(result => {            this.success = true          })         .catch(error => {            this.error = error          })       this.sent = true     }   } })

To add the markup for each message type (success, failure, pending), we could make another component like the others we’ve built so far. But since these messages are meaningless when the server renders the page, we’re better off rendering them only when necessary. To do this we’re going to place our markup in a native HTML <template> tag, which doesn’t render anything in the browser. Then we’ll reference it by id as our component’s template.

<?php /* components/form-status.php */ ?>  <template id="form-status-component" v-if="false">   <div class="form-message-wrapper">     <div class="pending-message" v-if="pending">       <img src="<?= get_template_directory_uri() ?>/spinner.gif">       <p>Patience, young one.</p>     </div>     <div class="success-message" v-else-if="success">       <img src="<?= get_template_directory_uri() ?>/beer.gif">       <p>Huzzah!</p>     </div>     <div class="success-message" v-else-if="error">       <img src="<?= get_template_directory_uri() ?>/broken.gif">       <p>Ooh, boy. It would appear that: {{ error.text }}</p>     </div>   </div </template>

Why add v-if="false" at the top, you ask? It’s a tricky little thing. Once Vue picks up the HTML <template>, it will immediately think of it as a Vue <template> and render it. Unless, you guessed it, we tell Vue not to render it. A bit of a hack, but there you have it.

Since we only need this markup once on the page, we’ll include the PHP component in the footer.

<?php /* footer.php */ ?>  </div> <!-- #site-wrapper --> <?php get_template_part('components/form-status'); ?> <?php wp_footer(); ?>

Now we’ll register the component with Vue…

// main.js  Vue.component('form-status', {   template: '#form-status-component'   props: {     pending: { type: Boolean, required: true },     success: { type: Boolean, required: true },     error: { type: [Object, null], required: true },   } })

…and call it inside our form component:

<?php // components/feedback-form.php  $  recipe_id = get_the_ID(); ?> <div is="feedback-form" inline-template recipe-id="<?= $  recipe_id ?>">   <form action="path/to/feedback-form-handler.php"          @submit.prevent="submit"         class="recipe-feedback-form"          id="feedback-form-<?= $  recipe_id ?>">     <input type="text" :id="first-name-<?= $  recipe_id ?>" v-model="name">     <label for="first-name-<?= $  recipe_id ?>">Your name</label>     <?php // ... ?>   </form>   <form-status v-if="sent" :pending="pending" :success="success" :error="error" /> </div>

Since we registered <form-status> using Vue.component, it’s available globally, without specifically including it in the parent’s components: { }.

Filtering recipes

Now that users can personalize some bits of their experience on our blog, we can add all kinds of useful functionality. Specifically, let’s allow users to set a minimum rating they want to see, using an input at the top of the page.
The first thing we need is some global state to track the minimum rating set by the user. Since we started off by initializing a Vue app on the whole page, global state will just be data on the Vue instance:

// main.js // Same as before  new Vue({   el: document.getElementById('site-wrapper'),   data: {     minimumRating: 0   } })

And where can we put the controls to change this? Since the whole page is the app, the answer is almost anywhere. For instance, at the top of the archive page:

<?php /* archive.php */ ?>  <label for="minimum-rating-input">Only show me recipes I've rated at or above:</label> <input type="number" id="minimum-rating-input" v-model="minimumRating">  <?php if (have_posts()): while ( have_posts()): the_post(); ?> <article class="recipe">   <?php /* Post excerpt, featured image, etc. */ ?>    <?php get_template_part('components/star-rating'); ?> </article> <?php endwhile; endif; ?>

As long as it’s inside our site-wrapper and not inside another component, it’ll just work. If we want, we could also build a filtering component that would change the global state. And if we wanted to get all fancy, we could even add Vuex to the mix (since Vuex can’t persist state between pages by default, we could add something like vuex-persist to use localStorage).

So, now we need to hide or show a recipe based on the filter. To do this, we’ll need to wrap the recipe content in its own component, with a v-show directive. It’s probably best to use the same component for both the single page and the archive page. Unfortunately, neither require nor get_template_part can pass parameters into the called file — but we can use global variables:

<?php /* archive.php */ ?>  <label for="minimum-rating-input">Only show me recipes I've rated at or above:</label> <input type="number" id="minimum-rating-input" v-model="minimumRating">  <?php  $  is_archive_item = true; if (have_posts()): while ( have_posts()): the_post();   get_template_part('components/recipe-content'); endwhile; endif; ?>

We can then use $ is_archive_item as a global variable inside the PHP component file to check if it is set and true. Since we won’t need to hide the content on the single post page, we’ll conditionally add the v-show directive.

<?php  // components/recipe-content.php  global $  is_archive_item; ?> <div is="recipe-content">   <article class="recipe"      <?php if ($  is_archive_item): ?>        v-show="show"     <?php endif; ?>   >     <?php     if ($  is_archive_item):       the_excerpt();     else       the_content();     endif;          get_template_part('components/star-rating');     ?>   </article> </div>

In this specific example, we could have also tested with  is_archive() inside the component, but in most cases we’ll need to set explicit props.

We’ll need to move the rating state and logic up into the <recipe-content> component so it can know if it needs to hide itself. Inside <star-rating>, we’ll make a custom v-model by replacing rating with value, and this.rating = i with  $ emit('input', i) as well . So our component registration will now look like this:

// main.js  Vue.component('recipe-content', {   data () {     rating: 0   },   watch: {     rating (val) {       // ...     }   },   mounted () {     this.rating = someKindOfStorageDefinedElsewhere.load(this.recipeId)       } })  Vue.component('star-rating', {   props: {     maxRating: { /* ... */ },     recipeId: { /* ... */ },     value: { type: Number, required: true }   },   methods: {     rate (i) { this.$  emit('input', i) }   }, })

We’ll add v-model in star-rating.php and change rating to value. In addition, we can now move the <feedback-form> up into <recipe-content>:

<?php // components/star-rating.php  $  max_rating = get_field('max_rating'); $  recipe_id = get_the_ID(); ?> <div is="star-rating"    inline-template    :max-rating="<?= $   max_rating ?>"    recipe-id="<?= $  recipe_id ?>"    v-model="value" >   <div class="star-rating">     <p>Rate recipe:</p>     <button @click="rate(0)">       <svg><path d="..." :fill="value === 0 ? 'black' : 'transparent'"></svg>     </button>     <button v-for="(i in maxRating) @click="rate(i)">       <svg><path d="..." :fill="value >= i ? 'black' : 'transparent'"></svg>     </button>   </div> </div>
<?php // components/recipe-content.php  global $  is_archive_item; ?> <div is="recipe-content">   <article class="recipe"      <?php if ($  is_archive_item): ?>        v-show="show"     <?php endif; ?>   >          <?php     if ($  is_archive_item):       the_excerpt();     else       the_content();     endif;          get_template_part('components/star-rating');     get_template_part('components/feedback-form');     ?>   </article> </div>

Now everything is set up so the initial render shows all recipes, and then the user can filter them based on their rating. Moving forward, we could add all kinds of parameters to filter content. And it doesn’t have to be based on user input — we can allow filtering based on the content itself (e.g. number of ingredients or cooking time) by passing the data from PHP to Vue.

Conclusion

Well, that was a bit of a long ride, but look at what we’ve built: independent, composable, maintainable, interactive, progressively enhanced components in our WordPress theme. We brought together the best of all worlds!

I’ve been using this approach in production for a while now, and I love the way it allows me to reason about the different parts of my themes. I hope I’ve inspired you to try it out too.


  1. Of course, two days before launch, the client’s legal department decided they don’t want to collect all that info. Currently the live form is but a shadow of its development self.
  2. Fun fact: Rasmus Lerdorf said that his original intent was for PHP to be templating only, with all business logic handled in C. Let that sink in for a moment. Then clear an hour from your schedule and watch the whole talk.
  3. There are third-party WordPress templating engines that can compile down to optimized PHP. Twig, for example, comes to mind. We’re trying to go the reverse route and send vanilla PHP to be handled by JavaScript.

The post How to Build Vue Components in a WordPress Theme appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Third-Party Components at Their Best

I’m a fan of the componentization of the web. I think it’s a very nice way to build a website at just about any scale (except, perhaps, the absolute most basic). There are no shortage of opinions about what makes a good component, but say we scope that to third-party for a moment. That is, components that you just use, rather than components that you build yourself as part of your site’s unique setup.

What makes a third-party component good? My favorite attribute of a third-party component is when it takes something hard and makes it easy. Particularly things that recognize and properly handle nuances, or things that you might not even know enough about to get right.

Perhaps you use some component that does pop-up contextual menus for you. It might perform browser edge detection, such as ensuring the menu never appears cut off or off-screen. That’s a tricky little bit of programming that you might not get right if you did it yourself — or even forget to do.

I think of the <Link /> component that React Router has or what’s used on Gatsby sites. It automatically injects aria-current="page" for you on the links when you’re on that page. You can and probably should use that for a styling hook! And you probably would have forgotten to program that if you were handling your own links.

In that same vein, Reach UI Tabs have rigorous accessibility baked into them that you probably wouldn’t get right if you hand-rolled them. This React image component does all sorts of stuff that is relatively difficult to pull off with images, like the complex responsive images syntax, lazy loading, placeholders, etc. This is, in a sense, handing you best practices for “free.”

Here’s a table library that doesn’t even touch UI for you, and instead focuses on other needs you’re likely to have with tables, which is another fascinating approach.

Anyway! Here’s what y’all said when I was asking about this. What makes a third-party component awesome? What do the best of them do? (besides the obvious, like good docs and good accessibility)? Some of these might be at-odds. I’m just listing what people said they like.

  • Plug-and-play. It should “just work” with minimal config.
  • Lots of editable demos
  • Highly configurable
  • “White label” styling. Don’t bring too strong of design choices.
  • Styled via regular CSS so you can BYO own styling tools
  • Fast
  • Small
  • Is installable via a package manager
  • Can be manually instantiated
  • Can be given a DOM node where it can go
  • Follows a useful versioning scheme
  • Is manintained, particularly for security
  • Has a public roadmap
  • Is framework-agnostic
  • Doesn’t have other dependencies
  • Uses intuitive naming conventions
  • Supports internationalization
  • Has lots of tests

Anything you’d add to that list?

The post Third-Party Components at Their Best appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Thinking Through Styling Options for Web Components

Where do you put styles in web components?

I’m assuming that we’re using the Shadow DOM here as, to me, that’s one of the big draws of a web component: a platform thing that is a uniquely powerful thing the platform can do. So this is about defining styles for a web component in a don’t-leak-out way, and less so a way to get global styles to leak in (although that’s very interesting as well, which can be done via custom properties which we’ll look at later in the article).

If you’re building the template inside the JavaScript — which is nice because of template literals and how we can sprinkle our data into the template nicely — you need access to those styles in JavaScript.

const template = `   <style>$  {styles}</style>   <div class="$  {class}">     <h2>$  Thinking Through Styling Options for Web Components</h2>     $  {content}   </div> `;

Where does that style variable come from? Maybe also a template literal?

const style = `   :host {     background: white;   }   h2 {     font: 900 1.5rem/1.1 -system-ui, sans-serif;   } `;

I guess that’s fine, but it makes for a big messy block of code just dunked somewhere in the class where you’re trying to build this web component.

Another way is to <template> the template and make a <style> block part of it.

<template id="card-template">   <style>     :host {       background: white;     }     h2 {       font: 900 1.5rem/1.1 -system-ui, sans-serif;     }   </style>    <div id="card-hook">     <h2 id="title-hook"></h2>     <p id="desc-hook"></p>   </div> </template>

I can see the appeal with this because it keeps HTML in HTML. What I don’t love about it is that you have to do a bunch of manual shadowRoot.querySelector("#title-hook").innerHTML = myData.title; work in order to flesh out that template. That doesn’t feel like a convenient template. I also don’t love that you need to just chuck this template somewhere in your HTML. Where? I dunno. Just chuck it in there. Chuck it.

The CSS is moved out of the JavaScript too, but it just moved from one awkward location to another.

If we wanted to keep the CSS in a CSS file, we can sorta do that like this:

<template id="card-template">   <style>     @import "/css/components/card.css";   </style>    <div id="card-hook">     <h2 id="title-hook"></h2>     <p id="desc-hook"></p>   </div> </template>

(The use of <link rel="import" type="css" href=""> is deprecated, apparently.)

Now we have @import which is an extra HTTP Request, and notorious for being a performance hit. An article by Steven Lambert says it clocked in at half a second slower. Not ideal. I don’t suppose it would be much better to do this instead:

class MyComponent extends HTMLElement {        constructor() {     super();     this.attachShadow({ mode: "open" });      fetch('/css/components/card.css')       .then(response => response.text())       .then(data => {         let node = document.createElement('style');         node.innerHTML = data;         document.body.appendChild(node);       });   }    // ... }

Seems like that would potentially be a Flash-of-Unstyled-Web-Component? I guess I should get off my butt and test it.

Now that I’m digging into this again, it seems like ::part has gotten some steam (explainer). So I can do…

const template = `   <div part="card">     <h2>$  Thinking Through Styling Options for Web Components</h2>     $  {content}   </div> `;

…then write styles in a global stylesheet that only apply inside that Shadow DOM, like:

my-card::part(card) {   background: black;   color: white; }

…which has a smidge of browser support, but maybe not enough?

These “part” selectors can only touch the exact element it’s connected to. You’d have to do all your styling by applying a part name to every single DOM node and then styling each entirely on its own. That’s no fun, particularly because the appeal of the Shadow DOM is this isolated styling environment in which we’re supposed to be able to write looser CSS selectors and not be worried our h2 { } style is going to leak all over the place.

Looks like if native CSS modules becomes a thing, that will be the most helpful thing that could happen.

import styles from './styles.css';  class MyElement extends HTMLElement {   constructor() {     this.attachShadow({mode: open});     this.shadowRoot.adoptedStyleSheets = [styles];   } }

I’m not sure, however, if this is any sort of performance boost. Seems like it would be a wash between this and @import. I have to say I prefer the clarity and syntax with native CSS modules. It’s nice to be writing JavaScript when working with JavaScript.

Constructable Stylesheets also look helpful for sharing a stylesheet across multiple components. But the CSS modules approach looks like it could also do that since the stylesheet has already become a variable at that point.

The post Thinking Through Styling Options for Web Components appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

Two Lessons I Learned From Making React Components

Here’s a couple of lessons I’ve learned about how not to build React components. These are things I’ve come across over the past couple of months and thought they might be of interest to you if you’re working on a design system, especially one with a bunch of legacy technical decisions and a lot of tech debt under the hood.

Lesson 1: Avoid child components as much as you can

One thing about working on a big design system with lots of components is that the following pattern eventually starts to become problematic real quick:

<Card>   <Card.Header>Title</Card.Header>   <Card.Body><p>This is some content</p></Card.Body> </Card>

The problematic parts are those child components, Card.Body and Card.Header. This example isn’t terrible because things are relatively simple — it’s when components get more complex that things can get bonkers. For example, each child component can have a whole series of complex props that interfere with the others.

One of my biggest pain points is with our Form components. Take this:

<Form>   <Input />   <Form.Actions>     <Button>Submit</Button>     <Button>Cancel</Button>   </Form.Actions> </Form>

I’m simplifying things considerably, of course, but every time an engineer wants to place two buttons next to each other, they’d import Form.Actions, even if there wasn’t a Form on the page. This meant that everything inside the Form component gets imported and that’s ultimately bad for performance. It just so happens to be bad system design implementation as well.

This also makes things extra difficult when documenting components because now you’ll have to ensure that each of these child components are documented too.

So instead of making Form.Actions a child component, we should’ve made it a brand new component, simply: FormActions (or perhaps something with a better name like ButtonGroup). That way, we don’t have to import Form all the time and we can keep layout-based components separate from the others.

I’ve learned my lesson. From here on out I’ll be avoiding child components altogether where I can.

Lesson 2: Make sure your props don’t conflict with one another

Mandy Michael wrote a great piece about how props can bump into one another and cause all sorts of confusing conflicts, like this TypeScript example:

interface Props {   hideMedia?: boolean   mediaIsEdgeToEdge?: boolean   mediaFullHeight?: boolean   videoInline?: boolean }

Mandy writes:

The purpose of these props are to change the way the image or video is rendered within the card or if the media is rendered at all. The problem with defining them separately is that you end up with a number of flags which toggle component features, many of which are mutually exclusive. For example, you can’t have an image that fills the margins if it’s also hidden.

This was definitely a problem for a lot of the components we inherited in my team’s design systems. There were a bunch of components where boolean props would make a component behave in all sorts of odd and unexpected ways. We even had all sorts of bugs pop up in our Card component during development because the engineers wouldn’t know which props to turn on and turn off for any given effect!

Mandy offers the following solution:

type MediaMode = 'hidden'| 'edgeToEdge' | 'fullHeight'  interface Props {   mediaMode: 'hidden'| 'edgeToEdge' | 'fullHeight' }

In short: if we combine all of these nascent options together then we have a much cleaner API that’s easily extendable and is less likely to cause confusion in the future.


That’s it! I just wanted to make a quick note about those two lessons. Here’s my question for you: What have you learned when it comes to making components or working on design systems?

The post Two Lessons I Learned From Making React Components appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]

Demonstrating Reusable React Components in a Form

Components are the building blocks of React applications. It’s almost impossible to build a React application and not make use of components. It’s widespread to the point that some third-party packages provide you with components you can use to integrate functionality into your application.

These third-party components tend to be reusable. The difference between them and components you probably have in your application has to do with specificity.

Here’s what I mean. Say you run a company that sells polo shirts. There are two things you can do:

  1. You can produce polos that already have a design on them, or
  2. You can have the buyer choose the design they want.

Some fundamental things will be consistent, like all polos should be short-sleeved. But the user can pick variations of the shirts, such as the color and size. A short-sleeve polo would make a good React component in that case: it’s the same item with different variations.

Now let’s say you’re working on a login form. Like polo shirts, the form has consistent characteristics, but instead of size and color variations, we’d be looking at input fields, a submit button, perhaps even a lost password link. This can be componentized and implemented with variations in the inputs, buttons, links, etc.

Input Element Example

Let’s look at it from the perspective of creating an input field for your form. Here is how a typical text input will look like as a React component:

class Form extends React.Component {   this.state = {     username: ''   }      handleChange = (event) => {     this.setSate({ username: event.target.value })   }    render() {     return (       <input         name="username         type={type}         placeholder="Enter username"         onChange={this.handleChange}         value={this.state.username}       />     )   } }

To make this input element reusable in other places and projects, we’ll have to extract it into its own component. Let’s call it FormInput.

const FormInput = ({   name,   type,   placeholder,   onChange,   className,   value,   error,   children,   label,   ...props }) => {      return (     <React.Fragment>       <label htmlFor={name}>{label}</label>       <input         name={name}         type={type}         placeholder={placeholder}         onChange={onChange}         value={value}         className={className}         style={error && {border: 'solid 1px red'}}       />       { error && <p>{ error }</p>}     </React.Fragment>   ) }  FormInput.defaultProps = {   type: "text",   className: "" }  FormInput.propTypes = {   name: PropTypes.string.isRequired,   type: PropTypes.string,   placeholder: PropTypes.string.isRequired,   type: PropTypes.oneOf(['text', 'number', 'password']),   className: PropTypes.string,   value: PropTypes.any,   onChange: PropTypes.func.isRequired }

The component accepts certain props, such as the attributes that we need to make the input with valid markup, including the placeholder, value and name. We set up the input element in the render function, setting the attribute values as the props that are passed to the component. We even bind the input to a label to ensure they’re always together. You can see that we’re not making assumptions by predefining anything. The idea is to ensure that the component can be used in as many scenarios as possible.

This makes for a good component because it enforces good markup (something Brad Frost calls dumb React) which goes to show that not every component has to be some highly complex piece of functionality. Then again, if we were talking about something super basic, say a static heading, then reaching for a React component might be overkill. The possible yardstick for making something a reusable component probably ought to be when you need the same functionality in other parts of an application. There’s generally no need for a “reusable” component if that component is only used once.

We can make use of our input component in another component, the LoginPage.

class LoginPage extends React.Component {   state = {     user: {       username: "",       password: ""     },     errors: {},     submitted: false   };    handleChange = event => {     const { user } = this.state;     user[event.target.name] = event.target.value;     this.setState({ user });   };    onSubmit = () => {     const {       user: { username, password }     } = this.state;     let err = {};      if (!username) {       err.username = "Enter your username!";     }      if (password.length < 8) {       err.password = "Password must be at least 8 characters!";     }      this.setState({ errors: err }, () => {       if (Object.getOwnPropertyNames(this.state.errors).length === 0) {         this.setState({ submitted: true });       }     });   };    render() {     const {       submitted,       errors,       user: { username, password }     } = this.state;     return (       <React.Fragment>         {submitted ? (           <p>Welcome onboard, {username}!</p>         ) : (           <React.Fragment>             <h3>Login!</h3>             <FormInput               label="Username"               name="username"               type="text"               value={username}               onChange={this.handleChange}               placeholder="Enter username..."               error={errors.username}               required               className="input"             />              <FormInput               label="Password"               name="password"               type="password"               value={password}               onChange={this.handleChange}               placeholder="Enter password..."               error={errors.password}               className="input"               required             />              <Button               type="submit"               label="Submit"               className="button"               handleClick={this.onSubmit}             />           </React.Fragment>         )}       </React.Fragment>     );   } }

See how LoginPage uses the FormInput twice? We’re using it both as the text input for a username and another text input for a password. If we want to make any changes to how the input functions, we can make those changes inside the FormInput component file we created and they will be applied to every instance where the input component is used. That’s the fundamental upside to having reusable components: you don’t have to repeat yourself.

Even the errors are displayed from the FormInput component.

The onSubmit function first validates the user object which we get from the form, ensuring that it fits the structure that there is a value for username. Notice that we can even extend the input’s functionality, like we did to check that the password contains at least eight characters.

If you look at the code, you’ll see a have a Button component in there. That’s not the same as a HTML <button> element, but instead another component that takes the props that define the type of button we want (submit, reset, button), its class name, what to do on click, and the label. There are lots of other button attributes we could integrate to enforce whatever standard is needed.

const Button = (props) => (   <button     type={props.type}     className={props.className}     onClick={props.handleClick}   >     {props.label}   </button> )

Here’s our final login form when all of our components are put together.

See the Pen
Reusable Button Component
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.


Wanna give this a try yourself? Try working on a reusable <select> element. If that’s too difficult, you can start with a <textarea> element, then perhaps a checkbox before hopping over to <select>. The key idea is to make it generic. I’ll like to see what you came up with, so link up your work in the comment section!

The post Demonstrating Reusable React Components in a Form appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

Weekly Platform News: CSS ::marker pseudo-element, pre-rendering web components, adding Webmention to your site

Šime posts regular content for web developers on webplatform.news.

In this week’s roundup: datepickers are giving keyboard users headaches, a new web component compiler that helps fight FOUC, we finally get our hands on styling list item markers, and four steps to getting webmentions on your site.

Using plain text fields for date input

Keyboard users prefer regular text fields over complex date pickers, and voice users are frustrated by the native control (<input type="date">).

Previously, I have relied on plain text inputs as date fields with custom validation for the site, typically using the same logic on the client and the server. For known dates — birthdays, holidays, anniversaries, etc. — it has tested well.

(via Adrian Roselli)

Pre-rendering web components

Stencil is a “web component compiler” that can be used to pre-render web components (including Shadow DOM) or hide them until they are fully styled to avoid the flash of unstyled content (FOUC).

This tool also makes sure that polyfills are only loaded when needed, and its Component API includes useful decorators and hooks that make writing web components easier (e.g., the Prop decorator handles changes to attributes).

import { Component, Prop, h } from "@stencil/core";  @Component({   tag: "my-component" }) export class MyComponent {   @Prop() age: number = 0;    render() {     return <div>I am {this.age} years old</div>;   } }

(via Max Lynch)

The CSS ::marker pseudo-element

When the CSS display: list-item declaration is applied to an element, the element generates a marker box containing a marker, e.g., a list bullet (the <li> and <summary> elements have markers by default).

Markers can be styled via the ::marker pseudo-element (useful for changing the color or font of the marker), but this CSS feature is currently only supported in Firefox.

(via Rachel Andrew)

Adding Webmention to your website

  1. Sign up on Webmention.io; this is a service that collects webmentions on your behalf.
  2. Add <link rel="webmention"> (with the appropriate href value) to your web pages.

    There are also Webmention plugins available for all major content management systems (CMS) if you prefer building on top of your CMS.

  3. Fetch webmentions from Webmention.io (via Ajax) to display them on your page.
  4. Use webmention.app to automate sending webmentions (when you publish content that includes links to other sites that support Webmention).

(via Daniel Aleksandersen)

The post Weekly Platform News: CSS ::marker pseudo-element, pre-rendering web components, adding Webmention to your site appeared first on CSS-Tricks.

CSS-Tricks

, , , , , , , , ,
[Top]