2

I see myself using declarations like:

const [state, setState] = React.useState({value: ''});
    
return <InputComponent {...properties} value={state.value} onChange={(event) => setState({value: event.target.value || event.target.checked}

Whenever a component needs state I have to rewrite this down and it feels not reusable at all. I looked for render props and HOC, but couldn't reuse state.

I tried creating:

const StatefulInput = (InputComponent) => {

  const [state, setState] = React.useState({value: ''});

  return <InputComponent value={state.value} onChange={(event) => setState({value: event.target.value || event.target.checked />
}

But I couldn't reuse, I got 'invalid Hook', 'render received object not something else' errors and some other ones.

How can I do it? Is it possible to reuse this hook?

1

1 Answer 1

4

To avoid the boilerplate, you can write a custom hook that holds the state, returning a value and a handler that you can pass to the child component:

const InputComponent = ({ value, onChange }) => <input {...{value, onChange}} />;

const useInputValue = (initialValue) => {
  const [value, setValue] = React.useState(initialValue);
  return [
    value,
    event => setValue(event.target.value || event.target.checked)
  ];
};
const App = () => {
  const [inputValue, inputHandler] = useInputValue('');
    
  return <InputComponent
    value={inputValue}
    onChange={inputHandler}
  />
};
ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div class="react"></div>

Or, have the custom hook return an object that can be spread into the child component:

const InputComponent = ({ value, onChange }) => <input {...{value, onChange}} />;

const useInputValue = (initialValue) => {
  const [value, setValue] = React.useState(initialValue);
  return {
    value,
    onChange: event => setValue(event.target.value || event.target.checked)
  };
};
const App = () => {
  const inputProps = useInputValue('');
    
  return <InputComponent {...inputProps} />
};
ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div class="react"></div>

Also note that there's no need for the stateful value in useState to be an object - here, since all you need to store is a string (the current value), you can have the state be just that string, rather than wrapping the string in an object unnecessarily.

Sign up to request clarification or add additional context in comments.

2 Comments

Is there even a need or possibility of wrapping components in such case? Because I thought of doing so instead of just reusing the function. I want to know if I went the wrong way.
Do you mean the wrapping of <input inside <InputComponent? No, that's not needed, I just copied your original code and made up contents for InputComponent that can illustrate how things could work. If the InputComponent isn't really doing anything else, then definitely drop it and just use <input>.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.