Reactjs 如何使用输入字段更新页面的url?
我尝试在我的应用程序中使用react router v5集成一个搜索页面 如何使用搜索框更新url的查询参数 刷新应用程序时,我会丢失搜索结果和搜索字段的值 我使用redux来管理我的搜索字段和搜索结果的值的状态,我认为通过url的参数将是一个更好的解决方案,但我不知道如何做到这一点 我尝试了一种解决方案(请参阅我的代码),但url的查询参数与文本字段的值不同步 编辑: 我的组件Routes.jsReactjs 如何使用输入字段更新页面的url?,reactjs,Reactjs,我尝试在我的应用程序中使用react router v5集成一个搜索页面 如何使用搜索框更新url的查询参数 刷新应用程序时,我会丢失搜索结果和搜索字段的值 我使用redux来管理我的搜索字段和搜索结果的值的状态,我认为通过url的参数将是一个更好的解决方案,但我不知道如何做到这一点 我尝试了一种解决方案(请参阅我的代码),但url的查询参数与文本字段的值不同步 编辑: 我的组件Routes.js const Routes = (props) => { return (
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@EricHasselbringreplace
将销毁历史记录中以前的条目,这很少是预期的行为。它应该是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...
})
}