I’ve had to implement the same hero for several years now, so like a good lazy programmer, I figured I’d automate it.
Direct Link to Article — Permalink
The post The Hero Generator appeared first on CSS-Tricks.
I’ve had to implement the same hero for several years now, so like a good lazy programmer, I figured I’d automate it.
Direct Link to Article — Permalink
The post The Hero Generator appeared first on CSS-Tricks.
Hey hey, these “chronicle” posts are little roundups of news that I haven’t gotten a chance to link up yet. They are often things that I’ve done off-site, like be a guest on a podcast or online conference. Or it’s news from other projects I work on. Or some other thing I’ve been meaning to shout out. Stuff like that! Enjoy the links!
I chatted with Paul Campbell the other day during Admission Online, an online conference put together by the Tito crew . They’ve published all the videos there including mine.
I had a chance to chat with Paul about his Tito service about last year on ShopTalk in a really great episode. Tito is a best-in-class software tool for running a conference. It helps you build a site, sell tickets, manage attendees, run reports, and all that. Clearly the COVID-19 situation has impacted that business a lot, so I admire the accelerated pivot they are doing by creating Vito, a new platform for running online conferences, and running these conferences super quickly as a way to showcase it. If you’re running an online conference, I’d get on that invite list ASAP.
Jina Anne has been doing something new as well in the online event space. She’s been doing these 30-minute AMA (Ask Me Anything) sessions with interesting folks (excluding me). Upcoming events are here. They are five bucks, and that gets you live access and the ability to actually ask a question. Jina publishes past events to YouTube. Here’s one with me:
I was interviewed on Balance the Grid. Here’s one exchange:
What do you think are some of the best habits or routines that you’ve developed over the years to help you achieve success in your life?
I’m quite sure I have more bad habits than good, so take all this with a bucket of salt. But one thing I like to do is to try to make as much of the time I spend working is spent working on something of lasting value.That’s why I like to blog, for example. If I finish a blog post, that’s going to be published at a URL and that URL is going to get some traffic now, and at least a little bit of traffic forever. The more I do that the more I build out my base of lasting content that will serve me forever.
Over at CodePen, we’ve been busier than ever working toward our grand vision of what CodePen can become. We have a ton of focus on things lately, despite this terrible pandemic. It’s nice to be able to stay heads down into work you find important and meaningful in the best of times, and if that can be a mental escape as well, well, I’ll take it.
We’ve been building more community-showcasing features. On our Following page there are no less than three new features: (1) A “Recent” feed¹, (2) a “Top” feed, and (3) Follow suggestions. The Following page should be about 20× more interesting by my calculation! For example, the recent feed is the activity of all the people you follow, surfacing things you likely won’t want to miss.
You can toggle that feed from “Recent” over to “Top.” While that seems like a minor change, it’s actually an entirely different feed that we create that is like a ranked popularity feed, only scoped to people you follow.
Below that is a list of other recommended CodePen folks to follow that’s created just for you. I can testify that CodePen is a lot more fun when you follow people that create things you like, and that’s a fact we’re going to keep making more and more true.
We’re always pushing out little stuff, but while I’m focusing on big new things, the biggest is the fact that we’ve taken some steps toward “Custom Editors.” That is, Pen Editors that can do things that our normal Pen Editor can’t do. We’ve released two: Flutter and Vue Single File Components.
This is about the time of year I would normally be telling you about the Smashing Conference I went to and the wonderful time I had there, but those in-person conferences have, of course, been re-scheduled for later in the year. At the moment, I’m still planning on Austin in October and San Francisco in November, but of course, nobody knows what the world will be like then. One thing is for sure though: online workshops. Smashing has been doing lots of these, and many of them are super deep courses that take place over several weeks.
Lots of conferences are going online and that’s kinda cool to see. It widens the possibility that anyone in the world can join, which is the web at its best. Conferences like All Day Hey are coming up in a few weeks (and is only a handful of bucks). Jamstack Conf is going virtual in May. My closest-to-home conference this year, CascadiaJS, is going virtual in September.
I got to be on the podcast Coding Zeal. I can’t figure out how to embed a BuzzSprout episode, so here’s a link.
The post CSS-Tricks Chronicle XXXVIII appeared first on CSS-Tricks.
The numbers so far show that the difference between no compression and Gzip are vast, whereas the difference between Gzip and Brotli are far more modest. This suggests that while the nothing to Gzip gains will be noticeable, the upgrade from Gzip to Brotli might perhaps be less impressive.
The rub?
Gzip made files 72% smaller than not compressing them at all, but Brotli only saved us an additional 5.7% over that. In terms of FCP, Gzip gave us a 23% improvement when compared to using nothing at all, but Brotli only gained us an extra 3.5% on top of that.
So Brotli is just like spicy gzip.
Still, I’ll take a handful of points by flipping a switch in Cloudflare.
Direct Link to Article — Permalink
The post Real-World Effectiveness of Brotli appeared first on CSS-Tricks.
The idea came while watching a mandatory training video on bullying in the workplace. I can just hear High School Geoff LOL-ing about a wimp like me have to watch that thing.
But here we are.
The video UI was actually lovely, but it was the progress bar that really caught my attention – or rather the [progress].value
. It was a simple gradient going from green to blue that grew as the video continued playing.
I already know it’s possible to create the same sort gradient on the <progress>
element. Pankaj Parashar demonstrated that in a CSS-Tricks post back in 2016.
I really wanted to mock up something similar but haven’t played around with video all that much. I’m also no expert in JavaScript. But how hard can that actually be? I mean, all I want to do is get know how far we are in the video and use that to update the progress value. Right?
My inner bully made so much fun of me that I decided to give it a shot. It’s not the most complicated thing in the world, but I had some fun with it and wanted to share how I set it up in this demo.
HTML5 all the way, baby!
<figure> <video id="video" src="http://html5videoformatconverter.com/data/images/happyfit2.mp4"></video> <figcaption> <button id="play" aria-label="Play" role="button">►</button> <progress id="progress" max="100" value="0">Progress</progress> </figcaotion> </figure>
The key line is this:
<progress id="progress" max="100" value="0">Progress</progress>
The max
attribute tells us we’re working with 100 as the highest value while the value
attribute is starting us out at zero. That makes sense since it allows us to think of the video’s progress in terms of a percentage, where 0% is the start and 100% is the end, and where our initial starting point is 0%.
I’m definitely not going to get deep into the process of styling the <progress>
element in CSS. The Pankaj post I linked up earlier already does a phenomenal job of that. The CSS we need to paint a gradient on the progress value looks like this:
/* Fallback stuff */ progress[value] { appearance: none; /* Needed for Safari */ border: none; /* Needed for Firefox */ color: #e52e71; /* Fallback to a solid color */ } /* WebKit styles */ progress[value]::-webkit-progress-value { background-image: linear-gradient( to right, #ff8a00, #e52e71 ); transition: width 1s linear; } /* Firefox styles */ progress[value]::-moz-progress-bar { background-image: -moz-linear-gradient( to right, #ff8a00, #e52e71 ); }
The trick is to pay attention to the various nuances that make it cross-browser compatible. Both WebKit and Mozilla browsers have their own particular ways of handling progress elements. That makes the styling a little verbose but, hey, what can you do?
I knew there would be some math involved if I wanted to get the current time of the video and display it as a value expressed as a percentage. And if you thought that being a nerd in high school gained me mathematical superpowers, well, sorry to disappoint.
I had to write down an outline of what I thought needed to happen:
part / whole = % / 100
. In the context of the video, we can re-write that as currentTime / duration = progress value
.That gives us all the marching orders we need to get started. In fact, we can start creating variables for the elements we need to select and figure out which properties we need to work with to fill in the equation.
// Variables const progress = document.getElementById( "progress" ); // Properties // progress.value = The calculated progress value as a percent of 100 // video.currentTime = The current time of the video in seconds // video.duration = The length of the video in seconds
Not bad, not bad. Now we need to calculate the progress value by plugging those things into our equation.
function progressLoop() { setInterval(function () { document.getElementById("progress").value = Math.round( (video.currentTime / video.duration) * 100 ); }); }
I’ll admit: I forgot that the equation would result to decimal values. That’s where Math.round()
comes into play to update those to the nearest whole integer.
That actually gets the gradient progress bar to animate as the video plays!
I thought I could call this a win and walk away happy. Buuuut, there were a couple of things bugging me. Plus, I was getting errors in the console. No bueno.
Not a big deal, but certainly a nice-to-have. We can chuck a timer next to the progress bar and count seconds as we go. We already have the data to do it, so all we need is the markup and to hook it up.
Let’s add a wrap the time in a <label>
since the <progress>
element can have one.
<figure> <video controls id="video" src="http://html5videoformatconverter.com/data/images/happyfit2.mp4"></video> <figcaption> <label id="timer" for="progress" role="timer"></label> <progress id="progress" max="100" value="0">Progress</progress> </figcaotion> </figure>
Now we can hook it up. We’ll assign it a variable and use innerHTML
to print the current value inside the label.
const progress = document.getElementById("progress"); const timer = document.getElementById( "timer" ); function progressLoop() { setInterval(function () { progress.value = Math.round((video.currentTime / video.duration) * 100); timer.innerHTML = Math.round(video.currentTime) + " seconds"; }); } progressLoop();
Hey, that works!
Extra credit would involve converting the timer to display in HH:MM:SS
format.
The fact there there were two UIs going on at the same time sure bugged me. the <video>
element has a controls
attribute that, when used, shows the video controls, like play, progress, skip, volume, and such. Let’s leave that out.
But that means we need — at the very minimum — to provide a way to start and stop the video. Let’s button that up.
First, add it to the HTML:
<figure> <video id="video" src="http://html5videoformatconverter.com/data/images/happyfit2.mp4"></video> <figcaption> <label id="timer" for="progress" role="timer"></label> <button id="play" aria-label="Play" role="button">►</button> <progress id="progress" max="100" value="0">Progress</progress> </figcaotion> </figure>
Then, hook it up with a function that toggles the video between play and pause on click.
button = document.getElementById( "play" ); function playPause() { if ( video.paused ) { video.play(); button.innerHTML = "❙❙"; } else { video.pause(); button.innerHTML = "►"; } } button.addEventListener( "click", playPause ); video.addEventListener("play", progressLoop);
Hey, it’s still working!
I know it seems weird to take out the rich set of controls that HTML5 offers right out of the box. I probably wouldn’t do that on a real project, but we’re just playing around here.
I really want to thank my buddy Neal Fennimore. He took time to look at this with me and offer advice that not only makes the code more legible, but does a much, much better job defining states…
// States const PAUSED = 'paused'; const PLAYING = 'playing'; // Initial state let state = PAUSED;
…doing a proper check for the state before triggering the progress function while listening for the play, pause and click events…
// Animation loop function progressLoop() { if(state === PLAYING) { progress.value = Math.round( ( video.currentTime / video.duration ) * 100 ); timer.innerHTML = Math.round( video.currentTime ) + ' seconds'; requestAnimationFrame(progressLoop); } } video.addEventListener('play', onPlay); video.addEventListener('pause', onPause); button.addEventListener('click', onClick);
…and even making the animation more performant by replacing setInterval
with requestAnimationFrame
as you can see highlighted in that same snippet.
Oh, and yes: I was working on this while “watching” the training video. And, I aced the quiz at the end, thank you very much. 🤓
The post Some Innocent Fun With HTML Video and Progress appeared first on CSS-Tricks.
Early congratulations, A Book Apart! That’s a hell of a milestone. I’m quite sure I’ve read more A Book Apart books than any other tech book publisher.
Katel LeDu runs the ship over there, and she’s given me very special pack of discount codes that will get you my book, Practical SVG, for free. So now it’s my job to get you those codes. There are only 10 of them—not enough for everyone. So I’m going to do some low-down, dirty-rotten, absolutely-shameless cross-marketing: I’m going to give them to the first 10 people who are CodePen PRO who email me at chriscoyier@gmail.com. CodePen PRO is only $ 12/month if you pay monthly or $ 8/month if you pay yearly, and this discount code is worth $ 14, so in the end, you get both and save a few bucks. If you’re already PRO, cool, thanks, you still qualify.
You know what’s cool about Practical SVG? Even though I wrote it 4 years ago, SVG just doesn’t change that fast, so I’d say 90%+ I wouldn’t even change in a re-write. If you’re just learning about SVG as a front-end developer, it’s a fine choice.
In addition to my conniving scheme above, if you just really would like this book and have zero budget for it, or know someone else in that situation, you can also email me about that and we’ll work it out. I just may have a few copies around here I could get you. Hey, I’m trying to make money off you but I ain’t trying to lock away knowledge from anyone that really needs it.
The post A Book Apart Turning 10 appeared first on CSS-Tricks.
A bonafide CSS trick from Will Boyd!
user-select: all;
forwards
-ending @keyframes
animation when the element is in :focus
to change it to user-select: text;
Will’s article has a bunch of more useful information and use-cases for user-select
.
Direct Link to Article — Permalink
The post Click Once, Select All; Click Again, Select Normally appeared first on CSS-Tricks.
We have big JavaScript frameworks that tons of people already use and like, including React, Vue, Angular, and Svelte. Do we need another JavaScript library? Let’s take a look at Alpine.js and you can decide for yourself. Alpine.js is for developers who aren’t looking to build a single page application (SPA). It’s lightweight (~7kB gzipped) and designed to write markup-driven client-side JavaScript.
The syntax is borrowed from Vue and Angular directive. That means it will feel familiar if you’ve worked with those before. But, again, Alpine.js is not designed to build SPAs, but rather enhance your templates with a little bit of JavaScript.
For example, here’s an Alpine.js demo of an interactive “alert” component.
The alert message is two-way bound to the input using x-model="msg"
. The “level” of the alert message is set using a reactive level
property. The alert displays when when both msg
and level
have a value.
Alpine.js is a Vue template-flavored replacement for jQuery and vanilla JavaScript rather than a React/Vue/Svelte/WhateverFramework competitor.
Since Alpine.js is less than a year old, it can make assumptions about DOM APIs that jQuery cannot. Let’s briefly draw a comparison between the two.
The bulk of jQuery’s size and features comes in the shape of a cross-browser compatibility layer over imperative DOM APIs — this is usually referred to as jQuery Core and sports features that can query the DOM and manipulate it.
The Alpine.js answer to jQuery core is a declarative way to bind data to the DOM using the x-bind
attribute binding directive. It can be used to bind any attribute to reactive data on the Alpine.js component. Alpine.js, like its declarative view library contemporaries (React, Vue), provides x-ref
as an escape hatch to directly access DOM elements from JavaScript component code when binding is not sufficient (eg. when integrating a third-party library that needs to be passed a DOM Node).
jQuery also provides a way to handle, create and trigger events. Alpine.js provides the x-on
directive and the $ event
magic value which allows JavaScript functions to handle events. To trigger (custom) events, Alpine.js provides the $ dispatch
magic property which is a thin wrapper over the browser’s Event and Dispatch Event APIs.
One of jQuery’s key features is its effects, or rather, it’s ability to write easy animations. Where we might use slideUp
, slideDown
, fadeIn
, fadeOut
properties in jQuery to create effects, Alpine.js provides a set of x-transition
directives, which add and remove classes throughout the element’s transition. That’s largely inspired by the Vue Transition API.
Also, jQuery’s Ajax client has no prescriptive solution in Alpine.js, thanks to the Fetch API or taking advantage of a third party HTTP library (e.g. axios, ky, superagent).
It’s also worth calling out jQuery plugins. There is no comparison to that (yet) in the Alpine.js ecosystem. Sharing Alpine.js components is relatively simple, usually requiring a simple case of copy and paste. The JavaScript in Alpine.js components are “just functions” and tend not to access Alpine.js itself, making them relatively straightforward to share by including them on different pages with a script
tag. Any magic properties are added when Alpine initializes or is passed into bindings, like $ event
in x-on
bindings.
There are currently no examples of Alpine.js extensions, although there are a few issues and pull requests to add “core” events that hook into Alpine.js from other libraries. There are also discussions happening about the ability to add custom directives. The stance from Alpine.js creator Caleb Porzio, seems to be basing API decisions on the Vue APIs, so I would expect that any future extension point would be inspired on what Vue.js provides.
Alpine.js is lighter weight than jQuery, coming in at 21.9kB minified — 7.1kB gzipped — compared to jQuery at 87.6kB minified — 30.4kB minified and gzipped. Only 23% the size!
Most of that is likely due to the way Alpine.js focuses on providing a declarative API for the DOM (e.g. attribute binding, event listeners and transitions).
For the sake of comparison, Vue comes in at 63.5kB minified (22.8kB gzipped). How can Alpine.js come in lighter despite it’s API being equivalent Vue? Alpine.js does not implement a Virtual DOM. Instead, it directly mutates the DOM while exposing the same declarative API as Vue.
Alpine is compact because since application code is declarative in nature, and is declared via templates. For example, here’s a Pokemon search page using Alpine.js:
This example shows how a component is set up using x-data
and a function that returns the initial component data, methods, and x-init
to run that function on load.
Bindings and event listeners in Alpine.js with a syntax that’s strikingly similar to Vue templates.
x-bind:attribute="express"
and x-on:eventName="expression"
, shorthand is :attribute="expression"
and @eventName="expression"
respectivelyv-bind:attribute="express"
and v-on:eventName="expression"
, shorthand is :attribute="expression"
and @eventName="expression"
respectivelyRendering lists is achieved with x-for
on a template
element and conditional rendering with x-if
on a template
element.
Notice that Alpine.js doesn’t provide a full templating language, so there’s no interpolation syntax (e.g. {{ myValue }}
in Vue.js, Handlebars and AngularJS). Instead, binding dynamic content is done with the x-text
and x-html
directives (which map directly to underlying calls to Node.innerText
and Node.innerHTML
).
An equivalent example using jQuery is an exercise you’re welcome to take on, but the classic style includes several steps:
$ ('button').click(/* callback */)
.If you’re interested in a side by side comparison of the same code in jQuery and Alpine.js, Alex Justesen created the same character counter in jQuery and in Alpine.js.
Alpine.js takes inspiration from TailwindCSS. The Alpine.js introduction on the repository is as “Tailwind for JavaScript.”
Why is that important?
One of Tailwind’s selling points is that it “provides low-level utility classes that let you build completely custom designs without ever leaving your HTML.” That’s exactly what Alpine does. It works inside HTML so there is no need to work inside of JavaScript templates the way we would in Vue or React Many of the Alpine examples cited in the community don’t even use script tags at all!
Let’s look at one more example to drive the difference home. Here’s is an accessible navigation menu in Alpine.js that uses no script tags whatsoever.
This example leverages aria-labelledby
and aria-controls
outside of Alpine.js (with id
references). Alpine.js makes sure the “toggle” element (which is a button), has an aria-expanded
attribute that’s true
when the navigation is expanded, and false
when it’s collapsed. This aria-expanded
binding is also applied to the menu itself and we show/hide the list of links in it by binding to hidden
.
Being markup-centric means that Alpine.js and TailwindCSS examples are easy to share. All it takes is a copy-paste into HTML that is also running Alpine.js/TailwindCSS. No crazy directories full of templates that compile and render into HTML!
Since HTML is a fundamental building block of the web, it means that Alpine.js is ideal for augmenting server-rendered (Laravel, Rails, Django) or static sites (Hugo, Hexo, Jekyll). Integrating data with this sort of tooling can be a simple as outputting some JSON into the x-data="{}"
binding. The affordance of passing some JSON from your backend/static site template straight into the Alpine.js component avoids building “yet another API endpoint” that simply serves a snippet of data required by a JavaScript widget.
Alpine.js is designed to be used as a direct script include from a public CDN. Its developer experience is tailored for that. That’s why it makes for a great jQuery comparison and replacement: it’s dropped in and eliminates a build step.
While it’s not traditionally used this way, the bundled version of Vue can be linked up directly. Sarah Drasner has an excellent write-up showing examples of jQuery substituted with Vue. However, if you use Vue without a build step, you’re actively opting out of:
So, yes, while Vue boasts a buildless implementation, its developer experience is really depedent on the Vue CLI. That could be said about Create React App for React, and the Angular CLI. Going build-less strips those frameworks of their best qualities.
There you have it! Alpine.js is a modern, CDN-first library that brings declarative rendering for a small payload — all without the build step and templates that other frameworks require. The result is an HTML-centric approach that not only resembles a modern-day jQuery but is a great substitute for it as well.
If you’re looking for a jQuery replacement that’s not going to force you into a SPAs architecture, then give Alpine.js a go! Interested? You can find out more on Alpine.js Weekly, a free weekly roundup of Alpine.js news and articles.
The post Alpine.js: The JavaScript Framework That’s Used Like jQuery, Written Like Vue, and Inspired by TailwindCSS appeared first on CSS-Tricks.
This is just a tiny little trick that might be helpful on a site where you don’t have the time or desire to build out a really good on-site search solution. Google.com itself can perform searches scoped to one particular site. The trick is getting people there using that special syntax without them even knowing it.
Make a search form:
<form action="https://google.com/search" target="_blank" type="GET"> <label> Search CSS-Tricks on Google: <input type="search" name="q"> </label> <input type="submit" value="Go"> </form>
When that form is submitted, we’ll intercept it and change the value to include the special syntax:
var form = document.querySelector("form"); form.addEventListener("submit", function (e) { e.preventDefault(); var search = form.querySelector("input[type=search]"); search.value = "site:css-tricks.com " + search.value; form.submit(); });
That’s all.
The post How to Redirect a Search Form to a Site-Scoped Google Search appeared first on CSS-Tricks.
There is no doubt that web forms play an integral role in our web site or applications. By default, they provide a useful set of elements and features — from legends and fieldsets to native validation and states — but they only get us so far when we start to consider the peculiarities of using them. For example, how can we manipulate the state of a form? How about different forms of validation? Even hooking a form up to post submissions is a daunting effort at times.
Component-driven front-end libraries, like React, can ease the task of wiring web forms but can also get verbose and redundant. That’s why I want to introduce you to Formik, a small library that solves the three most annoying parts of writing forms in React:
We’re going to build a form together in this post. We’ll start with a React component then integrate Formik while demonstrating the way it handles state, validation, and submissions.
Components live and breathe through their state and prop. What HTML form elements have in common with React components is that they naturally keep some internal state. Their values are also automatically stored in their value attribute.
Allowing form elements to manage their own state in React makes them uncontrolled components. That’s just a fancy way of saying the DOM handles the state instead of React. And while that works, it is often easier to use controlled components, where React handles the state and serves as the single source of truth rather than the DOM.
The markup for a straightforward HTML form might look something like this:
<form> <div className="formRow"> <label htmlFor="email">Email address</label> <input type="email" name="email" className="email" /> </div> <div className="formRow"> <label htmlFor="password">Password</label> <input type="password" name="password" className="password" /> </div> <button type="submit">Submit</button> </form>
We can convert that into a controlled React component like so:
function HTMLForm() { const [email, setEmail] = React.useState(""); const [password, setPassword] = React.useState("");
return ( <form> <div className="formRow"> <label htmlFor="email">Email address</label> <input type="email" name="email" className="email" value={email} onChange={e => setEmail(e.target.value)} /> </div> <div className="formRow"> <label htmlFor="password">Password</label> <input type="password" name="password" className="password" value={password} onChange={e => setPassword(e.target.value)} /> </div> <button type="submit">Submit</button> </form> ); }
This is a bit verbose but it comes with some benefits:
As it is with anything JavaScript, there’s already a bevy of form management libraries out there, like React Hook Form and Redux Form, that we can use. But there are several things that make Formik stand out from the pack:
Sound good? Let’s implement Formik into our form component.
We will be building a basic login form to get our beaks wet with the fundamentals. We’ll be touching on three different ways to work with Formik:
useFormik
hookFormik
with React contextwithFormik
as a higher-order componentI’ve created a demo with the packages we need, Formik and Yup.
As it is right now, our form does nothing tangible. To start using Formik, we need to import the useFormik
hook. When we use the hook, it returns all of the Formik functions and variables that help us manage the form. If we were to log the returned values to the console, we get this:
We’ll call useFormik
and pass it initialValues
to start. Then, an onSubmit
handler fires when a form submission happens. Here’s how that looks:
// This is a React component function BaseFormik() { const formik = useFormik({ initialValues: { email: "", password: "" }, onSubmit(values) { // This will run when the form is submitted } }); // If you're curious, you can run this Effect // useEffect(() => { // console.log({formik}); // }, [])
return ( // Your actual form ) }
Then we’ll bind Formik to our form elements:
// This is a React component function BaseFormik() { const formik = useFormik({ initialValues: { email: "", password: "" }, onSubmit(values) { // This will run when the form is submitted } }); // If you're curious, you can run this Effect // useEffect(() => { // console.log({formik}); // }, [])
return ( // We bind "onSubmit" to "formik.handleSubmit" <form className="baseForm" onSubmit={formik.handleSubmit} noValidate> <input type="email" name="email" id="email" className="email formField" value={formik.values.email} // We also bind our email value onChange={formik.handleChange} // And, we bind our "onChange" event. /> </form> ) }
This is how the binding works:
onSubmit={formik.handleSubmit}
.value={formik.values.email}
and onChange={formik.handleChange}
.If you take a closer look, we didn’t have to set up our state, nor handle the onChange
or onSubmit
events as we’d typically do with React. The complete change to our form goes:
However as you might have noticed, our form contains some redundancy. We had to drill down formik and manually bind the form input’s value
and onChange
event. That means we should de-structure the returned value and immediately bind the necessary props to a dependent field, like this:
// This is a React component function BaseFormik() { const {getFieldProps, handleSubmit} = useFormik({ initialValues: { email: "", password: "" }, onSubmit(values) { // This will run when the form is submitted } }); // If you're curious, you can run this Effect // useEffect(() => { // console.log({formik}); // }, [])
return ( <form className="baseForm" onSubmit={handleSubmit} noValidate> <input type="email" id="email" className="email formField" {...getFieldProps("email")} // We pass the name of the dependent field /> </form> ) }
Let’s take things even further with the included <Formik/>
component.
The <Formik/>
component exposes various other components that adds more abstraction and sensible defaults. For example, components like <Form/
>, <Field/>
, and <ErrorMessage/>
are ready to go right out of the box.
Keep in mind, you don’t have to use these components when working with <Formik/>
but they do require <Formik/>
(or withFormik
) when using them.
Using <Formik/>
requires an overhaul because it uses the render props pattern as opposed to hooks with useFormik
. The render props pattern isn’t something new in React. It is a pattern that enables code re-usability between components — something hooks solve better. Nevertheless, <Formik/>
has a bagful of custom components that make working with forms much easier.
import { Formik } from "formik";
function FormikRenderProps() { const initialValues = { email: "", password: "" }; function onSubmit(values) { // Do stuff here... alert(JSON.stringify(values, null, 2)); } return ( <Formik {...{ initialValues, onSubmit }}> {({ getFieldProps, handleSubmit }) => ( <form className="baseForm" onSubmit={handleSubmit} noValidate> <input type="email" id="email" className="email formField" {...getFieldProps("email")} /> </form> )} </Formik> ); }
Notice that initialValues
and onSubmit
have been completely detached from useFormik
. This means we are able to pass the props that <Formik/>
needs, specifically initialValues
and useFormik
.
<Formik/>
returns a value that’s been de-structured into getFieldProps
and handleSubmit
. Everything else basically remains the same as the first method using useFormik
.
Here’s a refresher on React render props if you’re feeling a little rusty.
We haven’t actually put any <Formik/>
components to use just yet. I’ve done this intentionally to demonstrate Formik’s adaptability. We certainly do want to use those components for our form fields, so let’s rewrite the component so it uses the <Form/>
component.
import { Formik, Field, Form } from "formik";
function FormikRenderProps() { const initialValues = { email: "", password: "" }; function onSubmit(values) { // Do stuff here... alert(JSON.stringify(values, null, 2)); } return ( <Formik {...{ initialValues, onSubmit }}> {() => ( <Form className="baseForm" noValidate> <Field type="email" id="email" className="email formField" name="email" /> </Form> )} </Formik> ); }
We replaced <form/>
with <Form/>
and removed the onSubmit
handler since Formik handles that for us. Remember, it takes on all the responsibilities for handling forms.
We also replaced <input/>
with <Field/>
and removed the bindings. Again, Formik handles that.
There’s also no need to bother with the returned value from <Formik/>
anymore. You guessed it, Formik handles that as well.
Formik handles everything for us. We can now focus more on the business logic of our forms rather than things that can essentially be abstracted.
We’re pretty much set to go and guess what? We’ve haven’t been concerned with state managements or form submissions!
“What about validation?” you may ask. We haven’t touched on that because it’s a whole new level on its own. Let’s touch on that before jumping to the last method.
If you’ve ever worked with forms (and I bet you have), then you’re aware that validation isn’t something to neglect.
We want to take control of when and how to validate so new opportunities open up to create better user experiences. Gmail, for example, will not let you input a password unless the email address input is validated and authenticated. We could also do something where we validate on the spot and display messaging without additional interactions or page refreshes.
Here are three ways that Formik is able to handle validation:
Validation at the form level means validating the form as a whole. Since we have immediate access to form values, we can validate the entire form at once by either:
validate
, orvalidationSchema
.Both validate
and validationSchema
are functions that return an errors
object with key/value pairings that those of initialValues
. We can pass those to useFormik
, <Formik/>
or withFormik
.
While validate
is used for custom validations, validationSchema
is used with a third-party library like Yup.
Here’s an example using validate
:
// Pass the `onSubmit` function that gets called when the form is submitted. const formik = useFormik({ initialValues: { email: "", password: "" }, // We've added a validate function validate() { const errors = {}; // Add the touched to avoid the validator validating all fields at once if (formik.touched.email && !formik.values.email) { errors.email = "Required"; } else if ( !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,4}$ /i.test(formik.values.email) ) { errors.email = "Invalid email address"; } if (formik.touched.password && !formik.values.password) { errors.password = "Required"; } else if (formik.values.password.length <= 8) { errors.password = "Must be more than 8 characters"; } return errors; }, onSubmit(values) { // Do stuff here... } }); // ...
And here we go with an example using validationSchema
instead:
const formik = useFormik({ initialValues: { email: "", password: "" }, // We used Yup here. validationSchema: Yup.object().shape({ email: Yup.string() .email("Invalid email address") .required("Required"), password: Yup.string() .min(8, "Must be more than 8 characters") .required("Required") }), onSubmit(values) { // Do stuff here... } });
Validating at the field level or using manual triggers are fairly simple to understand. Albeit, you’ll likely use form level validation most of the time. It’s also worth checking out the docs to see other use cases.
withFormik
is a higher-order component and be used that way if that’s your thing. Write the form, then expose it through Formik.
So far, we’ve become acquainted with Formik, covered the benefits of using it for creating forms in React, and covered a few methods to implement it as a React component while demonstrating various ways we can use it for validation. What we haven’t done is looked at examples of those key concepts.
So, let’s look at a couple of practical applications: displaying error messages and generating a username based on what’s entered in the email input.
We’ve built our form and validated it. And we’ve caught some errors that can be found in our errors
object. But it’s no use if we aren’t actually displaying those errors.
Formik makes this a pretty trivial task. All we need to do is check the errors
object returned by any of the methods we’ve looked at — <Formik/>
, useFormik
or withFormik
— and display them:
<label className="formFieldLabel" htmlFor="email"> Email address <span className="errorMessage"> {touched["email"] && errors["email"]} </span> </label> <div className="formFieldWrapInner"> <input type="email" id="email" className="email formField" {...getFieldProps("email")} /> </div>
If there’s an error during validation, {touched["email"] && errors["email"]}
will display it to the user.
We could do the same with <ErrorMessage/>
. With this, we only need to tell it the name of the dependent field to watch:
<ErrorMessage name="email"> {errMsg => <span className="errorMessage">{errMsg}</span>} </ErrorMessage>
Imagine a form that automatically generates a username for your users based on their email address. In other words, whatever the user types into the email input gets pulled out, stripped of @
and everything after it, and leaves us with a username with what’s left.
For example: jane@doe.com
produces @jane
.
Formik exposes helpers that can “intercept” its functionality and lets us perform some effects.In the case of auto-generating a username, one way will be through Formik’s setValues
:
onSubmit(values) { // We added a `username` value for the user which is everything before @ in their email address. setValues({ ...values, username: `@$ {values.email.split("@")[0]}` }); }
Type in an email address and password, then submit the form to see your new username!
Wow, we covered a lot of ground in a short amount of space. While this is merely the tip of the iceberg as far as covering all the needs of a form and what Formik is capable of doing, I hope this gives you a new tool to reach for the next time you find yourself tackling forms in a React application.
If you’re ready to take Formik to the next level, I’d suggest looking through their resources as a starting point. There are so many goodies in there and it’s a good archive of what Formik can do as well as more tutorials that get into deeper use cases.
Good luck with your forms!
The post Using Formik to Handle Forms in React appeared first on CSS-Tricks.