【发布时间】:2021-07-15 09:16:29
【问题描述】:
我正在创建一个 react/redux 应用程序,用户可以在其中登录并能够通过提交表单来添加故事。故事作为对象提交,我想将该故事添加到数组对象中。但是我不断收到错误消息 state.stories is not iterable ,即使我在减速器中写 stories: [...state.stories, action.payload] 也是如此。 action.payload 返回一个对象,我想将该对象放入一个数组中。
如何将 action.payload 对象插入到数组中?我希望故事是一组对象。
对于任何错误的格式,我深表歉意。
App.js
import './App.scss';
import Login from './components/Login';
import { Router, Switch, Route, NavLink } from 'react-router-dom';
import PrivateRoute from './utils/PrivateRoute';
import CreateStory from './components/CreateStory';
import history from './utils/history';
function App() {
return (
<div className="App">
<Router history={history}>
<Switch>
<Route exact path="/" component={Login} />
<PrivateRoute path="/user" component={CreateStory}/>
</Switch>
</Router>
</div>
);
}
export default App;
PrivateRoute.js
import { useSelector } from 'react-redux'
// handle the private routes
function PrivateRoute({ component: Component, ...rest }) {
const getToken = useSelector((state)=> state.loginReducer.token)
console.log(getToken)
return (
<Route
{...rest}
render={(props) => getToken ? <Component {...props} /> : <Redirect to={{ pathname: '/', state: { from: props.location } }} />}
/>
)
}
export default PrivateRoute;
CreateStory.js
import React, { useState } from 'react'
import { createStory } from '../redux/actions'
import { useDispatch, useSelector } from "react-redux";
import history from '../utils/history';
import { withRouter } from 'react-router-dom';
const CreateStory = () => {
const [summary, setSummary] = useState("");
const [description, setDescription] = useState("");
const [type, setType] = useState("");
const [complexity, setcomplexity] = useState("");
const [time, setTime] = useState("");
const [cost, setCost] = useState(0);
const usedispatch = useDispatch();
const userCreateStory = (summary, description, type, complexity) => usedispatch(createStory({
'summary': summary,
'description': description,
'type': type,
'complexity': complexity,
'time': time,
'cost': cost
}));
const handleSummaryChange = e => {
setSummary(e.target.value)
}
const handleDescriptionChange = e => {
setDescription(e.target.value)
}
const handleTypeChange = e => {
setType(e.target.value)
}
const handleComplexityChange = e => {
setcomplexity(e.target.value)
}
const handleTimeChange = e => {
setTime(e.target.value)
}
const handleCostChange = e => {
setCost(e.target.value)
}
// const currStory = useSelector((state)=> state.storyReducer.story)
const handleSubmit = e => {
e.preventDefault();
userCreateStory(summary,description,type,complexity,time,cost)
setTimeout(()=> history.push("/userStories"), 1000 );
//setTimeout(()=> console.log(currStory) ,1000)
}
return (
<div>
<form className='create-story-form'>
<label for="summary">Summary:</label>
<input name="summary" type='text' onChange={handleSummaryChange}/>
<label for="desc">Description:</label>
<textarea name="desc" type='text' onChange={handleDescriptionChange}/>
<label for="type">Type:</label>
<select name="type" onChange={handleTypeChange}>
<option value="enhancement" defaultValue>Enchancement</option>
<option value="bugfix">Bugfix</option>
<option value="development">Development</option>
<option value="qa">QA</option>
</select>
<label for="complexity">Complexity:</label>
<select name="complexity" onChange={handleComplexityChange}>
<option value="low" defaultValue>Low</option>
<option value="mid">Mid</option>
<option value="high">High</option>
</select>
<label for="time">Estimated time for completion:</label>
<input name="time" type='text' onChange={handleTimeChange}/>
<label for="cost">Cost:</label>
<input name="cost" type='number' onChange={handleCostChange}/>
<button onClick={handleSubmit}>Submit</button>
</form>
</div>
)
}
export default withRouter(CreateStory);
登录.js
import React, { useState } from "react";
import { useDispatch } from "react-redux";
import { login, roleChange } from '../redux/actions' //OUR ACTIONS
import { useSelector } from 'react-redux'
import history from '../utils/history';
import { withRouter } from 'react-router-dom';
const Login = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const usedispatch = useDispatch();
const userLogin = (email, password) => usedispatch(login({'email': email, 'password': password }));
const switchToAdmin = () => usedispatch(roleChange('admin'));
const switchToUser = () => usedispatch(roleChange('user'));
const currentRole = useSelector((state)=> state.loginReducer.role)
const handleRoleChange = e => {
e.preventDefault();
if(currentRole === 'user')
switchToAdmin();
else if(currentRole === 'admin' )
switchToUser()
}
const handleEmailChange = e => {
setEmail(e.target.value)
}
const handlePasswordChange = e => {
setPassword(e.target.value)
}
const handleSubmit = e => {
e.preventDefault();
userLogin(email, password)
setTimeout(()=> history.push("/user"), 1000 );
}
const disabled = () => {
return email === "" || password === ""
}
return (
<div>
<form className='login-form'>
<input type='email' name='email' placeholder='Email' onChange={handleEmailChange}/>
<input type='password' name='password' placeholder='Password' onChange={handlePasswordChange}/>
<button type='submit' disabled={disabled()} onClick={handleSubmit}>Login</button>
</form>
<button onClick={handleRoleChange}>Switch to {currentRole === 'user' ? 'admin' : 'user'}</button>
</div>
)
}
export default withRouter(Login);
actionTypes.js
export const SET_LOGIN_STATE = "SET_LOGIN_STATE"
export const SET_ROLE_STATE = "SET_ROLE_STATE"
export const CREATE_STORY = "CREATE_STORY"
initialState.js:
import { getToken } from '../utils/Common'
export const initialState = {
isLoggedIn: false,
userId: '',
role: 'user',
token: getToken,
data: '',
};
reducers.js
import { initialState } from './initialState';
import * as t from './actionTypes';
export const loginReducer = (state = initialState, action) => {
switch (action.type) {
case t.SET_ROLE_STATE:
return {
...state,
role: action.payload,
};
case t.SET_LOGIN_STATE:
return {
...state,
...action.payload, // this is what we expect to get back from API call and login page input
isLoggedIn: true, // we set this as true on login
};
default:
return state;
}
};
export const storyReducer = (state = initialState, action) => {
switch (action.type) {
case t.CREATE_STORY:
return {
...state,
story: [...state.stories, action.payload], //the error pops up at this line
};
default:
return state;
}
}
actions.js:
import * as t from './actionTypes';
import { setUserSession } from '../utils/Common';
// this is what our action should look like which dispatches the "payload" to reducer
const setLoginState = (loginData) => {
return {
type: t.SET_LOGIN_STATE,
payload: loginData, //{ ...json, userId: email }
};
};
const setStoryState = (storyData) => {
return {
type: t.CREATE_STORY,
payload: storyData,
};
};
export const login = (loginInput) => { //our login action
const { email, password } = loginInput;
return (dispatch) => { // don't forget to use dispatch here!
return fetch('http://localhost:3000/api/v1/signin', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(loginInput),
})
.then((response) => response.json()) //json will be the response body
.then((json) => {
// if (json.msg === 'success') { // response success checking logic could differ
// console.log(json)
dispatch(setLoginState({ ...json, userId: email })); // our action is called here with object as parameter, this is our payload
//we appended json object to our state
// } else {
// alert('Login Failed', 'Email or Password is incorrect');
// }
setUserSession(json.token, json.lastName)
})
.catch((err) => {
alert('Login Failed', 'Some error occured, please retry');
console.log(err);
});
};
};
export const roleChange = role => {
return {
type: t.SET_ROLE_STATE,
payload: role
};
}
export const createStory = storyInput => {
const { summary, description, type, complexity, time, cost } = storyInput;
return (dispatch) => { // don't forget to use dispatch here!
return fetch('http://localhost:3000/api/v1/stories', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify(storyInput),
})
.then((response) => response.json()) //json will be the response body
.then((json) => {
// if (json.msg === 'success') { // response success checking logic could differ
console.log(json)
dispatch(setStoryState({ // our action is called here with object as parameter, this is our payload
summary: summary,
description: description,
type: type,
complexity: complexity,
time: time,
cost: cost
})); // our action is called here
// } else {
// alert('Login Failed', 'Email or Password is incorrect');
// }
})
.catch((err) => {
alert('Some error occured, please retry');
console.log(err);
});
};
}
Common.js
// return the user data from the session storage
export const getUser = () => {
const userStr = sessionStorage.getItem('user');
if (userStr) return JSON.parse(userStr);
else return null;
}
// return the token from the session storage
export const getToken = () => {
return sessionStorage.getItem('token') || null;
}
// remove the token and user from the session storage
export const removeUserSession = () => {
sessionStorage.removeItem('token');
sessionStorage.removeItem('user');
}
// set the token and user from the session storage
export const setUserSession = (token, user) => {
sessionStorage.setItem('token', token);
sessionStorage.setItem('user', JSON.stringify(user));
}
更新: 我已将 storyReducer 更改为:
export const storyReducer = (state = {stories: []}, action) => {
switch (action.type) {
case t.CREATE_STORY:
return {
...state,
stories: [...state, action.payload],
};
// case t.ADD_STORY:
// return {
// ...state,
// stories: [...state.stories, action.payload], //stories is an object
// };
default:
return state;
}
}
现在添加第一个故事时,我没有收到错误消息。但是在向数组中添加超过 1 个商店时出现错误。
【问题讨论】:
-
您的
initialState不包含stories属性,因此initialState.stories是undefined和undefined不可迭代。 -
@secan 我试过没有运气:( 仍然收到相同的错误消息
-
您也有错字,请检查您所说的显示错误的行。你有
story: [...state.stories, action.payload],而不是stories: [...state.stories]。还请确保在您的初始状态中为stories数组设置一个默认值,如提到的@secan。 -
尝试检查developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… 和
state.stories的存在,并添加else 块进行调试。这应该给你它不可迭代的地方。
标签: javascript arrays reactjs redux state