【问题标题】:How to update the url of a page using an input field?如何使用输入字段更新页面的 url?
【发布时间】:2019-12-20 08:31:16
【问题描述】:

我尝试在我的应用程序中使用 React Router v5 集成搜索页面。

如何使用搜索框更新网址的查询参数?

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

我使用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 是一个标志,它阻止我在每次输入字符时推送到搜索页面。

要了解更多,我已经打开 = 此处的帖子 => How to change the route according to the value of a field

  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,
                     })
                  }
               })
             }
         }

【问题讨论】:

  • 如果你想用 react-router 改变 url,你可以在 history 上使用 replace 方法
  • 如果你想持久化一个 redux 存储,你应该查看像 github.com/rt2zz/redux-persist 这样的库
  • 感谢您的回复,确实我认为 Redux 坚持。目前我正在为我的搜索组件使用 Redux,但也许我可以使用我的 URL 参数而不是 redux 从我的组件进行管理。我不确定
  • 我只是尝试替换,它可以工作,但我需要使用它一次,因为我使用事件onChange,来更改页面,突然我想知道如何适应我的情况
  • @EricHasselbring replace 将销毁历史记录中的先前条目,这很少是预期的行为。它应该是push,同时确保字符串前面没有/

标签: reactjs


【解决方案1】:

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

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

针对问题更新编辑

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

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

动作创建者

   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;
   }
}

然后,您仍然可以在触发 fetch 操作的组件中直接获得结果/错误。虽然您仍将通过商店获得结果,但您可以使用此类触发器让您在组件状态下管理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?query=foo,而不是/search/foo,因此搜索上的匹配参数也将为空。

我还认为您管理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...
       })
     }

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

我没有在此处复制另一段代码,而是在代码和框here 上制作了一个工作示例来解决这些问题。它仍然可以使用一些优化,但是如果您想查看我如何处理搜索页面、搜索栏和动作创建者,这个概念就在那里。

搜索栏还有一个切换按钮,可以在两种不同的 url 格式之间切换(查询如 /search?query=foo 与 match.param 如/search/foo),因此您可以了解如何将每一种格式协调到代码中。

【讨论】:

  • 我正在尝试您提出的解决方案。我看到你使用本地状态来存储搜索结果,没有经过redux,我如何更新包含搜索结果的搜索组件,你应该知道我的搜索栏集成在我的栏导航中。
  • 我尝试调整您的解决方案,我有两个错误。看起来this.props.match.params.search 仍然未定义。然后我有一个then 错误,我认为我的函数不是异步的。 this.props.applyInitialResult (query, 1, this.signal) 是一个执行 redux 动作的函数,我用这个函数的代码更新了我的消息。
  • 我更新了我的答案以响应您分享的其他信息。但是,我不确定为什么 this.props.match.params.search 是未定义的。您能否确认您将搜索路线合并到仅&lt;Route path="/search/:search?" component={Search} /&gt;?此外,该路由中的 Search 组件是从 Searchbar.js 导出的吗?如果Searchbar 是父Search 组件中的一个组件,那么您需要将历史记录传递给它并将对象作为道具匹配。
  • 哈不,我忘了精确,编写它 SearchBar.js 嵌入在导航栏中(组件 NavBar.js)。 Search 组件用于显示结果和管理分页,这就是为什么我不能传递本地状态而我更喜欢管理全局状态的原因。如果您有更好的解决方案,我很感兴趣。
  • 好吧,据我所知,这个解决方案应该仍然可行。您仍然可以使用 withRouter 在 SearchBar 中访问这些道具。但是,如果这仍然没有达到您的预期,我建议您进一步澄清您的答案,以解释您想象的确切情况以及您想要的行为(即this.props.history.goBack()initialChange 状态的目的值)
猜你喜欢
  • 1970-01-01
  • 2015-07-13
  • 2018-11-24
  • 2012-02-26
  • 1970-01-01
  • 1970-01-01
  • 2021-11-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多