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

Reactjs 如何使用输入字段更新页面的url?,reactjs,Reactjs,我尝试在我的应用程序中使用react router v5集成一个搜索页面 如何使用搜索框更新url的查询参数 刷新应用程序时,我会丢失搜索结果和搜索字段的值 我使用redux来管理我的搜索字段和搜索结果的值的状态,我认为通过url的参数将是一个更好的解决方案,但我不知道如何做到这一点 我尝试了一种解决方案(请参阅我的代码),但url的查询参数与文本字段的值不同步 编辑: 我的组件Routes.js const Routes = (props) => { return (

我尝试在我的应用程序中使用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>

  )}
组件App.js

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

        export default App;

您可以只使用可选参数,通过将
更改为
并完全删除
}/>
来处理组件中的查询或缺少查询,而不是拆分路由

通过该更改,您可以通过查看此组件中
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
                  })
              })
          }
      }
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;
   }
}
然后,您仍然可以在触发获取操作的组件中直接获得结果/错误。虽然您仍然可以通过存储获取结果,但您可以使用这些触发器来管理组件状态下的
initialChange
,以避免冗余动作分派或在这些情况下可能弹出的无限循环

在这种情况下,搜索栏组件可能看起来像:

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)
这就是我所建议的模式所实现的——与其立即关闭处理程序,不如为分派它设置一个延迟,并继续侦听更改,只在用户完成键入后搜索一次

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


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

如果您想使用react router更改url,那么您可以使用
history
上的
replace
方法。如果您试图持久化redux存储,您应该查看libs,就像感谢您的回复一样,事实上我认为redux persist。目前,我正在为搜索组件使用Redux,但也许我可以使用URL参数而不是Redux从组件中进行管理。我不确定我只是试着去替换它,但我需要使用它一次,因为我使用事件onChange来更改页面,突然我想知道如何适应我的case@EricHasselbring
replace
将销毁历史记录中以前的条目,这很少是预期的行为。它应该是
push
,同时确保您的字符串前面没有
/
。我正在尝试您提出的解决方案。我看到您使用本地状态存储搜索结果,而无需通过redux,我如何更新包含搜索结果的搜索组件,您应该知道我的搜索栏集成在我的栏导航中。我尝试调整您的解决方案,我有两个错误。看起来
此.props.match.params.search仍未定义。然后我有一个
错误,我认为我的函数不是异步的
this.props.applyInitialResult(query,1,this.signal)
是一个执行redux操作的函数,我用此函数的代码更新了我的消息。我更新了我的答案以响应您共享的其他信息。但是,我不确定为什么
这个.props.match.params.search
没有定义。您能否确认您已将搜索路径合并为仅
?另外,该路由中的搜索组件是否是从Searchbar.js导出的组件?如果
Searchbar
是父
Search
组件中的一个组件,那么您需要将历史记录传递给它,并将对象作为道具进行匹配。不,我忘了预先设定,将其合成Searchbar.js嵌入到导航栏(组件NavBar.js)中。搜索组件用于显示结果和管理分页,这就是为什么我不能传递本地状态,而我更喜欢管理全局状态。如果你有更好的解决方案,我很感兴趣。嗯,据我所知,这个解决方案应该仍然可行。您仍然可以使用在搜索栏中访问这些道具。但是,如果这仍然没有达到您的预期,我建议您进一步澄清您的答案,以解释您正在想象的确切情况以及您想要的行为(即
this.props.history.goBack()
initialChange
状态值的目的)
// 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;
   }
}
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)
      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...
       })
     }