title: Best practice to create reusable and decoupled React components in 18n/l10n apps with custom formatting
body: <p>I have the following React component which I would like to reuse in several applications:</p>
  
  <pre><code>import React from "react";
  import { useFactory } from "react-js-utl/hooks";
  import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
  
  /**
   * Shows current account balance with variation since previous balance.
   */
  const Balance = compose(React.memo)(function Balance({
      current,
      previous,
      label,
      sinceLabel,
      increaseColor = "light-green",
      increaseIcon = "arrow-up",
      decreaseColor = "red",
      decreaseIcon = "arrow-down",
      sameColor = "blue",
      sameIcon = "equals",
      formatNum = void 0,
      formatPerc = void 0
  } = {}) {
      // Compute the change.
      let change = ((current - previous) / previous) * 100;
  
      // Implementation detail. Switch objects depeding on runtime value of change.
      const outcomeFactory = useFactory(
          () =&gt; [
              // Increase:
              [
                  change &gt; 0, // Condition, if true, the object following it will be returned by `useFactory()`.
                  {
                      outcome: "increase",
                      color: increaseColor,
                      icon: increaseIcon
                  }
              ],
              // Decrease:
              [
                  change &lt; 0,
                  {
                      outcome: "decrease",
                      color: decreaseColor,
                      icon: decreaseIcon
                  }
              ],
              // Same (default, returned if the previous conditions evaluate to false):
              {
                  outcome: "same",
                  color: sameColor,
                  icon: sameIcon
              }
          ],
          // deps array
          [
              change,
              increaseColor,
              increaseIcon,
              decreaseColor,
              decreaseIcon,
              sameColor,
              sameIcon
          ]
      );
  
      change = Math.abs(change);
  
      return (
          &lt;div className="balance"&gt;
              &lt;div className="balance-label"&gt;{label}&lt;/div&gt;
              &lt;div className="balance-current"&gt;
                  {formatNum ? formatNum(current) : current}
              &lt;/div&gt;
              &lt;span
                  className={`balance-outcome balance-outcome-${outcomeFactory.outcome} balance-outcome-${outcomeFactory.color}`}
              &gt;
                  {typeof outcomeFactory.icon === "string" ? (
                      // Defaults to FontAwesome's icon.
                      &lt;FontAwesomeIcon icon={outcomeFactory.icon} /&gt;
                  ) : (
                      // Custom component rendered by client code.
                      outcomeFactory.icon
                  )}
                  {outcomeFactory.outcome === "same"
                      ? // Same (no change since previous):
                        ""
                      : // Increase or decrease since previous:
                      // Edge case: previous balance was zero
                      Number(previous) === 0
                      ? ""
                      : // Percentage increase/decrease:
                      formatPerc
                      ? formatPerc(change)
                      : change}
              &lt;/span&gt;
              &lt;span className="balance-since-label"&gt;{sinceLabel}&lt;/span&gt;
          &lt;/div&gt;
      );
  });
  Balance.displayName = "Balance";
  export default Balance;
  
  </code></pre>
  
  <p>Which I use in an app like this:</p>
  
  <pre><code>...
  
  function myFormattingFunctionForNumbers(t, num) {
     // I use accounting.js to format numbers:
     return accounting.format(num, {
         decimal: t("formatting:numbers.decimal_separator"), // For EN lang this will be "."
         thousand: t("formatting:numbers.thousand_separator") // For EN this this will be ","
     })
  }
  
  function myFormattingFunctionForPercentages(t, perc) {
     return myFormattingFunctionForNumbers(t, perc) + "%";
  }
  
  ...
  
  // Inside app component:
  // Using react-i18next:
  const { t, i18n } = useTranslation();
  
  // If the language changes, so does formatting:
  formatNum = useCallback(current =&gt; myFormattingFunctionForNumbers(t, current), [t, i18n.language])
  formatPerc = useCallback(changePerc =&gt; myFormattingFunctionForPercentages(t, changePerc), [t, i18n.language])
  
  ...
  
  &lt;Balance
      current={11234.56} // These hardcoded values could be props, of course.
      previous={9321.45}
      label={t("Your account balance")} // i18n
      sinceLabel={t("Since previous month")} // i18n
      formatNum={formatNum} // Function to format current value.
      formatPerc={formatPerc} // Function to format change/variation percentage.
  /&gt;
  
  ...
  </code></pre>
  
  <p>Which outputs something looking roughly like this:</p>
  
  <pre><code>Your account balance
  11,234.56
  
  ↑ 20,52% Since previous month
  </code></pre>
  
  <p>Right now I am facing the following "issues":</p>
  
  <ol>
  <li><p>The reusable component is pure (uses <code>React.memo</code>), so its formatting functions <code>formatNum</code> and <code>formatPerc</code> need to change when the app's language changes even if the other props like <code>current</code> and <code>previous</code> don't change because a different language potentially involves different formatting and therefore the component should rerender;</p></li>
  <li><p>Because of point 1, the client is responsible of wiring all the formatting functions inside the consuming component using <code>useCallback</code> which creates lot of biolerplate...;</p></li>
  <li><p>The two <code>useCallback</code>s do not warn me that I need to pass <code>i18n.language</code> to the <code>deps</code> array, simply because the current language is not referenced directly by the formatting functions <code>myFormattingFunctionForNumbers</code> and <code>myFormattingFunctionForPercentages</code>, which only use the <code>t</code> function of <code>i18next</code> (which as far as I know does not change when the language changes);</p></li>
  <li><p>Maybe there is a point 4 and even a point 5, of which I am not aware of for now.</p></li>
  </ol>
  
  <p>What is the current best practice for reusable React components which support formatting and i18n/l10n?</p>
  
  <p>Tips and tricks on how to organize formatting/i18n/l10n code and separate these concerns from reusable components will be appreciated.</p>
  
  <p>Thank you for the attention.</p>