Ionic/React/TypeScript,using react-router history.push, history.replace and history.goBack Animation triggers twice

Viewed 1964

So basically i'm Working on a Ionic/React Typescript application and there is this strange page transition that occurs twice when I navigate thought the app (see gif below)

I checked and it's not the renders that's call twice as the componentWillMount/didMount/willUnmount are all trigger once like it should for the pusher page and also for the pushed page.

As you can see, the transition always happens twice and found no solution anywhere ... Versions used : - Ionic : 5.2.1 - React : 16.9.0 (npm package) - Typescript 3.6.3 (npm package)


Here is the code for the page with the "Diplomes" Title:
class Diplome extends React.Component<RouteComponentProps> {

  // Function who redirect to the url for edit an situation
  redirectUrlToEditSituation = () => {
    this.props.history.push('/app/edit/diplome/', null);
  }

  render() {
    return (
      <IonPage>
          <IonHeader>
            <IonToolbar>
                <ButtonHambToolBar />
                <ToolBarLogo toolbarName={ToolBarName.DIPLOME}/>
            </IonToolbar>
          </IonHeader>
          <IonContent>
            <FabButtonAction ClickHandler={() => this.redirectUrlToEditSituation()} icon={add}/>
            <GenericList type={ModelType.DIPLOME}/>
          </IonContent>
      </IonPage>
    );
  }
}

export default (Diplome);

And here is the page with the "Editions" title when click on the button :

class DiplomeEdit extends Component<DiplomeEditProps, {
    dataIsLoad: boolean,
    label_field: string,
    isAnCreate: boolean,
    openModalDelete: boolean,
    isValidation: boolean,
    currentDiplome: any
}>
{

    constructor(props: DiplomeEditProps) {
        super(props);
        this.state = {
            label_field: '',
            isAnCreate: true,
            openModalDelete: false,
            dataIsLoad: false,
            currentDiplome: '',
            isValidation: false,
        };
    }

    async componentWillMount() {
        console.log("component will mount");
        await this.getCurrentDiplomeToUpdate();
    }

    // Function who check if they are param on the url
    // If param exist get the current diplome on the store
    getCurrentDiplomeToUpdate = async () => {
        if (this.props.match.params.idDiplome !== undefined) {

            const diplomes: DiplomeInterface[] = this.props.diplomes;
            if (diplomes.length > 0) {
                const currentDiplomeReceive: DiplomeInterface | undefined = diplomes.find((res: DiplomeInterface) => {
                    return this.props.match.params.idDiplome === res.idDiplome;
                });

                if (currentDiplomeReceive !== undefined) {
                    this.setState({
                        isAnCreate: false,
                        label_field: currentDiplomeReceive.labelDiplome,
                        currentDiplome: currentDiplomeReceive,
                        dataIsLoad: true,
                    });
                }
            }

        } else {
            this.setState({
                isAnCreate: true,
                dataIsLoad: true,
            });
        }
    }

    // Function who render update button
    renderUpdateButton = () => {
        return (
            <div className="contenaire_button_action_situation_edit">
                <Button hidden={this.state.isAnCreate}
                    onClick={() => this.openOrCloseModalDeleteDialog()}
                    className="button_delete_situation">Supprimer</Button>
                <Button
                    onClick={e => this.actionClick()}
                    className="button_action_situation_edit">Enregistrer</Button>
            </div>
        );
    }

    // Function who delete diplome on the databse and on the store
    removeDiplome = async () => {
        await REQUEST.deleteDiplome(this.state.currentDiplome.idDiplome);
        store.dispatch(DELETE_DIPLOME(this.state.currentDiplome.idDiplome));
        this.props.history.replace('/app/diplomes');
    }

    // Function who get the label and id for create new diplome on the databae
    // Reset the state to the default init
    saveDiplome = async () => {
        const obj: DiplomeInterface = {
            idDiplome: Math.random().toString(36).substr(2, 9),
            labelDiplome: this.state.label_field.toLowerCase(),
        };
        this.setState({
            label_field: '',
            isAnCreate: false,
            openModalDelete: false,
        });
        await REQUEST.postDiplome(obj);
        store.dispatch(ADD_DIPLOME(obj));
        //this.props.history.replace('/app/diplomes');
        window.smartAlert("Diplome ajouté avec succès", "success", 5000);
        this.props.history.goBack();
    }

    // Function who update a diplome on the store
    // Need the current diplome
    // Label update
    updateDiplome = async () => {
        const currentDiplomeReceive: DiplomeInterface = this.state.currentDiplome;

        const obj: DiplomeInterface = {
            idDiplome: currentDiplomeReceive.idDiplome,
            labelDiplome: this.state.label_field,
        };

        if (this.props.userConnected.typeAccount === TypeConnect.ADMIN) {
            await REQUEST.updateDiplome(obj);
            store.dispatch(UPDATE_DIPLOME(obj));
            this.props.history.replace('/app/diplomes');
        }
    }

    // Function who checked what action we need
    actionClick = () => {
        if (this.state.isAnCreate) {
            this.saveDiplome();
        } else {
            this.updateDiplome();
        }
    }

    // Function call when input change
    inputChange = (e: any) => {
        this.setState({
            label_field: e.target.value,
        });
    }

    // Function who change the openModalDelete to true
    openOrCloseModalDeleteDialog = () => {
        if (this.props.userConnected.typeAccount === TypeConnect.ADMIN) {
            this.setState({
                openModalDelete: !this.state.openModalDelete,
            });
        }
    }

    render() {

        return (
            <IonPage>
                <IonHeader>
                    <IonToolbar className="task_cat_toolbar">
                        <IonBackButton
                            className="situation_edit_back_button" />
                        <ToolBarLogo toolbarName={ToolBarName.EDIT} />
                    </IonToolbar>
                </IonHeader>

                <IonContent>
                    <div className="contenaire_edit">
                        <div className="contenaire_form_situation">
                            <div className="contenaire_field">
                                <TextField
                                    onChange={e => this.inputChange(e)}
                                    value={this.state.label_field}
                                    className="field_form_situation"
                                    label="Libelle" />
                            </div>

                            <div className="container_task_situation_edit">

                                {!this.state.dataIsLoad && <SpinnerCircular />}

                                {this.renderUpdateButton()}

                                {
                                    this.state.openModalDelete &&
                                    <DialogDelete
                                        actionRemove={() => this.removeDiplome()}
                                        open={this.state.openModalDelete}
                                        actionCloseModal={() => this.openOrCloseModalDeleteDialog()} />
                                }

                            </div>
                        </div>
                    </div>
                </IonContent>

            </IonPage>
        );
    }

}

const mapStateToProps = (state: DiplomeEditProps) => ({
    diplomes: state.diplomes,
    network: state.network,
    role: state.role,
    userConnected: state.userConnected,
});

export default connect(mapStateToProps)(DiplomeEdit);
1 Answers

You just can't declare componentWillMount lifecycle method as async.

From the React docs about UNSAFE_componentWillMount()

Avoid introducing any side-effects or subscriptions in this method.
For those use cases, use componentDidMount() instead.

and React blog "Update on Async Rendering" (03/2018):

One of the biggest lessons we’ve learned is that some of our legacy component
lifecycles tend to encourage unsafe coding practices. They are:

    componentWillMount
    componentWillReceiveProps
    componentWillUpdate

These lifecycle methods have often been misunderstood and subtly misused; 
furthermore, we anticipate that their potential misuse may be more problematic
with async rendering. Because of this, we will be adding an “UNSAFE_” prefix
to these lifecycles in an upcoming release. (Here, “unsafe” refers not to 
security but instead conveys that code using these lifecycles will be more 
likely to have bugs in future versions of React, especially once async 
rendering is enabled.)

Victoria Fluharty wrote the helpful post componentWillMount() vs componentDidMount() on that topic.

tl;dr: if you have async logic, you need to move it to componentDidMount() (or migrate to more modern approaches like functional components with React hooks instead of the partly-deprecated lifecycle methods.

You can declare componentDidMount() as async, but there might be slight caveats:

Related