【问题标题】:Getting error state.stories is not iterable获取错误 state.stories 是不可迭代的
【发布时间】: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.storiesundefinedundefined 不可迭代。
  • @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


【解决方案1】:

您为您的loginReducer 和您的storyReducer 使用相同的initialState - 后者确实无法使用它,因为它没有stories 属性,因此您试图传播undefined .

使用两种不同的初始状态。那些也不必有那个名字,你可以随意命名它们;)

【讨论】:

  • 我已将其更改为 storyReducer = (state = {stories: []}, action) 现在我可以将第一个故事添加到 stories 数组中,当我尝试添加另一个时,我得到了同样的错误.
  • 好的,现在我还不能添加任何东西,得到相同的数组,故事数组是空的
  • 哦,你的CREATE_STORY减速器也应该有stories: [...state.stories, action.payload],而不是stories: [...state, action.payload],
  • 另外,我建议你看看官方的 redux 教程和现代的 redux - 你在这里写的是一种非常古老的 redux 风格,我们不再推荐了。现代 redux 可能不会发生所有这些事情,所以看看 ;) redux.js.org/tutorials/fundamentals/part-8-modern-redux redux.js.org/tutorials/index
猜你喜欢
  • 2019-01-10
  • 2020-10-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-21
  • 1970-01-01
  • 2023-03-06
相关资源
最近更新 更多