简体   繁体   中英

Fetch API data with react and filter search input with autocomplete

I have a question regarding a React filter search input.

I have two Components the the moment.

FirstPageComponent.js

import React from "react"
import AutoComplete from "./AutoComplete"

export class FirstPageComponent extends React.Component {
  constructor() {
    super()
    this.state = {
      rows: [],
      loading: true,
    }
  }
  async componentDidMount(searchTerm = "marvel") {
    await fetch(
      "https://api.themoviedb.org/3/search/movie?api_key=&query=" +
        searchTerm
    )
      .then(response => response.json())
      .then(responseData => {
        this.setState({
          rows: responseData.results,
          loading: false,
        })
      })
      .catch(error => {
        console.log("Error fetching and parsing data", error)
      })
  }
  render() {
    console.log(this.state.rows)
    return <AutoComplete movies={["hej"]} />
  }
}

and

AutoComplete.js

import React from "react"

export default class AutoComplete extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      rows: [],
      loading: false,
      userInput: "",
    }
  }

  onChange = e => {
    const { movies } = this.props
    const userInput = e.currentTarget.value

    const rows = movies.filter(
      suggestion =>
        suggestion.toLowerCase().indexOf(userInput.toLowerCase()) > -1
    )

    this.setState({
      rows,
      loading: true,
      userInput: e.currentTarget.value,
    })
  }

  onClick = e => {
    this.setState({
      rows: [],
      loading: false,
      userInput: e.currentTarget.innerText,
    })
  }

  render() {
    const {
      onChange,
      onClick,
      state: { rows, loading, userInput },
    } = this
    let moviesListComponent
    if (loading && userInput) {
      if (rows.length) {
        moviesListComponent = (
          <ul>
            {rows.map((movie, index) => {
              return (
                <li key={index} onClick={onClick}>
                  {movie}
                </li>
              )
            })}
          </ul>
        )
      } else {
        moviesListComponent = (
          <>
            <p>Couldn't find any movies. Try searching for another movie.</p>
          </>
        )
      }
    }

    return (
      <React.Fragment>
        <input type="search" onChange={onChange} value={userInput} />
        {moviesListComponent}
      </React.Fragment>
    )
  }
}
  • I basically want to know if i'm approaching this the right way. I want to make a dynamic request for movie titles from fetch(” https://themovie.db ”) API.

  • Send down the movie titles as props and then filter the values in autocomplete component if the user input is similar to the movie title props.

Am i thinking about this the right way? I've tried to have the fetch call in the same component as AutoComplete but haven't gotten it to work as i want it to. How would you setup the component structure if you'd be solving this issue for example?

Feel free to ask if there're any confusions.

Thanks

These are my assumptions about the above code, let me know if any are incorrect.

  • You're doing an initial API request for a list of movies by search term. That search term is given outside of the code above and doesn't change by any mechanism above.
  • You're further filtering the data returned by the API request dynamically via the AutoComplete component.

Most of your React code here looks pretty good, but I've got some recommendations:

  1. In general, I find it's best to keep the list of things completely separate, so I would create a separate MovieList function component that simply takes an array of movie objects, and a titleFilter string and renders them.
  2. componentDidMount doesn't take arguments. searchTerm should be a prop of FirstPageComponent (presumably set by the parent component by some kind of selection or user input)
  3. I'd rename AutoComplete to TitleFilter or the like. It's not really auto-completing, it's more of a filter field.
  4. This line: if (loading && userInput) { will result in displaying no movies when the user hasn't yet filled in text into the filter field. I'm not sure that is what you would want.
  5. This line is a bit misleading: <p>Couldn't find any movies. Try searching for another movie.</p> <p>Couldn't find any movies. Try searching for another movie.</p> because you could have just filtered out all the results returned. In this case, it wasn't that you couldn't find movies, it's that your filter is too restrictive.
  6. For the onClick handler, I'm not sure such a general approach is best. It could create a strange interaction if you were to somehow click on the <ul> , for example. To improve this, I'd recommend each movie row component implement its own onClick and call a function to set the filter.
  7. You're going to have to deal with pagination on this, I'd bet. Check your API and see what it does for that and make sure you're accounting for it.

Small nits:

  1. userInput: e.currentTarget.value, could be shortened to userInput, , which is shorthand for userInput: userInput, (you already have a variable named userInput .)
  2. For key props, an actual unique id would be better, in the case you change your filtering and index numbers don't line up with the unique rows you're rendering anymore.
  3. I like to go ahead and create a separate component for each row, so I'd make a MovieRow function component which takes a movie object and renders it. It just keeps the code a bit cleaner. (and makes #6 above easier to implement)

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