如何使用输入字段更新页面的 url?

IT技术 reactjs
2021-05-11 22:57:31

我尝试在我的应用程序中使用 react router v5 集成一个搜索页面。

如何使用我的搜索框更新我的 url 查询参数?

当我刷新应用程序时,我会丢失搜索结果和搜索字段的值。

我使用 redux 来管理我的搜索字段和我的搜索结果的值的状态,我认为通过 url 的参数将是一个更好的解决方案,但我不知道该怎么做。

我尝试了一个解决方案(见我的代码),但是url的查询参数与我的文本字段的值不同步

编辑:

我的组件 Routes.js

 const Routes = (props) => {
    return (
       <div>
          <Route exact path="/" component={Home} />
          <Route
             exact
             path='/search'
             component={() => <Search query={props.text} />}
          />
          <Route path="/film/:id" component={MovieDetail} />  
          <Route path="/FavorisList" component={WatchList} />
          <Route path="/search/:search" component={Search} />
          <Route path="*" component={NotFound} />  
       </div>

  )}

我的组件SearchBar.js(内嵌在导航栏中,Search路由显示搜索结果)

编辑:

我想实现Netflix用于系列研究的方法。

我希望无论我在哪个页面都能够进行搜索,如果输入字段中有条目,我会search使用this.props.history.push (`` search /)导航到该页面,如果输入字段为空,我会使用this.props.history.goBack ().

状态 inputChange 是一个标志,可防止我每次输入字符时都推送到搜索页面。

要了解更多,我已经打开了 = 在这里发帖 =>如何根据字段的值更改路由

  class SearchBar extends React.Component {
      constructor(props) {
         super(props);

         this.state = {
            inputValue:'',
         };

       }

      setParams({ query }) {
         const searchParams = new URLSearchParams();
         searchParams.set("query", query || "");
         return searchParams.toString();
      }

      handleChange = (event) => {

          const query = event.target.value
          this.setState({ inputValue: event.target.value})

          if (event.target.value === '') {
             this.props.history.goBack()
             this.setState({ initialChange: true }) 
             return;
          } 

          if(event.target.value.length && this.state.initialChange){
               this.setState({
                  initialChange:false
               }, ()=> {

              const url = this.setParams({ query: query });
              this.props.history.push(`/search?${url}`)
              this.search(query)
           })
         }
       }

       search = (query) => {
           //search results retrieved with redux 
           this.props.applyInitialResult(query, 1)
       }

       render() {
          return (
            <div>
               <input
                   type="text"
                   value={this.state.inputValue}
                   placeholder="Search movie..."
                   className={style.field}
                   onChange={this.handleChange.bind(this)}
               />  
            </div>
          );
        }


       export default SearchBar;

组件 App.js

       class App extends React.Component {
          render(){
              return (
                 <div>
                    <BrowserRouter>
                       <NavBar />
                       <Routes/>
                    </BrowserRouter>
                 </div>
              );
           }
        }

        export default App;

查询搜索结果(使用 Redux 管理)

      export function applyInitialResult(query, page){
          return function(dispatch){
              getFilmsFromApiWithSearchedText(query, page).then(data => {
                 if(page === 1){
                     dispatch({
                        type:AT_SEARCH_MOVIE.SETRESULT,
                        query:query,
                        payload:data,
                     })
                  }
               })
             }
         }
1个回答

您可以只使用可选参数并通过更改<Route path="/search/:search" component={Search} /><Route path="/search/:search?" component={Search} /><Route exact path='/search' component={() => <Search query={props.text} />} />完全删除处理组件中的查询或缺少查询,而不是拆分路由

通过该更改,您可以通过查看props.match.params.search此组件中的值来获取当前查询由于每次用户更改输入时都会更新 URL,因此您无需担心在组件状态下管理它。此解决方案的最大问题是您可能希望在渲染后稍微延迟搜索,否则您将在每次击键时触发调用。

为回应问题更新而编辑

你是对的,如果 applyInitialResult 只是一个动作创建者,它就不会是异步的或可调用的。不过,您仍然有选择。

例如,您可以更新您的动作创建者,使其接受回调来处理数据获取的结果。我没有测试过这个,所以把它当作伪代码,但这个想法可以这样实现:

动作创建者

   export function applyInitialResult(
      query, 
      page,
      // additional params
      signal,
      onSuccess,
      onFailure
      // alternatively, you could just use an onFinished callback to handle both success and failure cases
   ){
      return function(dispatch){
          getFilmsFromApiWithSearchedText(query, page, signal) // pass signal so you can still abort ongoing fetches if input changes
             .then(data => {
                onSuccess(data); // pass data back to component here
                if(page === 1){
                    dispatch({
                       type:AT_SEARCH_MOVIE.SETRESULT,
                       query:query,
                       payload:data,
                    })
                 }
              })
              .catch(err => {
                  onFailure(data); // alert component to errors
                  dispatch({
                     type:AT_SEARCH_MOVIE.FETCH_FAILED, // tell store to handle failure
                     query:query,
                     payload:data,
                     err
                  })
              })
          }
      }

搜索电影减速器:

// save in store results of search, whether successful or not
export function searchMovieReducer(state = {}, action) => {
   switch (action.type){
      case AT_SEARCH_MOVIE.SETRESULT:
         const {query, payload} = action;
         state[query] = payload;
         break;
      case AT_SEARCH_MOVIE.FETCH_FAILED:
         const {query, err} = action;
         state[query] = err;
         break;
   }
}

然后您仍然可以在触发获取操作的组件中直接获得结果/错误。虽然您仍将通过 store 获取结果,但您可以使用这些触发器来让您initialChange在组件状态中进行管理,以避免在这些情况下可能出现的冗余动作分派或无限循环。

在这种情况下,您的 Searchbar 组件可能如下所示:

class SearchBar extends React.Component {
    constructor(props){

       this.controller = new AbortController();
       this.signal = this.controller.signal;

       this.state = {
           fetched: false,
           results: props.results // <== probably disposable based on your feedback
       }
    }

    componentDidMount(){
        // If search is not undefined, get results
        if(this.props.match.params.search){
            this.search(this.props.match.params.search);
        }
    }

    componentDidUpdate(prevProps){
        // If search is not undefined and different from prev query, search again
        if(this.props.match.params.search
          && prevProps.match.params.search !== this.props.match.params.search
        ){
            this.search(this.props.match.params.search);
        }
    }

    setParams({ query }) {
       const searchParams = new URLSearchParams();
       searchParams.set("query", query || "");
       return searchParams.toString();
    }

    handleChange = (event) => {
       const query = event.target.value
       const url = this.setParams({ query: query });
       this.props.history.replace(`/search/${url}`);
    }

    search = (query) => {
        if(!query) return; // do nothing if empty string passed somehow
        // If some search occurred already, let component know there's a new query that hasn't yet been fetched
        this.state.fetched === true && this.setState({fetched: false;})

        // If some fetch is queued already, cancel it
        if(this.willFetch){
            clearInterval(this.willFetch)
        }

        // If currently fetching, cancel call
        if(this.fetching){
            this.controller.abort();
        }

        // Finally queue new search
        this.willFetch = setTimeout(() => {
            this.fetching = this.props.applyInitialResult(
                query,
                1,
                this.signal,
                handleSuccess,
                handleFailure
            )
        },  500 /* wait half second before making async call */);
    }

    handleSuccess(data){
       // do something directly with data
       // or update component to reflect async action is over
    }

    handleFailure(err){
       // handle errors
       // or trigger fetch again to retry search
    }

    render() {
       return (
         <div>
            <input
                type="text"
                defaultValue={this.props.match.params.search} // <== make input uncontrolled
                placeholder="Search movie..."
                className={style.field}
                onChange={this.handleChange.bind(this)}
            />  
         <div>
       );
    }
}

const mapStateToProps = (state, ownProps) => ({
   // get results from Redux store based on url/route params
   results: ownProps.match.params.search
       ? state.searchMovie[ownProps.match.params.search]
       : []
});

const mapDispatchToProps = dispatch => ({
   applyInitialResult: // however you're defining it
})

export default connect(
   mapStateToProps,
   mapDispatchToProps
)(SearchBar)

编辑 2

感谢您澄清您的想象。

原因this.props.match.params总是空白是因为它只对搜索组件可用 - 搜索栏完全在路由设置之外。它还呈现当前路径是否为/search/:search,这就是withRouter不工作的原因。

另一个问题是您的 Search 路由正在寻找该匹配参数,但您正在重定向到/search?query=foo,而不是/search/foo,因此匹配参数在 Search 上也将为空。

我还认为您管理initialChange状态的方式是导致您的搜索值保持不变的原因。您的处理程序在输入的每个更改事件上都会被调用,但它会在第一次击键后自行关闭,并且在输入被清除之前不会再次打开。看:

      if (event.target.value === '') {
         this.props.history.goBack()
         this.setState({ initialChange: true }) // <== only reset in class
         return;
      } 
      ...
      if(event.target.value.length && this.state.initialChange){
           this.setState({
              initialChange:false
           }, ()=> {
           // etc...
       })
     }

这就是我建议的模式所完成的 - 而不是立即关闭您的处理程序,而是设置调度延迟并继续监听更改,仅在用户完成输入后进行搜索。

我没有在这里复制另一个代码块,而是在这里制作了一个关于 codeandbox 的工作示例解决这些问题。它仍然可以使用一些优化,但如果你想看看我是如何处理搜索页面、搜索栏和动作创建者的,那么这个概念就在那里。

搜索栏还有一个切换按钮,可以在两种不同的 url 格式(查询之类的/search?query=foovs match.param 之类/search/foo之间切换,这样您就可以看到如何将每一种格式都协调到代码中。