reactjstypescriptreact-hookspure-function

Must a React reducer be a pure function?


I wrote a UI element as a function component which uses React's userReducer hook and it seems to run without errors.

useReducer references a function I wrote (called, imaginatively, reducer):

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

There's state data which is input and output by the reducer function; and there are "managed" UI elements which depend on state, something like ...

  return (
    <div>
      <div>
        {state.elements.map(getElement)}
      </div>
      <ShowHints hints={state.hints} inputValue={state.inputValue} />
    </div>
  );

... which is normal.

My concern is that the reducer function isn't pure.

The side-effect is that there is an <input> element whose state is controlled by one of these:

const inputRef = React.createRef<HTMLInputElement>();

The <input> control is only semi-managed, something like this:

<input type="text" ref={inputRef} onKeyDown={handleKeyDown} onChange={handleChange}

The onKeyDown and onChange events are actions dispatched to the reducer (which is good) but the reducer is passed the HTMLInputElement instance (i.e. the inputRef.current value) as an input parameter, and the reducer sets properties of that HTMLInputElement to mutate its state -- that is instead of the <input> being a fully-managed component whose content is defined by the state that's output from the reducer.

The reason why the <input> element isn't fully-managed is that I need to control the selection range (i.e. start and end) within the <input> and not only its text value.

Questions:

(I think @Fyodor's answer below answers the second question, I'm still not sure about the first question).


What dictates the values that are to be set on the HTML element? Does the use information passed in or does it contain the logic?

The component's design and source code are shown here, and quite long.

It's a complicated "component" which is implemented using several elements -- a couple of <div>s, several <span>s, some clickable <svg>s, and the <input> element.

The reducer is given, as its input parameters:

Two of the several event handlers or actions are the onKeyDown and onChange events of the <input> so the current state of the <input> is passed-in to the reducer when there's an event which changes the state of the <input>.


Solution

  • Technically reducer may work with different side effects in it. I don't think it is a good practice, at least due to bad separation of concerns (it is expected that reducer only produce new state based on action, but not mutate something else). (Sorry for presenting my own opinion, as it seems, that side effects in useReducer are used, like here). Also in Redux, all side effect are moved to action creators.

    For your specific question I may recommend to move inputRef mutation to separate useEffect hook, which in turn will depends on state like below

    useEffect (() => {
        inputRef.currect // do work with inputRef
    }, [state]); // make dependent from state
    

    Here is sample of making useEffect depended form state

    You can also move useEffect to custom hook to make code reusable like below (not tested, use as hint)

    function mutateRef (inputRef: React.RefObject<HTMLInputElement>, state: /* type of state */) {
        useEffect (() => {
            inputRef.currect // do work with inputRef
        }, [state, inputRef]);
    }