Search code examples
javascriptajaxreactjsmodal-dialogsuperagent

ReactJS: show modal with loading message during superagent ajax request


I'm having trouble showing a modal dialog with loading message during ajax request(with superagent).

I have a reactjs app that has a single input field, a button, and a modal region. I want it to work like so...

  1. A user inserts a keyword into the input field and clicks on the button.

  2. Once the button has been clicked, an ajax call is made to scrape information from a certain website using the keyword.

  3. During the ajax call, a modal dialog that shows a loading message appears.

  4. Once the the ajax call is over, the modal dialog is closed and a list of information that has been scraped will be listed under the input field.

I can't seem to get 3. to work.

Here's what I have: I tried putting in a method that changes the props of the modal component before and after the ajax call that didn't work.

Any help is appreciated!

app.js

import React from 'react'
import ReactDOM from 'react-dom'
import PageBody from './pageBody'

ReactDOM.render(
     <PageBody />
    ,document.getElementById('root')
)

pageBody.js

import React, {Component} from 'react'
import Search from './Search'
import Modal from './modal'

export default class PageBody extends Component{
    constructor (props){
        super(props)
        this.state = {
             is_crawling: false
        }
    }

    crawlState (is_crawling) {
        this.setState({is_crawling: is_crawling})
    }

    render () {
        const show_modal = this.state.is_crawling
        this.crawlState = this.crawlState.bind(this)

        return (
            <div>
                <Search crawlState={this.crawlState}/>
                <Modal show_modal={show_modal}/>
            </div>
        )
    }
}

search.js

import React, {Component} from 'react'
import request from 'superagent'

export default class Search extends Component{
    constructor (props) {
        super(props)
        this.state = {
            keyword: ''
            ,result: []
        }
    }

    // method used to make ajax request
    crawl (){
        const keyword = this.state.keyword
        this.props.crawlState(true) // set crawlstate as true to show modal
        request // actual ajax request (superagent)
            .get('/crawl')
            .query({keyword: keyword})
            .end((err, res) => {
                if (err) console.log('superagent failed')
                const r = res.body.result
                this.setState({result: r})
            })
        this.props.crawlState(false) // set crawlstate as false to hide modal
    }

    onChangeHandler (e) {
        this.setState({keyword: e.target.value})
    }

    render () {
        const onChangeHandler = e => this.onChangeHandler(e)
        const crawl = this.crawl()
        const keyword = this.state.keyword
        const arr = this.state.result.map((e, idx) => {
            return <div key={idx}>{e}</div>
        })

        return (
            <div>
                <input type="text" onChange={onChangeHandler} value={keyword} />
                <button onClick={crawl}>CRAWL</button>
                {arr}
            </div>
        )
    }
}

modal.js

import React, {Component} from 'react'

export default class Modal extends Component{
    constructor (props) {
        super(props)
        this.state = {
            show: false
        }
    }

    componentWillReceiveProps(nextProps){
        const show_modal = nextProps.show_modal
        this.setState({show: show_modal})
    }

    render () {
       if (this.state.show){
            return <div id="modal"></div>
        } else {
            return null
        }
    }
}

Solution

  • In pageBody.js component,you should bind crawlState() function inside the constructor not inside the render or you can use arrow function like crawlState = () => {}

    In search.js component, use this.crawl on onClick function rather than making new const variable.

    You should bind crawl function to use this inside the function and you have called this.props crawlState() in the same level without any conditions which means you have called setState() twice at the same time which you shouldn't do, so you should call this.props crawlState(false) inside end after the request completion.

    pageBody.js

    import React, {Component} from 'react'
    import Search from './Search'
    import Modal from './modal'
    
    export default class PageBody extends Component{
      constructor (props){
        super(props)
        this.state = {
          is_crawling: false
        }
        this.crawlState = this.crawlState.bind(this)
    
      }
    
      crawlState (is_crawling) {
        this.setState({is_crawling: is_crawling})
      }
    
      render () {
        const show_modal = this.state.is_crawling;
    
        return (
            <div>
              <Search crawlState={this.crawlState}/>
              <Modal show_modal={show_modal}/>
            </div>
        )
      }
    }
    

    search.js

    import React, {Component} from 'react'
    import request from 'superagent'
    
    export default class Search extends Component{
      constructor (props) {
        super(props)
        this.state = {
          keyword: ''
          ,result: []
        }
      }
    
      // method used to make ajax request
      crawl = () => {
        const keyword = this.state.keyword
        this.props.crawlState(true) // set crawlstate as true to show modal
        request // actual ajax request (superagent)
            .get('/crawl')
            .query({keyword: keyword})
            .end((err, res) => {
              if (err) console.log('superagent failed')
              const r = res.body.result
              this.setState({result: r})
              this.props.crawlState(false) // set crawlstate as false to hide modal
            })
      }
    
      onChangeHandler (e) {
        this.setState({keyword: e.target.value})
      }
    
      render () {
        const onChangeHandler = e => this.onChangeHandler(e)
        const keyword = this.state.keyword
        const arr = this.state.result.map((e, idx) => {
          return <div key={idx}>{e}</div>
        })
    
        return (
            <div>
              <input type="text" onChange={onChangeHandler} value={keyword} />
              <button onClick={this.crawl}>CRAWL</button>
              {arr}
            </div>
        )
      }
    }
    

    Hope this will help you.