2

This is the example from: https://reactjs.org/docs/hooks-reference.html#usereducer

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

In this example, the reference for the reducer function stays the same across renders.

Can I do this to recreate the reducer function on every render?

const initialState = {count: 0};

function Counter() {

  // REDUCER FUNCTION WILL BE RECREATED ON EVERY RENDER

  function reducer(state, action) {
    switch (action.type) {
      case 'increment':
        return {count: state.count + 1};
      case 'decrement':
        return {count: state.count - 1};
      default:
        throw new Error();
    }
  }

  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

MOTIVE:

I'm working on a currency converter, and my reducer depends on the THOUSAND_SEPARATOR and DECIMAL_SEPARATOR being dots . or commas ,, which might change between renders, so I need to recreate it on every render.

SNIPPET

It seems to work, but is it an anti-pattern?

function App() {

  const initialState = {count: 0};

  const [bool,setBool] = React.useState(false);

  function reducer(state, action) {
    switch (action.type) {
      case 'increment':
        return {count: bool ? state.count - 1 : state.count + 1};
      case 'decrement':
        return {count: bool ? state.count + 1 : state.count - 1};
      default:
        throw new Error();
    }
  }

  const [state,dispatch] = React.useReducer(reducer, initialState);

  return (
    <React.Fragment>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => setBool((prevState) => !prevState)}>Invert Direction</button>
    </React.Fragment>
  );
}

ReactDOM.render(<App/>, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>
cbdeveloper
  • 27,898
  • 37
  • 155
  • 336
  • 1
    Why do you want to implement thousands/decimals formatting on reducers? It's more like presentation layer and a helper function will do just fine. Something like `currencyFormatter(value)`. – Rajender Joshi May 15 '20 at 16:02
  • The information about the separators are inside my `` component. My `reducer` is in an external file. My reducer will use the information about the separator, 'cause it will format strings based on that. I have the `currencyFormatter` function. But it changes with the separators information that comes from the render of the ``. – cbdeveloper May 15 '20 at 16:07

2 Answers2

1

That approach is fine.

As I understand it, JavaScript engines are pretty good at reusing the function bodies even if function objects are created multiple times. The main difference is that the function defined inside of the render function will be referencing an additional closure (variables declared in the render function), so it'll be a bit less efficient. But since you need it to reference your component's state, it's a reasonable solution.

Jacob
  • 77,566
  • 24
  • 149
  • 228
0

A bit late to the party here.

The idea of having the reducer function inside the component will lose some of the efficiency you would get. At every render the function will be recreated. Probably not that impacting (unless it is a huge function). But sure, wasting resources.

For alternatives: it is a bit hard to understand what type of polymorphism your hook will include on its logic.

But here are a couple of them:

Is the reducer simply just taking values from the parent component and converting the given input?

  • Declare it as a simple helper function that receives the data to be converted and the separators data, and call the data to be converted with it. If the reducer has a single action, or does a lot of work on the initialization and not through actions dispatch, you probably don't need it.

No, Caue, I will actually have more complex logic / actions that I didn't share on my post!

  • In that case, you can either: dispatch an action that will receive the separators as the payload (e.g. SET_SEPARATORS). the action handler will use the separators and reformat the returned values to be consumed by the component.

  • Finally, if you have props / state in the parent component that are being used to store what separators to use, you can create a custom Hook (instead of a reducer), and send the props / values to the hook initialization e.g.

const formatted = useCurrencyFormatter(decimalSeparator, thousandSeparator)

That way, on prop change, the hook will re-render with the new values. If needed you can use a reducer inside it (but based on your short description in the question I don't think you will need it).

Feel free to reach out if you have more detailed examples of your use case.