React:在 axios 调用之后,渲染了错误的模态组件。

huangapple go评论88阅读模式
英文:

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.

我已经使用react-semantic-ui中的模态组件,并配置它可以具有确认/警告对话框或传统模态。

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

我主要这样做是为了练习创建更有用且可重用的组件,间接练习管理和将状态移动到存储中,即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...

但是在我的个人资料页面上,我创建了一个ImageUploader组件,用于处理该页面的图像加载,正如你现在可能已经猜到的那样,当axios请求成功时,我也希望弹出一个模态框,并在失败时弹出一个以提供一些反馈...

=======================================
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:

这是ImageUploader组件:

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

英文:

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 i.e. redux...

import React, { Component } from &#39;react&#39;
import { Button, Modal } from &#39;semantic-ui-react&#39;
import PropTypes from &#39;prop-types&#39;

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


class MyModal extends Component {

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

 logOutUser = () =&gt; {
  const { logOutUser } = this.props
  logOutUser()
 }

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

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

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

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

 return { modalActive }
}
const mapDispatchToProps = dispatch =&gt;
 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 &#39;react&#39;;
import &#39;./ImageUploader.css&#39;;
import FooModal from &#39;../Modal/MyModal&#39; 
import axios from &#39;axios&#39;;

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

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(&quot;this.props.userAvatar in componentDidUpdate&quot;, this.props.userAvatar);
   loadAvatarImage(this.props.userAvatar)
  }
 }

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

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

 uploadImage(e, method) {

  const { modalStateOn } = this.props
  console.log(&#39;this.props in ImageUploader uploadImageFunction&#39;, this.props)

  if (method === &quot;multer&quot;) {

   let imageFormObj = new FormData();

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

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

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

 }

 render() {
  var {  userAvatar } = this.props
  return (
   &lt;&gt;
    &lt;div className=&quot;main-container&quot;&gt;
     &lt;h3 className=&quot;main-heading&quot;&gt;Image Upload App&lt;/h3&gt;

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

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

 return { userAvatar, modalActive }
}

const mapDispatchToProps = dispatch =&gt;
 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?!

  &lt;FooModal
   isAlertModal={true}
   open={true}
   affirmativeUsed=&quot;Yes&quot;
   message=&quot;Your image has been uploaded succesfully&quot;
  /&gt;

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.

答案1

得分: 3

你的 FooModal 应该在渲染函数中,而不是回调函数中。

渲染函数在每次状态或属性更改时都会被调用。因此,你应该在你的状态中有一个标志 isUploaded 或类似的东西,并将其用作渲染你的 FooModal 的条件。

在你的 post 方法的回调函数中,只需更新状态。它将使用新状态再次触发渲染函数。

英文:

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.

答案2

得分: 0

I discovered the problem in your code. The way you were breaking up the Modal component was incorrect. Here's the corrected version:

<>
  <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>
</>

Also, you had one piece of state serving both Modals, which has been updated as follows:

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

if (avatarModalActive) open = avatarModalActive;
else open = modalActive;

This should resolve the issues in your code.

英文:

So I discovered what the problem was:

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

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

Had only one &lt;Model.Header&gt;, this.props.message and &lt;Modal.Actions&gt; serving two out comes in the ternary:

It should be written like this:

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

 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 &#39;react&#39;
import { Button, Modal } from &#39;semantic-ui-react&#39;
import PropTypes from &#39;prop-types&#39;

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


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

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

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

 logOutUser = () =&gt; {
  const { logOutUser } = this.props
  logOutUser()
 }

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

  if (avatarModalActive) open = avatarModalActive
  else open = modalActive

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

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

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

 return { modalActive, avatarModalActive }
}
const mapDispatchToProps = dispatch =&gt;
 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: &#39;MODAL_ACTIVE&#39;,
    MODAL_INACTIVE: &#39;MODAL_INACTIVE&#39;,
    AVATAR_MODAL_ACTIVE: &#39;AVATAR_MODAL_ACTIVE&#39;,
    AVATAR_MODAL_INACTIVE: &#39;AVATAR_MODAL_INACTIVE&#39;,
}

/* 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 = () =&gt; {
    return { type: actionTypes.MODAL_ACTIVE }
}

export const modalStateOff = () =&gt; {
    return { type: actionTypes.MODAL_INACTIVE }
}

export const avatarModalStateOn = () =&gt; {
    return { type: actionTypes.AVATAR_MODAL_ACTIVE }
}

export const avatarModalStateOff = () =&gt; {
    return { type: actionTypes.AVATAR_MODAL_INACTIVE }
}

Hope this makes sense!

huangapple
  • 本文由 发表于 2020年1月7日 01:10:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/59616242.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定