Create popup edit comment like facebook’s editing comment modal

Create popup edit comment like facebook’s editing comment modal

Today, I’m going to share how to create a modal similar to facebook edit, delete comment modal.

As you can see in the video, user use their finger to touch the comment for 1-2 seconds, the popup will show up right at the position user touch in the screen. So the idea is: we will catch the event onLongPress, this even provide us the position where our finger touch in the screen. We pass this position to our modal then show the popup at that position.

  <TouchableOpacity onLongPress={evt => this.handleOnLongPressComment(evt, comment)}
    delayLongPress={500}
  >
   {this.renderCommentItem(comment)}
  </TouchableOpacity>

In callback handleOnLongPressComment, we get the position (evt.nativeEvent.pageX, evt.nativeEvent.pageY) then pass it to our modal

  handleOnLongPressComment(evt, comment) {
    dismissKeyboard();

    const currentFeed = this.props.currentFeedComment;
    const currentUser = this.props.currentUser;

    if (
      comment.user.id === currentUser.user.id ||
      (comment.user.id !== currentUser.user.id && currentFeed.user.id === currentUser.user.id)
    ) {
      this.setState({
        commentActionModalVisible: true,
        commentPosition: {
          x: evt.nativeEvent.pageX,
          y: evt.nativeEvent.pageY,
        },
      });
      this.props.setActiveComment(comment);
    }
  }
  renderCommentActionModal() {
    return (
      <CommentActionModal actionModalVisible={this.state.commentActionModalVisible} commentPosition={this.state.commentPosition} onCloseActionModal={this.handleOnCloseCommentActionModal} onPressEditComment={this.handleOnPressEditComment} />
    );
  }

Now, we look at the CommentActionModal
As usual, we use react native Modal component, animationType will be set to none, we don’t want to show any animation here.

  render() {
    return (
      <Modal animationType="none" transparent visible={this.props.actionModalVisible} onRequestClose={() => {
          this.props.onCloseActionModal();
        }}
      >
        <View style={{ flex: 1 }}>
          <View style={styles.background} />
          <TouchableWithoutFeedback onPress={() => this.props.onCloseActionModal()}>
            <View style={styles.content}>{this.renderBody()}</View>
          </TouchableWithoutFeedback>
        </View>
      </Modal>
    );
  }

Apart from two required props actionModalVisible and onCloseActionModal, this component also accept the prop name commentPosition.

  commentPosition: PropTypes.shape({
    x: PropTypes.number,
    y: PropTypes.number,
  }).isRequired,

In event componentWillReceiveProps, we use this commentPosition to calculate the position (top,left) of the popup which we want to show.

  componentWillReceiveProps({ actionModalVisible, commentPosition }) {
    if (actionModalVisible && commentPosition) {
      let py = commentPosition.y;
      if (py + 130 > height) {
        py -= 100;
      }
      this.setState({
        customStyle: {
          top: py,
          left: width / 2 - 50,
        },
      });
    }
  }

Hmm, it’s pretty easy, right? Here is the full code if you guys interested in fullcode
Thanks.

Full code of facebook’s comment editing popup

Popup edit comment like facebook’s modal

Full code of facebook’s comment editing popup. code explanation here

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import PropTypes from 'prop-types';
import {
  Modal,
  View,
  Text,
  StyleSheet,
  TouchableOpacity,
  InteractionManager,
  TouchableWithoutFeedback,
  Dimensions,
  Alert,
} from 'react-native';
import I18n from '../app/i18n';
import { removeComment, focusCommentBox } from './feedActions';
import { RALEWAY_REGULAR } from '../commons/fonts';

const { width, height } = Dimensions.get('window');

class CommentActionModal extends Component {
  constructor(props) {
    super(props);
    this.state = {
      isLoadingScreen: true,
      customStyle: {
        top: 0,
        left: 0,
      },
    };
  }

  componentDidMount() {
    // this.showModalAnimation();
    InteractionManager.runAfterInteractions(() => {
      this.setState({
        isLoadingScreen: false,
      });
    });
  }

  componentWillReceiveProps({ actionModalVisible, commentPosition }) {
    if (actionModalVisible && commentPosition) {
      let py = commentPosition.y;
      if (py + 130 > height) {
        py -= 100;
      }
      this.setState({
        customStyle: {
          top: py,
          left: width / 2 - 50,
        },
      });
    }
  }

  onPressAction(action) {
    switch (action.id) {
      case 1: // edit
        this.props.onPressEditComment();
        this.props.onCloseActionModal();
        break;
      case 2: // delete
        Alert.alert(
            '',
            I18n.t('delete_comment_confirm_message'),
            [
              {
                text: I18n.t('cancel').toUpperCase(),
                onPress: () => this.props.onCloseActionModal(),
              },
              {
                text: I18n.t('delete').toUpperCase(),
                onPress: () => {
                  this.props.onCloseActionModal();
                  this.props.removeComment(this.props.activeComment.id);
                  this.props.focusCommentBox(false);
                },
              },
            ],
            { cancelable: true }
          );
        break;
      case 3: // cancel
        this.props.onCloseActionModal();
        break;
      default:
        break;
    }
  }

  renderBody() {
    let feedActions = [
        {
          id: 1,
          name: I18n.t('edit'),
        },
        {
          id: 2,
          name: I18n.t('delete'),
        },
        {
          id: 3,
          name: I18n.t('cancel'),
        },
    ];

    const currentUser = this.props.currentUser;
    const comment = this.props.activeComment;
    if (comment) {
      if (comment.user.id !== currentUser.user.id) {
        feedActions.splice(0, 1);
      }
    }

    return (
      <TouchableWithoutFeedback>
        <View style={[styles.body, this.state.customStyle]}>
          {feedActions.map(action => (
            <TouchableOpacity key={action.id} onPress={() => this.onPressAction(action)}>
              <Text style={[styles.actionItemText]}>{action.name}</Text>
            </TouchableOpacity>
          ))}
        </View>
      </TouchableWithoutFeedback>
    );
  }

  render() {
    return (
      <Modal
        animationType="none"
        transparent
        visible={this.props.actionModalVisible}
        onRequestClose={() => {
          this.props.onCloseActionModal();
        }}
      >
        <View style={{ flex: 1 }}>
          <View style={styles.background} />
          <TouchableWithoutFeedback onPress={() => this.props.onCloseActionModal()}>
            <View style={styles.content}>{this.renderBody()}</View>
          </TouchableWithoutFeedback>
        </View>
      </Modal>
    );
  }
}

const styles = StyleSheet.create({
  background: {
    position: 'absolute',
    top: 0,
    right: 0,
    bottom: 0,
    left: 0,
    backgroundColor: '#000',
    opacity: 0.3,
  },
  content: {
    backgroundColor: 'transparent',
    position: 'absolute',
    top: 0,
    right: 0,
    bottom: 0,
    left: 0,
    flex: 1,
  },
  body: {
    backgroundColor: '#fff',
    position: 'absolute',
    borderRadius: 3,
    borderWidth: 1,
    borderColor: '#fff',
  },
  actionItemText: {
    padding: 13,
    paddingLeft: 20,
    paddingRight: 20,
    color: '#000',
    fontFamily: RALEWAY_REGULAR,
  },
  cancel: {
    color: 'red',
  },
});

CommentActionModal.propTypes = {
  onPressEditComment: PropTypes.func.isRequired,
  onCloseActionModal: PropTypes.func.isRequired,
  activeComment: PropTypes.shape({
    id: PropTypes.number,
  }),
  currentUser: PropTypes.shape({
    user: PropTypes.shape({
      id: PropTypes.number,
    }),
  }),
  actionModalVisible: PropTypes.bool,
  removeComment: PropTypes.func,
  focusCommentBox: PropTypes.func,
  commentPosition: PropTypes.shape({
    x: PropTypes.number,
    y: PropTypes.number,
  }).isRequired,
};

CommentActionModal.defaultProps = {
  activeComment: null,
  currentUser: null,
  actionModalVisible: false,
  removeComment: null,
  focusCommentBox: null,
};

function mapDispatchToProps(dispatch) {
  return bindActionCreators({ removeComment, focusCommentBox }, dispatch);
}

function mapStateToProps(state) {
  return {
    appTheme: state.appTheme,
    activeComment: state.activeComment,
    currentUser: state.currentUser,
  };
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(CommentActionModal);

How to solve issue “native base toast is not showing in react native modal”

How to solve issue “native base toast is not showing in react native modal”

If you are using native base in your project, you might encounter this issue when you want to use component native base Toast inside react native Modal component. Native base Toast

It’s very easy to fix this issue, we just need to create another class extend from native base Toast

import { Toast } from 'native-base';

class MyToast extends Toast {}

export default MyToast;

Then in the react native modal, you import it and use it just like you are using native base Toast

import MyToast from '../commons/myToast';

...

MyToast.show({
  text: I18n.t('please_select_segment'),
  buttonText: I18n.t('okay'),
  duration: 3000,
  position: 'bottom',
});

Thanks.

Create swipable modal like facebook’s comment modal

Hi guys, today I will create a swipable modal like facebook’s comment modal.

I would like to share it here so you guys can have an idea how to create a similar thing using react-native.

As you see in the video, the modal will be appeared from the bottom then swipe up slowly then stop at the top edge. User can also use there finger to swipe it up or down to make it disappeared.

Using this way, you just need to custom a little bit in the code the make it works as you want it does.

Let’s start.

The idea is: we will use react-native component “Modal” to make it show in fullscreen mode and display on top of the screen. We won’t use any animation properties of this “Modal” component, we will create our own animation using Animated which is provided by react-native.

Let’s have a look at the react-native components which we will use in this modal.

There are 3 main components which we will use to create this.

  1. Modal: use as the main component
  2. Animated: handle our custom animation (move from bottom to top)
  3. PanResponder: handle event when users use there fingers to move the modal up or down

Firstly, we create a class name CommentModal, let’s initialize the basic class structure

As you can see, the animationType prop in Modal we are not using it, instead we set it to “none”.

We also declare two proptypes: onClose and visible to be able to control the visibility and event onClose of the modal.

Now, we will create our own animation.

In the constructor, we declare two variables: pan and isAnimating.

  1. Pan: initial animation value with x and y. (refer to this https://facebook.github.io/react-native/docs/animated#docsNav for more information about it). In this modal, we only handle the swipable up and down, so x value is always 0, the initial y value will be screenHeight, because it will move from bottom to top.
  2. IsAnimating: to handle the panResponder, to avoid the conflict of the animation when the modal is moving.

Next step: We will create the animation move the modal from bottom to top when the modal is visible.

We create ComponentWillReceiveProps to catch the property “visible” and show modal with animation moving up from bottom to top. The modal need to be always existed in the place where we want to use it but the visible will be controlled and pass to it.

You can see in the showCommentAnimation function, we are using Animated.timing to move the model to position (0,0), mean the top edge of the screen.

The TIMING_CONFIG, we declared at the beginning with duration 300 and type of easing ( more detail here (https://facebook.github.io/react-native/docs/easing)

OK, we are done for the appear of the modal.

Now we will continue handle the panResponder to interact with the the modal by fingers.

Very easy, in this step, we will declare function componentWillMount.

componentWillMount() {
    if (!this) {
      return;
    }
    this.animatedValueX = 0;
    this.animatedValueY = 0;
    this.state.pan.x.addListener(value => (this.animatedValueX = value.value));
    this.state.pan.y.addListener(value => (this.animatedValueY = value.value));

    this.panResponder = PanResponder.create({
      // Ask to be the responder:
      onStartShouldSetPanResponder: () => false,
      onStartShouldSetPanResponderCapture: () => false,
      onMoveShouldSetPanResponder: (evt, gestureState) => {
        if (this.state.isAnimating) {
          return false;
        }
        if (gestureState.dy > 30 || gestureState.dy < -30) {
          return true;
        }
        return false;
      },
      onPanResponderGrant: () => {
        this.state.pan.setOffset({
          x: this.animatedValueX,
          y: this.animatedValueY,
        });
        this.state.pan.setValue({ x: 0, y: 0 }); // Initial value
      },
      onPanResponderMove: (evt, gestureState) => {
        this.setState({
          backgroundColor: 'transparent',
        });
        this.state.pan.setValue({ x: 0, y: gestureState.dy });
      },
      onPanResponderRelease: (evt, gestureState) => {
        // The user has released all touches while this view is the
        // responder. This typically means a gesture has succeeded
        // this.state.pan.flattenOffset(); // Flatten the offset so it resets the default positioning
        if (gestureState.vy <= -0.7 || gestureState.dy <= -300) {
          Animated.timing(this.state.pan, {
            toValue: { x: 0, y: -screenHeight },
            ...TIMING_CONFIG,
          }).start(() => {
            this.props.onClose();
          });
        } else if (gestureState.vy >= 0.5 || gestureState.dy >= 300) {
          Animated.timing(this.state.pan, {
            toValue: { x: 0, y: screenHeight },
            ...TIMING_CONFIG,
          }).start(() => {
            this.props.onClose();
          });
        } else {
          Animated.spring(this.state.pan, {
            toValue: 0,
          }).start();
          this.setState({
            backgroundColor: '#000',
          });
        }
      },
    });
  }

Let’s look at the code above:

  1. We declare panResponder to catch and handle event when user use there fingers to touch and move the modal.

onStartShoudSetPanResponder and onStartShoudSetPanResponderCapture, we set them to false, because we don’t need to catch those events, at the moment the modal appeared, we handle animation ourself (function showCommentModalAnimation).

We catch event onMoveShouldSetPanResponder, the condition inside the callback will return true if dy (the distance of moving y direction) > 30 or < -30. The reason is that we want to make sure what the finger is moving after touching to the screen. At the moment finger touch the screen, the dy will be 0, if it moves, the dy will change up or down.

We also check isAnimating value because we don’t want it to be conflict to the animation we create at the beginning (showCommentModalAnimation)

onPanResponderGrant

 

Inside this callback, we use setOffset to set the position at the moment the finger touch to the screen, to prevent the effect of the modal will be suddenly moved.

this.animatedValueX, this.animatedValueY we declared above.

And also we set the modal position to (0,0)

onPanResponderMode, we set the value dy to state pan to make the modal moving along with the finger.

onPanResponderRelease: We catch the velocity and the distance of the moving by Y direction to make the modal disappear. We also use the Animated Timing to make it move up or down to position -screenHeight or screenHeight.

Now we replace the modal body by Animated View to see the effect of the animation we have just created above

The first Animated.View to handle the background opacity will change from 0 -> 1 base of the position of the modal. We here use interpolate to get the value base on the modal position. More detail about interpolate here (https://facebook.github.io/react-native/docs/0.8/animations#interpolation

const interpolateBackgroundOpacity = this.state.pan.y.interpolate({
      inputRange: [-screenHeight, 0, screenHeight],
      outputRange: [0, 1, 0],
    });

handleGetStyle will handle the moving of the modal

We use transform to move the modal by position (x,y). More detail https://facebook.github.io/react-native/docs/animatedvaluexy#gettranslatetransform

Opacity just for the effect of appearance.

Now, It’s ready to run.

How to use this modal

Here is the fullcode, you guys can have look at it (here)

Please leave a comment bellow if you have any inquiries.

Full code of swipable Facebook’s comment modal

Full code of swipable Facebook’s comment modal, code explanation http://reactnative.vn/create-swipable-modal-like-facebooks-comment-modal/

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import {
  Modal,
  View,
  StyleSheet,
  Animated,
  Dimensions,
  PanResponder,
  StatusBar,
  TouchableWithoutFeedback,
  Platform,
  Easing,
} from 'react-native';

import CommentList from './commentList';
import CommentBox from './commentBox';

const dismissKeyboard = require('dismissKeyboard');

const { height: screenHeight } = Dimensions.get('window');
const TIMING_CONFIG = { duration: 300, easing: Easing.inOut(Easing.ease) };
class CommentModal extends Component {
  constructor(props) {
    super(props);
    this.state = {
      text: '',
      height: 50,
      isSaving: false,
      canSendComment: false,
      pan: new Animated.ValueXY({ x: 0, y: screenHeight }),
      isAnimating: false,
    };

    this.handleOnClose = this.handleOnClose.bind(this);
  }

  componentWillMount() {
    if (!this) {
      return;
    }
    this.animatedValueX = 0;
    this.animatedValueY = 0;
    this.state.pan.x.addListener(value => (this.animatedValueX = value.value));
    this.state.pan.y.addListener(value => (this.animatedValueY = value.value));

    this.panResponder = PanResponder.create({
      // Ask to be the responder:
      onStartShouldSetPanResponder: () => false,
      onStartShouldSetPanResponderCapture: () => false,
      onMoveShouldSetPanResponder: (evt, gestureState) => {
        if (this.state.isAnimating) {
          return false;
        }
        if (gestureState.dy > 30 || gestureState.dy < -30) {
          return true;
        }
        return false;
      },
      onPanResponderGrant: () => {
        this.state.pan.setOffset({
          x: this.animatedValueX,
          y: this.animatedValueY,
        });
        this.state.pan.setValue({ x: 0, y: 0 }); // Initial value
      },
      onPanResponderMove: (evt, gestureState) => {
        this.setState({
          backgroundColor: 'transparent',
        });
        this.state.pan.setValue({ x: 0, y: gestureState.dy });
      },
      onPanResponderRelease: (evt, gestureState) => {
        // The user has released all touches while this view is the
        // responder. This typically means a gesture has succeeded
        // this.state.pan.flattenOffset(); // Flatten the offset so it resets the default positioning
        if (gestureState.vy <= -0.7 || gestureState.dy <= -300) {
          Animated.timing(this.state.pan, {
            toValue: { x: 0, y: -screenHeight },
            ...TIMING_CONFIG,
          }).start(() => {
            this.props.onClose();
          });
        } else if (gestureState.vy >= 0.5 || gestureState.dy >= 300) {
          Animated.timing(this.state.pan, {
            toValue: { x: 0, y: screenHeight },
            ...TIMING_CONFIG,
          }).start(() => {
            this.props.onClose();
          });
        } else {
          Animated.spring(this.state.pan, {
            toValue: 0,
          }).start();
          this.setState({
            backgroundColor: '#000',
          });
        }
      },
    });
  }

  componentWillReceiveProps(newProp) {
    if (newProp.modalVisible) {
      this.showCommentAnimation();
    }
  }

  showCommentAnimation() {
    setTimeout(() => {
      if (Platform.OS === 'android') {
        StatusBar.setBackgroundColor('#000');
      }
    }, 100);
    this.setState({
      isAnimating: true,
    });
    Animated.timing(this.state.pan, {
      ...TIMING_CONFIG,
      toValue: { x: 0, y: 0 },
    }).start(() => {
      this.setState({
        isAnimating: false,
      });
    });
  }

  handleOnClose() {
    dismissKeyboard();
    if (Platform.OS === 'android') {
      StatusBar.setBackgroundColor(this.props.appTheme.mainColor);
    }

    Animated.timing(this.state.pan, {
      ...TIMING_CONFIG,
      toValue: { x: 0, y: screenHeight },
    }).start(() => {
      this.props.onClose();
    });
  }

  handleOnChange(event) {
    this.setState({
      text: event.nativeEvent.text,
      height: event.nativeEvent.contentSize.height + 10,
    });

    if (event.nativeEvent.text) {
      this.setState({
        canSendComment: true,
      });
    } else {
      this.setState({
        canSendComment: false,
      });
    }
  }

  handleGetStyle(opacity) {
    return [
      styles.container,
      {
        transform: [...this.state.pan.getTranslateTransform()],
        opacity: opacity,
      },
    ];
  }

  render() {
    const body = <CommentList />;
    const interpolateBackgroundOpacity = this.state.pan.y.interpolate({
      inputRange: [-screenHeight, 0, screenHeight],
      outputRange: [0, 1, 0],
    });
    const { appTheme } = this.props;
    return (
      <Modal
        animationType="none"
        transparent
        visible={this.props.modalVisible}
        onRequestClose={() => {
          this.handleOnClose();
        }}
      >
        <Animated.View style={[styles.background, { opacity: interpolateBackgroundOpacity }]} />
        <Animated.View
          style={this.handleGetStyle(interpolateBackgroundOpacity)}
          {...this.panResponder.panHandlers}
        >
          <TouchableWithoutFeedback>
            <View style={{ flex: 1 }}>
              <View style={styles.body}>{body}</View>
              <View style={[styles.seperator, { backgroundColor: appTheme.mainColor }]} />
              <CommentBox isCommentModal isRoundCorner onClose={this.handleOnClose} />
            </View>
          </TouchableWithoutFeedback>
        </Animated.View>
      </Modal>
    );
  }
}

const styles = StyleSheet.create({
  loadingScreen: {
    flex: 1,
    marginLeft: 10,
    marginRight: 10,
  },
  background: {
    opacity: 0,
    flex: 1,
    backgroundColor: '#000',
  },
  container: {
    flex: 1,
    backgroundColor: '#fff',
    borderColor: '#000',
    borderRadius: 10,
    position: 'absolute',
    top: 0,
    right: 0,
    bottom: 0,
    left: 0,
  },
  body: {
    flex: 1,
    backgroundColor: '#fff',
    borderTopLeftRadius: 10,
    borderTopRightRadius: 10,
  },
  seperator: {
    backgroundColor: '#ddd',
    height: 0.5,
    marginLeft: 10,
    marginRight: 10,
  },
});

CommentModal.propTypes = {
  onClose: PropTypes.func.isRequired,
  appTheme: PropTypes.shape({
    mainColor: PropTypes.string,
  }).isRequired,
  modalVisible: PropTypes.bool.isRequired,
};

function mapStateToProps(state) {
  return {
    appTheme: state.appTheme,
  };
}

export default connect(mapStateToProps)(CommentModal);

Validation flow in react native components

How to handle validation flow react native components.

Let’s say you have a parent component which will contains many children components.

Each children components are reusable, and you want to have specific validation for each components.

In the image, you can see, we have one container component with a save button.

Inside it, there are 3 children components which contain different forms with different fields (could be text or anything).

When click save button, we want to validate data from children component before sending to server, also display the validation message inside each children components.

To do it, we will define the data flow first.

We put the validation logic in container component.

To collect data from children components, we define a callback function onFormChange and pass it to each children components

From children components, we pass data back to parent container and validate data here.

And if we want to pass the validation message back to children, we pass it to data object and pass it back to children.

In children components, we catch event componentWillReceiveProps

componentWillReceiveProps(newProps) {
this.setState({ …newProps.data });
}

Now if we want to show the validation message from children component, we can get it from this.state.textValidationMessage without any problem.

This is how I handle validation flow in my react app. Please let me know by comment bellow if you guys have others solutions for this topic (validation flow react native)

[issue] React native could not connect to development server genymotion

React native cannot connect development server genymotion

There could be many reason, but one of reason which you guys might never noticed.

It tooked me some days (exactly 5 days) to figured out how to solve it. I researched and tried many ways I found from internet but no luck.

At the moment I intended to gave up, I figure out the root cause. The stupid skype was using 8080 port, that why I could not build react native to genymotion simulator. Oh my god, I was very happy and thought how stupid I am.

But anyhow, just want to share with you guys that one of the reason cause that error is skype. Check it as well when you encounter above issue.

React native cannot connect development server genymotion

 

[Performance Issue] React native app crashes after several seconds

App crashed after several seconds.

Sometime, app is running smoothly and suddenly crashed after several seconds or minutes, we don’t know what happened. With my experience, I found some reasons which could be your problems. So, lets check it out and see if one of them are your issues.

  1. The first reason could be somewhere in your code, you don’t check null or undefined before using that object. It’s easy to figure out this problem. Just need to enable debugging mode and use app for a while then you are able to reproduce problem.
  2. The second reason: it’s really tough to figure out this issue even in debugging mode. That’s heap memory size. You could encounter this issue if you are using many large PNG images in your app. This is a known issue which is mentioned in react-native official page (https://facebook.github.io/react-native/releases/0.23/docs/known-issues.html) . To fix this issue, there are some ways to do like this:
    1. If you know native code, you can write a module to reduce image size ( I don’t choose this way because I don’t know native code :D)
    2. Try to use images with size as small as possible. To do this, you can handle images from server side, whenever an image is uploaded to server, save this image to different versions such as: thumbnail, medium and actual size, save them to cdn. From client, fetch the properly images with specified size.

If you guys know others reason, please let me know then I can add them to the list, it would be useful for others in future.

Thanks

Create redux middleware to refresh token in react native app

Redux middleware to refresh token automatically

In your react native app, if you are using token to authenticate with server, you could encounter the token expire issue. The issue is that for example, your token timeout is 1 hour, you open app and leave it for a long time, the token will be expired, if at that moment, you do something which interact with server, those request will be failed because the token is timeout. What you have to do now is that send a refresh token to server to refresh the token, and use that token for new requests to server.

To do it in redux, we create a middle-ware to detect every requests to server. The idea is:

  1. In token object responded from server, it should contain a timeout
  2. Every request from client should go through this middle-ware before sending to server
  3. In this middle-ware, with every requests to server, check the token timeout, if token is expired, hold those request, send request to server to refresh token, wait until received new token, use this new token to send those request to server. If the token is still valid, let it be.

Now, let see the code

import * as types from './authenticationTypes';
import Api from '../libs/api';
import Utils from '../commons/utils';

export default function authMiddleware({ dispatch, getState }) {
  return (next) => (action) => {
    if (typeof action === 'function') {
      let state = getState();
      if(!state) {
        if(state.token && isExpired(state.token)) {
          // make sure we are not already refreshing the token
          if(!state.refreshTokenPromise) {
            return refreshToken(dispatch, state).then(() => next(action));
          } else {
            return state.refreshTokenPromise.then(() => next(action));
          }
        }
      }

    }
    return next(action);
  }
}

function isExpired(token) {
  let currentTime = new Date();
  let expires_date = new Date(token.expires_date);
  return currentTime > expires_date;
}

function refreshToken(dispatch, state) {
  let refreshTokenPromise = Api.post('/token', {
    grant_type: 'refresh_token',
    username: state.token.username,
    refresh_token: state.token.refresh_token
  }, null, true).then(resp => {
    dispatch({
      type: types.DONE_REFRESHING_TOKEN
    });
    dispatch({
      type: types.LOGIN_SUCCESS,
      data: resp
    });
    dispatch({
      type: types.SET_HEADER,
      header: {
        Authorization: resp.token_type + ' ' + resp.access_token,
        Instance: state.currentInstance.id
      }
    });
    return resp ? Promise.resolve(resp) : Promise.reject({
        message: 'could not refresh token'
    });
  }).catch(ex => {
    console.log('exception refresh_token', ex);
    dispatch({
      type: types.DONE_REFRESHING_TOKEN
    });
    dispatch({
      type: types.LOGIN_FAILED,
      exception: ex
    });
  });

  dispatch({
    type: types.REFRESHING_TOKEN,
    // we want to keep track of token promise in the state so that we don't     try to refresh the token again while refreshing is in process
    refreshTokenPromise
  });

  return refreshTokenPromise;
}

Lets go through the code.

  1. Create authMiddleware: every request will go to this middle-ware before sending to server.
refreshTokenPromise: just for make sure that if there are many requests to server at the same time, but we only send one refresh token request to server.
export default function authMiddleware({ dispatch, getState }) {
  return (next) => (action) => {
    // check if token is valid => return to current action
    // otherwise refresh token before returning to current action
    if (typeof action === 'function') {
      let state = getState();
      if(!state) {
        if(state.token && isExpired(state.token)) {
          // make sure we are not already refreshing the token
          if(!state.refreshTokenPromise) {
            return refreshToken(dispatch, state).then(() => next(action));
          } else {
            return state.refreshTokenPromise.then(() => next(action));
          }
        }
      }
    }
    return next(action);
  }
}

2. Create action to refresh token (see the code and comment above)

Note:

  • API is a class which I create to send request to server using Fetch library
  • If you don’t understand the code above, please take a look http://redux.js.org/

Redux middleware to refresh token automatically

Thanks

React native remote push notification Integrate remote push notification GCM, azure hubs and react native in android

React native remote push notification GCM, Azure hubs in android

Hi every one, I have finished integrating react native remote push notification with GCM and azure.
I realized that there is not many articles talk about this, it took me some days to figured out how to do.
Today is weekend, I decide to write it here, hope it can help others in some cases.

Before starting, I would like to say “sorry for my poor English”, if you guys don’t understand any part of this article, just let me know, I can fix it.
Everything can be fixed, right 😀

OK, Lets begin.

Firstly, we are going to use this third party library (https://github.com/zo0r/react-native-push-notification) to help doing some stuff behind the scene.

Just follow the instruction on that page and install the lib to your project.

I just have a notice here, you could get a building error after installing the lib even you followed exactly the instruction :D.

No worries, I’m here to help you.

The reason is the path to your project is still fine until you install this push notification lib, there could be two reasons here:

  1. Your project path is long, when combine with this lib, there are something inside this lib which has the path is also long -> error
  2. Your project path has space between -> error

How to fix : you just need to change your project directory to a new place which has a path as sort as possible.

If it’s still not working after changing the to the new sort path, then try to choose another path without space between.

We have finished the first step to install push notification lib.

Lets move to next step: register a GCM server.

To do it, navigate to this page (https://console.developers.google.com). Click to Google Cloud Messaging url

Previously, it will navigate you to google cloud messaging page, but starting from Sept. 2016 new server key can only be created in the Firebase Console using the Cloud Messaging tab of the Settings panel.

This is google policy, so we just need to follow them.

On this page, click on Go to console. It will navigate us to firebase management screen.

Click “Create new project”. Fill up the fields and click Create to create a new project.

When creating process is finished, it will navigate you to management screen.

Click to Project settings to get into the setting page.

Click on tab cloud messaging

Ah, from this page we can see the server key and sender id. These two values are very important, we will use them right now in the next step.

We have finished creating FCM (Firebase cloud messaging) server.

This server in charge of listening event from our azure hubs and send notification to client (android or ios phone).

Now, we continue setup our azure server to connect to FCM.

To to that, you can follow the instruction from Microsoft, link here (https://docs.microsoft.com/en-us/azure/notification-hubs/notification-hubs-android-push-notification-google-gcm-get-started).

It that document, they also guide us how to create GCM server, but that doc is obsoleted, that why I had to create a new instruction above.

Follow that document will help you connect to FCM server, only one thing I should mention here is that the server key which is created in above step now is using in azure in this step.

I won’t mention how to create service to send notifications from your system to azure, the reason is that I’m not back-end guy so I’m not good at this part.

But you can follow the instruction from the URL which I added above to setup it by yourself ( it’s a good document by the way :D).

Okay. We have finished connecting FCM to azure.

Now move to next step, configure our react native app connecting to FCM server (should be interesting and easiest part).

Lets open the code.

Now you can see, we are using sender ID which is created in above step (FCM settings page).

In this step, you don’t need to care about onRegister event, I will explain you guys later. So, to make the phone to be able to connect to FCM, very simple, just follow these steps:

  1. Create a component
  2. In componentDidMount event, write code to config push notification which is mentioned clearly in the push notification document
  3. Add the sender ID(collect it from FCM server, settings page)

That’s it. You should be able to connect and receive push notification from now.

One notice here: remote push notification is not working in genymotion (not sure why, the reason could be because of I’m using free version of it).

Anyway, what I mean is that lets use real devices to test it.

Now your devices can receive notification, but the thing is we are receiving all notifications, it’s not really good in some cases.

For example: in Facebook, if we post a new feed, we want only users who commented or liked that post will receive notification, not all.

If you did not comment or like that feed, you should not receive any notification related to that post, it’s very annoying if you haven’t touched that post but keep receiving notification from that post.

To restrict them, we will move to next step.

In this step, we will restrict notifications which are send to corresponding users only. The idea is:

  1. From client (android or ios phone), connect to FCM and register a token ID
  2. Send token ID and user information (such as user ID, user role, …) to our server (back-end) for notifying that I have registered to FCM and received this token, please handle and send correct messages to me, don’t send all messages  or I’ll kill you.
  3. Server received those information, base on those information (user id, user role …), handle the messages and send to FCM with token id.
  4. Base on the token id which received from our server, FCM server send the corresponding messages to client.

As I mentioned above, I will not guide you how to do from back-end side, the attached link will help set it up.

But from front-end side, I’ll give you guys a hand :D.

Now, it’s time to use the event onRegister, this event will be fired when app open and return a token id, use this token send to server.

In case of you are using azure, then the endpoint should contain two parameters: RegistrationID and Platform (gcm, apns, wns).

  1. RegistrationID is token id,
  2. Platform: gcm (google cloud messaging). apns (push notification server from apple), wns( window notification server).

Now, let me see if I miss anything.

hmm …. I think it’s pretty enough for you guys to start integrating with GCM now.

If you have any inquiries, just leave a comment or send me message by mail baotoan1905@gmail.com, skype: baotoan1905, facebook: baotoan1905@gmail.com

Chào thân ái và quyết thắng.