【问题标题】:Getting a value from a particular state before component renders using react/redux?在使用 react/redux 渲染组件之前从特定状态获取值?
【发布时间】:2017-07-02 03:32:58
【问题描述】:

我正在使用带有 nodejs(express)/mongodb 后端的 react/redux 以提供帮助。

我希望在这里发生的是,如果用户尝试编辑不属于他们的帖子,我希望他们立即重新路由并且永远不会看到该页面。

例如。用户“A”转到路由localhost:8080/posts/post_id/edit,但该帖子属于用户“B”。我希望用户 A 立即重新路由回该帖子或localhost:8080/posts/post_id

在我的代码中,我可以通过名为 getUser() 的操作获取用户,该操作向后端发送 axios.get 请求以获取当前登录的用户。我正在使用 JWT 令牌。不确定这是否是需要的信息。

这是向您展示我正在尝试做什么的代码。

import React , { Component } from 'react';
import { bindActionCreators } from 'redux';
import * as actions from '../../actions/posts_actions';
import * as actionsIndex from '../../actions/index';
import { reduxForm, Field } from 'redux-form';
import {connect} from 'react-redux';
import {Link} from 'react-router-dom';

class EditPost extends Component {
  componentWillMount() {
    if(this.props.auth) {
      console.log(this.props.auth); // -> returns true
      this.props.getUser(); // -> this fires off
    }
  }

  componentDidMount() {
    const {id} = this.props.match.params;
    this.props.getOnePost(id);
    if(this.props.auth){
      if(this.props.user._id !== this.props.post.author.id){
        this.props.history.push(`/posts/${id}`);
      }
    }
  }

  renderField(field) {
    const { meta: {touched, error} } = field;
    const className = `form-group ${touched && error ? 'has-danger' : ''}`;

    return (
      <div className={className}>
        <label><strong>{field.label}:</strong></label>
        <input
          className="form-control"
          type={field.type}
          {...field.input}
        />
        <div className="text-help">
          { touched ? error : ''}
        </div>
      </div>
    )
  }

  onSubmit(values) {
    const {id} = this.props.match.params;

    this.props.updatePost(values, id, () => {
      this.props.history.push(`/posts/${id}`);
    });
  }

  render() {
    const {handleSubmit} = this.props;

    const {id} = this.props.match.params;
    console.log(this.props.user); // -> shows me the user after three nulls
    return (
      <form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
        <Field
          label="Title"
          name="title"
          type="text"
          component={this.renderField}
        />
        <Field
          label="Content"
          name="content"
          type="text"
          component={this.renderField}
        />
        <button type="submit" className="btn btn-success">Submit</button>
        <Link to={`/posts/${id}`} className="btn btn-danger">Cancel</Link>
      </form>
    );
  }
}

function validate(values) {
  const errors = {};

  if(!values.title) {
    errors.title = "Enter a title!";
  }
  if(!values.content) {
    errors.content = "Enter some content please!";
  }

  return errors;
}

function mapStateToProps({ posts, auth, user }, ownProps) {
  return {
    initialValues: posts[ownProps.match.params.id],
    post: posts[ownProps.match.params.id],
    auth: auth.authenticated,
    user: user
  };
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators({...actions, ...actionsIndex}, dispatch);
}

export default connect(mapStateToProps, mapDispatchToProps)(reduxForm({
  validate,
  form: 'editform'
})(EditPost));

以下是 console.log 语句:

这是 index.js 页面的编辑,有什么方法可以在这里更新用户状态吗?:

"use strict"
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import {BrowserRouter, Route, Switch} from 'react-router-dom';
import { applyMiddleware, createStore } from 'redux';
import reduxThunk from 'redux-thunk';
import reducers from './reducers/index';
import App from './components/app';
import '../style/style.css';

import Home from './components/pages/home';
import Header from './components/header';
import Footer from './components/footer';
import RequireAuth from './components/auth/require_auth';
import RequireUnAuth from './components/auth/require_unauth';

import Signin from './components/auth/signin';
import Signup from './components/auth/signup';
import Signout from './components/auth/signout';

import Posts from './components/pages/posts';

import {AUTH_USER} from './actions/types';

const createStoreWithMiddleware = applyMiddleware(reduxThunk)(createStore);
const store = createStoreWithMiddleware(reducers);

const token = localStorage.getItem('token');

if(token) {
  store.dispatch({ type: AUTH_USER });
}

const Routes = (
  <Provider store={store}>
    <BrowserRouter>
    <App />
    </BrowserRouter>
  </Provider>
)

ReactDOM.render(Routes, document.querySelector('.container'));

【问题讨论】:

    标签: reactjs redux react-redux


    【解决方案1】:

    如果 this.props.getUser() 是异步(thunk-ed)动作创建者,则无法保证在您到达 componentDidMount 时获得可用的结果

    在您尝试访问其中的字段之前,在 componentDidMount 中添加对 this.props.user 的空检查。

    你可以做的就是移动

    if(this.props.user._id !== this.props.post.author.id){
      this.props.history.push(`/posts/${id}`);
    }
    

    转入componentWillReceiveProps

    componentWillReceiveProps ({ user: nextUser }) {
        const { history, match, user: currentUser, post } = this.props
        const { id } = match.params
    
        /* if you didn't previously have currentUser (i.e. after first load) - !currentUser
         * or the user has changed - nextUser !== currentUser*/
        if (!currentUser || nextUser !== currentUser) { // probably shouldn't use === here
          /* the component might update before `this.props.getUser()` "returns", so make sure this is the
           * update we are looking for by ensuring that we have nextUser -  nextUser &&
           * then get the id (nextUser._id), and run the check (!== post.author.id)*/
          if (nextUser && (nextUser._id !== post.author.id)) {
            history.push(`/posts/${id}`)
          }
        }
      }
    

    componentWillReceiveProps 上的Here's a little light Friday reading - 可能会解决一些问题。

    关于您的编辑的几件事:

    1. 如果您有很多依赖于user 的组件,HoC 可能是个好主意

    AuthComponent HoC 概念

    class AuthComponent extends React.Component {
      componentDidMount() {
        if (!this.props.user) {
          this.props.getUser()
        }
      }
    
      render() {
        // you could inject `user` into children here, but I would suggest that you 
        // instead store the result of `getUser` into a store like `redux` or `flux` and fetch from that in child components
        return this.props.children
      }
    }
    

    显然,您需要使用connect 并将user 抢占该组件的状态。你会像这样使用它

    class SomeComponent extends React.Component {
      render () {
        return (
          <AuthComponent>
            <WrappedComponent/> // <- this is what's rendered
          </AuthComponent>
        )
      }
    } 
    

    不过,您使用的是 react-router,因此更好的解决方案是将其合并到您的路由中

    <Route path="" component={AuthComponent}>
      <Route path="some-path" component={WrappedComponent}/>
    </Route> 
    
    1. 就像我在评论中所说,缓存并从您的商店返回 user...虽然看起来您已经在这样做了
    2. 不要假设你总是可以访问user - 确保你检查它是空的

    【讨论】:

    • 这确实有效,但是我并没有真正了解最后一个 if 语句的逻辑。能稍微解释一下吗?
    • 是的,抱歉 - 有点乱 - 我添加了一些 cmets。
    • 稍微简化了一点 - nextUser 应该始终拥有当前用户或新用户,因此您不必进行我正在做的检查。
    • 谢谢!我添加了一个编辑,想知道是否有办法使用 store.dispatch 在 index.js 页面中触发 getUser 操作?
    • 非常感谢!你非常了解 react 以及如何解释它!有没有办法我们可以把它移到聊天中,我让 HOC 工作,但是如果我在页面上点击一次刷新,我希望用户变成 null。我认为这是因为反应状态会被硬刷新擦除?
    【解决方案2】:

    您在 componentWillMount 中使用的代码不属于这里。

    this.props.getUser();
    

    你最好创建一个动作 redux-thunk creator 来处理 promise + async 动作,然后才通过 state/dispatch 机制返回结果,以便稍后在 componentDidMount 生命周期挂钩中使用。

    调用 API 的 Redux thunk 示例,请查看redux-thunk docs:

    const store = createStore(
      reducer,
      applyMiddleware(thunk.withExtraArgument(api))
    )
    
    // later
    function fetchUser(id) {
      return (dispatch, getState, api) => {
        // you can use api here
      }
    }
    To pass multiple things, just wrap them in a single object and use destructuring:
    
    const store = createStore(
      reducer,
      applyMiddleware(thunk.withExtraArgument({ api, whatever }))
    )
    
    // later
    function fetchUser(id) {
      return (dispatch, getState, { api, whatever }) => {
        // you can use api and something else here here
      }
    }
    

    【讨论】:

    • this.props.getUser 是一个 action create,它使用 axios 并向后端发送请求以获取用户,然后通过 reducer 将该用户状态设置给该用户。
    • 正如我所说的 redux thunk 的经典
    • 那么我在哪里调用这个动作呢?
    • 哪里都好(不过componentDidMount 会更好),就是您希望它已经完成,并且商店会在您到达componentDidMount 时更新跨度>
    • 我在创建商店的地方添加了我的 index.js 页面,有没有办法在那里调用该操作?也很抱歉我对 react/redux 很陌生。
    猜你喜欢
    • 1970-01-01
    • 2019-08-01
    • 2020-03-04
    • 1970-01-01
    • 2019-07-21
    • 2021-07-20
    • 1970-01-01
    • 1970-01-01
    • 2021-10-28
    相关资源
    最近更新 更多