iegik
12/6/2016 - 12:15 PM

Frontend tips and tricks

Frontend tips and tricks

Frontend tips and tricks

Style

  • Transforms cause font-smoothing weirdness in Webkit
transform: perspective(1px) ...
  • Unitless line heights

Unitless line heights are recommended due to the fact that child elements will inherit the raw number value, rather than the computed value. With this, child elements can compute their line heights based on their computed font size, rather than inheriting an arbitrary value from a parent that is more likely to need overriding.

line-height: 1.5;
  • Do not use * > * selectors

https://github.com/yannickcr/eslint-plugin-react/issues/1153

  • Placeholder text within a form field makes it difficult for people to remember what information belongs in a field, and to check for and fix errors. It also poses additional burdens for users with visual and cognitive impairments.

https://www.nngroup.com/articles/form-design-placeholders/

BEM

  • modifiers only on block
  • do not mix blocks

.ComponentAsBlock-layoutPropertyAsElement—boolAttributeAsModifier.is-state

Bad:

<div className="SomeBlock">
  <div className="SomeBlock-someElement SomeBlock-someElement--someModifier SomeAnotherBlock">
        <div className="SomeAnotherBlock-someElement">
        </div>
  </div>
</div>

.SomeBlock {
    // Some block styles
    &-someElement {}

    // Some modifier styles
    &--someModifier {}
}

Good:

<div className="SomeBlock SomeBlock--someModifier">
  <div className="SomeBlock_someElement">
        <div className="SomeAnotherBlock SomeAnotherBlock--someModifier">
          <div className="SomeAnotherBlock_someElement">
          </div>
        </div>
  </div>
</div>

// Some block styles
.SomeBlock {}
.SomeBlock_someElement {}

// Some modifier styles
.SomeBlock--someModifier {}
.SomeBlock--someModifier .SomeBlock_someElement {}

Code

  • async getters
get method() {
  return (async () => {
    try {
      let data = await service.call()
      if(!data) throw Error('No data')
      return {data};
    } catch(e) {
      return e;
    }
  })()
}

  • tuchk4/awesome-css-in-js
  • The Case against Switch
const choice = (opt) => {
  const cases = {
    foo: 'bar'
  }
  return cases[opt] || 'baz';
}

choice('foo') // bar
choice('bar') // baz
const createChoice = (cases, default) => opt => cases[opt] || default;

const choice = createChoice({
  foo: 'bar'
}, 'baz')

choice('foo') // bar
choice('bar') // baz

Find untranslated text

RegEx: [\p{IsCyrillic}]+

Extending function

const foo = () => {}

const bar = (foo => {
    return (...args) => {
        foo(...args);
        // ...
    };
})(foo);

Cannot set height of input[type=submit]

input[type=submit] {
    // box-sizing: border-box; // not work?
    border: 1px solid transparent;
}

Video instead GIF

<img src="animation.mp4" />

Classes

const Foo = function (args) {
  let options = args || {};
  let i = options.start || 0;
  const step = options.step || 1;
  const enc = () => i+=step;
  const dec = () => i-=step;
  const state = () => i;
  return Object.freeze({
    constructor: Foo,
    enc: enc,
    dec: dec,
  });
}

const Bar = function () {
  let self = Foo.apply({},arguments);
  const {set,state} = self;
  const pow = times(() => set(state() * state()));
  const root = times(() => set(Math.sqrt(state())));
  return Object.freeze({
    ...self,
    constructor: Bar,
    pow,
    root
  })
}

Naming

reduce to*

let dest = reduce(src, toFoo, init)

filter where*

let dest = filter(src, whereFoo)

forEach * (action it self)

forEach(src, foo)

Dependency injection

export default (React, Button) => {
    'use strict';

    return (props = {}) => {
        let {title, ...rest} = props;
        return <Button {...rest}>{title}</Button>
    }
}
  • Do not use else
if (...) return ...;
return ...;
  • Do not use ...rest props

Always destructorize pros into named properties, because of React principles

-<Foo {...rest} />
+<Foo bar={bar} />

Except decorators:

const MyInput ({ label, ...rest }) => <label>{label}<input {...rest}/></label>

System fonts

sans-serif:

font-family: -apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;

monospace:

font-family: 'SFMono-Regular', 'SF Mono', 'Ubuntu Mono', Consolas, 'DejaVu Sans Mono', Menlo, monospace;

This solution is harmless and very useful. It is used by GitHub, Wordpress, Bootstrap, Medium, Ghost, etc.

The main reason for using "system" fonts is performance. Fonts are typically one of the largest/heaviest resources loaded on a website. If we can use a font already available on the user’s machine, we can completely eliminate the need to fetch this resource, making load times noticeably faster. The beauty of system fonts is that it matches what the current OS uses, so it can be a comfortable look.

-apple-system targets San Francisco in Safari (on Mac OS X and iOS), and it targets Neue Helvetica and Lucida Grande on older versions of Mac OS X. It properly selects between San Francisco Text and San Francisco Display depending on the text’s size. system-ui represents the default UI font on a given platform. BlinkMacSystemFont is the equivalent for Chrome on Mac OS X. Segoe UI targets Windows and Windows Phone. Roboto targets Android and newer Chrome OS. It is deliberately listed after Segoe UI so that if you’re an Android developer on Windows and have Roboto installed, Segoe UI will be used instead. The bottom line: It's truly the ultimate solution for any website/webapp in any OS.

More info src

CSS Imports with JS

(new CSSStyleSheet())
  .replace('@import url("//fonts.googleapis.com/css2?family=Montserrat:wght@100&display=swap")')
  .then((sheet) => {
    sheet.replaceSync("body { font-family: 'Montserrat', sans-serif; }");
    document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
  })
  .catch(({ message }) => { console.warn(message); });

stopEventPropagation

As a general rule, stopping event propagation should never be a solution to a problem.

https://css-tricks.com/dangers-stopping-event-propagation/

preconnect to domains

<link rel"preconnect" href="https://fonts.google.com" crossorigin>

Try not use nested objects

Nested objects are difficult to support

Good:

const moo = {
    fooBar: 1,
    fooTar: 2,
}

Bad:

const moo = {
    foo: {
        bar: 1,
        tar: 2,
    }
}

Negotation first

if (this == null) {
    throw new TypeError('"this" is null or not defined');
}

Do not use Binary operators

Except these:

// Sometimes you only want an integer
const mix = 0xFFFFFFFF;
mix >>> 0; // converts to UInt32 2^32 will be rounded down (truncated, as in Math.floor) to the nearest integer. Numbers ≥ 2^32 are turned to 0. Numbers less than 0 will turn into a positive value (thanks to the magic of two's-complement representation).
mix | 0;   // converts to Int32  0xFFFFFFFF | 0 = -1

Memorize

WARN: Response may change after POST requests, so, need to pass some additional increment parameter to pass trough memoization. Cache-Control HTTP header already has caching mehanisms. Better to use debounce.

import memoizeOne from 'memoize-one';
const getCommands = memoizeOne(() => axios.get(`${url}/commands`))

Promises are simplier then you think

f = async () => await Promise.resolve(1);
f = async () => Promise.resolve(1);
f = () => Promise.resolve(1);
f = () => ({ then: fn => fn(1) });

f().then(console.log) // 1

unique list with objects

[...new Set(data.map(x => x.foo))]

empty object values

Object.values(someObject).find(Boolean)

real time

draw = () => {
  document.body.innerHTML = (new Date()).toLocaleTimeString();
  requestAnimationFrame(draw);
}
draw();

Alternatives for typeof

Object.prototype.toString.call(...)
array instanceof Array

Replace concrete item in collection

Object.assign([], collection, {[key]: value});

Proxy

const withDefaultValue = (defaultValue = null) => (target) => new Proxy(target, {
  get: (target, prop) => prop in target
      ? target[name]
      : defaultValue,
});

const IndexedArray = new Proxy(Array, {
  construct(target, [args]) {
    const index = new Map();
    args.forEach(item => index.set[item.id, item]);

    return new Proxy(new target(...args), {
      get(arr, prop) {
        switch (prop) {
          case 'push': return item => {
            index.set(item.id, item);
            arr[prop].call(arr, item);
          }
          case 'findById': return index.get;
          default: return arr[prop];
        }
      }
    })
  }
})

Immutable object

const foo = Object.freeze({});

const makes the variable binding immutable but it’s value can still be modified. Object.freeze() ignores the value modification to an object but there is no restriction on the binding.

Import/Export

Linking foo/foo.js to foo/index.js:

export * from './foo';

Exporting components from one file:

export { default as Foo } from './foo'
export { default as Bar } from './bar'

FormData

JSON.stringify(Object.fromEntries(formData));

Left Pad

('0000'+123).substr(-4) is same as (''+123).padStart(4,'0')

Phone pattern

'+0123456789876'.replace(/(..)?(...)?(...)?(...)?(...)?/, '$1 ($2) $3 $4 $5')

Swich values

[a,b] = [b,a];

Matches

[/a/, /b/, /c/, /d/].find(r => r.test('abcd')) // matches
![/a/, /b/, /c/, /d/].find(r => !r.test('abcd')) // not matches

Code Style

Add "precommit" script into package.json

npx mrm lint-staged

Local Storage

  • Stores data with no expiry date
  • Cleared only via JavaScript or clearing browser cache
  • Storage limit is the largest of the 3 as b ig as 5MB
  • Not supported by older browsers IE7 or lower
  • Works on same-origin policy. So, data stored will only be available on the same origin.

Session Storage

  • Stores data only for the duration of the session, when the user closes their browser the data is lost
  • Top-level browsing context, therefore it's unique to each browser tab
  • Storage limit is larger than cookies at 5MB
  • Not supported by older browsers IE7 or lower

Cookies

  • Stores data that can be transferred to the server via headers
  • LocalStorage and SessionStorage can only be accessed on client-side
  • Expiry is set on creation of the cookie
  • Storage limit is smallest at 4kb
  • Cookies can be made secure making client side unable to read the contents. This is important for authentication to store user tokens.

https://paulund.co.uk/local-storage-vs-session-storage-vs-cookie-storage

Variables

  • The maximum browser storage space is dynamic — it is based on your hard drive size. The global limit is calculated as 50% of free disk space.

SharedWorker

https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker

Do not create variables for font sizes.

They are all same sequence: 13pt, 14pt, 15pt, 16pt, 18pt, 21pt, 24pt Assume that 1rem = 10px or 10pt

:root {
    font-size: 100%;
}
h1 {
    font-size: 2.1rem;
}
p {
    font-size: 1.3rem;
}

No-follow, no-index, no-ML

<meta name="robots" content="noindex, nofollow, noml">
X-Robots-Tag: noindex, nofollow, noml