HollyPony
HollyPony

Reputation: 847

handle useReducer if pass from props

I'm trying to make an overridable usage of a useReducer in a Component for specific use cases.

Following my overfly simplified working state of work :

function defaultToggleReducer (state, action) {
  console.log('Default toggler used')
  return !state
}

function customTogglerReducer (state, action) {
  console.log('Custom toggler used')
  return !state
}

// Dummy wrap to log the initialization from `Togller` component to point the twice initializations
function toggleInitializer (state) {
  console.log('toggleInitializer')
  return state
}

function Toggler (props) {
  const [
    toggleState,
    toggleDispatch,
  ] = React.useReducer(defaultToggleReducer, false, toggleInitializer)
  
  // Here is the prt making the previous `useReducer` useless
  const state = props.toggleState !== undefined ? props.toggleState : toggleState
  const dispatch = props.toggleDispatch || toggleDispatch
  
  return (
    <button
      type="button"
      onClick={() => dispatch({ type: 'add' })}>
      {Boolean(state).toString()}
    </button>
  )
}

function App () {
  const [customToggleState, customToggleDispatch] = React.useReducer(
    customTogglerReducer,
    false
  )

  return (
    <div>
      <fieldset>
        <legend>Default</legend>
        <Toggler />
      </fieldset>
      <fieldset>
        <legend>Customized</legend>
        <Toggler
          toggleState={customToggleState}
          toggleDispatch={customToggleDispatch} />
        <button
          type="button"
          onClick={() => customToggleDispatch({ type: 'parentAction' })}>
          This is why I want dispatch
        </button>
      </fieldset>
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))
<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 id="root" />

Here, there is something smell bad, I have no doubts about this is working but the fact where useReducer is called in Toggler even if not used let me suspicious. So, it's a dead reducer I think.

So my question is:

Did you have a way to achieve a reduce control from parent component properly (at least: better than this) ?

Upvotes: 1

Views: 1253

Answers (1)

Ori Drori
Ori Drori

Reputation: 191976

Use components' composition to solve this problem. Break Toggler to two components - dumb Toggler that expects an outside reducer, and DefaultToggler that renders Toggler, and supplies the reducer.

If you need a standard toggler, use DefaultToggler, and if you need a custom version use the dumb Toggler, and supply a custom reducer.

function defaultToggleReducer (state, action) {
  console.log('Default toggler used')
  return !state
}

function customTogglerReducer (state, action) {
  console.log('Custom toggler used')
  return !state
}

// Dummy wrap to log the initialization from `Togller` component to point the twice initializations
function toggleInitializer (state) {
  console.log('toggleInitializer')
  return state
}

function Toggler ({ toggleState: state, toggleDispatch: dispatch }) {  
  return (
    <button
      type="button"
      onClick={() => dispatch({ type: 'add' })}>
      {Boolean(state).toString()}
    </button>
  )
}

function DefaultToggler () {
  const [
    toggleState,
    toggleDispatch,
  ] = React.useReducer(defaultToggleReducer, false, toggleInitializer)

  return (
    <Toggler 
      toggleState={toggleState} 
      toggleDispatch={toggleDispatch} 
      />
  );
};

function App () {
  const [customToggleState, customToggleDispatch] = React.useReducer(
    customTogglerReducer,
    false
  )

  return (
    <div>
      <fieldset>
        <legend>Default</legend>
        <DefaultToggler />
      </fieldset>
      <fieldset>
        <legend>Customized</legend>
        <Toggler
          toggleState={customToggleState}
          toggleDispatch={customToggleDispatch} />
        <button
          type="button"
          onClick={() => customToggleDispatch({ type: 'parentAction' })}>
          This is why I want dispatch
        </button>
      </fieldset>
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))
<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 id="root" />

Upvotes: 1

Related Questions