Today I Learned

9 posts about #react

Styled-components pleasant class names

Styled-components generate unique hashes for each styled-component you create. They look like .nNUbag or .JbCpiZ. From a production perspective, these names are beneficial, since it reduces bundle size, but the development process is a nightmare.

Thankfully, there is a solution: babel-plugin-styled-components. It changes the class names to use the component name, which will help you in debugging process. The exact pattern is fileName__componentName-hash, eg. styles__StyledSearchBar-hXxaWb. Also, you don’t have to worry about production bundle size, cause it works only in development.

If you use create-react-app, the plugin is already set up. The only thing you have to do is update your imports.

import styled from 'styled-components/macro';

React Optimization of lists in big components

Big or often rerendering component which displays list of elements, can be optimized by extracting the list to separate component and using React.memo.

In example below it provided 40x speed improvement. YMMV of course.

In the examples I have provided different stages and mistakes this optimization. They also help understand why are useMemo and useCallback hooks useful.

react optimalization examples

Loading React & Redux dev tools in Cypress

Install webpack plugin: npm install --save-dev react-dev-tools-iframe-webpack-plugin

Update webpack config to enable the react dev tools iframe:

const ReactDevToolsIFramePlugin = require('react-dev-tools-iframe-webpack-plugin');

plugins: [
  // other plugins,
  new ReactDevToolsIFramePlugin(),
],

Update cypress plugins to load React & Redux dev tools for chrome. Add the paths to these extensions in a comma-separated string:

module.exports = (on, config) => {
  on('before:browser:launch', (browser = {}, args) => {
    if (browser.name === 'chrome') {
      args.push('--load-extension=/home/work/.config/google-chrome/Default/Extensions/lmhkpmbekcpmknklioeibfkpmmfibljd/2.17.0_0,/home/work/.config/google-chrome/Default/Extensions/fmkadmapgofadopljbjfkapdkoienihi/4.2.1_0');
      return args;
    }
    return null;
  });
};

Here’s a post where you can find out where the extensions are.

Use formik with context/hooks instead of render-props

Note that this feature is available in formik >= 2.0

Usually when you’re creating forms using formik you access formik props using render-prop pattern

export function MyForm() {
  return (
    <Formik
      initialValues={{ /* something here */ }}
      onSubmit={(values, actions) => {/* handler here */}}
    >
      {(formikProps) => (
        <form onSubmit={formikProps.handleSubmit}>
          <MyInputComponent value={formikProps.values.name} />
        </form>
      )}
    </Formik>
  )
}

Instead you can use formik as context provider (which it is under the hood actually) and use useFormikContext() hook or FormikConsumer component to consume provided form state.

Example below:

export function MyFormContainer() {
  return (
    <Formik
      {/* Under the hood Formik uses Context Provider */}
      initialValues={{ /* something here */}}
      onSubmit={(values, actions) => { /* handler here */ }}
    >
      {/* You do not have to use function here! */}
      {/* However you can, if you would like to :) */}
      <MyForm />
    </Formik>
  )
}

export function MyForm() {
  const { handleSubmit, values } = useFormikContext(); // formikProps

  return (
    <form onSubmit={handleSubmit}>
      <MyInputComponent value={values.name} />
    </form>
  );
}

This allows us to use formik props deep down in our nested components without drilling down formikProps.

Babel loader transpilation for Jest

Let’s imagine a following situation: we need to create a new npm package with create-react-app, in order to bundle some files in a package to have them available in a project (it’s possible to use create-component-lib for this task). The aforementioned sometimes requires from us to update our babel config.

In my latest task I had a situation where my babel config contained invalid (for a situation) presets:

module.exports =
{
  "presets": ["@babel/preset-env", ["react-app", { "absoluteRuntime": false }]]
}

In the above example, the build process creates a transpiled code, which contains babel runtime for minimalizing package size:

var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/defineProperty"));

var _objectSpread4 = _interopRequireDefault(require("@babel/runtime/helpers/esm/objectSpread"));

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/classCallCheck"));

var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/createClass"));

var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/possibleConstructorReturn"));

var _getPrototypeOf3 = _interopRequireDefault(require("@babel/runtime/helpers/esm/getPrototypeOf"));

var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/inherits"));

Now when we start our test which uses a transpiled component it’s possible we receive error code like this:

Jest encountered an unexpected token

    By default, if Jest sees a Babel config, it will use that to transform your files, ignoring "node_modules".

    Here's what you can do:
     • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
     • If you need a custom transformation to specify a "transform" option in your config.
     • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.

    You'll find more details and examples of these config options in the docs:
    https://jestjs.io/docs/en/configuration.html

    Details:

    /Users/mobile32/projects/formik-generator/node_modules/@babel/runtime/helpers/esm/defineProperty.js:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){export default function _defineProperty(obj, key, value) {
                                                                                             ^^^^^^

    SyntaxError: Unexpected token export

      10 | exports.default = void 0;
      11 |
    > 12 | var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/esm/defineProperty"));
         |                                               ^

      at ScriptTransformer._transformAndBuildScript (node_modules/jest-runtime/build/script_transformer.js:403:17)
      at Object.require (dist/FormGenerator.js:12:47)

This happens because by default jest ignores transformations for everything in node_modules directory.

Now we have two possibilities to resolve this problem. In the first scenario we can add transformIgnorePatterns for our jest config to transpile babel runtime module:

transformIgnorePatterns: [
  '/node_modules/(?!@babel\/runtime)'
],

In the above example transpiled code still will be smaller in application runtime but it doesn’t crash our tests.

The second option is using another babel preset for proper transpilation:

module.exports = {
  plugins: [
    '@babel/plugin-proposal-class-properties',
  ],
  presets: [
    '@babel/preset-env',
    '@babel/preset-react',
  ],
}

Now after build, we get transpiled code working in a test environment, however, our code will be bloated with helpers required to mimic transpiled features (for example class inheritance):

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

var _react = _interopRequireWildcard(require("react"));

var _propTypes = _interopRequireDefault(require("prop-types"));

var _formik = require("formik");

var _yup = require("yup");

var _classnames = _interopRequireDefault(require("classnames"));

var _moment = _interopRequireDefault(require("moment"));

var _BooleanField = _interopRequireDefault(require("./FieldTypes/BooleanField"));

var _EnumField = _interopRequireDefault(require("./FieldTypes/EnumField"));

var _MoneyField = _interopRequireDefault(require("./FieldTypes/MoneyField"));

var _TextField = _interopRequireDefault(require("./FieldTypes/TextField"));

var _QuarterDateField = _interopRequireDefault(require("./FieldTypes/QuarterDateField"));

Reducing main bundle size with React lazy

On one of our projects we were able to reduce main bundle size from ~1080kB to ~820kB only by lazy loading one library (recharts).

in router file

import React, {Component, lazy, Suspense} from 'react'
//other imports

const StatisticsComponent = lazy(() => import('./path/to/component'))

export class Root extends Component {
  
  render(){
    return(
      <Router history={history}> 
        <Switch>
          // other routes
          <Suspense fallback={<div>Loading...</div>}>
            <Route exact component={StatisticsComponent} path='/statistics' />
          </Suspense>
        </Switch>
      </Router > 
      )
  }
}

ReactDatePicker Day off in Summer time issue

If you’re using react-datepicker, and the last time you’ve tested your date-picker was in winter time or sooner, please check if your date-picker still works properly.

Issue:

<DatePicker
  dateFormat="DD/MM/YYYY"
  onChange={val => this.setValue(input, val)}
  selected={input.value ? moment(input.value) : null}
  />

Seems pretty basic, right?

Date displayed:

React-date-picker displayed value

Real value:

Real value of date-picker

Solution:

Add

utcOffset={0}

to props in your react-date-picker.

<DatePicker
  dateFormat="DD/MM/YYYY"
  onChange={val => this.setValue(input, val)}
  selected={input.value ? moment(input.value) : null}
  utcOffset={0}
  />

You can read more about this issue Here.