How do I add Redux implementation in my code?

2019-08-26 05:29发布

问题:

I want to add redux implementation to my simple Login application with some react navigations.

This is my App.js file where I'm importing my AppDrawerNavigator

   import React, {Component} from 'react';
import {createAppContainer, createStackNavigator} from 'react-navigation';
import HomeScreen from './screens/HomeScreen.js';

/** Importing navigator */
import AppDrawerNavigator from './drawerNavigator';


class App extends React.Component {
    render() {
      return <AppContainer />;
    }
}

export default App;



const AppStackNavigator = createStackNavigator(
  {
    Home: {screen: HomeScreen},
    Welcome: AppDrawerNavigator
  },
  {
    initialRouteName: 'Home',
    headerMode: "none",
  }
);

const AppContainer = createAppContainer(AppStackNavigator);

This is my index.js file pointing to my main App.js file

import {AppRegistry} from 'react-native';
import App from './App';


import {name as appName} from './app.json';

AppRegistry.registerComponent(appName, () => App);

Below shows my different screen files. HomeScreen.js

    import React, {Component} from 'react';
import {
  Platform, 
  StyleSheet, 
  Text, 
  View, 
  TouchableOpacity, 
  Alert, 
  Keyboard, 
  TextInput,
} from 'react-native';
//HomeScreen
export default class HomeScreen extends React.Component {
  constructor(props) {
  super(props);
  this.state = {username: null, password: null, isPasswordHidden: true, toggleText: 'Show'};
}

handleToggle = () => {
  const { isPasswordHidden } = this.state;

  if (isPasswordHidden) {
    this.setState({isPasswordHidden: false});
    this.setState({toggleText: 'Hide'});
  } else {
    this.setState({isPasswordHidden: true});
    this.setState({toggleText: 'Show'});
  }
}

//Validate() to check whether the input username is in Mail format
validate = (inputValue) => {
  let reg = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/ ; // Regex for Emails
  // let reg = /^(\+\d{1,3}[- ]?)?\d{10}$/; // Regex for phone numbers
  return reg.test(inputValue);
}
clearText(fieldName) {
  this.refs[fieldName].clear(0);
}

render() {
  return (
    <View style={styles.container}>
      <Text style={styles.welcome}></Text>


      <TextInput
        ref={'input1'}
        style={styles.input}
        placeholder="Username"
        onChangeText={value => this.setState({username: value})}
        // placeholderTextColor="Grey"
        // maxLength={13} // For Indian phone numbers
        // onChangeText={(text) => this.validate(text)}
        // value={this.state.username}
      />

      <TextInput
        ref={'input2'}
        style={styles.input}
        placeholder="Password"
        maxLength={10}
        secureTextEntry={this.state.isPasswordHidden}
        onChangeText={value => this.setState({password: value})}
        // placeholderTextColor="rgb(225,225,225)"
      />

      <TouchableOpacity
        onPress={this.handleToggle}
      >
        <Text>{this.state.toggleText}</Text>

      </TouchableOpacity>

      <View style={{padding: 20}}>
        <TouchableOpacity onPress={() => {

          if (!this.validate(this.state.username)) {
            Alert.alert("Invalid");
            Keyboard.dismiss();
          } else if (this.state.username === 'vinay@gmail.com' && this.state.password === 'password') {
            //Alert.alert("Login Successful");
            if(this.state.username && this.state.password){
              this.props.navigation.navigate('Welcome', {
                username: this.state.username,
                password: this.state.password,
              });
              this.setState({username: ""});
              this.setState({password: ""});
            }else{
              alert("Invalid");
            }

            Keyboard.dismiss();
            this.clearText('input1');
            this.clearText('input2');
          } else if (this.state.username === null && this.state.password === null) {
            Alert.alert("Invalid");
          } else {
            Alert.alert("Login Failed");
            this.clearText('input1');
            this.clearText('input2');
            Keyboard.dismiss();
          }

        }}>
          <View style={styles.button}>
            <Text style={styles.buttonText}>LOGIN</Text>
          </View>
        </TouchableOpacity>
      </View>

    </View>
  );
}
}

/** Stylesheets Defined **/
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    // backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 40,
    margin: 10,
    padding: 20
    // textAlign: 'center',
  },
  input:{
    // height: 40,
    // margin: 10,
    width: 260,
    backgroundColor: 'lightgrey',
    marginBottom: 10,
    padding: 10,
    color: 'black'
  },
  button: {
    marginBottom: 30,
    width: 260,
    alignItems: 'center',
    backgroundColor: '#2196F3',
    fontWeight: 'bold'

  },
  buttonText: {
    padding: 20,
    color: 'white'
  }
});

This is the screen ProfileScreen.js

 import React, {Component} from 'react';
import {
  Platform, 
  StyleSheet, 
  Text, 
  Image,
  View, 
} from 'react-native';

export default class Profile extends Component {
    render() {
      return(
        <View>
          <Image 
          style={styles.image}
          source={{uri: 'https://facebook.github.io/react/logo-og.png'}}
          />
        </View>
      );
    }
}

/** Stylesheets Defined **/
const styles = StyleSheet.create({
    container: {
      flex: 1,
      justifyContent: 'center',
      alignItems: 'center',
      // backgroundColor: '#F5FCFF',
    },
    welcome: {
      fontSize: 40,
      margin: 10,
      padding: 20
      // textAlign: 'center',
    },
    input:{
      // height: 40,
      // margin: 10,
      width: 260,
      backgroundColor: 'lightgrey',
      marginBottom: 10,
      padding: 10,
      color: 'black'
    },
    button: {
      marginBottom: 30,
      width: 260,
      alignItems: 'center',
      backgroundColor: '#2196F3',
      fontWeight: 'bold'

    },
    buttonText: {
      padding: 20,
      color: 'white'
    },
    image: {
      width: 200,
      height: 200,
      margin: 10
    }
});

This is the screen SettingsScreen.js

    import React, {Component} from 'react';
import {
  Platform, 
  StyleSheet, 
  Text, 
  View, 
} from 'react-native';

export default class Settings extends Component {
    render() {
      return(
        <View style={styles.container}>
          <Text>Settings</Text>
        </View>
      );
    }
}

/** Stylesheets Defined **/
const styles = StyleSheet.create({
    container: {
      flex: 1,
      justifyContent: 'center',
      alignItems: 'center',
      // backgroundColor: '#F5FCFF',
    },
    welcome: {
      fontSize: 40,
      margin: 10,
      padding: 20
      // textAlign: 'center',
    },
    input:{
      // height: 40,
      // margin: 10,
      width: 260,
      backgroundColor: 'lightgrey',
      marginBottom: 10,
      padding: 10,
      color: 'black'
    },
    button: {
      marginBottom: 30,
      width: 260,
      alignItems: 'center',
      backgroundColor: '#2196F3',
      fontWeight: 'bold'

    },
    buttonText: {
      padding: 20,
      color: 'white'
    }
});

This is the screen TabA.js

    import React, { Component } from 'react'
import {
  View,
  Text,
  StyleSheet,
} from 'react-native'

export default class TabA extends React.Component {

  // static navigationOptions = ({ navigation }) => ({
  //   title: 'Tab A',
  // })

  render () {
    return (
      <View style={styles.container}>
        <Text style={styles.text}>I'm Tab A</Text>
      </View>
      )
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: '#c0392b',
    padding: 20,
  },
  text: {
    color: 'white',
    fontSize: 40,
    fontWeight: 'bold',
  }
})

This is the screen TabB.js

    import React, { Component } from 'react'
import {
  View,
  Text,
  StyleSheet,
} from 'react-native'

export default class TabB extends React.Component {

  // static navigationOptions = ({ navigation }) => ({
  //   title: 'Tab B',
  // })

  render () {
    return (
      <View style={styles.container}>
        <Text style={styles.text}>I'm Tab B</Text>
      </View>
      )
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: '#8e44ad',
    padding: 20,
  },
  text: {
    color: 'white',
    fontSize: 40,
    fontWeight: 'bold',
  }
})

This is the screen WelcomeScreen.js

    import React, {Component} from 'react';
import {
  Platform, 
  StyleSheet, 
  Text, 
  View, 
} from 'react-native';

export default class WelcomeScreen extends Component {

  render() {
    const { navigation } = this.props;
    const u_name = navigation.getParam('username', 'name');
    const p_word = navigation.getParam('password', 'word');
    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>WELCOME</Text>
        <Text>USERNAME: {JSON.stringify(u_name)}</Text>
        <Text>PASSWORD: {JSON.stringify(p_word)}</Text>

        {/* <View style={{padding: 20}}>
          <Button style={{margin: 20}}
            title="LOGOUT"
            onPress={() => this.props.navigation.navigate('Home')}
          />
        </View> */}
      </View>
    );
  }
}

/** Stylesheets Defined **/
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    // backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 40,
    margin: 10,
    padding: 20
    // textAlign: 'center',
  },
  input:{
    // height: 40,
    // margin: 10,
    width: 260,
    backgroundColor: 'lightgrey',
    marginBottom: 10,
    padding: 10,
    color: 'black'
  },
  button: {
    marginBottom: 30,
    width: 260,
    alignItems: 'center',
    backgroundColor: '#2196F3',
    fontWeight: 'bold'

  },
  buttonText: {
    padding: 20,
    color: 'white'
  }
});

Below shows my different navigator files This is the drawer navigator file drawerNavigator.js

    import React, {Component} from 'react';
import {
    View, 
    Button, 
    SafeAreaView,
} from 'react-native';

import {
    createDrawerNavigator,
    DrawerItems,
} from 'react-navigation';

import TabA from './screens/TabA.js';
import TabB from './screens/TabB.js';
import WelcomeStackNavigator from './stackNavigator';

class Hidden extends React.Component {
    render() {
      return null;
    }
}

const AppDrawerNavigator = createDrawerNavigator({
    Welcome: { 
        screen: WelcomeStackNavigator,
        navigationOptions: {
            drawerLabel: <Hidden />
        } 
    },
    TabA: { screen: TabA },
    TabB: { screen: TabB },
    // TabC: { screen: TabC },
},{
    contentComponent:(props) => (
      <View style={{flex:1}}>
          <SafeAreaView forceInset={{ top: 'always', horizontal: 'never' }}>
              <DrawerItems {...props} />
              <Button 
                title="Logout" 
                onPress={() => {
                    props.navigation.navigate('Home')
                }}
              />
          </SafeAreaView>
      </View>
    ),
    drawerOpenRoute: 'DrawerOpen',
    drawerCloseRoute: 'DrawerClose',
    drawerToggleRoute: 'DrawerToggle'
})

export default AppDrawerNavigator;

This is the stack navigator file stackNavigator.js

import React, {Component} from 'react';
import Icon from 'react-native-vector-icons/Ionicons';

import {
  createStackNavigator,
} from 'react-navigation';

import WelcomeTabNavigator from './tabNavigator';

const WelcomeStackNavigator = createStackNavigator({
    WelcomeTabNavigator: WelcomeTabNavigator
  },
  {
    defaultNavigationOptions:({navigation}) => {
      return {
        headerLeft: (
          <Icon 
            style={{paddingLeft: 20}}
            onPress={() => navigation.openDrawer()}
            name="md-menu" 
            size={30}
          />
        )
      };
    }
  }
);

export default WelcomeStackNavigator;

This is the tab navigator file tabNavigator.js

import React, {Component} from 'react';
import WelcomeScreen from './screens/WelcomeScreen.js';
import Profile from './screens/ProfileScreen.js';
import Settings from './screens/SettingsScreen.js';

import Icon from 'react-native-vector-icons/Ionicons';

import {
  createBottomTabNavigator,
} from 'react-navigation';

const WelcomeTabNavigator = createBottomTabNavigator(
    {
        Welcome: { 
            screen: WelcomeScreen,
            navigationOptions: {

                tabBarIcon: ({tintColor}) => (
                    <Icon
                        name="md-home"
                        size={20}
                    />
                )
            }
        },
        Profile: {
            screen: Profile,
            navigationOptions: {

                tabBarIcon: ({tintColor}) => (
                    <Icon
                        name="md-book"
                        size={20}
                    />
                )
            }
        },
        Settings: {
            screen: Settings,
            navigationOptions: {

                tabBarIcon: ({tintColor}) => (
                    <Icon
                        name="md-settings"
                        size={20}
                    />
                )
            }
        },
    }, 
    {
        tabBarOptions: {
            activeTintColor: '#fb9800',
            inactiveTintColor: '#7e7b7b',
            style: { height: 40,backgroundColor: '#fff',borderTopWidth:0.5,borderTopColor: '#7e7b7b' },
            labelStyle: {fontSize: 15}
        },
        // navigationOptions:({navigation}) => {
        //     const {routeName} = navigation.state.routes[navigation.state.index];
        //     return {
        //         headerTitle: routeName
        //     };
        // },
        navigationOptions:({navigation}) => {
            const {routeName} = navigation.state.routes[navigation.state.index];
            return {
                headerTitle: routeName,
                // headerLeft: (
                //     <Icon 
                //     style={{paddingLeft: 20}}
                //     onPress={() => navigation.openDrawer()}
                //     name="md-menu" 
                //     size={30}
                //     />
                // )
            };
        }
    }
)

export default WelcomeTabNavigator;

How do I structure my project and add redux implementation on this login application?

回答1:

Have you checked their doc? https://redux.js.org/ also, you can see ignite boilerplate implementation for redux in react native apps https://github.com/infinitered/ignite



回答2:

It seems like a good practice to have your screen code(jsx) separated from your container code (where you'll have your mapDispatchToProps and your mapStateToProps).

So a good 'flow of information' using redux would look something like this:

Screen + container -> (dispatch) -> actions -> (dispatch) -> reducers -> and then stored in the store.

So to give you an idea of how to implement this, I'll show an example on how to use it for your purpose.

LoginScreen.js

export default class LoginScreen extends Component {
  constructor(props) {
    super(props);
  }

  login() {
  //Here i'm assuming that you have clicked some kind of button 
  //to submit the login information that you filled in your text input (username and password)

    const { username, password } = this.props;
    const loginData = {
      username,
      password
    }

    //here you'll be passing it to the dispatchToProps in your container
    this.props.login(loginData)
  }
}

LoginScreenContainer.js

const mapStateToProps = state => {
  ...state.userReducer,
}

const mapDispatchToProps = (dispatch) => {
  //The function that you called on your screen, 
  //and then we'll be dispatching the loginData to the user_actions

  login: (loginData) => {
    dispatch(loginUser(loginData))
  },
}

//Dont forget to connect both mapStateToProps and mapDispatchToProps to your screen
export default connect(mapStateToProps, mapDispatchToProps)(LoginScreen);

User_Actions.js

export function loginUser(loginData) {
  //The code to login would be here, if you are using firebase, 
  //back4app or any other provider, you would implement the login required here

  //Now assuming that the user successfully logged on you would dispatch a success and then store that user in the store 
  //(just as an example, you can store the username and any other information that you'd like):
  if (user) {
    dispatch({ type: 'USER_LOGIN', payload: { user } })
  } else {
    dispatch({ type: 'USER_LOGIN_REJECTED' });
  }
}

User_Reducer.js this is your reducer, you can have reducers also to handle the navigation in your app (not recommended though). It's basically a giant switch case that you'll get the dispatch actions.

export default function reducer(state = {
  user: null,
}, action) {

  const { type, payload } = action
  switch (type) {

    case 'USER_LOGIN': {
      return { ...state, user: payload.user }
    }
    case 'USER_LOGIN_REJECTED': {
      return { ...state, user: null }
    }

    default: {
      return state
    }
  }
}

Store.js

const rootReducer = combineReducers({
  user_reducer,
})

let middleware = [thunk]
if (process.env.NODE_ENV !== 'production') {
  middleware = [...middleware, logger] //Logger is an useful library to log your state changes
}

export default createStore(
  rootReducer,
  undefined,
  applyMiddleware(...middleware)
)

App.js and then here you'll wrap your main stackNavigator in the provider tag

import { Provider } from 'react-redux';
import store from './src/redux/store';

render() {
  return (
    <Provider store={store}>
      <RootNavigator />
    </Provider>
  )
}

Hopefully this helps you understand a basic flow (I'm sure there are other ways to do it) and how to implement redux for your needs.