简体   繁体   中英

React: Incorrect Modal component gets rendered called after axios call

I have taken a modal component from the react-semantic-ui and configured it to either have a confirm/alert like dialog or a traditional Modal.

I did this mostly to practice making more useful & reusable components and indirectly to practice managing and moving state for it to a store ie redux...

import React, { Component } from 'react'
import { Button, Modal } from 'semantic-ui-react'
import PropTypes from 'prop-types'

import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { logOutUser  } from '../../store/reducers/users/index'
import { modalStateOn, modalStateOff  } from '../../store/reducers/ui/index'


class MyModal extends Component {

 close = () => {
  const { modalStateOff } = this.props
  modalStateOff();
 }

 logOutUser = () => {
  const { logOutUser } = this.props
  logOutUser()
 }

 render() {
  const { modalActive } = this.props

   return (
    <>
      <Modal dimmer={'blurring'} centered={true} size={'mini'} open={modalActive} onClose={this.close}>
        <Modal.Header>
         <p>{this.props.message}</p>
        </Modal.Header>
        <Modal.Actions>
         {this.props.isAlertModal ?
         <Button
          color='black'
          onClick={this.close}
          content={this.props.affirmativeUsed}
         />
         :
        <>
         <Button
           color='black'
           onClick={this.close}
          >
           No
          </Button>
          <Button
           positive
           icon='checkmark'
           labelPosition='right'
           content={this.props.affirmativeUsed}
           onClick={() => { this.close(); this.logOutUser() }}
          />
        </>
         }
        </Modal.Actions>
      </Modal>
    </>
   )
 }
}

MyModal.propTypes = {
 message: PropTypes.string,
 affirmativeUsed: PropTypes.string
}

function mapStateToProps(state) {
 const { ui } = state
 const { modalActive } = ui

 return { modalActive }
}
const mapDispatchToProps = dispatch =>
 bindActionCreators({ logOutUser, modalStateOn, modalStateOff }, dispatch)

export default connect(mapStateToProps, mapDispatchToProps)(MyModal)

This worked fantastically for a Modal I wanted to use for Logging out:

=======================================
home | profile | *dashboard | logout          /* You get a Modal to confirm your desire to log out */
=======================================

However on my profile page I have created an ImageUploader component which handles loading images for that page, As you might have guessed by now, I want a Modal to pop up as well when the axios request is successful and one for a failure to give some feedback...

=======================================
home | *profile | dashboard | logout          /* You get a Modal to confirm your desire to log out */
=======================================

        -------------
       | choose file |                       /* AND!!! Get a Modal to confirm with a success OR failure!!
        -------------

This is the ImageUploader component:

import React, { Component } from 'react';
import './ImageUploader.css';
import FooModal from '../Modal/MyModal' 
import axios from 'axios';

import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { loadAvatar } from '../../store/reducers/users/index'
import { modalStateOn, modalStateOff } from '../../store/reducers/ui/index'

class ImageUploader extends Component {
 constructor(props) {
  super(props);
    this.uploadImage = this.uploadImage.bind(this);
 }

 componentDidUpdate(previousProps, previousState) {
  if (previousProps.userAvatar !== this.props.userAvatar) {
  console.log("this.props.userAvatar in componentDidUpdate", this.props.userAvatar);
   loadAvatarImage(this.props.userAvatar)
  }
 }

 setDefaultImage(){
  var defaultImage =  '../../static/profile-avatars/assets/default-img.jpg';
  this.loadAvatarImage(defaultImage)
 }

 loadAvatarImage(img) {
  var { loadAvatar } = this.props;
  loadAvatar(img)
 }

 uploadImage(e, method) {

  const { modalStateOn } = this.props
  console.log('this.props in ImageUploader uploadImageFunction', this.props)

  if (method === "multer") {

   let imageFormObj = new FormData();

   imageFormObj.append("imageName", "multer-image-" + Date.now());
   imageFormObj.append("imageData", e.target.files[0]);

   this.loadAvatarImage(window.URL.createObjectURL(e.target.files[0]))

   var config = { headers: { 'content-type': 'multipart/form-data' }}
   axios.post(`http://localhost:8016/images/uploadmulter`, imageFormObj, config )
    .then((data) => {
     if (data.data.success) {
      console.log("data ", data);
      modalStateOn();
     return (
       <FooModal
       isAlertModal={true}
       open={true}
       affirmativeUsed="Yes"
       message="Your image has been uploaded succesfully"
      />
     )
     }
    })
    .catch((err) => {
     alert("Error while uploading image using multer");
      this.setDefaultImage();
    });
  }
    e.stopPropagation();

 }

 render() {
  var {  userAvatar } = this.props
  return (
   <>
    <div className="main-container">
     <h3 className="main-heading">Image Upload App</h3>

     <div className="image-container">
      <div className="process">
       <h4 className="process__heading">Process: Using Multer</h4>
       <p className="process__details">Upload image to a node server, connected to a MongoDB database, with the help of multer</p>
       <form action="/uploadmulter" method="post" encType="multipart/form-data" >
        <input type="file" name="avatar" className="process__upload-btn"
         onChange={(e) => {
          this.uploadImage(e, "multer");
        }} />
        <img src={userAvatar} alt="upload-image" className="process__image" />
       </form>
      </div>
     </div>
    </div>
  </>
  );
 }
}

function mapStateToProps(state) {
 const { ui, users } = state
 const { userAvatar } = users
 const { modalActive } = ui

 return { userAvatar, modalActive }
}

const mapDispatchToProps = dispatch =>
 bindActionCreators({ loadAvatar, modalStateOn }, dispatch)

export default connect(mapStateToProps, mapDispatchToProps)(ImageUploader)

Interestingly enough the Modal for the logging out action get rendered instead of the one in the ImageUploader ???

AND

When I pass in the prop value to isAlertModal it is ignored?!

  <FooModal
   isAlertModal={true}
   open={true}
   affirmativeUsed="Yes"
   message="Your image has been uploaded succesfully"
  />

So I suppose maybe I have to unmount the Modal in the nav to allow the image loader Modal to propagate?

*Keep in mind in the uploadImage function has e.stopPropagation(); called, but no dice!

Any help would be appreciated!

UPDATE

As Eneias suggested the rendering of this component (ImageModal) goes in the render function. Now this worked however that Modal is rendering for the logging out function.

Your FooModal should be on render funciton instead of the callback function.

The render function is called every time your state or your props change. So, you should have a flag in your state isUploaded , or something like that, and use it as condition to render your FooModal .

On the callback function of your post method, you simply update the state. It will trigger the render function again with the new state.

So I discovered what the problem was:

1. The way I was breaking up the Modal component was incorrect!!!

 <Modal dimmer={'blurring'} centered={true} size={'mini'} open={modalActive} onClose={this.close}>
  <Modal.Header>
   <p>{this.props.message}</p>
  </Modal.Header>
    <Modal.Actions>
     {this.props.isAlertModal ?
      <Button
       color='black'
       onClick={this.close}
       content={this.props.affirmativeUsed}
      />
     :
     <>
      <Button
       color='black'
       onClick={this.close}
      >
       No
      </Button>
      <Button
       positive
       icon='checkmark'
       labelPosition='right'
       content={this.props.affirmativeUsed}
       onClick={() => { this.close(); this.logOutUser() }}
      />
     </>
    }
   </Modal.Actions>
 </Modal>

Had only one <Model.Header> , this.props.message and <Modal.Actions> serving two out comes in the ternary:

It should be written like this:

    <>
     <Modal dimmer={'blurring'} centered={true} size={'mini'} open={open} onClose={this.close}>
      {avatarModalActive === true ?
       <>
        <Modal.Header>
         <p>{this.props.message}</p>
        </Modal.Header>
         <Modal.Actions>
         <Button
          color='black'
          onClick={()=> {this.close()}}
          content={this.props.affirmativeUsed}
         />
         </Modal.Actions>
       </>
         :
        <>
        <Modal.Header>
         <p>{this.props.message}</p>
        </Modal.Header>
        <Modal.Actions>
         <Button
          color='black'
          onClick={this.close}
         >
           No
         </Button>
         <Button
          positive
          icon='checkmark'
          labelPosition='right'
          content={this.props.affirmativeUsed}
          onClick={() => {this.close(); this.logOutUser()}}
         />
         </Modal.Actions>
        </>
         }
      </Modal>
    </>

 And I would up realizing I had one piece of `state` serving both Modals.

    const { avatarModalActive, modalActive } = this.props /* Now there are two pieces of state managing each modal */
      var open;
    
      if (avatarModalActive) open = avatarModalActive 
      else open = modalActive 

This is the complete Modal File:

import React, { PureComponent, Component } from 'react'
import { Button, Modal } from 'semantic-ui-react'
import PropTypes from 'prop-types'

import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { logOutUser  } from '../../store/reducers/users/index'
import { avatarModalStateOff, modalStateOff  } from '../../store/reducers/ui/index'


class MyModal extends PureComponent {
 constructor(props) {
  super(props);
  this.state = {
   isAlertModal: this.props.isAlertModal
  }
 }

 componentDidMount() {
  const { isAlertModal } = this.props;
  this.setState({ isAlertModal: isAlertModal})
 }

 close = () => {
  if (this.props.avatarModalActive){
   this.props.avatarModalStateOff();
  }
  this.props.modalStateOff();
 }

 logOutUser = () => {
  const { logOutUser } = this.props
  logOutUser()
 }

 render() {
  const { avatarModalActive, modalActive } = this.props
  var open;

  if (avatarModalActive) open = avatarModalActive
  else open = modalActive

   return (
    <>
     <Modal dimmer={'blurring'} centered={true} size={'mini'} open={open} onClose={this.close}>
      {avatarModalActive === true ?
       <>
        <Modal.Header>
         <p>{this.props.message}</p>
        </Modal.Header>
         <Modal.Actions>
         <Button
          color='black'
          onClick={()=> {this.close()}}
          content={this.props.affirmativeUsed}
         />
         </Modal.Actions>
       </>
         :
        <>
        <Modal.Header>
         <p>{this.props.message}</p>
        </Modal.Header>
        <Modal.Actions>
         <Button
          color='black'
          onClick={this.close}
         >
           No
         </Button>
         <Button
          positive
          icon='checkmark'
          labelPosition='right'
          content={this.props.affirmativeUsed}
          onClick={() => {this.close(); this.logOutUser()}}
         />
         </Modal.Actions>
        </>
         }
      </Modal>
    </>
   )
 }
}

MyModal.propTypes = {
 message: PropTypes.string,
 affirmativeUsed: PropTypes.string
}

function mapStateToProps(state) {
 const { ui } = state
 const { modalActive, avatarModalActive } = ui

 return { modalActive, avatarModalActive }
}
const mapDispatchToProps = dispatch =>
 bindActionCreators({ logOutUser, avatarModalStateOff, modalStateOff }, dispatch)

export default connect(mapStateToProps, mapDispatchToProps)(MyModal)

And this is the Redux file:

`ui.js`

/* initial state */
export var uiStartState = { modalActive: false, avatarModalActive: false, }

/* action types */
export const actionTypes = {
    MODAL_ACTIVE: 'MODAL_ACTIVE',
    MODAL_INACTIVE: 'MODAL_INACTIVE',
    AVATAR_MODAL_ACTIVE: 'AVATAR_MODAL_ACTIVE',
    AVATAR_MODAL_INACTIVE: 'AVATAR_MODAL_INACTIVE',
}

/* reducer(s) */
export default function ui(state = uiStartState, action) {
    switch (action.type) {

        case actionTypes.MODAL_ACTIVE:
            return Object.assign({}, state, { modalActive: true });

        case actionTypes.MODAL_INACTIVE:
            return Object.assign({}, state, { modalActive: false });

        case actionTypes.AVATAR_MODAL_ACTIVE:
            return Object.assign({}, state, { avatarModalActive: true });

        case actionTypes.AVATAR_MODAL_INACTIVE:
            return Object.assign({}, state, { avatarModalActive: false });

        default:
            return state
    }
};

/* actions */
export const modalStateOn = () => {
    return { type: actionTypes.MODAL_ACTIVE }
}

export const modalStateOff = () => {
    return { type: actionTypes.MODAL_INACTIVE }
}

export const avatarModalStateOn = () => {
    return { type: actionTypes.AVATAR_MODAL_ACTIVE }
}

export const avatarModalStateOff = () => {
    return { type: actionTypes.AVATAR_MODAL_INACTIVE }
}

Hope this makes sense!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM