state.map 在调度的那一刻不是函数

时间:2021-04-05 17:37:02

标签: javascript reactjs react-hooks frontend

我有一个反应组件,它使用 useReducer 钩子来处理动态文本字段的添加和删除。 预期工作如下,

  1. type 'add' 会将当前状态追加一个空对象。 (添加按钮的onClick事件)
  2. type 'remove' 将从状态数组中删除对象。 (删除按钮的onClick事件)
  3. 使用状态变量上的 map 函数呈现字段。

但是当我尝试通过添加/删除进行分派时,state.map 函数将变为未定义状态。 任何形式的帮助都非常感谢

代码片段。

export default function Create(props) {
    const initialState = [{}];

    function reducer(state, action) {
        switch (action.type) {
            case 'add':
                state = {...state}
                console.log(state)
                return state;
            case 'remove':
                return state;
            default:
                throw new Error();
        }
    }

    const {dialogOpen, data} = {...props}
    const [open, setOpen] = React.useState(false);
    const [state, dispatch] = React.useReducer(reducer, initialState)


    return (
        <div>
                    {
                        state.map(() => {
                            return (
                                <div style={{display: "flex", alignItems: "center"}}>
                                    <ApplicationBudget/>
                                    <IconButton style={{marginLeft: 10}} onClick={() => dispatch({type: 'remove'})}
                                                aria-label="delete" margin={{margin: 2}}>
                                        <DeleteOutlined/>
                                    </IconButton>
                                    <IconButton aria-label="add" onClick={() => dispatch({type: 'add'})}
                                                margin={{margin: 1}}>
                                        <Add/>
                                    </IconButton>
                                </div>
                            )
                        })
                    }

        </div>
    );
}

2 个答案:

答案 0 :(得分:1)

你的初始状态是一个数组:

initialState = [{}]

所以 state.map 有效(因为 Array 的原型有一个 map 方法)。

但是,在您的减速器中,您返回一个对象:

state = {...state}

您的对象没有 map 方法。

这里有两个问题:

  1. 您不应修改 state 变量,而应保持不变并返回一个新状态。像这样工作的一种常见方法是将当前状态传播到返回状态。
  2. 您需要保持类型稳定,因此返回一个数组,而不是一个对象。

示例:

 function reducer(state, action) {
        switch (action.type) {
            case 'add':
                return [...state, "new entry"];
        }
    }

更新:我为您添加了一个最小示例。它有一个具有两个动作的减速器。一个和你有同样的错误行为,一个正常工作。

function App() {
  const reducer = (state, action) => {
    switch (action.type) {
      case "add-broken": {
        return {...state};
      }
      case "add-working": {
        return [...state, ...action.payload];
      }
      default: {
        throw new Error();
      }
    }
  };
  const [state, dispatch] = React.useReducer(reducer, ['a', 'b']);
  return (
    <div>
    <ul>
    {state.map(s => <li>{s}</li>)}
    </ul>
    <button onClick={() => dispatch({type: 'add-broken', payload: 'C'})}>Add (broken)</button>
    <button onClick={() => dispatch({type: 'add-working', payload: 'C'})}>Add (working)</button>
    </div>
  );
}
ReactDOM.render(<App />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="app"></div>

答案 1 :(得分:0)

感谢 fjc 的帮助。 最终得到以下解决方案

export default function Create(props) {
    const initialState = [{}];

    function reducer(state, action) {
        switch (action.type) {
            case 'add':
                return [...state, ...action.payload]
            case 'remove':
                let newState = [...state]
                newState.splice(action.payload, 1)
                return newState
            default:
                throw new Error();
        }
    }

    const {dialogOpen, data} = {...props}
    const [open, setOpen] = React.useState(false);
    const [state, dispatch] = React.useReducer(reducer, initialState)

    return (
        <div>
                    {

                        state.map((budget, index) => {
                            return (
                                <div key={index} style={{display: "flex", alignItems: "center"}}>
                                    <ApplicationBudget/>
                                    <IconButton style={{marginLeft: 10}}
                                                onClick={() => dispatch({type: 'remove', payload: index })}
                                                aria-label="delete" margin={{margin: 2}}>
                                        <DeleteOutlined/>
                                    </IconButton>
                                    <IconButton aria-label="add"
                                                onClick={() => dispatch({type: 'add', payload: [{}]})}
                                                margin={{margin: 1}}>
                                        <Add/>
                                    </IconButton>
                                </div>
                            )
                        })


                    }
        </div>
    );
}