Проблемы с получением данных из API в React-redux и Redux-Thunk - PullRequest
0 голосов
/ 11 октября 2018

Я изо всех сил пытаюсь понять, как работает библиотекаact-redux и thunks.

Чего я хочу добиться, так это получить все сообщения с помощью некоторого API при доступе к странице.

Я делаю мой вызов API в функции componentDidMount().Из того, что я заметил, мой код исполняется ровно 3 раза, из которых последний получает сообщения.

Вот мой postReducer.js

import * as types from "../actions/actionTypes";
import initialState from "../reducers/initialState";

export function postsHaveError(state = false, action) {
  switch (action.type) {
    case types.LOAD_POSTS_ERROR:
      return action.hasError;

    default:
      return state;
  }
}

export function postsAreLoading(state = false, action) {
  switch (action.type) {
    case types.LOADING_POSTS:
      return action.isLoading;

    default:
      return state;
  }
}

export function posts(state = initialState.posts, action) {
  switch (action.type) {
    case types.LOAD_POSTS_SUCCESS:
      return action.posts;

    default:
      return state;
  }
}
// export default rootReducer;

postAction.js

import * as types from "./actionTypes";
import axios from "axios";

    export function postsHaveError(bool) {
      return {
        type: types.LOAD_POSTS_ERROR,
        hasError: bool
      };
    }

    export function postsAreLoading(bool) {
      return {
        type: types.LOADING_POSTS,
        isLoading: bool
      };
    }

    export function postsFetchDataSuccess(posts) {
      return {
        type: types.LOAD_POSTS_SUCCESS,
        posts
      };
    }

    export function postsFetchData(url) {
      return dispatch => {
        dispatch(postsAreLoading(true));

        axios
          .get(url)
          .then(response => {
            if (response.status !== 200) {
              throw Error(response.statusText);
            }

            dispatch(postsAreLoading(false));

            return response;
          })
          .then(response => dispatch(postsFetchDataSuccess(response.data)))
          .catch(() => dispatch(postsHaveError(true)));
      };
    }

и компонент, в котором я пытаюсь получить сообщения.

import React from "react";
import PostItem from "./PostItem";
import { connect } from "react-redux";
import { postsFetchData } from "../../actions/postActions";

class BlogPage extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      data: null
    };
  }

  componentDidMount() {
   this.props.fetchData("http://localhost:3010/api/posts");
  }

  render() {
    if (this.props.hasError) {
      return <p>Sorry! There was an error loading the items</p>;
    }

    if (this.props.isLoading) {
      return <p>Loading…</p>;
    }

    console.log(this.props);
    return (
      <div>
        <div className="grid-style">
          <PostItem <<once i have posts they should go here>> />
        </div>
      </div>
    );
  }
}

const mapStateToProps = state => {
  return {
    posts: state.posts,
    hasError: state.postsHaveError,
    isLoading: state.postsAreLoading
  };
};

const mapDispatchToProps = dispatch => {
  return {
    fetchData: url => dispatch(postsFetchData(url))
  };
};

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(BlogPage);

index.js

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import registerServiceWorker from "./registerServiceWorker";
import { BrowserRouter } from "react-router-dom";
import configureStore from "./store/configureStore";
import { Provider } from "react-redux";

const store = configureStore();

ReactDOM.render(
  <Provider store={store}>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </Provider>,
  document.getElementById("root")
);

registerServiceWorker();

app.js

import React, { Component } from "react";
import "./App.css";
import Header from "./components/common/header.js";
import Footer from "./components/common/footer.js";
import Main from "./components/common/main.js";
import "./layout.scss";

class App extends Component {
  render() {
    return (
      <div className="App">
        <Header />
        <Main />
        <Footer />
      </div>
    );
  }
}

export default App;

иmain.js, в котором находится BlogPage.

import React from 'react';
import BlogPage from '../blog/BlogPage';
import AboutPage from '../about/AboutPage';
import { Route, Switch } from 'react-router-dom';
import LoginPage from '../authentication/LoginPage';

const Main = () => {
  return (
    <div>
      <section id="one" className="wrapper style2">
        <div className="inner">
          <Switch>
            <Route path="/about" component={AboutPage} />
            <Route path="/login" component={LoginPage} />
            <Route path="/" component={BlogPage} />
          </Switch>
        </div>
      </section>
    </div>
  );
};

export default Main;

1 Ответ

0 голосов
/ 11 октября 2018

Ваша проблема очень похожа на этот вопрос (я также включил коды и коробку для игры).Пожалуйста, прочитайте его, следуйте рабочему примеру и, самое главное, прочитайте 7 советов (некоторые могут не относиться к вашему проекту; однако я настоятельно рекомендую установить prop-types, чтобы предупредить вас, когда вы отклоняетесь от 1: 1избыточное состояние).

Проблема, с которой вы столкнулись, связана с тем, что эта функция postsFetchData не возвращает обещание axios (у вас также есть ненужный .then(), который был удален - этот пример выполняется сприведенный ниже пример):

actions / blogActions.js

import * as types from '../types';

export const postsFetchData = () => dispatch => {
  // dispatch(postsAreLoading(true)); // <== not needed

  return axios
    .get("http://localhost:3010/api/posts") // API url should be declared here
    .then(({ data }) => { // es6 destructuring, data = response.data
       /* if (response.status !== 200) {
          throw Error(response.statusText);
        } */ // <== not needed, the .catch should catch this

       // dispatch(postsAreLoading(false)); // <== not needed
       // dispatch(postsFetchDataSuccess(response.data)) // <== not needed, just return type and payload

       dispatch({ type: types.LOAD_POSTS_SUCCESS, payload: data })
     })
    .catch(err => dispatch({ type: types.LOAD_POSTS_ERROR, payload: err.toString() }));
}

Как уже упоминалось в связанном вопросе, вам не нужен isLoading с Reduxсвязанные контейнеры-компоненты.Поскольку реквизиты поступают из магазина redux, React увидит изменение реквизита и соответствующим образом обновит подключенный компонент.Вместо этого вы можете использовать локальное состояние React ИЛИ просто проверить, присутствуют ли данные.

В приведенном ниже примере проверяется, присутствуют ли данные, в противном случае он загружается ...

BlogPage.js

import isEmpty from "lodash/isEmpty";
import React, { PureComponent } from "react";
import { connect } from "react-redux";
import PostItem from "./PostItem";
import { postsFetchData } from "../../actions/blogActions";

class BlogPage extends PureComponent {

  componentDidMount => () => this.props.postsFetchData(); // the API url should be placed in action creator, not here, especially if it's static 

  render = () => (
    this.props.hasError // if this was an error...
      ? <p>Sorry! There was an error loading the items: {this.props.hasError}</p> // then an show error
      : isEmpty(this.props.posts) // otherwise, use lodash's isEmpty to determine if the posts array exists AND has a length of 0, if it does...
         ? <p>Loading…</p> // then show loading...
         : <div className="grid-style"> // otherwise, if there's no error, and there are posts in the posts array...
             <PostItem posts={this.props.posts} /> // then show PostItem
           </div>
   )
}

export default connect(state => ({ 
  // this is just inline mapStateToProps
  posts: state.blog.posts 
  hasError: state.blog.hasError
}),
  { postsFetchData } // this is just an inline mapDispatchToProps
)(BlogPage);

redurs / index.js

import { combineReducers } from 'redux';
import * as types from '../types';

const initialState = {
  posts: [], // posts is declared as an array and should stay that way
  hasError: '' // hasError is declared as string and should stay that way
}

const blogPostsReducer = (state = initialState, { type, payload }) => {
  switch (type) {
    case types.LOAD_POSTS_SUCCESS:
      return { ...state, posts: payload, hasError: '' }; // spread out any state, then update posts with response data and clear hasError
    case types.LOAD_POSTS_ERROR:
      return { ...state, hasError: payload }; // spread out any state, and update hasError with the response error
    default:
      return state;
  }
}

export default combineReducers({
  blog: blogPostReducer
  // include any other reducers here
})

BlogPage.js isLoading локальное состояние реакции)

import isEqual from "lodash/isEqual";
import isEmpty from "lodash/isEmpty";
import React, { Component } from "react";
import { connect } from "react-redux";
import PostItem from "./PostItem";
import { postsFetchData } from "../../actions/blogActions";

class BlogPage extends Component {
  state = { isLoading: true };

  componentDidUpdate = (prevProps) => { // triggers when props have been updated
    const { posts } =  this.props; // current posts
    const prevPosts = prevProps.posts; // previous posts
    const { hasError } = this.props; // current error
    const prevError = prevProps.hasError // previous error

    if (!isEqual(posts,prevPosts) || hasError !== prevError) { // if the current posts array is not equal to the previous posts array or current error is not equal to previous error...
      this.setState({ isLoading: false }); // turn off loading
    }
  }

  componentDidMount => () => this.props.postsFetchData(); // fetch data

  render = () => (
    this.state.isLoading // if isLoading is true...
      ? <p>Loading…</p> // then show loading...          
      : this.props.hasError // otherwise, if there was an error...
          ? <p>Sorry! There was an error loading the items: {this.props.hasError}</p> // then an show error
          : <div className="grid-style"> // otherwise, if isLoading is false and there's no error, then show PostItem
              <PostItem posts={this.props.posts} />
            </div>
   )
}

export default connect(state => ({ 
  // this is just inline mapStateToProps
  posts: state.blog.posts 
  hasError: state.blog.hasError
}),
  { postsFetchData } // this is just an inline mapDispatchToProps
)(BlogPage);
...