4

I have a gallery that show images, and i have a search textbox Im Trying to use Timeout on Input event to prevent the api call on every letter im typing : I try to handle the event with doSearch function onChange: but now I cant write anything on the textbox and it cause many errors Attached to this session the app and gallery components

Thanks in advance

class App extends React.Component {
  static propTypes = {
  };

  constructor() {
    super();
    this.timeout =  0;
    this.state = {
      tag: 'art'
    };
  }


  doSearch(event){
    var searchText = event.target.value; // this is the search text
    if(this.timeout) clearTimeout(this.timeout);
    this.timeout = setTimeout(function(){this.setState({tag: event.target.value})} , 500);
  }

  render() {
    return (
      <div className="app-root">
        <div className="app-header">
          <h2>Gallery</h2>
          <input className="input" onChange={event => this.doSearch(event)} value={this.state.tag}/>
        </div>
        <Gallery tag={this.state.tag}/>
      </div>
    );
  }
}

export default App;

This is the Gallery class:

import React from 'react';
import PropTypes from 'prop-types';
import axios from 'axios';
import Image from '../Image';
import './Gallery.scss';

class Gallery extends React.Component {
  static propTypes = {
    tag: PropTypes.string
  };

  constructor(props) {
    super(props);
    this.state = {
      images: [],
      galleryWidth: this.getGalleryWidth()

    };
  }

  getGalleryWidth(){
    try {
      return document.body.clientWidth;
    } catch (e) {
      return 1000;
    }
  }
  getImages(tag) {
    const getImagesUrl = `services/rest/?method=flickr.photos.search&api_key=522c1f9009ca3609bcbaf08545f067ad&tags=${tag}&tag_mode=any&per_page=100&format=json&safe_search=1&nojsoncallback=1`;
    const baseUrl = 'https://api.flickr.com/';
    axios({
      url: getImagesUrl,
      baseURL: baseUrl,
      method: 'GET'
    })
      .then(res => res.data)
      .then(res => {
        if (
          res &&
          res.photos &&
          res.photos.photo &&
          res.photos.photo.length > 0
        ) {
          this.setState({images: res.photos.photo});
        }
      });
  }

  componentDidMount() {
    this.getImages(this.props.tag);
    this.setState({
      galleryWidth: document.body.clientWidth
    });
  }

  componentWillReceiveProps(props) {
    this.getImages(props.tag);
  }

  render() {
    return (
      <div className="gallery-root">
        {this.state.images.map((dto , i) => {
          return <Image key={'image-' + dto.id+ i.toString()} dto={dto} galleryWidth={this.state.galleryWidth}/>;
        })}
      </div>
    );
  }
}
ShiGi
  • 91
  • 2
  • 3
  • 7
  • Just replace the function you're passing to `setTimeout` with an arrow function (`setTimeout(() => this.setState(...))`) to preserve lexical scope. – haim770 Aug 15 '18 at 09:09

3 Answers3

2

First of all why do you need to use setTimeout to set value that is entered by user. I don't see any use using setTimeout in doSearch function.

The reason your doSearch function won't work because you are not binding it.

You can directly set value to tag using setState in doSearch function in following ways.

ES5 way

constructor(props){
    super(props);
    this.doSearch = this.doSearch.bind(this);
}

doSearch(event){
    this.setState({
       tag: event.target.value
    });
}

ES6 way

doSearch = (event) => {
    this.setState({
       tag: event.target.value
    });
}

Doing setState inside setTimeout in doSearch function won't work because input tag has value assigned.

ES5 way

constructor(props){
    super(props);
    this.doSearch = this.doSearch.bind(this);
}

doSearch(event){
     if(this.timeout) clearTimeout(this.timeout);
     this.timeout = setTimeout(function(){
       this.setState({
          tag: event.target.value
       }) 
     }.bind(this),500);
}

setTimeout in ES6 way

doSearch = (event) => {
     if(this.timeout) clearTimeout(this.timeout);
     this.timeout = setTimeout(() => {
       this.setState({
          tag: event.target.value
       }) 
     },500);
}

Gallery component:

Check current props changes with previous change in componentWillRecieveProps to avoid extra renderings.

Try with below updated code

import React from 'react';
import PropTypes from 'prop-types';
import axios from 'axios';
import Image from '../Image';
import './Gallery.scss';

class Gallery extends React.Component {
  static propTypes = {
    tag: PropTypes.string
  };

  constructor(props) {
    super(props);
    this.state = {
      images: [],
      galleryWidth: this.getGalleryWidth()

    };
  }

  getGalleryWidth(){
    try {
      return document.body.clientWidth;
    } catch (e) {
      return 1000;
    }
  }
  getImages(tag) {
    const getImagesUrl = `services/rest/?method=flickr.photos.search&api_key=522c1f9009ca3609bcbaf08545f067ad&tags=${tag}&tag_mode=any&per_page=100&format=json&safe_search=1&nojsoncallback=1`;
    const baseUrl = 'https://api.flickr.com/';
    axios({
      url: getImagesUrl,
      baseURL: baseUrl,
      method: 'GET'
    })
      .then(res => res.data)
      .then(res => {
        if (
          res &&
          res.photos &&
          res.photos.photo &&
          res.photos.photo.length > 0
        ) {
          this.setState({images: res.photos.photo});
        }
      });
  }

  componentDidMount() {
    this.getImages(this.props.tag);
    this.setState({
      galleryWidth: document.body.clientWidth
    });
  }

  componentWillReceiveProps(nextProps) {
     if(nextProps.tag != this.props.tag){
        this.getImages(props.tag);
     }
  }

  shouldComponentUpdate(nextProps, nextState) {
      if(this.props.tag == nextProps.tag){
          return false;
      }else{
          return true;
      }
  }

  render() {
    return (
      <div className="gallery-root">
        {this.state.images.map((dto , i) => {
          return <Image key={'image-' + dto.id+ i.toString()} dto={dto} galleryWidth={this.state.galleryWidth}/>;
        })}
      </div>
    );
  }
}

I am keeping tag initial value to empty as you are not doing anything with value art.

Please try with below code

class App extends React.Component {
  static propTypes = {
  };

  constructor() {
    super();
    this.timeout =  0;
    this.state = {
      tag: '',
      callGallery: false
    };
  }


  doSearch = (event) => {
    this.setState({tag: event.target.value, callGallery: false});
  }

  handleSearch = () => {
     this.setState({
        callGallery: true
     });
  }

  render() {
    return (
      <div className="app-root">
        <div className="app-header">
          <h2>Gallery</h2>
          <input className="input" onChange={event => this.doSearch(event)} value={this.state.tag}/>
         <input type="button" value="Search" onClick={this.handleSearch} />
        </div>
        {this.state.callGallery && <Gallery tag={this.state.tag}/>}
      </div>
    );
  }
}

export default App;
Hemadri Dasari
  • 32,666
  • 37
  • 119
  • 162
  • Hi and thank you first of all, I wont to use timout to prevent a lot of redundant api calls. I dont dont why but your solution doesnt work for this case and i still cant write anything in the input box :( – ShiGi Aug 15 '18 at 10:01
  • Yes you can't type because you are doing setState in settimeout so when you type it won't work. So set the state without timeout it will work. By the way what do you mean by redundant api calls? – Hemadri Dasari Aug 15 '18 at 10:15
  • every letter im typing will refresh the page, and it makes the oage run very slow, i want that just after the timout pass i will make the seach – ShiGi Aug 15 '18 at 11:11
  • Can you also share your Gallery component code otherwise it’s bit difficult to help you with – Hemadri Dasari Aug 15 '18 at 11:18
  • I've upload the gallery component in the main message, Thank you – ShiGi Aug 15 '18 at 11:47
  • Hi, Thank you, but i have a clearly instruction that i need 500ms without typing before do the rendering – ShiGi Aug 15 '18 at 12:24
  • Now I can type in the textbox but there is no timeout so every charecter trigger a render – ShiGi Aug 15 '18 at 12:46
  • So you have to use a search button to work in your way. Without button its not possible to prevent rendering for every character. I updated App component code please try now – Hemadri Dasari Aug 15 '18 at 12:56
0

This is because you haven't bound this to your method.

Add the following to your constructor:

this.doSearch = this.doSearch.bind(this);

Also, you don't need the fat arrow notation for onChange. Just do:

onChange={this.doSearch}
Chris
  • 57,622
  • 19
  • 111
  • 137
0

onChange handler is just fine but you need to bind the setTimeout to render context.Currently,it is referring to window context.And the code as follows

   doSearch(event){
        var searchText = event.target.value; // this is the search text
        if(this.timeout) clearTimeout(this.timeout);
        this.timeout = setTimeout(function(){
                         this.setState({
                             tag: event.target.value
                         }) 
                       }.bind(this),500);
      }
CodeZombie
  • 2,027
  • 3
  • 16
  • 30