【问题标题】:React setState() not updating state after $.ajax() request在 $.ajax() 请求后反应 setState() 不更新状态
【发布时间】:2016-08-07 11:47:37
【问题描述】:

我正在使用 react-router。在 IndexRoute 上使用 onEnter 异步钩子检查身份验证后,App 组件将被渲染。 App 组件有一个初始状态 auth,它在渲染时设置为 undefined。 auth 状态作为 prop 传递给 Navbar 组件,用于决定是否显示登录、注册和注销链接。

当 App 组件完成渲染后,componentDidMount() 会再次调用 ajax 来检查用户是否通过了身份验证。在响应时,它会改变状态。从 ajax 请求状态更改后,我将状态记录到控制台,this.setState() 方法没有更改状态,但不知何故仍会触发 Navbar 组件上的 componentWillReceiveProps() 方法,this.props.auth 值仍未定义。

// Checks Authentication Asynchronously 
isAuthenticated(nextState, replace, callback) {
    $.ajax({
        type : 'GET',
        url : '/auth',
        success : function(res){
            if(!res){
                callback(replace({ pathname: '/login', query: { auth: 'false' } }));
            }else{
                callback();
            }
        }
    });
};

// routes
var routes = (
    <Router history={browserHistory}>
        <Route path="/" component={require('./components/app')}>
            <IndexRoute component={require('./components/dashboard/index')} onEnter={Auth.isAuthenticated}/>

            <Route path="/register"
                   component={require('./components/authentication/register')}
                   onEnter={Auth.isNotAuthenticated} />

            <Route path="/login"
                   component={require('./components/authentication/login')}
                   onEnter={Auth.isNotAuthenticated}/>

            <Route path="*"
                   component={require('./components/404/404')}/>
        </Route>
    </Router>
);

// App
const App = React.createClass({

    getInitialState(){
        return {
            auth : undefined
        }
    },

    componentDidMount(){
        console.log('App componentDidMount');
        this.checkAuth();
    },

    checkAuth(){
        var self = this;
        $.ajax({
            type : 'GET',
            url : '/auth',
            success : function(res){
                if(res){
                    self.setState({
                        auth : true
                    });
                }else{
                    self.setState({ auth : false});
                }
            }
        });
        console.log(this.state.auth);
    },

    render() {
        return(
            <div className="appWrapper">
                <Navbar auth={this.state.auth}/>

                <div className="container">
                    {this.props.children}
                </div>
            </div>
        );
    }
});

// Navbar
var Navbar = React.createClass({

    getInitialState(){
        return{
            user_actions : '' ,
            auth : this.props.auth
        }
    },

    componentDidMount(){
        console.log('Navbar componentDidMount ', this.props.auth);
        this.checkAuthState();
    },

    componentWillReceiveProps(){
        console.log('Navbar componentWillReceiveProps ', this.props.auth);
        this.setState({
            auth : this.props.auth
        });
        this.checkAuthState();
    },

    checkAuthState(){
        console.log('Nav Mounted with auth : ', this.state.auth);

        if(this.state.auth == undefined){
            this.state.user_actions = '';
        }
        if(!this.state.auth){
            this.state.user_actions =   <ul className="nav navbar-nav navbar-right">
                <li><a href="/login">Login</a></li>
                <li><a href="/register">Register</a></li>
            </ul>;
            this.setState({
                user_actions : this.state.user_actions
            });
        }

        if(this.state.auth){
            this.state.user_actions =   <ul className="nav navbar-nav navbar-right">
                <li><a href="/logout">Logout</a></li>
            </ul>;
            this.setState({
                user_actions : this.state.user_actions
            });
        }
    },

    render : function(){
        return (
            <nav className="navbar navbar-default">
                <div className="container">
                    <a href="/" className="navbar-brand">Reactor</a>
                    {this.state.user_actions}
                </div>
            </nav>
        );
    }
});

【问题讨论】:

    标签: reactjs react-router


    【解决方案1】:

    首先,我建议你重新阅读 React.JS 文档,因为有几点需要注意:

    1. 永远不要直接改变this.state,而是使用setState 方法。 (line: 108, 111, 121, 133, 136, 146)
    2. 您应该使用状态来存储随时间变化的数据,而不是一个元素。 (line: 111, 121, 136, 146)

    tl;博士; 让我们回到问题:

    1。 Ajax 响应正在更改状态值,但该值在您的日志中没有更改。

    如果在 ajax 请求后打印值,您将看不到它!原因是:

    首先,您使用 Ajax 进行异步请求,并尝试以同步方式查看结果。 JS会先执行你的console.log,它仍然包含请求之前的值,然后执行ajax请求回调。这是您的代码块:

    $.ajax({ ...,
        success: function(res) {
            if(res) { self.setState({ auth : true }); }/
            ...
        }  // will executed later (after ajax get response)
     });
     console.log(this.state.auth); // will executed first, this is why it always prints the value as undefined
    

    其次,您将无法在设置新状态值后立即看到更改后的状态值。例如,假设this.state.auth 的值为false

    this.setState({ auth: true});
    console.log(this.state.auth); // will print false, instead of true as your new value 
    

    您可以使用componentWillUpdate(nextProps, nextState) 方法查看新的状态值。您可以通过以下链接了解此内容:React.JS Component Specs and Lifecycle

    2。仍然会在 Navbar 组件上触发 componentWillReceiveProps() 方法,并且 this.props.auth 值仍然未定义。

    这意味着你的状态值被setState()在你的ajax响应中成功改变了。证明是 Navbar 组件接收到一个新的道具,该道具由 App 组件(更改了身份验证状态)向下发送,这将触发 componentWillReceiveProps() 方法。

    也许你的代码应该是这样的:

    // App
    const App = React.createClass({
        getInitialState : function(){
            return {
                auth : false
            }
        },
    
        componentDidMount : function() {
            console.log('App componentDidMount');
            this.checkAuth();
        },
    
        componentWillUpdate : function(nextProps, nextState) {
            //you'll see the changing state value in here
            console.log('Your prev auth state: ' + this.state.auth);
            console.log('Your next auth state: ' + nextState.auth);
        },
    
        checkAuth : function(){
            var self = this;
            $.ajax({
                type : 'GET',
                url : '/auth',
                success : function(res){
                    if(res){
                        self.setState({ auth : true });
                    }
                }
            });
        },
    
        render : function(){
            return(
                <div className="appWrapper">
                    <Navbar auth={this.state.auth}/>
                    <div className="container">
                        {this.props.children}
                    </div>
                </div>
            );
        }
    });
    
    // Navbar
    // Because the navbar component receive data (this.props.auth) from parent (app) via props, so we're no longer need to assign auth as a state in Navbar component. 
    const Navbar = React.createClass({
        render : function(){
            // you're no longer need checkAuthState method
            let navItems;
            if(!this.props.auth){
                navItems =  (<ul className="nav navbar-nav navbar-right">
                    <li><a href="/login">Login</a></li>
                    <li><a href="/register">Register</a></li>
                </ul>);
            } else {
                navItems =  (<ul className="nav navbar-nav navbar-right">
                    <li><a href="/logout">Logout</a></li>
                </ul>);
            }
    
            return (
                <nav className="navbar navbar-default">
                    <div className="container">
                        <a href="/" className="navbar-brand">Reactor</a>
                        { navItems }
                    </div>
                </nav>
            );
        }
    });
    

    希望对你有帮助!

    【讨论】:

    • 在 es6 中你可以使用 success: (data) =&gt;{...} 然后你不需要 self 变量。
    【解决方案2】:

    在 ajax 范围内。它无法访问反应状态。作为替代方案,您可以调用模块中的其他方法作为 ajax 成功调用,然后在那里更新状态。 按照这个例子。

    var reactModule = React.createClass({
       getInitialState:function(){
    
        },
      render: function() {
        return (      
               <div>
                content
              </div>
        );
      },
    componentDidMount: function() {
         var ajaxSuccess=this.ajaxSuccess;
    
                   $.ajax({
                       type: "POST",
                       url: $api_url + 'index.php/app/icon1_update',
                       dataType: "text",
                       data:fd,
                       contentType: false,
                       processData: false,
                       success: ajaxSuccess
                   });  
         }, 
    ajaxSuccess:function(e){
       //e is the result. update state here.
    }  
    });
    

    【讨论】:

      【解决方案3】:

      只需使用箭头函数访问“this”:

      success: () => {
      this.setState({ data: value })
      }
      

      【讨论】:

        【解决方案4】:

        请查看 componentWillReceiveProps 的文档:

        componentWillReceiveProps(
          object nextProps
        )
        

        https://facebook.github.io/react/docs/component-specs.html#updating-componentwillreceiveprops

        当您的属性将发生变化时,请访问属性“nextProps”。否则,您将访问旧属性。

        作为一个小提示: 在 render 方法中包含您的 checkAuthState() 代码,而不是在 componentDidMount 方法中,因为因此您可以避免 setState 调用。

        【讨论】:

          猜你喜欢
          • 2021-01-28
          • 2021-06-02
          • 1970-01-01
          • 2018-12-08
          • 1970-01-01
          相关资源
          最近更新 更多