3
votes

In this app, I'm fetching images from the Unsplash API (with an Express back end, React front end). On page load, general images appear (rendered inside the react-infinite-scroll-component). If you search for a term, the search state changes from false to true. I use a ternary operator to conditionally render the images inside the infinite scroll component:

{this.state.newSearch || this.state.search ? this.state.searchImages.map(image =>
          <Image key={image.id} image={image} />
        ) : this.state.images.map(image =>
          <Image key={image.id} image={image} />
 )}

The images that first loaded (from componentDidMount) are replaced after you type in the input and press enter, but if you type something else and hit enter, the new search results are instead added to the end. Instead, I'd like to replace the previous search results. I've been struggling to figure out how to accomplish that. I tried adding the newSearch state and setting searchImages: [] and searchStart: 1 in handleSubmit, but neither are making any difference. As a React novice I also tried setting newSearch back to false after the image render in the infinite scroll component, which broke the app due to the infinite loop.

Any help would be greatly appreciated.

Parent:

export class Images extends Component {
  state = {
    images: [],
    searchImages: [],
    count: 30,
    start: 1,
    searchStart: 1,
    term: '',
    search: false,
    newSearch: false
  };

  componentDidMount() {
    const { count, start } = this.state;
      axios
        .get(`/api/photos?count=${count}&start=${start}`)
        .then(res => this.setState({ images: res.data }));
  }

  fetchImages = () => {
    const { count, start, images } = this.state;
    // Will be at 1, then 31 (1 + 30), then 61 (31 + 30), and so forth
    this.setState({ start: start + count });
    axios
      .get(`/api/photos?count=${count}&start=${start}`)
      .then(res =>
        this.setState({ images: images.concat(res.data) })
      );
  }

  fetchSearchImages = () => {
    const { searchStart, count, term, searchImages } = this.state;
    this.setState({
      searchStart: searchStart + count
    });
    axios
      .get(`/api/photos/search?term=${term}&count=${count}&start=${searchStart}`)
      .then(res =>
        this.setState({
          searchImages: searchImages.concat(res.data.results)
        })
      );
  }

  handleSubmit = () => {
    this.setState({
      searchImages: [],
      searchStart: 1,
      search: true,
      newSearch: true
    });

    this.fetchSearchImages();
  }

  handleInputChange = (e) => {
    this.setState({
      term: e
    });
  }

  render() {
    return (
      <>

      <SearchInput onSearch={this.handleInputChange} onFormSubmit={this.handleSubmit} term={this.state.term} />

      <div className="images">
        <InfiniteScroll
          dataLength={this.state.newSearch || this.state.search ? this.state.searchImages.length : this.state.images.length}
          next={this.state.search ? this.fetchSearchImages : this.fetchImages}
          hasMore={true}
          loader={
            <div className="loader-dots">
              <span className="loader-dot"></span>
              <span className="loader-dot"></span>
              <span className="loader-dot"></span>
              <span className="loader-dot"></span>
            </div>
          }
        >
        {this.state.newSearch || this.state.search ? this.state.searchImages.map(image =>
          <Image key={image.id} image={image} />
        ) : this.state.images.map(image =>
          <Image key={image.id} image={image} />
        )}
      </InfiniteScroll>
      </div>

      </>
    );
  }
}

Child search component:

const SearchInput = props => {

  const onSubmit = e => {
    // Prevents GET request/page refresh on submit
    e.preventDefault();
    props.onFormSubmit();
  };

  return (
    <form onSubmit={onSubmit}>
      <div className="control">
        <input autoFocus value={props.term} onChange={e => props.onSearch(e.target.value)} className="input" type="text" placeholder="Search" />
      </div>
    </form>
  );
}
2
Be aware that the code / repo includes Unsplash API secrets. You should definitely revoke / change them - Narigo

2 Answers

1
votes

The current code in fetchSearchImages is relying on the state before React really sets it. I have sent a PR to your repository to to call fetchSearchImages only after the state is set, by using the callback argument of setState.

Basically this code

handleSubmit = () => {
  this.setState({
    searchImages: [],
    searchStart: 1,
    search: true,
    newSearch: true
  });

  this.fetchSearchImages();
}

Needs to be changed to this:

handleSubmit = () => {
  this.setState({
    searchImages: [],
    searchStart: 1,
    search: true,
    newSearch: true
  }, this.fetchSearchImages);
}
0
votes

I don't think there's anything wrong with your logic, except for this part:

this.setState({
      searchImages: searchImages.concat(res.data.results)
})

Where you concat your new search results to the previous ones, when all you should do is simply replace them:

this.setState({
      searchImages: res.data.results
})