【问题标题】:Redux reducer being called twiceRedux reducer 被调用两次
【发布时间】:2018-03-27 19:28:55
【问题描述】:

我正在使用 Redux 开发 React Native 应用程序。

应用程序有一个简单的注册屏幕:

import React from 'react';
import {
    Alert,
    Button,
    KeyboardAvoidingView,
    StyleSheet,
    Text,
    TextInput,
    TouchableOpacity,
    View
} from 'react-native';
import {connect} from "react-redux";
import {register} from "../redux/actions/Registration";
import {REGISTER} from "../redux/constants/ActionTypes";
import Loader from "./Loader";

const mapStateToProps = (state, ownProps) => {
    return {
        isLoggedIn: state.registration.isLoggedIn,
        token: state.registration.token,
        isLoading: state.common.isLoading,
        error: state.common.error
    };
};

const mapDispatchToProps = (dispatch) => {
    return {
        onRegister: (username, password, firstName, lastName) => {
            dispatch(register(username, password, firstName, lastName));
        }
    }
};

@connect(mapStateToProps, mapDispatchToProps)
export default class RegistrationForm extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            route: REGISTER,
            email: '',
            password: '',
            firstName: '',
            lastName: '',
            isLoading: false
        }
    }

    register(e) {
        this.props.onRegister(
            this.state.email,
            this.state.password,
            this.state.firstName,
            this.state.lastName
        );
        e.preventDefault();
    }

    componentDidUpdate() {
        if (this.props.error != null && this.props.error !== undefined && this.props.error !== '') {
            setTimeout(() => Alert.alert(this.props.error), 600);
        }
    }

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

        return (
            <View style={styles.container}>
                <KeyboardAvoidingView style={styles.registration_form} behaviour="padding">

                    <Loader loading={isLoading}/>

                    <TextInput style={styles.text_input} placeholder="First Name" placeholderTextColor="white"
                               underlineColorAndroid={'transparent'}
                               onChangeText={(text) => this.setState({firstName: text})}/>
                    <TextInput style={styles.text_input} placeholder="Last Name" placeholderTextColor="white"
                               underlineColorAndroid={'transparent'}
                               onChangeText={(text) => this.setState({lastName: text})}/>
                    <TextInput style={styles.text_input} placeholder="Email" placeholderTextColor="white"
                               underlineColorAndroid={'transparent'} keyboardType="email-address"
                               onChangeText={(text) => this.setState({email: text})}/>
                    <TextInput style={styles.text_input} placeholder="Password" placeholderTextColor="white"
                               underlineColorAndroid={'transparent'}
                               onChangeText={(text) => this.setState({password: text})}
                               secureTextEntry={true}/>

                    <TouchableOpacity style={styles.button} onPress={(e) => this.register(e)}>
                        <Text style={styles.btn_text}>Sign up</Text>
                    </TouchableOpacity>

                    <Button buttonStyle={{marginTop: 40}}
                            backgroundColor="transparent"
                            textStyle={{color: "#fff"}}
                            title="Login"
                            onPress={() => this.props.navigation.navigate('Login')}/>

                </KeyboardAvoidingView>
            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: 'center',
        backgroundColor: '#36485f',
        paddingLeft: 60,
        paddingRight: 60
    },
    registration_form: {
        alignSelf: 'stretch',

    },
    text_input: {
        alignSelf: 'stretch',
        height: 40,
        marginBottom: 30,
        color: '#fff',
        borderBottomColor: '#f8f8f8',
        borderBottomWidth: 1
    },
    button: {
        alignSelf: 'stretch',
        alignItems: 'center',
        padding: 20,
        backgroundColor: '#59cbbd',
        marginTop: 30
    },
    btn_text: {
        color: '#fff',
        fontWeight: 'bold'
    }
});

另外,我创建了动作:

1) Common.js

import {FAILED, LOADING} from "../constants/ActionTypes";


export const loading = (isLoading) => {
    return {
        type: LOADING,
        isLoading: isLoading
    }
};

export const failed = (error) => {
    return {
        type: FAILED,
        error: error
    }
};

2)Registration.js:

import {userService} from "../../service/UserService";
import {REGISTER} from "../constants/ActionTypes";
import {failed, loading} from "./Common";

export const register = (username, password, firstName, lastName) => {
    return dispatch => {

        dispatch(loading(true));

        userService.register(username, password, firstName, lastName)
            .then(resp => {
                    dispatch(loading(false));

                    dispatch({
                        type: REGISTER,
                        token: resp.json().token,
                        error: null
                    });
                }
            )
            .catch(err => {
                dispatch(loading(false));
                dispatch(failed(err.message))
            })
    };
};

还有减速器:

1)Common.js:

import {FAILED, LOADING, REGISTER} from "../constants/ActionTypes";

const defaultState = {
    isLoading: false,
    error: null
};

export default function reducer(state = defaultState, action) {
    console.log('COMMON STATE: ', state);
    console.log('COMMON ACTION: ', action);

    switch (action.type) {
        case LOADING:
            return {
                ... state,
                isLoading: action.isLoading
            };
        case FAILED:
            return {
                ... state,
                error: action.error
            };
        default:
            return state;
    }
}

2)Registration.js:

import {FAILED, LOADING, REGISTER} from "../constants/ActionTypes";

const defaultState = {
    isLoggedIn: false,
    token: null
};

export default function reducer(state = defaultState, action) {
    console.log('REGISTER STATE: ', state);
    console.log('REGISTER ACTION: ', action);

    switch (action.type) {
        case REGISTER:
            return Object.assign({}, state, {
                isLoggedIn: action.isLoggedIn,
                token: action.token
            });
        default:
            return state;
    }
}

3)Index.js:

import { combineReducers } from 'redux';
import registration from './Registration';
import login from './Login';
import common from './Common'

const rootReducer = combineReducers({
    registration,
    login,
    common
});

export default rootReducer;

当我第一次单击注册组件上的“注册”按钮时 - 一切正常 - 它显示一个微调器,然后显示一个警报。

当我第二次(或第三次等)单击“注册”按钮时,它还会显示一个微调器,然后它会一一打开两个警报。

我的期望:每次点击应用程序应该只显示一个警报。

在控制台中我看到以下输出:

10:27:07 PM: REGISTER STATE:  Object {
10:27:07 PM:   "isLoggedIn": false,
10:27:07 PM:   "token": null,
10:27:07 PM: }
10:27:07 PM: REGISTER ACTION:  Object {
10:27:07 PM:   "isLoading": false,
10:27:07 PM:   "type": "LOADING",
10:27:07 PM: }
10:27:07 PM: LOGIN STATE:  Object {
10:27:07 PM:   "isLoggedIn": false,
10:27:07 PM:   "token": null,
10:27:07 PM: }
10:27:07 PM: LOGIN ACTION:  Object {
10:27:07 PM:   "isLoading": false,
10:27:07 PM:   "type": "LOADING",
10:27:07 PM: }
10:27:07 PM: COMMON STATE:  Object {
10:27:07 PM:   "error": "Network request failed",
10:27:07 PM:   "isLoading": true,
10:27:07 PM: }
10:27:07 PM: COMMON ACTION:  Object {
10:27:07 PM:   "isLoading": false,
10:27:07 PM:   "type": "LOADING",
10:27:07 PM: }
10:27:07 PM: REGISTER STATE:  Object {
10:27:07 PM:   "isLoggedIn": false,
10:27:07 PM:   "token": null,
10:27:07 PM: }
10:27:07 PM: REGISTER ACTION:  Object {
10:27:07 PM:   "error": "Network request failed",
10:27:07 PM:   "type": "FAILED",
10:27:07 PM: }
10:27:07 PM: LOGIN STATE:  Object {
10:27:07 PM:   "isLoggedIn": false,
10:27:07 PM:   "token": null,
10:27:07 PM: }
10:27:07 PM: LOGIN ACTION:  Object {
10:27:07 PM:   "error": "Network request failed",
10:27:07 PM:   "type": "FAILED",
10:27:07 PM: }
10:27:07 PM: COMMON STATE:  Object {
10:27:07 PM:   "error": "Network request failed",
10:27:07 PM:   "isLoading": false,
10:27:07 PM: }
10:27:07 PM: COMMON ACTION:  Object {
10:27:07 PM:   "error": "Network request failed",
10:27:07 PM:   "type": "FAILED",
10:27:07 PM: } 

所以 reducer 被调用了两次。如何解决这个问题?或者如何防止打开第二个警报弹窗?

更新。

可以在GitHub上找到该项目的源代码:https://github.com/YashchenkoN/money-observer-client

更新 2。 从@Chase DeAnda 更改后,它看起来是这样的:

【问题讨论】:

    标签: javascript reactjs react-native react-redux


    【解决方案1】:

    register函数中你需要检查props来判断用户是否已经注册:

    register(e) {
      e.preventDefault();
      if (!this.props.token) {
        this.props.onRegister(
          this.state.email,
          this.state.password,
          this.state.firstName,
          this.state.lastName
        );
      } else {
        // User already registered
        // Redirect to login page
      }
    }
    

    编辑

    啊,好吧,我想我现在看到了问题所在。您正在使用 componentDidUpdate 生命周期方法。 每次都会调用此方法,将状态或 props 传递给它,不一定只有当它们发生变化时。您遇到的问题是您没有检查this.props.error 是否实际上与您已经显示的第一个错误不同。将其更改为:

    componentDidUpdate(prevProps,prevState) {
        if(prevProps.error !== this.props.error && this.props.error){
            setTimeout(() => Alert.alert(this.props.error), 600);
        }
    }
    

    由于您有多个 reducer 对失败的 XHR 请求做出反应,因此您的组件将从多个操作中获取传递给它的 props。您需要确保仅在错误与之前传入的不同时才显示错误。

    【讨论】:

    • 我不需要重定向。注册函数只被调用一次并且看起来不错(就我而言)。问题出在 redux 上,因为它不止一次调用 reducer
    • 注册 api 调用失败,如下所示:REGISTER ACTION: Object { "error": "Network request failed", "type": "FAILED"}
    • 是的,我知道。如果出现错误,它应该打开警报。问题是它打开了 2 个警报而不是 1 个
    • 试过你的代码 - 它没有帮助。现在,当我第一次单击按钮时,它会显示警报,但是当我第二次单击时 - 它根本不会显示警报
    • 好的,你的要求很不清楚。在这一点上,我不知道你在期待什么。我希望我能够提供的帮助。祝你好运。
    猜你喜欢
    • 1970-01-01
    • 2019-12-05
    • 2019-08-15
    • 1970-01-01
    • 1970-01-01
    • 2018-09-06
    • 2017-03-28
    • 1970-01-01
    • 2019-06-01
    相关资源
    最近更新 更多