如何使用输入字段更新页面的 url? [英] How to update the url of a page using an input field?

查看:47
本文介绍了如何使用输入字段更新页面的 url?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

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

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

我的组件 Routes.js

 const Routes = (props) =>{返回 (<div><路由精确路径="/" component={Home}/><路线精确的路径=/搜索"组件={() =><搜索查询={props.text}/>}/><Route path="/film/:id" component={MovieDetail}/><Route path="/FavorisList" component={WatchList}/><Route path="/search/:search" component={Search}/><Route path="*" component={NotFound}/>

)}

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

我想实现Netflix用于剧集研究的方法.

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

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

要了解更多信息,我在此处打开了 = 帖子 => 如何根据字段的值改变路由

 class SearchBar 扩展 React.Component {构造函数(道具){超级(道具);this.state = {输入值:'',};}setParams({ 查询 }) {const searchParams = new URLSearchParams();searchParams.set("查询", 查询 || "");返回 searchParams.toString();}handleChange = (事件) =>{const 查询 = event.target.valuethis.setState({ inputValue: event.target.value})if (event.target.value === '') {this.props.history.goBack()this.setState({ initialChange: true })返回;}if(event.target.value.length && this.state.initialChange){this.setState({初始更改:假}, ()=>{const url = this.setParams({查询:查询});this.props.history.push(`/search?${url}`)this.search(查询)})}}搜索 =(查询)=>{//使用redux检索的搜索结果this.props.applyInitialResult(query, 1)}使成为() {返回 (<div><输入类型=文本"值={this.state.inputValue}placeholder="搜索电影..."className={style.field}onChange={this.handleChange.bind(this)}/>

);}导出默认搜索栏;

组件 App.js

 class App extends React.Component {使成为(){返回 (<div><浏览器路由器><导航栏/><路线/></BrowserRouter>

);}}导出默认应用程序;

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

 导出函数 applyInitialResult(query, page){返回函数(调度){getFilmsFromApiWithSearchedText(query, page).then(data => {如果(页面 === 1){派遣({类型:AT_SEARCH_MOVIE.SETRESULT,查询:查询,有效载荷:数据,})}})}}

解决方案

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

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

根据问题更新进行编辑

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

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

动作创建者

 导出函数 applyInitialResult(询问,页,//附加参数信号,成功时,失败时//或者,您可以只使用 onFinished 回调来处理成功和失败的情况){返回函数(调度){getFilmsFromApiWithSearchedText(query, page, signal)//传递信号,因此如果输入更改,您仍然可以中止正在进行的提取.then(数据=> {成功(数据);//将数据传回组件这里如果(页面 === 1){派遣({类型:AT_SEARCH_MOVIE.SETRESULT,查询:查询,有效载荷:数据,})}}).catch(错误 => {onFailure(数据);//警告组件错误派遣({type:AT_SEARCH_MOVIE.FETCH_FAILED,//告诉商店处理失败查询:查询,有效载荷:数据,呃})})}}

searchMovie Reducer:

//保存搜索结果,不管成功与否导出函数 searchMovieReducer(state = {}, action) =>{开关(动作.类型){案例 AT_SEARCH_MOVIE.SETRESULT:const {查询,有效载荷} = 动作;状态[查询] = 有效载荷;休息;案例 AT_SEARCH_MOVIE.FETCH_FAILED:const {查询,错误} = 动作;状态[查询] = 错误;休息;}}

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

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

class SearchBar 扩展 React.Component {构造函数(道具){this.controller = new AbortController();this.signal = this.controller.signal;this.state = {获取:假,结果:props.results//<== 根据您的反馈可能是一次性的}}componentDidMount(){//如果搜索不是未定义的,则获取结果如果(this.props.match.params.search){this.search(this.props.match.params.search);}}componentDidUpdate(prevProps){//如果搜索不是未定义的并且与上一个查询不同,则再次搜索如果(this.props.match.params.search&&prevProps.match.params.search !== this.props.match.params.search){this.search(this.props.match.params.search);}}setParams({ 查询 }) {const searchParams = new URLSearchParams();searchParams.set("查询", 查询 || "");返回 searchParams.toString();}handleChange = (事件) =>{const 查询 = event.target.valueconst url = this.setParams({查询:查询});this.props.history.replace(`/search/${url}`);}搜索 =(查询)=>{如果(!查询)返回;//如果以某种方式传递空字符串,则不执行任何操作//如果一些搜索已经发生,让组件知道有一个新的查询还没有被获取this.state.fetched === true &&this.setState({fetched: false;})//如果一些 fetch 已经在排队,取消它如果(this.willFetch){clearInterval(this.willFetch)}//如果当前正在获取,则取消调用如果(this.fetching){this.controller.abort();}//最后排队新的搜索this.willFetch = setTimeout(() => {this.fetching = this.props.applyInitialResult(询问,1、这个信号,处理成功,处理失败)}, 500/* 在进行异步调用前等待半秒 */);}句柄成功(数据){//直接对数据做一些事情//或更新组件以反映异步操作已结束}处理失败(错误){//处理错误//或再次触发 fetch 重试搜索}使成为() {返回 (<div><输入类型=文本"defaultValue={this.props.match.params.search}//<== 使输入不受控制placeholder="搜索电影..."className={style.field}onChange={this.handleChange.bind(this)}/><div>);}}const mapStateToProps = (state, ownProps) =>({//根据 url/route 参数从 Redux 存储中获取结果结果: ownProps.match.params.search?state.searchMovie[ownProps.match.params.search]: []});const mapDispatchToProps = dispatch =>({applyInitialResult://不管你如何定义它})导出默认连接(mapStateToProps,mapDispatchToProps)(搜索栏)

编辑 2

感谢您澄清您的想象.

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

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

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

 if (event.target.value === '') {this.props.history.goBack()this.setState({ initialChange: true })//<== 只在类中重置返回;}...if(event.target.value.length && this.state.initialChange){this.setState({初始更改:假}, ()=>{//等等...})}

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

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

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

I try to integrate a search page in my application with react router v5.

How could I update my url's query parameter using my search box?

When I refresh my application, I lose my search results and the value of my search field.

I use redux to manage the state of the value of my search fields and my search results, I think that going through the parameters of the url would be a better solution but I do not know how to do that.

I tried a solution (see my code), but the query parameter of the url is not synchronized with the value of my text field

Edit:

My component 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>

  )}

My component SearchBar.js (Embedded in the navigation bar, the Search route displays the search results)

EDIT:

I wish to realize the method used by Netflix for its research of series.

I want to be able to search no matter what page I am in, if there is an entry in the input field, I navigate to the search page withthis.props.history.push (`` search / ), if the input field is empty, I navigate to the page with this.props.history.goBack ().

The state inputChange is a flag that prevents me from pushing to the search page each time I enter a character.

To know more, I had opened = a post here => 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;

Component App.js

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

        export default App;

Query for search results (Managed with 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,
                     })
                  }
               })
             }
         }

解决方案

Instead of splitting up the routes, you could just use an optional param and handle the query or lack thereof in the component by changing <Route path="/search/:search" component={Search} /> to <Route path="/search/:search?" component={Search} /> and removing <Route exact path='/search' component={() => <Search query={props.text} />} /> entirely.

With that change, you can then get the current query by looking at the value of props.match.params.search in this component. Since you're updating the URL each time the user changes the input, you then don't need to worry about managing it in the component state. The biggest issue with this solution is you'll probably want to delay the search for a little bit after render, otherwise you'll be triggering a call on every keystroke.

EDITED IN RESPONSE TO QUESTION UPDATE

You're right, if applyInitialResult is just an action creator, it won't be async or thenable. You still have options, though.

For example, you could update your action creator so it accepts callbacks to handle the results of the data fetch. I haven't tested this, so treat it as pseudocode, but the idea could be implemented like this:

Action creator

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

searchMovie Reducer:

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

Then you could still have the results/errors directly available in the component that triggered the fetch action. While you'll still be getting the results through the store, you could use these sort of triggers to let you manage initialChange in the component state to avoid redundant action dispatches or the sort of infinite loops that can pop up in these situations.

In this case, your Searchbar component could look like:

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)

EDIT 2

Thanks for the clarification about what you're imagining.

The reason this.props.match.params is always blank is because that's only available to the Search component - the Searchbar is entirely outside the routing setup. It also renders whether or not the current path is /search/:search, which is why withRouter wasn't working.

The other issue is that your Search route is looking for that match param, but you're redirecting to /search?query=foo, not /search/foo, so match params will be empty on Search too.

I also think the way you were managing the initialChange state was what caused your search value to remain unchanged. You handler gets called on every change event for the input, but it shuts itself off after the first keystroke and doesn't turn on again until the input is cleared. See:

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

This is what the pattern I was suggesting accomplishes - instead of immediately turning off your handler, set a delay for the dispatch it and keep listening for changes, searching only once user's done typing.

Rather than copying another block of code here, I instead made a working example on codesandbox here addressing these issues. It could still use some optimization, but the concept's there if you want to check out how I handled the Search page, SearchBar, and action creators.

The Searchbar also has a toggle to switch between the two different url formats (query like /search?query=foo vs match.param like /search/foo) so you can see how to reconcile each one into the code.

这篇关于如何使用输入字段更新页面的 url?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆