Efficient Infinite Utility Helpers Using Inline CSS Custom Properties and calc()
I recently wrote a very basic Sass loop that outputs several padding and margin utility classes. Nothing fancy, really, just a Sass map with 11 spacing values, looped over to create classes for both padding and margin on each side. As we’ll see, this works, but it ends up a pretty hefty amount of CSS. We’re going to refactor it to use CSS custom properties and make the system much more trim.
Here’s the original Sass implementation:
$space-stops: (
'0': 0,
'1': 0.25rem,
'2': 0.5rem,
'3': 0.75rem,
'4': 1rem,
'5': 1.25rem,
'6': 1.5rem,
'7': 1.75rem,
'8': 2rem,
'9': 2.25rem,
'10': 2.5rem,
);
@each $key, $val in $space-stops {
.p-#{$key} {
padding: #{$val} !important;
}
.pt-#{$key} {
padding-top: #{$val} !important;
}
.pr-#{$key} {
padding-right: #{$val} !important;
}
.pb-#{$key} {
padding-bottom: #{$val} !important;
}
.pl-#{$key} {
padding-left: #{$val} !important;
}
.px-#{$key} {
padding-right: #{$val} !important;
padding-left: #{$val} !important;
}
.py-#{$key} {
padding-top: #{$val} !important;
padding-bottom: #{$val} !important;
}
.m-#{$key} {
margin: #{$val} !important;
}
.mt-#{$key} {
margin-top: #{$val} !important;
}
.mr-#{$key} {
margin-right: #{$val} !important;
}
.mb-#{$key} {
margin-bottom: #{$val} !important;
}
.ml-#{$key} {
margin-left: #{$val} !important;
}
.mx-#{$key} {
margin-right: #{$val} !important;
margin-left: #{$val} !important;
}
.my-#{$key} {
margin-top: #{$val} !important;
margin-bottom: #{$val} !important;
}
}
This very much works. It outputs all the utility classes we need. But, it can also get bloated quickly. In my case, they were about 8.6kb uncompressed and under 1kb compressed. (Brotli was 542 bytes, and gzip came in at 925 bytes.)
Since they are extremely repetitive, they compress well, but I still couldn’t shake the feeling that all these classes were overkill. Plus, I hadn’t even done any small/medium/large breakpoints which are fairly typical for these kinds of helper classes.
Here’s a contrived example of what the responsive version might look like with small/medium/large classes added. We’ll re-use the $space-stops
map defined previously and throw our repetitious code into a mixin
@mixin finite-spacing-utils($bp: '') {
@each $key, $val in $space-stops {
.p-#{$key}#{$bp} {
padding: #{$val} !important;
}
.pt-#{$key}#{$bp} {
padding-top: #{$val} !important;
}
.pr-#{$key}#{$bp} {
padding-right: #{$val} !important;
}
.pb-#{$key}#{$bp} {
padding-bottom: #{$val} !important;
}
.pl-#{$key}#{$bp} {
padding-left: #{$val} !important;
}
.px-#{$key}#{$bp} {
padding-right: #{$val} !important;
padding-left: #{$val} !important;
}
.py-#{$key}#{$bp} {
padding-top: #{$val} !important;
padding-bottom: #{$val} !important;
}
.m-#{$key}#{$bp} {
margin: #{$val} !important;
}
.mt-#{$key}#{$bp} {
margin-top: #{$val} !important;
}
.mr-#{$key}#{$bp} {
margin-right: #{$val} !important;
}
.mb-#{$key}#{$bp} {
margin-bottom: #{$val} !important;
}
.ml-#{$key}#{$bp} {
margin-left: #{$val} !important;
}
.mx-#{$key}#{$bp} {
margin-right: #{$val} !important;
margin-left: #{$val} !important;
}
.my-#{$key}#{$bp} {
margin-top: #{$val} !important;
margin-bottom: #{$val} !important;
}
}
}
@include finite-spacing-utils;
@media (min-width: 544px) {
@include finite-spacing-utils($bp: '_sm');
}
@media (min-width: 768px) {
@include finite-spacing-utils($bp: '_md');
}
@media (min-width: 1024px) {
@include finite-spacing-utils($bp: '_lg');
}
That clocks in at about 41.7kb uncompressed (and about 1kb with Brotli, and 3kb with gzip). It still compresses well, but it’s a bit ridiculous.
I knew it was possible to reference data-*
attributes from within CSS using the [attr()
function, so I wondered if it was possible to use calc()
and attr()
together to create dynamically-calculated spacing utility helpers via data-*
attributes — like data-m="1"
or data-m="1@md"
— then in the CSS to do something like margin: calc(attr(data-m) * 0.25rem)
(assuming I’m using a spacing scale incrementing at 0.25rem
intervals). That could be very powerful.
But the end of that story is: no, you (currently) can’t use attr()
with any property except the content
property. Bummer. But in searching for attr()
and calc()
information, I found this intriguing Stack Overflow comment by Simon Rigét that suggests setting a CSS variable directly within an inline style attribute. Aha!
So it’s possible to do something like
:root {
--p: 0;
}
[style*='--p:'] {
padding: calc(0.25rem * var(--p)) !important;
}
In the case of the style="--p: 4;"
example, you’d effectively end up with padding: 1rem !important;
.
… and now you have an infinitely scalable spacing utility class monstrosity helper.
Here’s what that might look like in CSS:
:root {
--p: 0;
--pt: 0;
--pr: 0;
--pb: 0;
--pl: 0;
--px: 0;
--py: 0;
--m: 0;
--mt: 0;
--mr: 0;
--mb: 0;
--ml: 0;
--mx: 0;
--my: 0;
}
[style*='--p:'] {
padding: calc(0.25rem * var(--p)) !important;
}
[style*='--pt:'] {
padding-top: calc(0.25rem * var(--pt)) !important;
}
[style*='--pr:'] {
padding-right: calc(0.25rem * var(--pr)) !important;
}
[style*='--pb:'] {
padding-bottom: calc(0.25rem * var(--pb)) !important;
}
[style*='--pl:'] {
padding-left: calc(0.25rem * var(--pl)) !important;
}
[style*='--px:'] {
padding-right: calc(0.25rem * var(--px)) !important;
padding-left: calc(0.25rem * var(--px)) !important;
}
[style*='--py:'] {
padding-top: calc(0.25rem * var(--py)) !important;
padding-bottom: calc(0.25rem * var(--py)) !important;
}
[style*='--m:'] {
margin: calc(0.25rem * var(--m)) !important;
}
[style*='--mt:'] {
margin-top: calc(0.25rem * var(--mt)) !important;
}
[style*='--mr:'] {
margin-right: calc(0.25rem * var(--mr)) !important;
}
[style*='--mb:'] {
margin-bottom: calc(0.25rem * var(--mb)) !important;
}
[style*='--ml:'] {
margin-left: calc(0.25rem * var(--ml)) !important;
}
[style*='--mx:'] {
margin-right: calc(0.25rem * var(--mx)) !important;
margin-left: calc(0.25rem * var(--mx)) !important;
}
[style*='--my:'] {
margin-top: calc(0.25rem * var(--my)) !important;
margin-bottom: calc(0.25rem * var(--my)) !important;
}
This is a lot like the first Sass loop above, but there’s no loop going 11 times — and yet it’s infinite. It’s about 1.4kb uncompressed, 226 bytes with Brotli, or 284 bytes gzipped.
If you wanted to extend this for breakpoints, the unfortunate news is that you can’t put the “@” character in CSS variable names (although emojis and other UTF-8 characters are strangely permitted). So you could probably set up variable names like p_sm
or sm_p
. You’d have to add some extra CSS variables and some media queries to handle all this, but it won’t blow up exponentially the way traditional CSS classnames created with a Sass for-loop do.
Here’s the equivalent responsive version. We’ll use a Sass mixin again to cut down the repetition:
:root {
--p: 0;
--pt: 0;
--pr: 0;
--pb: 0;
--pl: 0;
--px: 0;
--py: 0;
--m: 0;
--mt: 0;
--mr: 0;
--mb: 0;
--ml: 0;
--mx: 0;
--my: 0;
}
@mixin infinite-spacing-utils($bp: '') {
[style*='--p#{$bp}:'] {
padding: calc(0.25rem * var(--p)) !important;
}
[style*='--pt#{$bp}:'] {
padding-top: calc(0.25rem * var(--pt)) !important;
}
[style*='--pr#{$bp}:'] {
padding-right: calc(0.25rem * var(--pr)) !important;
}
[style*='--pb#{$bp}:'] {
padding-bottom: calc(0.25rem * var(--pb)) !important;
}
[style*='--pl#{$bp}:'] {
padding-left: calc(0.25rem * var(--pl)) !important;
}
[style*='--px#{$bp}:'] {
padding-right: calc(0.25rem * var(--px)) !important;
padding-left: calc(0.25rem * var(--px)) !important;
}
[style*='--py#{$bp}:'] {
padding-top: calc(0.25rem * var(--py)) !important;
padding-bottom: calc(0.25rem * var(--py)) !important;
}
[style*='--m#{$bp}:'] {
margin: calc(0.25rem * var(--m)) !important;
}
[style*='--mt#{$bp}:'] {
margin-top: calc(0.25rem * var(--mt)) !important;
}
[style*='--mr#{$bp}:'] {
margin-right: calc(0.25rem * var(--mr)) !important;
}
[style*='--mb#{$bp}:'] {
margin-bottom: calc(0.25rem * var(--mb)) !important;
}
[style*='--ml#{$bp}:'] {
margin-left: calc(0.25rem * var(--ml)) !important;
}
[style*='--mx#{$bp}:'] {
margin-right: calc(0.25rem * var(--mx)) !important;
margin-left: calc(0.25rem * var(--mx)) !important;
}
[style*='--my#{$bp}:'] {
margin-top: calc(0.25rem * var(--my)) !important;
margin-bottom: calc(0.25rem * var(--my)) !important;
}
}
@include infinite-spacing-utils;
@media (min-width: 544px) {
@include infinite-spacing-utils($bp: '_sm');
}
@media (min-width: 768px) {
@include infinite-spacing-utils($bp: '_md');
}
@media (min-width: 1024px) {
@include infinite-spacing-utils($bp: '_lg');
}
That’s about 6.1kb uncompressed, 428 bytes with Brotli, and 563 with gzip.
Do I think that writing HTML like
It’s worth pointing out here that CSS variables assigned in inline styles do not leak out. They’re scoped only to the current element and don’t change the value of the variable globally. Thank goodness! The one oddity I have found so far is that DevTools (at least in Chrome, Firefox, and Safari) do not report the styles using this technique in the “Computed” styles tab.
Also worth mentioning is that I’ve used good old padding
and margin
properties with -top
, -right
, -bottom
, and -left
, but you could use the equivalent logical properties like padding-block
and padding-inline
. It’s even possible to shave off just a few more bytes by selectively mixing and matching logical properties with traditional properties. I managed to get it down to 400 bytes with Brotli and 521 with gzip this way.
Other use cases
This seems most appropriate for things that are on a (linear) incremental scale (which is why padding and margin seems like a good use case) but I could see this potentially working for widths and heights in grid systems (column numbers and/or widths). Maybe for typographic scales (but maybe not).
I’ve focused a lot on file size, but there may be some other uses here I’m not thinking of. Perhaps you wouldn’t write your code in this way, but a critical CSS tool could potentially refactor the code to use this approach.
Digging deeper
As I dug deeper, I found that Ahmad Shadeed blogged in 2019 about mixing calc()
with CSS variable assignments within inline styles particularly for avatar sizes. Miriam Suzanne’s article on Smashing Magazine in 2019 didn’t use calc()
but shared some amazing things you can do with variable assignments in inline styles.
The post Efficient Infinite Utility Helpers Using Inline CSS Custom Properties and calc() appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.
gridless.design
Donnie D’Amato built a whole site around the thesis that “digital designers still expect to use the grid while experienced layout engineers have moved beyond it.” The idea isn’t that we should never literally use display: grid;
but rather that strict adherence to an overall page grid isn’t necessary. Brad’s reaction was interesting, as someone in and out of a lot more projects than I am:
One of the most frequent, confusing conversations w/ designers is “No, the pink lines that overlay design comps aren’t all that helpful for how things actually work in the browser.”
[…] throw your transparent pink 12-column grids in the trash can.
Brad Frost, “Link post to gridless.desgn”
Donnie feels this is all in the spirit of responsive design, and I’m inclined to agree, except that browser technology has evolved quite a bit since the coining of responsive design and it might be time to call it something new. “Content-driven design” is one of Donnie’s headers and that’s a nice phrase.
This all resonated with Michelle as well:
CSS layout features like flexbox and Grid enable us to build more flexible layouts that prioritise content. We talk about intrinsic and extrinsic sizing in CSS — sizing based on both content and context. The promised container queries specification will put even more power in the hands of developers. But it feels to me like the design process is still stuck in the past.
Michelle Barker, “Is it Time to Ditch the Design Grid?”
When container queries are really here, overall page layouts are really going to be an endangered species. Donnie knows:
[…] you should truly consider all other options before using a [browser window size] breakpoint. Ask, is the component expected to always be related to the page size (headers, modals, etc.)? Then a breakpoint might be acceptable. However, components that are placed deep within the page should not be using breakpoints to inform their layout.
Direct Link to Article — Permalink
The post gridless.design appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.
Review: Should Designers Use Design Bundles?
One of the most challenging stages of a design project is laboriously producing all those assets that bring it to life.
Whether you’re a web designer in need of icons, or a brand designer looking for mockups, it’s a smart move to fall back on stock assets to speed up your turnaround, reduce costs, and enliven your designs.
With so many stock sites to choose from, which should you opt for? Today we’re going to take a look at DesignBundles.net and ask if it’s the right choice for your project.
What is Design Bundles?
Design Bundles is exactly what it sounds like: sets of design assets that you can download for free or at huge reductions on RRP.
Launched five years ago, Design Bundles is brought to you by the same team that delivers Font Bundles.
Over two million customers are already enjoying the benefits of Design Bundles’ weekly updates.
What Can You Download From Design Bundles?
There are currently over 800,000 products listed on Design Bundles, and the collection is growing all the time.
The files have been produced by 6,500 of the world’s leading independent designs. Because the products are top-quality, you can really make them work: scale them, flip them, distort them, recolor them; just about anything is possible when you’re using high-quality raw ingredients.
What’s really impressive is the range of products that are in the collection. You’ll find backgrounds, logos, icons, textures, patterns, and clip art. For product designers, there are mockups and web elements. Print designers will love the huge range of cards, invites, flyers, posters, and brochures. And if you’re looking for standard stock images, you’ll find a gargantuan range under popular themes such as technology, architecture, travel, and business.
Because Design Bundles is relatively new, you won’t find its collection padded out with out-of-date Gifs. In fact, Design Bundles is one of our favorite places for free SVGs, the best format for graphics on the web.
All the products are royalty-free and licensed for personal and commercial use, so you can relax knowing that you’re fully covered.
What Makes Design Bundles Different?
There are a lot of design bundles services online, so what makes Design Bundles different? Well, firstly, the quality; Design Bundles has a consistently high level of designs. We love the freebies. They make a huge difference and are great for trying out ideas you might not be ready to invest in.
Design Bundles differs from other stock sites because it offers curated bundles of complementary assets — you don’t just download a vector file; you download a set of vector files. This innovative approach means you don’t have to go hunting for matching images; once you’ve found the right download for your project, you have a whole range to enjoy.
The whole process of using Design Bundles is simple, from browsing through the available assets to choosing a design and through to the fast, simple checkout process. It’s all designed to make including design assets into your projects as simple as possible.
The website is user-friendly, and in the unlikely event that you’ll need them, the support team is friendly, helpful, and prompt to handle queries.
Is Design Bundles Good Value?
We’d be lying if we said that cost wasn’t a big bonus of using Design Bundles. Design Bundles offer up to 96% discounts on regular prices, meaning that you can get the same incredible assets that top design agencies use at a fraction of the price.
In terms of time-saved, inspiration-delivered, and graphics-acquired, your Design Bundles subscription will more than pay for itself.
Alongside the daily and weekly deals, you’ll find products you can try for free. For example, design Bundles provide a whole heap of free SVGs. And not just sample files. There are thousands of files to download and use in projects. You can’t get better value than free!
There are new freebies every week, so it’s worth subscribing just to ensure you don’t miss anything.
For freelancers, the licensing model is particularly attractive. Because there’s no limit on the places you can download design resources once you’ve bought them, you can download files at home, in the office, or at a client’s workspace — whatever is convenient. Meaning you carry a huge library of resources with you wherever you go.
Oh, and if you want to make the most of your new Design Bundles assets, don’t forget to check out its YouTube channel, where you can learn tons about how to create effects, edit images, and complete creative projects.
Should Designers Use Design Bundles?
When you’re designing a project, whether you’re a digital artist, a web developer, or a crafter, the result relies on the quality of its component parts.
Design Bundles offers you the chance to work with high-quality assets from some of the web’s top designers at a fraction of the price of creating them yourself.
We’re confident that if you give Design Bundles a go, you’ll quickly see the benefits for yourself. But if you’re still not sure, why not sign up for its newsletter and check out some of the daily and weekly deals and the freebies that are available to you.
Design Bundles is a recipe for success that you’ll be happy you discovered.
[— This is a sponsored post on behalf of Design Bundles —]
The post Review: Should Designers Use Design Bundles? first appeared on Webdesigner Depot.
Three Buggy React Code Examples and How to Fix Them
There’s usually more than one way to code a thing in React. And while it’s possible to create the same thing different ways, there may be one or two approaches that technically work “better” than others. I actually run into plenty of examples where the code used to build a React component is technically “correct” but opens up issues that are totally avoidable.
So, let’s look at some of those examples. I’m going to provide three instances of “buggy” React code that technically gets the job done for a particular situation, and ways it can be improved to be more maintainable, resilient, and ultimately functional.
This article assumes some knowledge of React hooks. It isn’t an introduction to hooks—you can find a good introduction from Kingsley Silas on CSS Tricks, or take a look at the React docs to get acquainted with them. We also won’t be looking at any of that exciting new stuff coming up in React 18. Instead, we’re going to look at some subtle problems that won’t completely break your application, but might creep into your codebase and can cause strange or unexpected behavior if you’re not careful.
Buggy code #1: Mutating state and props
It’s a big anti-pattern to mutate state or props in React. Don’t do this!
This is not a revolutionary piece of advice—it’s usually one of the first things you learn if you’re getting started with React. But you might think you can get away with it (because it seems like you can in some cases).
I’m going to show you how bugs might creep into your code if you’re mutating props. Sometimes you’ll want a component that will show a transformed version of some data. Let’s create a parent component that holds a count in state and a button that will increment it. We’ll also make a child component that receives the count via props and shows what the count would look like with 5 added to it.
Here’s a Pen that demonstrates a naïve approach:
This example works. It does what we want it to do: we click the increment button and it adds one to the count. Then the child component is re-rendered to show what the count would look like with 5 added on. We changed the props in the child here and it works fine! Why has everybody been telling us mutating props is so bad?
Well, what if later we refactor the code and need to hold the count in an object? This might happen if we need to store more properties in the same useState
hook as our codebase grows larger.
Instead of incrementing the number held in state, we increment the count
property of an object held in state. In our child component, we receive the object through props and add to the count
property to show what the count would look like if we added 5.
Let’s see how this goes. Try incrementing the state a few times in this pen:
Oh no! Now when we increment the count it seems to add 6 on every click! Why is this happening? The only thing that changed between these two examples is that we used an object instead of a number!
More experienced JavaScript programmers will know that the big difference here is that primitive types such as numbers, booleans and strings are immutable and passed by value, whereas objects are passed by reference.
This means that:
- If you put a number in a variable, assign another variable to it, then change the second variable, the first variable will not be changed.
- If you if you put an object in a variable, assign another variable to it, then change the second variable, the first variable will get changed.
When the child component changes a property of the state object, it’s adding 5 to the same object React uses when updating the state. This means that when our increment function fires after a click, React uses the same object after it has been manipulated by our child component, which shows as adding 6 on every click.
The solution
There are multiple ways to avoid these problems. For a situation as simple as this, you could avoid any mutation and express the change in a render function:
function Child({state}){
return <div><p>count + 5 = {state.count + 5} </p></div>
}
However, in a more complicated case, you might need to reuse state.count + 5
multiple times or pass the transformed data to multiple children.
One way to do this is to create a copy of the prop in the child, then transform the properties on the cloned data. There’s a couple of different ways to clone objects in JavaScript with various tradeoffs. You can use object literal and spread syntax:
function Child({state}){
const copy = {...state};
return <div><p>count + 5 = {copy.count + 5} </p></div>
}
But if there are nested objects, they will still reference the old version. Instead, you could convert the object to JSON then immediately parse it:
JSON.parse(JSON.stringify(myobject))
This will work for most simple object types. But if your data uses more exotic types, you might want to use a library. A popular method would be to use lodash’s deepClone. Here’s a Pen that shows a fixed version using object literal and spread syntax to clone the object:
One more option is to use a library like Immutable.js. If you have a rule to only use immutable data structures, you’ll be able to trust that your data won’t get unexpectedly mutated. Here’s one more example using the immutable Map
class to represent the state of the counter app:
Buggy code #2: Derived state
Let’s say we have a parent and a child component. They both have useState
hooks holding a count. And let’s say the parent passes its state down as prop down to the child, which the child uses to initialize its count.
function Parent(){
const [parentCount,setParentCount] = useState(0);
return <div>
<p>Parent count: {parentCount}</p>
<button onClick={()=>setParentCount(c=>c+1)}>Increment Parent</button>
<Child parentCount={parentCount}/>
</div>;
}
function Child({parentCount}){
const [childCount,setChildCount] = useState(parentCount);
return <div>
<p>Child count: {childCount}</p>
<button onClick={()=>setChildCount(c=>c+1)}>Increment Child</button>
</div>;
}
What happens to the child’s state when the parent’s state changes, and the child is re-rendered with different props? Will the child state remain the same or will it change to reflect the new count that was passed to it?
We’re dealing with a function, so the child state should get blown away and replaced right? Wrong! The child’s state trumps the new prop from the parent. After the child component’s state is initialized in the first render, it’s completely independent from any props it receives.
React stores component state for each component in the tree and the state only gets blown away when the component is removed. Otherwise, the state won’t be affected by new props.
Using props to initialize state is called “derived state” and it is a bit of an anti-pattern. It removes the benefit of a component having a single source of truth for its data.
Using the key prop
But what if we have a collection of items we want to edit using the same type of child component, and we want the child to hold a draft of the item we’re editing? We’d need to reset the state of the child component each time we switch items from the collection.
Here’s an example: Let’s write an app where we can write a daily list of five thing’s we’re thankful for each day. We’ll use a parent with state initialized as an empty array which we’re going to fill up with five string statements.
Then we’ll have a a child component with a text input to enter our statement.
We’re about to use a criminal level of over-engineering in our tiny app, but it’s to illustrate a pattern you might need in a more complicated project: We’re going to hold the draft state of the text input in the child component.
Lowering the state to the child component can be a performance optimization to prevent the parent re-rendering when the input state changes. Otherwise the parent component will re-render every time there is a change in the text input.
We’ll also pass down an example statement as a default value for each of the five notes we’ll write.
Here’s a buggy way to do this:
// These are going to be our default values for each of the five notes
// To give the user an idea of what they might write
const ideaList = ["I'm thankful for my friends",
"I'm thankful for my family",
"I'm thankful for my health",
"I'm thankful for my hobbies",
"I'm thankful for CSS Tricks Articles"]
const maxStatements = 5;
function Parent(){
const [list,setList] = useState([]);
// Handler function for when the statement is completed
// Sets state providing a new array combining the current list and the new item
function onStatementComplete(payload){
setList(list=>[...list,payload]);
}
// Function to reset the list back to an empty array
function reset(){
setList([]);
}
return <div>
<h1>Your thankful list</h1>
<p>A five point list of things you're thankful for:</p>
{/* First we list the statements that have been completed*/}
{list.map((item,index)=>{return <p>Item {index+1}: {item}</p>})}
{/* If the length of the list is under our max statements length, we render
the statement form for the user to enter a new statement.
We grab an example statement from the idealist and pass down the onStatementComplete function.
Note: This implementation won't work as expected*/}
{list.length<maxStatements ?
<StatementForm initialStatement={ideaList[list.length]} onStatementComplete={onStatementComplete}/>
:<button onClick={reset}>Reset</button>
}
</div>;
}
// Our child StatementForm component This accepts the example statement for it's initial state and the on complete function
function StatementForm({initialStatement,onStatementComplete}){
// We hold the current state of the input, and set the default using initialStatement prop
const [statement,setStatement] = useState(initialStatement);
return <div>
{/*On submit we prevent default and fire the onStatementComplete function received via props*/}
<form onSubmit={(e)=>{e.preventDefault(); onStatementComplete(statement)}}>
<label htmlFor="statement-input">What are you thankful for today?</label><br/>
{/* Our controlled input below*/}
<input id="statement-input" onChange={(e)=>setStatement(e.target.value)} value={statement} type="text"/>
<input type="submit"/>
</form>
</div>
}
There’s a problem with this: each time we submit a completed statement, the input incorrectly holds onto the submitted note in the textbox. We want to replace it with an example statement from our list.
Even though we’re passing down a different example string every time, the child remembers the old state and our newer prop is ignored. You could potentially check whether the props have changed on every render in a useEffect
, and then reset the state if they have. But that can cause bugs when different parts of your data use the same values and you want to force the child state to reset even though the prop remains the same.
The solution
If you need a child component where the parent needs the ability to reset the child on demand, there is a way to do it: it’s by changing the key
prop on the child.
You might have seen this special key
prop from when you’re rendering elements based on an array and React throws a warning asking you to provide a key for each element. Changing the key of a child element ensures React creates a brand new version of the element. It’s a way of telling React that you are rendering a conceptually different item using the same component.
Let’s add a key prop to our child component. The value is the index we’re about to fill with our statement:
<StatementForm key={list.length} initialStatement={ideaList[list.length]} onStatementComplte={onStatementComplete}/>
Here’s what this looks like in our list app:
Note the only thing that changed here is that the child component now has a key
prop based on the array index we’re about to fill. Yet, the behavior of the component has completely changed.
Now each time we submit and finish writing out statement, the old state in the child component gets thrown away and replaced with the example statement.
Buggy code #3: Stale closure bugs
This is a common issue with React hooks. There’s previously been a CSS-Tricks article about dealing with stale props and states in React’s functional components.
Let’s take a look at a few situations where you might run into trouble. The first crops up is when using useEffect
. If we’re doing anything asynchronous inside of useEffect
we can get into trouble using old state or props.
Here’s an example. We need to increment a count every second. We set it up on the first render with a useEffect
, providing a closure that increments the count as the first argument, and an empty array as the second argument. We’ll give it the empty array as we don’t want React to restart the interval on every render.
function Counter() {
let [count, setCount] = useState(0);
useEffect(() => {
let id = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(id);
},[]);
return <h1>{count}</h1>;
}
Oh no! The count gets incremented to 1 but never changes after that! Why is this happening?
It’s to do with two things:
- the behavior of closures in JavaScript
- the second argument of that
useEffect
call
Having a look at the MDN docs on closures, we can see:
A closure is the combination of a function and the lexical environment within which that function was declared. This environment consists of any local variables that were in-scope at the time the closure was created.
The “lexical environment” in which our useEffect
closure is declared is inside our Counter
React component. The local variable we’re interested is count
, which is zero at the time of the declaration (the first render).
The problem is, this closure is never declared again. If the count is zero at the time declaration, it will always be zero. Each time the interval fires, it’s running a function that starts with a count of zero and increments it to 1.
So how might we get the function declared again? This is where the second argument of the useEffect
call comes in. We thought we were extremely clever only starting off the interval once by using the empty array, but in doing so we shot ourselves in the foot. If we had left out this argument, the closure inside useEffect
would get declared again with a new count every time.
The way I like to think about it is that the useEffect
dependency array does two things:
- It will fire the
useEffect
function when the dependency changes. - It will also redeclare the closure with the updated dependency, keeping the closure safe from stale state or props.
In fact, there’s even a lint rule to keep your useEffect
instances safe from stale state and props by making sure you add the right dependencies to the second argument.
But we don’t actually want to reset our interval every time the component gets rendered either. How do we solve this problem then?
The solution
Again, there are multiple solutions to our problem here. Let’s start with the easiest: not using the count state at all and instead passing a function into our setState
call:
function Counter() {
let [count, setCount] = useState(0);
useEffect(() => {
let id = setInterval(() => {
setCount(prevCount => prevCount+ 1);
}, 1000);
return () => clearInterval(id);
},[]);
return <h1>{count}</h1>;
}
That was easy. Another option is to use the useRef
hook like this to keep a mutable reference of the count:
function Counter() {
let [count, setCount] = useState(0);
const countRef = useRef(count)
function updateCount(newCount){
setCount(newCount);
countRef.current = newCount;
}
useEffect(() => {
let id = setInterval(() => {
updateCount(countRef.current + 1);
}, 1000);
return () => clearInterval(id);
},[]);
return <h1>{count}</h1>;
}
ReactDOM.render(<Counter/>,document.getElementById("root"))
To go more in depth on using intervals and hooks you can take a look at this article about creating a useInterval
in React by Dan Abramov, who is one of the React core team members. He takes a different route where, instead of holding the count in a ref
, he places the entire closure in a ref
.
To go more in depth on useEffect
you can have a look at his post on useEffect
.
More stale closure bugs
But stale closures won’t just appear in useEffect
. They can also turn up in event handlers and other closures inside your React components. Let’s have a look at a React component with a stale event handler; we’ll create a scroll progress bar that does the following:
- increases its width along the screen as the user scrolls
- starts transparent and becomes more and more opaque as the user scrolls
- provides the user with a button that randomizes the color of the scroll bar
We’re going to leave the progress bar outside of the React tree and update it in the event handler. Here’s our buggy implementation:
<body>
<div id="root"></div>
<div id="progress"></div>
</body>
function Scroller(){
// We'll hold the scroll position in one state
const [scrollPosition, setScrollPosition] = useState(window.scrollY);
// And the current color in another
const [color,setColor] = useState({r:200,g:100,b:100});
// We assign out scroll listener on the first render
useEffect(()=>{
document.addEventListener("scroll",handleScroll);
return ()=>{document.removeEventListener("scroll",handleScroll);}
},[]);
// A function to generate a random color. To make sure the contrast is strong enough
// each value has a minimum value of 100
function onColorChange(){
setColor({r:100+Math.random()*155,g:100+Math.random()*155,b:100+Math.random()*155});
}
// This function gets called on the scroll event
function handleScroll(e){
// First we get the value of how far down we've scrolled
const scrollDistance = document.body.scrollTop || document.documentElement.scrollTop;
// Now we grab the height of the entire document
const documentHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;
// And use these two values to figure out how far down the document we are
const percentAlong = (scrollDistance / documentHeight);
// And use these two values to figure out how far down the document we are
const progress = document.getElementById("progress");
progress.style.width = `${percentAlong*100}%`;
// Here's where our bug is. Resetting the color here will mean the color will always
// be using the original state and never get updated
progress.style.backgroundColor = `rgba(${color.r},${color.g},${color.b},${percentAlong})`;
setScrollPosition(percentAlong);
}
return <div className="scroller" style={{backgroundColor:`rgb(${color.r},${color.g},${color.b})`}}>
<button onClick={onColorChange}>Change color</button>
<span class="percent">{Math.round(scrollPosition* 100)}%</span>
</div>
}
ReactDOM.render(<Scroller/>,document.getElementById("root"))
Our bar gets wider and increasingly more opaque as the page scrolls. But if you click the change color button, our randomized colors are not affecting the progress bar. We’re getting this bug because the closure is affected by component state, and this closure is never being re-declared so we only get the original value of the state and no updates.
You can see how setting up closures that call external APIs using React state, or component props might give you grief if you’re not careful.
The solution
Again, there are multiple ways to fix this problem. We could keep the color state in a mutable ref which we could later use in our event handler:
const [color,setColor] = useState({r:200,g:100,b:100});
const colorRef = useRef(color);
function onColorChange(){
const newColor = {r:100+Math.random()*155,g:100+Math.random()*155,b:100+Math.random()*155};
setColor(newColor);
colorRef.current=newColor;
progress.style.backgroundColor = `rgba(${newColor.r},${newColor.g},${newColor.b},${scrollPosition})`;
}
This works well enough but it doesn’t feel ideal. You may need to write code like this if you’re dealing with third-party libraries and you can’t find a way to pull their API into your React tree. But by keeping one of our elements out of the React tree and updating it inside of our event handler, we’re swimming against the tide.
This is a simple fix though, as we’re only dealing with the DOM API. An easy way to refactor this is to include the progress bar in our React tree and render it in JSX allowing it to reference the component’s state. Now we can use the event handling function purely for updating state.
function Scroller(){
const [scrollPosition, setScrollPosition] = useState(window.scrollY);
const [color,setColor] = useState({r:200,g:100,b:100});
useEffect(()=>{
document.addEventListener("scroll",handleScroll);
return ()=>{document.removeEventListener("scroll",handleScroll);}
},[]);
function onColorChange(){
const newColor = {r:100+Math.random()*155,g:100+Math.random()*155,b:100+Math.random()*155};
setColor(newColor);
}
function handleScroll(e){
const scrollDistance = document.body.scrollTop || document.documentElement.scrollTop;
const documentHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;
const percentAlong = (scrollDistance / documentHeight);
setScrollPosition(percentAlong);
}
return <>
<div class="progress" id="progress"
style={{backgroundColor:`rgba(${color.r},${color.g},${color.b},${scrollPosition})`,width: `${scrollPosition*100}%`}}></div>
<div className="scroller" style={{backgroundColor:`rgb(${color.r},${color.g},${color.b})`}}>
<button onClick={onColorChange}>Change color</button>
<span class="percent">{Math.round(scrollPosition * 100)}%</span>
</div>
</>
}
That feels better. Not only have we removed the chance for our event handler to get stale, we’ve also converted our progress bar into a self contained component which takes advantage of the declarative nature of React.
Also, for a scroll indicator like this, you might not even need JavaScript — have take a look at the up-and-coming @scroll-timeline
CSS function or an approach using a gradient from Chris’ book on the greatest CSS tricks!
Wrapping up
We’ve had a look at three different ways you can create bugs in your React applications and some ways to fix them. It can be easy to look at counter examples which follow a happy path and don’t show subtleties in the APIs that might cause problems.
If you still find yourself needing to build a stronger mental model of what your React code is doing, here’s a list of resources which can help:
- The React Docs
- MDN documentation on closures
- React articles on CSS Tricks
- Issues on the React repo can show common problems and their solutions
- React tag on Stack Overflow
- Eve Porcello’s blog
- Dan Abramov’s blog
- Kent C. Dodds’ blog
The post Three Buggy React Code Examples and How to Fix Them appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.
How to Build a Full-Stack Mobile Application With Flutter, Fauna, and GraphQL
(This is a sponsored post.)
Flutter is Google’s UI framework used to create flexible, expressive cross-platform mobile applications. It is one of the fastest-growing frameworks for mobile app development. On the other hand, Fauna is a transactional, developer-friendly serverless database that supports native GraphQL. Flutter + Fauna is a match made in Heaven. If you are looking to build and ship a feature-rich full-stack application within record time, Flutter and Fauna is the right tool for the job. In this article, we will walk you through building your very first Flutter application with Fauna and GraphQL back-end.
You can find the complete code for this article, on GitHub.
Learning objective
By the end of this article, you should know how to:
- set up a Fauna instance,
- compose GraphQL schema for Fauna,
- set up GraphQL client in a Flutter app, and
- perform queries and mutations against Fauna GraphQL back-end.
Fauna vs. AWS Amplify vs. Firebase: What problems does Fauna solve? How is it different from other serverless solutions? If you are new to Fauna and would like to learn more about how Fauna compares to other solutions, I recommend reading this article.
What are we building?
We will be building a simple mobile application that will allow users to add, delete and update their favorite characters from movies and shows.
Setting up Fauna
Head over to fauna.com and create a new account. Once logged in, you should be able to create a new database.
Give a name to your database. I am going to name mine flutter_demo
. Next, we can select a region group. For this demo, we will choose classic. Fauna is a globally distributed serverless database. It is the only database that supports low latency read and writes access from anywhere. Think of it as CDN (Content Delivery Network) but for your database. To learn more about region groups, follow this guide.
Generating an admin key
Once the database is created head, over to the security tab. Click on the new key button and create a new key for your database. Keep this key secure as we need this for our GraphQL operations.
We will be creating an admin key for our database. Keys with an admin role are used for managing their associated database, including the database access providers, child databases, documents, functions, indexes, keys, tokens, and user-defined roles. You can learn more about Fauna’s various security keys and access roles in the following link.
Compose a GraphQL schema
We will be building a simple app that will allow the users to add, update, and delete their favorite TV characters.
Creating a new Flutter project
Let’s create a new flutter project by running the following commands.
flutter create my_app
Inside the project directory, we will create a new file called graphql/schema.graphql
.
In the schema file, we will define the structure of our collection. Collections in Fauna are similar to tables in SQL. We only need one collection for now. We will call it Character
.
### schema.graphql
type Character {
name: String!
description: String!
picture: String
}
type Query {
listAllCharacters: [Character]
}
As you can see above, we defined a type called Character
with several properties (i.e., name
, description
, picture
, etc.). Think of properties as columns of SQL database or key-value paid of an NoSQL database. We have also defined a Query. This query will return a list of the characters.
Now let’s go back to Fauna dashboard. Click on GraphQL and click on import schema to upload our schema to Fauna.
Once the importing is done, we will see that Fauna has generated the GraphQL queries and mutations.
Don’t like auto-generated GraphQL? Want more control over your business logic? In that case, Fauna allows you to define your custom GraphQL resolvers. To learn more, follow this link.
Setup GraphQL client in Flutter app
Let’s open up our pubspec.yaml
file and add the required dependencies.
...
dependencies:
graphql_flutter: ^4.0.0-beta
hive: ^1.3.0
flutter:
sdk: flutter
...
We added two dependencies here. graphql_flutter
is a GraphQL client library for flutter. It brings all the modern features of GraphQL clients into one easy-to-use package. We also added the hive
package as our dependency. Hive is a lightweight key-value database written in pure Dart for local storage. We are using hive to cache our GraphQL queries.
Next, we will create a new file lib/client_provider.dart
. We will create a provider class in this file that will contain our Fauna configuration.
To connect to Fauna’s GraphQL API, we first need to create a GraphQLClient. A GraphQLClient requires a cache and a link to be initialized. Let’s take a look at the code below.
// lib/client_provider.dart
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:flutter/material.dart';
ValueNotifier<GraphQLClient> clientFor({
@required String uri,
String subscriptionUri,
}) {
final HttpLink httpLink = HttpLink(
uri,
);
final AuthLink authLink = AuthLink(
getToken: () async => 'Bearer fnAEPAjy8QACRJssawcwuywad2DbB6ssrsgZ2-2',
);
Link link = authLink.concat(httpLink);
return ValueNotifier<GraphQLClient>(
GraphQLClient(
cache: GraphQLCache(store: HiveStore()),
link: link,
),
);
}
In the code above, we created a ValueNotifier
to wrap the GraphQLClient
. Notice that we configured the AuthLink in lines 13 – 15 (highlighted). On line 14, we have added the admin key from Fauna as a part of the token. Here I have hardcoded the admin key. However, in a production application, we must avoid hard-coding any security keys from Fauna.
There are several ways to store secrets in Flutter application. Please take a look at this blog post for reference.
We want to be able to call Query
and Mutation
from any widget of our application. To do so we need to wrap our widgets with GraphQLProvider
widget.
// lib/client_provider.dart
....
/// Wraps the root application with the `graphql_flutter` client.
/// We use the cache for all state management.
class ClientProvider extends StatelessWidget {
ClientProvider({
@required this.child,
@required String uri,
}) : client = clientFor(
uri: uri,
);
final Widget child;
final ValueNotifier<GraphQLClient> client;
@override
Widget build(BuildContext context) {
return GraphQLProvider(
client: client,
child: child,
);
}
}
Next, we go to our main.dart
file and wrap our main widget with the ClientProvider
widget. Let’s take a look at the code below.
// lib/main.dart
...
void main() async {
await initHiveForFlutter();
runApp(MyApp());
}
final graphqlEndpoint = 'https://graphql.fauna.com/graphql';
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ClientProvider(
uri: graphqlEndpoint,
child: MaterialApp(
title: 'My Character App',
debugShowCheckedModeBanner: false,
initialRoute: '/',
routes: {
'/': (_) => AllCharacters(),
'/new': (_) => NewCharacter(),
}
),
);
}
}
At this point, all our downstream widgets will have access to run Queries
and Mutations
functions and can interact with the GraphQL API.
Application pages
Demo applications should be simple and easy to follow. Let’s go ahead and create a simple list widget that will show the list of all characters. Let’s create a new file lib/screens/character-list.dart
. In this file, we will write a new widget called AllCharacters
.
// lib/screens/character-list.dart.dart
class AllCharacters extends StatelessWidget {
const AllCharacters({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
pinned: true,
snap: false,
floating: true,
expandedHeight: 160.0,
title: Text(
'Characters',
style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: 36,
),
),
actions: <Widget>[
IconButton(
padding: EdgeInsets.all(5),
icon: const Icon(Icons.add_circle),
tooltip: 'Add new entry',
onPressed: () {
Navigator.pushNamed(context, '/new');
},
),
],
),
SliverList(
delegate: SliverChildListDelegate([
Column(
children: [
for (var i = 0; i < 10; i++)
CharacterTile()
],
)
])
)
],
),
);
}
}
// Character-tile.dart
class CharacterTile extends StatefulWidget {
CharacterTilee({Key key}) : super(key: key);
@override
_CharacterTileState createState() => _CharacterTileeState();
}
class _CharacterTileState extends State<CharacterTile> {
@override
Widget build(BuildContext context) {
return Container(
child: Text("Character Tile"),
);
}
}
As you can see in the code above, [line 37] we have a for loop to populate the list with some fake data. Eventually, we will be making a GraphQL query to our Fauna backend and fetch all the characters from the database. Before we do that, let’s try to run our application as it is. We can run our application with the following command
flutter run
At this point we should be able to see the following screen.
Performing queries and mutations
Now that we have some basic widgets, we can go ahead and hook up GraphQL queries. Instead of hardcoded strings, we would like to get all the characters from our database and view them in AllCharacters
widget.
Let’s go back to the Fauna’s GraphQL playground. Notice we can run the following query to list all the characters.
query ListAllCharacters {
listAllCharacters(_size: 100) {
data {
_id
name
description
picture
}
after
}
}
To perform this query from our widget we will need to make some changes to it.
import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:todo_app/screens/Character-tile.dart';
String readCharacters = ";";";
query ListAllCharacters {
listAllCharacters(_size: 100) {
data {
_id
name
description
picture
}
after
}
}
";";";;
class AllCharacters extends StatelessWidget {
const AllCharacters({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
pinned: true,
snap: false,
floating: true,
expandedHeight: 160.0,
title: Text(
'Characters',
style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: 36,
),
),
actions: <Widget>[
IconButton(
padding: EdgeInsets.all(5),
icon: const Icon(Icons.add_circle),
tooltip: 'Add new entry',
onPressed: () {
Navigator.pushNamed(context, '/new');
},
),
],
),
SliverList(
delegate: SliverChildListDelegate([
Query(options: QueryOptions(
document: gql(readCharacters), // graphql query we want to perform
pollInterval: Duration(seconds: 120), // refetch interval
),
builder: (QueryResult result, { VoidCallback refetch, FetchMore fetchMore }) {
if (result.isLoading) {
return Text('Loading');
}
return Column(
children: [
for (var item in result.data['listAllCharacters']['data'])
CharacterTile(Character: item, refetch: refetch),
],
);
})
])
)
],
),
);
}
}
First of all, we defined the query string for getting all characters from the database [line 5 to 17]. We have wrapped our list widget with a Query widget from flutter_graphql
.
Feel free to take a look at the official documentation for flutter_graphql library.
In the query options argument we provide the GraphQL query string itself. We can pass in any float number for the pollInterval argument. Poll Interval defines how often we would like to refetch data from our backend. The widget also has a standard builder function. We can use a builder function to pass the query result, refetch callback function and fetch more callback function down the widget tree.
Next, I am going to update the CharacterTile
widget to display the character data on screen.
// lib/screens/character-tile.dart
...
class CharacterTile extends StatelessWidget {
final Character;
final VoidCallback refetch;
final VoidCallback updateParent;
const CharacterTile({
Key key,
@required this.Character,
@required this.refetch,
this.updateParent,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
},
child: Padding(
padding: const EdgeInsets.all(10),
child: Row(
children: [
Container(
height: 90,
width: 90,
decoration: BoxDecoration(
color: Colors.amber,
borderRadius: BorderRadius.circular(15),
image: DecorationImage(
fit: BoxFit.cover,
image: NetworkImage(Character['picture'])
)
),
),
SizedBox(width: 10),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
Character['name'],
style: TextStyle(
color: Colors.black87,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 5),
Text(
Character['description'],
style: TextStyle(
color: Colors.black87,
),
maxLines: 2,
),
],
)
)
],
),
),
);
}
}
Adding new data
We can add new characters to our database by running the mutation below.
mutation CreateNewCharacter($data: CharacterInput!) {
createCharacter(data: $data) {
_id
name
description
picture
}
}
To run this mutation from our widget we can use the Mutation
widget from flutter_graphql
library. Let’s create a new widget with a simple form for the users to interact with and input data. Once the form is submitted the createCharacter
mutation will be called.
// lib/screens/new.dart
...
String addCharacter = ";";";
mutation CreateNewCharacter($data: CharacterInput!) {
createCharacter(data: $data) {
_id
name
description
picture
}
}
";";";;
class NewCharacter extends StatelessWidget {
const NewCharacter({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Add New Character'),
),
body: AddCharacterForm()
);
}
}
class AddCharacterForm extends StatefulWidget {
AddCharacterForm({Key key}) : super(key: key);
@override
_AddCharacterFormState createState() => _AddCharacterFormState();
}
class _AddCharacterFormState extends State<AddCharacterForm> {
String name;
String description;
String imgUrl;
@override
Widget build(BuildContext context) {
return Form(
child: Padding(
padding: EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
decoration: const InputDecoration(
icon: Icon(Icons.person),
labelText: 'Name *',
),
onChanged: (text) {
name = text;
},
),
TextField(
decoration: const InputDecoration(
icon: Icon(Icons.post_add),
labelText: 'Description',
),
minLines: 4,
maxLines: 4,
onChanged: (text) {
description = text;
},
),
TextField(
decoration: const InputDecoration(
icon: Icon(Icons.image),
labelText: 'Image Url',
),
onChanged: (text) {
imgUrl = text;
},
),
SizedBox(height: 20),
Mutation(
options: MutationOptions(
document: gql(addCharacter),
onCompleted: (dynamic resultData) {
print(resultData);
name = '';
description = '';
imgUrl = '';
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => AllCharacters())
);
},
),
builder: (
RunMutation runMutation,
QueryResult result,
) {
return Center(
child: ElevatedButton(
child: const Text('Submit'),
onPressed: () {
runMutation({
'data': {
";picture";: imgUrl,
";name";: name,
";description";: description,
}
});
},
),
);
}
)
],
),
),
);
}
}
As you can see from the code above Mutation widget works very similar to the Query widget. Additionally, the Mutation widget provides us with a onComplete function. This function returns the updated result from the database after the mutation is completed.
Removing data
To remove a character from our database we can run the deleteCharacter
mutation. We can add this mutation function to our CharacterTile
and fire it when a button is pressed.
// lib/screens/character-tile.dart
...
String deleteCharacter = ";";";
mutation DeleteCharacter($id: ID!) {
deleteCharacter(id: $id) {
_id
name
}
}
";";";;
class CharacterTile extends StatelessWidget {
final Character;
final VoidCallback refetch;
final VoidCallback updateParent;
const CharacterTile({
Key key,
@required this.Character,
@required this.refetch,
this.updateParent,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
print(Character['picture']);
return Mutation(
options: MutationOptions(
document: gql(deleteCharacter),
onCompleted: (dynamic resultData) {
print(resultData);
this.refetch();
},
),
builder: (
RunMutation runMutation,
QueryResult result,
) {
return Container(
height: 400,
padding: EdgeInsets.all(30),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(Character['description']),
ElevatedButton(
child: Text('Delete Character'),
onPressed: () {
runMutation({
'id': Character['_id'],
});
Navigator.pop(context);
},
),
],
),
),
);
}
);
}
);
},
child: Padding(
padding: const EdgeInsets.all(10),
child: Row(
children: [
Container(
height: 90,
width: 90,
decoration: BoxDecoration(
color: Colors.amber,
borderRadius: BorderRadius.circular(15),
image: DecorationImage(
fit: BoxFit.cover,
image: NetworkImage(Character['picture'])
)
),
),
SizedBox(width: 10),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
Character['name'],
style: TextStyle(
color: Colors.black87,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 5),
Text(
Character['description'],
style: TextStyle(
color: Colors.black87,
),
maxLines: 2,
),
],
)
)
],
),
),
);
}
}
Editing data
Editing data works same as add and delete. It is just another mutation in the GraphQL API. We can create an edit character form widget similar to the new character form widget. The only difference is that the edit form will run updateCharacter
mutation. For editing I created a new widget lib/screens/edit.dart
. Here’s the code for this widget.
// lib/screens/edit.dart
String editCharacter = """
mutation EditCharacter($name: String!, $id: ID!, $description: String!, $picture: String!) {
updateCharacter(data:
{
name: $name
description: $description
picture: $picture
}, id: $id) {
_id
name
description
picture
}
}
""";
class EditCharacter extends StatelessWidget {
final Character;
const EditCharacter({Key key, this.Character}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Edit Character'),
),
body: EditFormBody(Character: this.Character),
);
}
}
class EditFormBody extends StatefulWidget {
final Character;
EditFormBody({Key key, this.Character}) : super(key: key);
@override
_EditFormBodyState createState() => _EditFormBodyState();
}
class _EditFormBodyState extends State<EditFormBody> {
String name;
String description;
String picture;
@override
Widget build(BuildContext context) {
return Container(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
initialValue: widget.Character['name'],
decoration: const InputDecoration(
icon: Icon(Icons.person),
labelText: 'Name *',
),
onChanged: (text) {
name = text;
}
),
TextFormField(
initialValue: widget.Character['description'],
decoration: const InputDecoration(
icon: Icon(Icons.person),
labelText: 'Description',
),
minLines: 4,
maxLines: 4,
onChanged: (text) {
description = text;
}
),
TextFormField(
initialValue: widget.Character['picture'],
decoration: const InputDecoration(
icon: Icon(Icons.image),
labelText: 'Image Url',
),
onChanged: (text) {
picture = text;
},
),
SizedBox(height: 20),
Mutation(
options: MutationOptions(
document: gql(editCharacter),
onCompleted: (dynamic resultData) {
print(resultData);
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => AllCharacters())
);
},
),
builder: (
RunMutation runMutation,
QueryResult result,
) {
print(result);
return Center(
child: ElevatedButton(
child: const Text('Submit'),
onPressed: () {
runMutation({
'id': widget.Character['_id'],
'name': name != null ? name : widget.Character['name'],
'description': description != null ? description : widget.Character['description'],
'picture': picture != null ? picture : widget.Character['picture'],
});
},
),
);
}
),
]
)
),
);
}
}
You can take a look at the complete code for this article below.
Where to go from here
The main intention of this article is to get you up and running with Flutter and Fauna. We have only scratched the surface here. Fauna ecosystem provides a complete, auto-scaling, developer-friendly backend as a service for your mobile applications. If your goal is to ship a production-ready cross-platform mobile application in record time give Fauna and Flutter is the way to go.
I highly recommend checking out Fauna’s official documentation site. If you are interested in learning more about GraphQL clients for Dart/Flutter checkout the official GitHub repo for graphql_flutter
.
Happy hacking and see you next time.
The post How to Build a Full-Stack Mobile Application With Flutter, Fauna, and GraphQL appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.
The State Of Web Workers In 2021
You gotta appreciate the tenacity of Surma. He’s been advocating for Web Workers as a path forward to better-feeling websites for a lot of years now. He’s at it again making sure we all understand the landscape:
[…] regardless of where you look, multithreading is used everywhere. iOS empowers developers to easily parallelize code using Grand Central Dispatch, Android does this via their new, unified task scheduler WorkManager and game engines like Unity have job systems. The reason for any of these platforms to not only support multithreading, but making it as easy as possible is always the same: Ensure your app feels great.
So pretty much every platform has its own version of multi-threading, including the web. It’s just that on the web we have to sort of “fight” against the single-threaded nature of JavaScript by using Web Workers (which are “universally supported” if you’re wondering about that). The question is: use them how and for what? For the latter, Surma shows off an example of a game where “the entire app state and game logic is running in a worker.” For the former, the helper library comlink looks like a big reduction in toil.
Personally, I wish popular tooling would just kinda… do it. I don’t know what that really looks like, but it kinda feels like developer outreach isn’t really moving the needle on this. What if popular tooling like Apollo — which is in charge of a lot of “app state” — were to magically handle all of that off the main thread. Does that make sense? Is it possible?
Direct Link to Article — Permalink
The post The State Of Web Workers In 2021 appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.
Cultivating Customer Confidence with UX
Businesses rely on designers to help them build the perfect relationship with visitors.
After all, as much as companies may wish it wasn’t true, many consumers still judge a book by its cover. A website that fails to prioritize concepts like trust and transparency could instantly lose the confidence of its target audience.
As a designer, it can be tempting to focus on figuring out ways of convincing an audience to convert or give up their money instantly. However, while design experts know how to enhance conversions, they also understand how important it is to make sure that customers feel confident in a website.
Here’s what you need to know about cultivating confidence in user experiences.
How Does Transparency Affect Confidence?
In a survey conducted in 2016, 94% of consumers stated that they would happily be loyal to a fully transparent brand. As customers continue to search for more honest and reliable companies, the demand for transparency in UX will likely continue.
As a web designer, you can’t force a company to share all vital information with its clients. However, you can use your design knowledge to help the honesty of a company stand out.
Here are some other strategies that designers can use to build transparency for their clients.
Create an Eye-Catching “About” Page
One of the first things that today’s businesses need to be honest about if they want to delight their customers is their people.
For instance, creating a page where your client can highlight the nature of the products that they sell is an excellent first start. Maurele has a beautifully designed “About” page to tell its customers everything they need to know about the business.
At the same time, drawing attention to the footer where customers can check things like the terms and policies of the website or FAQs that answer their most common questions is another fantastic way to build transparency.
Giving a brand a human face makes it easier for that company to establish lasting connections. That’s why designers should always prioritize using real, authentic images over stock photos where possible. For example, a large feature image on an “About” page that addresses the company’s unique nature makes it easier to connect with a target audience.
Make sure that there’s plenty of space on the “About” page to introduce significant members of staff that can give a face and personality to both the website and the brand. For instance, Mociun.com uses a fantastic hero image of a person with her cat.
Add Space for Testimonials and Customers
While you don’t need to post a customer’s entire consumer list online to prove that they’re a reputable company, it is worth highlighting some of their clients. No one wants to be the first person online to trust a new website. Testimonials and reviews from other people are how you add instant confidence to any experience.
For designers, social proof can come in a lot of different formats. For instance, if you’re building a website for a company that sells directly to other businesses, you could add pictures of the logos of the brands that the company has worked with. Alternatively, for a B2C brand, basic reviews and quotes will often work wonders. For instance, there’s a list of great reviews included on the product pages of the PlaySuperlative.com website.
Unless customers leave comments directly on the web page themselves, remind your customer that they need to get permission from the client to use their quotes on any web page.
Additionally, remember that adding pictures and names to testimonials where possible can sometimes make them more believable.
Tell Your Client’s Story
When building a website for a client, there are many different things that you’ll need to think about. For instance, you need to focus on the company’s USP or whatever makes them unique. You’ll also need to ensure that clients have all the information they need to make purchases easily.
At the same time, it’s important not to go over the top with too many features. Simplicity is often the key to good UX.
Where possible, however, if you want to boost confidence for your client, it’s a good idea to highlight their unique motives and vision as a business. Ask the company that you’re designing for what their mission is. Do they want to transform the way people communicate and collaborate like Trello? Do they want to fill the world with information, like Google?
Focusing on the unique ambitions of the business, beyond the desire to make money, makes them seem more three-dimensional and real.
Highlight Security
Use your skills to ensure that certain aspects of safety and security stand out for your client. For instance, notice how Fetching Fields instantly pulls attention to the fact that they’re using certified and organic, human-grade wellness solutions.
Other steps you can take include making sure there’s an SSL certificate installed to cement the website’s safety. Additionally, if your customer has any badges or certifications that can highlight their security strategies, it may be a good idea to include those too.
If your customer takes payments online, you can use several secure website seals to boost confidence. For instance, showing that you’re “Verified by Visa” or using Mastercard Secure Code is a wonderful choice.
Make Sure Visitors Can Find Contact Information
As a website designer, one of the best things you can do for your customer is making sure that they have excellent navigation, complete with easy-to-find information.
On any website, innovative navigation ensures that an audience can find the pages they need to make sure that they feel as comfortable as possible, making their purchases.
Make it easy for visitors on a website to track down useful insights about the products they want to buy or the kind of services available from the company in question. Additionally, if your client has any FAQ pages or additional resources, make sure that customers will have no trouble tracking those down.
The example above from Petersham Nurseries makes it easy for clients to find everything they need on the website, thanks to a convenient vertical navigation bar.
Remember, one piece of information that should always be as easy as possible to find for your customers should be the contact page. A contact page shows that the business is willing to answer any questions a client might have via many channels.
List a physical street address and phone number for the website where possible, or at the very least include an email address where people can get in touch.
Make Sure Pricing is Clear
Finally, if you want to ensure that visitors can trust your client’s website, you need to avoid hiding any critical information with fine print or content hidden in the website’s footer. When designing product pages or service information, make sure that you’re as transparent as possible about the company’s pricing.
You don’t want a customer to wait until they click through into the checkout page to discover that they have to spend a fortune on postage and packaging. And, no one purchasing a service wants to wait until they’ve got their credit card out to discover that they’re going to be paying extra for things like set-up fees.
According to Jakob Nielsen, one of the top mistakes anyone can make on a website is not listing their pricing as clearly as possible.
As tempting as it may be to hide certain expenses and send customers through to the checkout page faster, avoid any opportunity to hide information about costs.
Designing for Confidence
Ultimately, many different things can make a website stand out today.
Designers can experiment with unique strategies like dynamic loading and video-based backgrounds. You might even decide to explore new styles with the right company or adapt certain pages to take advantage of things like 5G and new connectivity options.
However, before you can begin exploring new opportunities on any website, one of the most important things you can do is ensure that you get the foundations of the website’s credibility right. Take an approach to design that focuses on transparency and trust first, and the rest will naturally fall into place.
In a world where consumers are less trusting of brands than ever before, people who design for transparency will be sure to stand out from the crowd. Don’t underestimate the power of embracing trust for your clients in 2020.
The post Cultivating Customer Confidence with UX first appeared on Webdesigner Depot.
Using CSS Shapes for Interesting User Controls and Navigation
Straight across or down, that’s the proverbial order for user controls on a screen. Like a list of menu items. But what if we change that to a more fluid layout with bends, curves, and nooks? We can pull it off with just a few lines of code. In the age of modern minimalistic designs, curved layouts for the user controls add just the right amount of pep to a web design.
And coding them couldn’t be more easier, thanks to CSS Shapes.
CSS Shapes (notably, the shape-outside
property) is a standard that assigns geometric shapes to float elements. The content then wraps around the floated element along the boundaries of those shapes.
The use cases for this standard are usually showcased as designs for textual, editorial content — where plain text flow along the shapes floating at their sides. However, in this post, in place of just plain text, we use user controls to see how these shapes can breathe some fluid silhouettes into their layouts.
For the first demo, here’s a design that can be used in product pages, where any product-related action controls can be aligned along the shape of the product itself.
<img src="bottle.png">
<div>
<input type="radio" name=blue checked> <br>
<input type="radio" name=blue> <br>
<input type="radio" name=blue> <br>
<input type="radio" name=blue>
</div>
img {
height: 600px;
float: left;
shape-outside: url("bottle.png");
filter: brightness(1.5);
}
input {
-webkit-appearance: none;
appearance: none;
width: 25px;
height: 25px;
margin-left: 20px;
box-sizing: content-box;
border: 10px solid #231714;
border-radius: 50%;
background: linear-gradient(45deg, pink, beige);
cursor: pointer;
}
The image of the bottle is floated left and given a shape boundary using the shape-outside
property. The image itself is referenced for the shape.
Note: Only images with transparent backgrounds can produce shapes according to the silhouettes of the images.
The default style of the radio buttons is replaced with a custom style. Once the browser applies the shape to the floated image, the radio buttons automatically align themselves along the shape of the bottle.
Like this, we don’t have to bother with individually assigning positions for each radio button to create such a design. Any buttons later added will automatically be aligned with the buttons before them according to the shape of the bottle.
Here’s another example, inspired by the Wikipedia homepage. This is a perfect example of the sort of unconventional main menu layouts we’re looking at.
It’s not too crazy to make with shape-outside
:
<div>
<img src="earth.png">
<div class=l>
<a href="#">Formation</a><br>
<a href="#">Atmosphere</a><br>
<a href="#">Heat</a><br>
<a href="#">Gravitation</a>
</div>
</div>
<div>
<img src="earth.png">
<div class=r>
<a href="#">Moon</a><br>
<a href="#">Climate</a><br>
<a href="#">Rotation</a><br>
<a href="#">Orbit</a>
</div>
</div>
img {
height: 250px;
float: left;
shape-outside: circle(40%);
}
/* stack both sets of menus on the same grid cell */
main > div { grid-area: 1/1; }
/* one set of menus is flipped and moved sideways over the other */
.r { transform: rotatey(180deg) translatex(250px); }
/* links inside the flipped set of menus are rotated back */
.r > a {
display: inline-block;
transform: rotateY(180deg) translateX(-40px);
}
/* hide one of the images */
main > div:nth-of-type(2) img { visibility: hidden; }
An element only ever floats left or right. There’s no center floating element where content wraps around both the sides. In order to achieve the design where links wrap on both the sides of the image, I made two sets of links and flipped one of the sets horizontally. I used the same image with a circle()
CSS shape value in both the sets so the shapes match even after the rotation. The text of the links of the flipped set will appear upside down sideways, so it’s rotated back.
Although both the images can sit on top of each other with no visible overflow, it’s best to hide one of them with either opacity or visibility property.
The third example is a bit lively thanks to the use of one of the dynamic HTML elements,
<img src="diamond.png">
<details>
<summary>Click to know more!</summary>
<ul>
<li>The diamond is now known as the Sancy
<li>It comprises two back-to-back crowns
<li>It's likely of Indian origin
</ul>
</details>
img {
height: 200px;
float: left;
shape-outside: url("diamond.png");
shape-margin: 20px;
}
summary {
background: red;
color: white;
cursor: pointer;
font-weight: bold;
width: 80%;
height: 30px;
line-height: 30px;
}
The image is floated left and is given a CSS shape that’s same as the image. The shape-margin
property adds margin space around the shape assigned to the floated element. When the
element is clicked, the parent
element reveals its content that automatically wraps along the shape of the floated diamond image.
The content of the
element doesn’t necessarily have to be a list, like in the demo. Any inline content would wrap along the floated image’s shape.
The final example works with a polygon shape instead of images or simple shapes like circle and ellipse. Polygons give us more angular geometric shapes that can easily be bent by adding just another coordinate in the shape.
<img src="nasa.png">
<div><!-- triangle --></div>
<ul>
<li><a href="#">Home</a>
<li><a href="#">Projects</a>
<li><a href="#">Shop</a>
<li><a href="#">Contact</a>
<li><a href="#">Media</a>
</ul>
div {
width: 0;
height: 0;
--d: 200px;
/* red triangle */
border-right: var(--d) solid transparent;
border-bottom: var(--d) solid transparent;
border-left: var(--d) solid red;
float: left;
/* triangle CSS shape */
shape-outside: polygon(0 0, var(--d) 0, 0 var(--d));
}
ul {
list-style: none;
padding-top: 25px;
}
A left-floated red triangle is created using border properties. To create a triangular CSS shape that matches the red triangle, we’re using the polygon function as a value for the shape-outside
property. The value for the polygon()
function is the three coordinates of the triangle, separated by commas. The links automatically align around the floated triangle, forming a slanted menu layout down the triangle’s hypotenuse.
As you can see, even for a simple diagonal layout of user controls, CSS Shapes do a nice job adding a little pizzazz to a design. Using CSS Shapes is a much better option than rotating a line of user controls — the alignment of the individual controls and text also rotate, creating layout weirdness. By contrast, a CSS Shape simply lays out the individual controls along the provided shape’s boundary.
The post Using CSS Shapes for Interesting User Controls and Navigation appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.
The final example works with a polygon shape instead of images or simple shapes like circle and ellipse. Polygons give us more angular geometric shapes that can easily be bent by adding just another coordinate in the shape.
<img src="nasa.png">
<div><!-- triangle --></div>
<ul>
<li><a href="#">Home</a>
<li><a href="#">Projects</a>
<li><a href="#">Shop</a>
<li><a href="#">Contact</a>
<li><a href="#">Media</a>
</ul>
div {
width: 0;
height: 0;
--d: 200px;
/* red triangle */
border-right: var(--d) solid transparent;
border-bottom: var(--d) solid transparent;
border-left: var(--d) solid red;
float: left;
/* triangle CSS shape */
shape-outside: polygon(0 0, var(--d) 0, 0 var(--d));
}
ul {
list-style: none;
padding-top: 25px;
}
A left-floated red triangle is created using border properties. To create a triangular CSS shape that matches the red triangle, we’re using the polygon function as a value for the shape-outside
property. The value for the polygon()
function is the three coordinates of the triangle, separated by commas. The links automatically align around the floated triangle, forming a slanted menu layout down the triangle’s hypotenuse.
As you can see, even for a simple diagonal layout of user controls, CSS Shapes do a nice job adding a little pizzazz to a design. Using CSS Shapes is a much better option than rotating a line of user controls — the alignment of the individual controls and text also rotate, creating layout weirdness. By contrast, a CSS Shape simply lays out the individual controls along the provided shape’s boundary.
The post Using CSS Shapes for Interesting User Controls and Navigation appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.
Modern UX Trends Aren’t Novel — They’re Evolution In Action
When it comes to e-commerce, the experience that your website users encounter can feel like a vague, distant consideration.
Without an in-person interaction involved, it’s difficult to stay aware of what consumers are encountering when they interact with your brand’s various customer-facing channels.
And yet, it’s that very disconnected nature of the online marketplace that makes user experience (UX) such a crucial part of any online operation.
With so many businesses building up their online presence in the wake of the coronavirus pandemic, it’s important that each one evaluates the user experience that they are offering. Companies can no longer adopt simplified options of past UX e-commerce strategies. They must look for cutting-edge approaches to traditional methods.
Here are two critical UX trends that businesses, great and small, can learn from going forward.
Videos Continue to Answer User Needs
The evolution of e-commerce in recent years has followed a very visual direction. After starting with text, images became a critical part of both website design and online marketing endeavors. From there, videos rose to prominence, and they continue to set the trend in 2021 and beyond.
Video content has proven to be a dynamic way for brands to connect with their customers. From detailed how-tos to quick, 30-second promotions, videos have been and remain an integral part of a good user experience.
They are information-filled, are great for audible learners, and are easily accessible on mobile devices. In addition, video metadata can be instrumental in boosting search engine optimization (SEO.) This can make it easier for the right consumers to find your content without trouble — a key initial step in a good UX experience.
Speaking of good UX, when it comes to an elite user experience, it’s important that companies embrace a thoughtful video creation strategy. You don’t want to pump out meaningless clips that answer irrelevant questions or dive into unwanted explanations. That’s akin to the old-school black hat SEO method of keyword stuffing. Instead, you want to research your customers’ pain points and ensure that you design each video to resonate with their collective needs.
On top of that, the quality of your videos should also be a top concern. For instance, using software like Dolby.io can help you create videos that will smooth out the user experience. The tool’s advanced audio processing capabilities create pristine HD video with next-level audio. This can generate a more lifelike experience.
The motion-picture element of online marketing has been around for a decade and more. Rather than being replaced, though, it continues to dig in as an integral part of a good UX experience. This highlights a critical factor in many current UX trends: they aren’t replacing old trends so much as they’re refining them. Videos were used five years ago. However, a current, innovative video is going to put an older copy to shame, not because it’s different but because it’s better. From its overall quality to its ability to meet customer needs, videos continue to have a major impact on a good UX experience.
Harmonized Experiences Are Rewriting Omnichannel Marketing
For years a huge part of the user experience refrain has revolved around building an omnichannel experience. Omnichannel has always been an ill-defined concept. But in general, the marketing strategy focuses on covering every channel where your customers could potentially interact with your brand. It doesn’t matter if it’s in a store, on your website, through a social profile, or anywhere else. The goal with the omnichannel approach is to be everywhere at all times, ready to usher a consumer through the customer journey on their terms.
The only problem with the omnichannel approach is that it tends to be non-specific and resource-heavy — read: it’s a bit scattered and takes a lot of investment. It requires a lot of effort, time, and money to monitor interactions in-person, on the phone, through videos and webinars, via social media content, from search engines and ads, and on your website.
This is why, all the way back in 2019, Steve Dennis, a senior contributor at Forbes, was already hailing the dawn of a new era, the era of harmonized retail. The main focus of this new marketing moniker is to steer away from an obsession with marrying together the independent channels themselves. Instead, businesses should fix their attention on consumers.
In other words, businesses shouldn’t invest in maintaining open communication channels at all times, regardless of their traffic. They should focus on steering their resources toward meeting consumers wherever and whenever they pop up on a business’s radar. As Dennis puts it, “the customer is the channel.”
Of course, this opens up the question of how to blend all of your marketing channels into a truly seamless UX experience. On the surface, it can seem like nothing short of a crystal ball can do the trick. Enter data.
User data has been a critical factor of 21st-century marketing. And yet, over time, it’s become clear how little data is truly utilized in most retail interactions. Key metrics and data sets often hog the spotlight, like certain demographic data or user tendencies. But vast quantities of information often remain untapped and unutilized.
This has led to a rise in the development of intricate data management platforms. These tools harness the unused “dark data” and use it to create deep customer insights. This can help a business better understand how their customers behave, their natural proclivities, and so on.
This processed data becomes a utilitarian part of your UX strategy. You can leverage it to smooth out weak points in the customer journey. You can also ensure that the various elements of a traditional omnichannel approach aren’t just connected. They should also work in harmonious concert together. By extension, this allows you to show up, as Dennis explains it, “for the right customers, where it really matters, in remarkable ways.”
Modern UX: Evolution in Motion
Two decades into the 21st-century, the user remains central to all e-commerce activity. From marketing strategies to website design, content creation to data management, what the customer wants always wins out.
This has continued to feed an ever-refining cycle of improved user experience. In 2021, both video content and harmonized retail represent current manifestations of this refining process in action.
However, they are just the tip of an enormous iceberg that includes a variety of pre-existing UX concerns like website navigation, social media marketing, unique selling points, and countless other items. Like video and harmonized retail, these aren’t exactly new concepts.
Instead, the name of the game going forward is perfection. Audio processing tools are making videos more lifelike. Data analysis is smoothing out bumps in the established customer journey. No matter where you look, cutting-edge technology is finding ways to up the ante in every area of the user experience.
And at the end of the day, it is this tendency toward refinement that is the biggest UX trend of them all.
Modern UX Trends Aren’t Novel — They’re Evolution In Action
When it comes to e-commerce, the experience that your website users encounter can feel like a vague, distant consideration.
Without an in-person interaction involved, it’s difficult to stay aware of what consumers are encountering when they interact with your brand’s various customer-facing channels.
And yet, it’s that very disconnected nature of the online marketplace that makes user experience (UX) such a crucial part of any online operation.
With so many businesses building up their online presence in the wake of the coronavirus pandemic, it’s important that each one evaluates the user experience that they are offering. Companies can no longer adopt simplified options of past UX e-commerce strategies. They must look for cutting-edge approaches to traditional methods.
Here are two critical UX trends that businesses, great and small, can learn from going forward.
Videos Continue to Answer User Needs
The evolution of e-commerce in recent years has followed a very visual direction. After starting with text, images became a critical part of both website design and online marketing endeavors. From there, videos rose to prominence, and they continue to set the trend in 2021 and beyond.
Video content has proven to be a dynamic way for brands to connect with their customers. From detailed how-tos to quick, 30-second promotions, videos have been and remain an integral part of a good user experience.
They are information-filled, are great for audible learners, and are easily accessible on mobile devices. In addition, video metadata can be instrumental in boosting search engine optimization (SEO.) This can make it easier for the right consumers to find your content without trouble — a key initial step in a good UX experience.
Speaking of good UX, when it comes to an elite user experience, it’s important that companies embrace a thoughtful video creation strategy. You don’t want to pump out meaningless clips that answer irrelevant questions or dive into unwanted explanations. That’s akin to the old-school black hat SEO method of keyword stuffing. Instead, you want to research your customers’ pain points and ensure that you design each video to resonate with their collective needs.
On top of that, the quality of your videos should also be a top concern. For instance, using software like Dolby.io can help you create videos that will smooth out the user experience. The tool’s advanced audio processing capabilities create pristine HD video with next-level audio. This can generate a more lifelike experience.
The motion-picture element of online marketing has been around for a decade and more. Rather than being replaced, though, it continues to dig in as an integral part of a good UX experience. This highlights a critical factor in many current UX trends: they aren’t replacing old trends so much as they’re refining them. Videos were used five years ago. However, a current, innovative video is going to put an older copy to shame, not because it’s different but because it’s better. From its overall quality to its ability to meet customer needs, videos continue to have a major impact on a good UX experience.
Harmonized Experiences Are Rewriting Omnichannel Marketing
For years a huge part of the user experience refrain has revolved around building an omnichannel experience. Omnichannel has always been an ill-defined concept. But in general, the marketing strategy focuses on covering every channel where your customers could potentially interact with your brand. It doesn’t matter if it’s in a store, on your website, through a social profile, or anywhere else. The goal with the omnichannel approach is to be everywhere at all times, ready to usher a consumer through the customer journey on their terms.
The only problem with the omnichannel approach is that it tends to be non-specific and resource-heavy — read: it’s a bit scattered and takes a lot of investment. It requires a lot of effort, time, and money to monitor interactions in-person, on the phone, through videos and webinars, via social media content, from search engines and ads, and on your website.
This is why, all the way back in 2019, Steve Dennis, a senior contributor at Forbes, was already hailing the dawn of a new era, the era of harmonized retail. The main focus of this new marketing moniker is to steer away from an obsession with marrying together the independent channels themselves. Instead, businesses should fix their attention on consumers.
In other words, businesses shouldn’t invest in maintaining open communication channels at all times, regardless of their traffic. They should focus on steering their resources toward meeting consumers wherever and whenever they pop up on a business’s radar. As Dennis puts it, “the customer is the channel.”
Of course, this opens up the question of how to blend all of your marketing channels into a truly seamless UX experience. On the surface, it can seem like nothing short of a crystal ball can do the trick. Enter data.
User data has been a critical factor of 21st-century marketing. And yet, over time, it’s become clear how little data is truly utilized in most retail interactions. Key metrics and data sets often hog the spotlight, like certain demographic data or user tendencies. But vast quantities of information often remain untapped and unutilized.
This has led to a rise in the development of intricate data management platforms. These tools harness the unused “dark data” and use it to create deep customer insights. This can help a business better understand how their customers behave, their natural proclivities, and so on.
This processed data becomes a utilitarian part of your UX strategy. You can leverage it to smooth out weak points in the customer journey. You can also ensure that the various elements of a traditional omnichannel approach aren’t just connected. They should also work in harmonious concert together. By extension, this allows you to show up, as Dennis explains it, “for the right customers, where it really matters, in remarkable ways.”
Modern UX: Evolution in Motion
Two decades into the 21st-century, the user remains central to all e-commerce activity. From marketing strategies to website design, content creation to data management, what the customer wants always wins out.
This has continued to feed an ever-refining cycle of improved user experience. In 2021, both video content and harmonized retail represent current manifestations of this refining process in action.
However, they are just the tip of an enormous iceberg that includes a variety of pre-existing UX concerns like website navigation, social media marketing, unique selling points, and countless other items. Like video and harmonized retail, these aren’t exactly new concepts.
Instead, the name of the game going forward is perfection. Audio processing tools are making videos more lifelike. Data analysis is smoothing out bumps in the established customer journey. No matter where you look, cutting-edge technology is finding ways to up the ante in every area of the user experience.
And at the end of the day, it is this tendency toward refinement that is the biggest UX trend of them all.