Reputation: 557
I have a react component which uses useReducer hook to handle dynamic Text field addition and removal. expected working is as follows,
but when I try to dispatch with add/remove the state.map function will goes undefined. any sort of helps are really appreciated
code snippet.
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>
);
}
Upvotes: 0
Views: 205
Reputation: 557
Thanks fjc for your help. end up with below solution
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>
);
}
Upvotes: 0
Reputation: 5805
Your initial state is an array:
initialState = [{}]
So state.map
works (as the prototype of Array
has a map
method).
However, in your reducer, you return an object:
state = {...state}
Your object has no map
method.
There are two issues here:
state
variable, but leave it untouched and return a new state instead. A common way to work like this is to spread the current state into the returned state.Example:
function reducer(state, action) {
switch (action.type) {
case 'add':
return [...state, "new entry"];
}
}
Update: I added a minimal example for you. It has a reducer with two actions. One with the same faulty behavior as yours, one that's working.
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>
Upvotes: 1