Source: app/enhanced-redux-form/components/FormRouteSync/FormRouteSync.js

import { useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import debugLib from 'debug';

const debug = debugLib('SlimmingWorld:FormRouteSync');

const usePrevious = value => {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
};

/**
 * Utility component that syncs the form values of the form it is placed in
 * to the route query string. When the fields given to the `fields` prop change value,
 * the component calls the `syncValuesToRoute` action, which will update the route
 * to match the values.
 *
 * Note: this util works one-way. It will not sync updates in the query back to the form
 * values.
 */
const FormRouteSync = ({
  values,
  syncValuesToRoute,
  transformValues,
  fields,
  pathname,
  validateValues,
}) => {
  const prevValues = usePrevious(values);
  const prevpathname = usePrevious(pathname);
  useEffect(() => {
    if (values && prevValues && fields.some(key => values[key] !== prevValues[key])) {
      if (pathname !== prevpathname) {
        // prevent the route from updating while we are navigating to another page
        debug('Route pathname changed since mount. Ignoring form updates');
        return;
      }
      debug('Detected form value change. Updating route...');
      if (transformValues) {
        syncValuesToRoute(transformValues(values), fields);
      } else if (validateValues) {
        const filteredValues = Object.keys(values).reduce((filtered, key) => {
          const value = values[key];

          if (value > 0 || (typeof value === 'string' && value.length > 0)) {
            // eslint-disable-next-line no-param-reassign
            filtered[key] = value;
          }
          return filtered;
        }, {});
        syncValuesToRoute(filteredValues, fields);
      } else {
        syncValuesToRoute(values, fields);
      }
    }
  });
  return null;
};

FormRouteSync.propTypes = {
  /**
   * The names of the fields to sync to the query string
   */
  fields: PropTypes.arrayOf(PropTypes.string).isRequired,
  /**
   * The current form values of the fields. Provided by the `connect()` redux wrapper
   */
  values: PropTypes.objectOf(PropTypes.any),
  /**
   * The current route pathname. Used to block FormRouteSync if the pathname changes
   */
  pathname: PropTypes.string,
  /**
   * An optional function to transform the values object before syncing it to the route.
   * Should accept an object with values and return a new object with modifications.
   */
  transformValues: PropTypes.func,
  /**
   * The action to sync values to route. Provided by the `connect()` redux wrapper
   */
  syncValuesToRoute: PropTypes.func.isRequired,
};

export default FormRouteSync;