admin管理员组

文章数量:1327969

I've implemented a fairly simple means to add undo to useReducer with the following:

export const stateMiddleware = reducerFunc => {
  return function(state = { history: [] }, action) {
    return {
      history:
        action.type === UNDO
          ? drop(state.history) // lodash drop
          : [reducerFunc(state.history[0], action), ...state.history],
    }
  }
}

and

const [state, dispatch] = useReducer(stateMiddleware(reducer), { history: [initialState] })

The state variable consists of the entire history so present state will have to be extracted from the first element.

I'm wondering whether useReducer will accept an array for the state parameter (and also wele to ments on my approach - it avoids using what seem to be hefty packages to do something similar)

I've implemented a fairly simple means to add undo to useReducer with the following:

export const stateMiddleware = reducerFunc => {
  return function(state = { history: [] }, action) {
    return {
      history:
        action.type === UNDO
          ? drop(state.history) // lodash drop
          : [reducerFunc(state.history[0], action), ...state.history],
    }
  }
}

and

const [state, dispatch] = useReducer(stateMiddleware(reducer), { history: [initialState] })

The state variable consists of the entire history so present state will have to be extracted from the first element.

I'm wondering whether useReducer will accept an array for the state parameter (and also wele to ments on my approach - it avoids using what seem to be hefty packages to do something similar)

Share edited Oct 10, 2019 at 8:59 timbo asked Jul 30, 2019 at 21:47 timbotimbo 14.4k9 gold badges53 silver badges75 bronze badges
Add a ment  | 

3 Answers 3

Reset to default 3

If all you are really asking is if you can initialize useReducer hook state with a plain array, then yes, you can.

Example:

const reducer = (state, action) => {
  switch (action.type) {
    ....
  }
};

function App() {
  const [state, dispatch] = useReducer(
    reducer,
    [] // <-- initial array value
  );

  return (
    <div className="App">
      <div>State: {JSON.stringify(state)}</div>
      <button
        onClick={() =>
          dispatch({ type: "add", data: Math.floor(Math.random() * 10) })
        }
      >
        +
      </button>
      <button onClick={() => dispatch({ type: "remove" })}>-</button>
    </div>
  );
}

To give you some feedback on your general approach, I'll quote the Redux docs, as they provide many best practices about global state management. It makes switching easier, if you need a full blown store later on. I'll assume here that you want use the useReducer not just for one local ponent, but a more global state tree (thanks @DrewReese for the ment on this). If it's only about one ponent or so, your approach is perfectly fine!

Redux docs concerning basic state shape and wether an array is possible at the top level (Link):

A Redux state usually has a plain Javascript object as the top of the state tree. (It is certainly possible to have another type of data instead, such as a single number, an array, or a specialized data structure, but most libraries assume that the top-level value is a plain object.) The most mon way to organize data within that top-level object is to further divide data into sub-trees, where each top-level key represents some "domain" or "slice" of related data.

So basically, its OK, but other libraries could be possibly opiniated and expect an object, as it is by far the most likely choice.

If you implement this shape, be sure that your requirement is to always need an undo action for your whole state. Because it es with a cost (besides mentioned 3rd party lib patibility):

  • adds one additional level of nesting directly at the top level
  • scalability and granularity (see next point)

What, if your app gets really huge and you have hundreds of reducers. Each time, your action is dispatched, there will be a new state object put in the history array, which could get messy. What, if you don't want a global undo, but a very fine granular one (in one particular reducer, say for one particular form element in the UI). You would have to distinguish between a global undo and a specific undo then. What instead about just one "UNDO" action which can be handled in each interested reducer in its own manner?

If that still fits your requirements, give it a try !

Cheers

It is possible, but as ford04 pointed out it may not be a great idea for a variety of reasons.

The demo below shows it works to use a plain array as the state in useReducer.

const initialState = [];

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

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

ReactDOM.render(<Counter />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare./ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>

<div id="app"></div>

本文标签: javascriptCan useReducer work with an array for stateStack Overflow