【问题标题】:Re-rendering react component after clicking Like button (with Redux)单击 Like 按钮后重新渲染反应组件(使用 Redux)
【发布时间】:2019-05-22 08:48:28
【问题描述】:

我有以下 React 组件,它通过“renderPosts”方法显示所有用户的帖子。在它下方有一个喜欢/不喜欢按钮,用于显示当前登录的用户是否喜欢该帖子。

但是,当我单击like 按钮时,组件不会重新渲染,以便“renderPosts”方法创建一个不同的按钮,并且“like 字符串”按预期进行修改。只有当我转到另一个组件然后返回到该组件时,才会显示不同的按钮,反之亦然。

无论如何,我可以在我的应用程序中使用 Redux 解决这个问题吗?我在 onClick 事件之后尝试了 this.forceUpdate 但仍然不起作用...

我还尝试创建一个名为“likers”的新 Reducer,根据 robinsax,它基本上可以获取喜欢特定帖子并将其作为道具导入组件但得到的用户数组

"this.props.likers.includes(currentUser)" is not a function

app第一次到主页面(PostIndex)的时候,可能是因为this.props.likers还是reducer返回的一个空对象

这是我的动作创建者的代码:

export function likePost(username,postId) {
    // body...
    const request = {
        username,
        postId
    }
    const post = axios.post(`${ROOT_URL}/likePost`,request);
    return{
        type: LIKE_POST,
        payload: post
    }
}
export function unlikePost(username,postId){
    const request = {
        username,
        postId
    }
    const post = axios.post(`${ROOT_URL}/unlikePost`,request);
    return{
        type: UNLIKE_POST,
        payload: post
    }
}

这是我的减速器:

import {LIKE_POST,UNLIKE_POST} from '../actions/index.js';

export default function(state = {},action){
    switch(action.type){
        case LIKE_POST:
            const likers = action.payload.data.likedBy;
            console.log(likers);
            return likers;
        case UNLIKE_POST:
            const unlikers = action.payload.data.likedBy;
            console.log(unlikers);
            return unlikers;
        default:
            return state;
    }
}

由于我是初学者,我非常感谢任何帮助

import { fetchPosts } from "../actions/";
import { likePost } from "../actions/";
import { unlikePost } from "../actions/";
class PostsIndex extends Component {
    componentDidMount() {
        this.props.fetchPosts();
    }
    renderPost() {
        const currentUser = Object.values(this.props.users)[0].username;
        return _.map(this.props.posts, post => {
            return (
                <li className="list-group-item">
                    <Link to={`/user/${post.username}`}>
                        Poster: {post.username}
                    </Link>
                    <br />
                    Created At: {post.createdAt}, near {post.location}
                    <br />
                    <Link to={`/posts/${post._id}`}>{post.title}</Link>
                    <br />
                    //error here, with this.props.likers being an 
                    //array
                    {!this.props.likers.includes(currentUser) ? (
                        <Button
                            onClick={() => this.props.likePost(currentUser,post._id)}
                            bsStyle="success"
                        >
                            Like
                        </Button>
                    ) : (
                        <Button
                            onClick={() => this.props.unlikePost(currentUser,post._id)}
                            bsStyle="warning"
                        >
                            Unlike
                        </Button>
                    )}{" "}
                    {post.likedBy.length === 1
                        ? `${post.likedBy[0]} likes this`
                        : `${post.likedBy.length} people like this`}
                </li>
            );
        });
    }
function mapStateToProps(state) {
    return {
        posts: state.posts,
        users: state.users,
        likers: state.likers
    };
}
}

【问题讨论】:

  • 能否也为likePost()添加reducer代码?
  • 我刚做了。感谢您的建议
  • 喜欢或不喜欢之后的状态是什么?形状是对象结构。
  • 你在说什么状态?如果你说的是likers状态,那么它就是一个数组。

标签: javascript reactjs redux


【解决方案1】:

似乎喜欢/不喜欢帖子功能不会导致您的stateprops 中的任何内容发生变化,因此组件不会重新渲染。

您应该更改要存储的数据结构,以便 post.likedBy.includes(currentUser) 的值包含在其中之一中,或者 forceUpdate() 包含在 likePostunlikePost 调用之后的组件中。

请按第一种方式做,这样我晚上就可以睡觉了。让组件的render() 受到不在其propsstate 中的事物的影响,违背了使用React 的目的。

【讨论】:

    【解决方案2】:

    如其他答案所述,您需要使用 redux-thunkredux-saga 进行异步调用以更新您的减速器。我个人更喜欢redux-saga。这是 React、Redux 和 Redux-Saga 的基本实现。

    Redux-Saga 使用 JavaScript generator functionsyield 来完成处理异步调用的目标。

    下面你会看到很多熟悉的 React-Redux 代码,Redux-Saga 的关键部分如下:

    • watchRequest - 将调度操作映射到生成器函数的生成器函数
    • loadTodo - 一个从 watchRequest 调用到 yield 的生成器函数,来自异步调用的值并为 reducer 调度操作
    • getTodoAPI - 发出fetch 请求的常规函数​​
    • applyMiddleware - 来自 Redux 用于连接 Redux-Saga 和 createStore

    const { applyMiddleware, createStore } = Redux;
    const createSagaMiddleware = ReduxSaga.default;
    const { put, call } = ReduxSaga.effects;
    const { takeLatest } = ReduxSaga;
    const { connect, Provider } = ReactRedux;
    
    // API Call
    const getTodoAPI = () => {
      return fetch('https://jsonplaceholder.typicode.com/todos/1')
        .then(response => {
          return response.json()
            .then(response =>  response);
        })
        .catch(error => {
          throw error;
        })
    };
    
    // Reducer
    const userReducer = (state = {}, action) => {
      switch (action.type) {
        case 'LOAD_TODO_SUCCESS':
          return action.todo;
        default:
          return state;
      }
    };
    
    // Sagas, which are generator functions
    // Note: the asterix
    function* loadTodo() {
      try {
        const todo = yield call(getTodoAPI);
        yield put({type: 'LOAD_TODO_SUCCESS', todo});
      } catch (error) {
        throw error;
      }
    }
    
    // Redux-Saga uses generator functions,
    // which are basically watchers to wait for an action
    function* watchRequest() {
      yield* takeLatest('LOAD_TODO_REQUEST', loadTodo);
    }
    
    class App extends React.Component {
      render() {
        const { data } = this.props;
          return (
            <div>
              <button onClick={() => this.props.getTodo()}>Load Data</button>
              {data ?
                <p>data: {JSON.stringify(data)}</p>
                : null
              }
            </div>
          )
        }
    }
    // Setup React-Redux and Connect Redux-Saga
    const sagaMiddleware = createSagaMiddleware();
    const store = createStore(userReducer, applyMiddleware(sagaMiddleware));
    sagaMiddleware.run(watchRequest);
    
    // Your regular React-Redux stuff
    const mapStateToProps = (state) => ({ data: state }); // Map the store's state to component's props
    const mapDispatchToProps = (dispatch) => ({ getTodo: () => dispatch({type: 'LOAD_TODO_REQUEST'}) })  // wrap action creator with dispatch method
    const RootComponent = connect(mapStateToProps, mapDispatchToProps)(App);
    
    ReactDOM.render(
      <Provider store={store}>
        <RootComponent />
      </Provider>,
      document.getElementById('root')
    );
    <script src="https://npmcdn.com/babel-regenerator-runtime@6.3.13/runtime.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.1/redux.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/6.0.0/react-redux.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/redux-saga/0.16.2/redux-saga.min.js"></script>
    <div id="root"></div>

    【讨论】:

      【解决方案3】:

      您需要使用 redux-thunk 中间件才能使用异步操作。 首先,在创建 store 时添加 redux-thunk

      import thunk from 'redux-thunk';
      const store = createStore(
         rootReducer,
         applyMiddleware(thunk)  
      );
      

      然后像这样改变你的方法

      export function likePost(username,postId) {
          return function(dispatch) {
              // body...
               const request = {
                  username,
                  postId
                }
               axios.post(`${ROOT_URL}/likePost`,request)
                .then(res => {
                    dispatch({
                       type: LIKE_POST,
                       payload: res
                 });
            });
          }
      }
      

      现在在 mapStateToProps 之后的组件中,定义 mapDispatchToProps,

      const mapDispatchToProps = dispatch => {
             return {
                 likePost: (currentUser,postId) => dispatch(likePost(currentUser, postId)),
                 // same goes for "unlike" function
             }
       }
      
      export default connect(mapStateToProps, mapDispatchToProps)(PostsIndex);
      

      【讨论】:

        【解决方案4】:

        问题在于您的动作创建者。

        export function likePost(username,postId) {
            // body...
            const request = {
                username,
                postId
            }
            // this is an async call
            const post = axios.post(`${ROOT_URL}/likePost`,request);
            // next line will execute before the above async call is returned
            return{
                type: LIKE_POST,
                payload: post
            }
        }
        

        因此,您的状态可能永远不会更新并保持在初始值。

        您需要使用redux-thunkredux-saga 来处理异步操作。

        【讨论】:

        • 你知道我该怎么做吗?
        【解决方案5】:

        正如他们所说,使用 redux-thunk 或 redux-saga。如果你是 redux 的新手,我更喜欢 redux-thunk,因为它比 redux-saga 更容易学习。你可以像这样重写你的代码

        export function likePost(username,postId) {
        // body...
        const request = {
            username,
            postId
        }
        const post = axios.post(`${ROOT_URL}/likePost`,request);
        return dispatch => {
            post.then(res => {
              dispatch(anotherAction) //it can be the action to update state
            });
        }
        

        }

        【讨论】:

          猜你喜欢
          • 2020-05-21
          • 2021-08-11
          • 1970-01-01
          • 1970-01-01
          • 2022-11-30
          • 2020-06-07
          • 2021-11-08
          • 1970-01-01
          • 2022-08-13
          相关资源
          最近更新 更多