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:
inputRef.current
properties) as well as returning new state
valuestart
and end
properties of an <input type="text">
element, e.g. a way to define the <input>
element such that its start
and end
values are controlled by he state
returned by a reducer?(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:
<input>
instance (from which it can read the current state of the <input>
)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>
.
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]);
}