我目前在如何执行AJAX请求方面遇到问题,该请求可以由页面上的几个不同的UI元素触发。AJAX请求始终发送到相同的端点,并且始终将相同的属性从redux存储发送到端点(尽管属性值可能会由于用户交互而更改)。
我完全意识到我目前的执行情况很糟糕。
为了绘制更清晰的图片,我正在构建一个搜索页面,其中几个UI元素可以触发新的搜索被激活。有一个端点称为“ / api / search /”,该端点期望查询字符串包含从Redux Store提取的数据。它看起来像这样:
term=some%20string&categories=243,2968,292&tags=11,25,99&articleType=All
当UI元素需要触发对商店的同步更新时,还需要触发执行搜索的Thunk时,我会陷入困境。这是我的顶级组件,在这里我将“ executeSearch” Thunk函数传递给所有需要触发搜索的子组件。我最初的想法是,我可以使用一个thunk来处理所有需要执行搜索的交互,而不必为每个交互编写一个thunk。
如果您对您不敏感,请注意以下代码。如果您浏览了以下部分,请阅读“三种情况”部分,这可能有助于您更好地理解一切。该部分还包含图片。
class App extends Component {
executeSearch = () => {
this.props.executeSearch(this.props.store); // This is my Thunk
};
render() {
const { updateSearchTerm, clearAll, dropdownActive, dropdownType } = this.props;
return (
<section className="standard-content">
<div className="inner-container narrow">
<div className="centered">
<h1>Search</h1>
<h2 className="alt">Search our extensive database of research articles.</h2>
</div>
<SearchTerm initSearch={this.executeSearch} updateSearchTerm={updateSearchTerm} />
<ExtraOptions clearAll={clearAll} />
<Filters executeSearch={this.executeSearch} />
</div>
{dropdownActive ? (
dropdownType === 'categories' ? (
<CategoryDropdown executeSearch={this.executeSearch} />
) : (
<TagDropdown executeSearch={this.executeSearch} />
)
) : null}
<SearchResults />
</section>
);
}
}
const mapStateToProps = state => {
return {
store: state,
dropdownActive: state.dropdownActive,
dropdownType: state.dropdownType
};
};
executeSearch函数从存储中获取所有值,但仅使用本期开始时概述的值。如果有帮助的话,这篇文章的底部有整个redux存储的代码示例。无论如何,Thunk的外观如下:
export const executeSearch = criteria => {
const searchQueryUrl = craftSearchQueryUrl(criteria);
// if (term === '' && !selectedCategories && !selectedTags && articleType === 'All') {
// return { type: ABORT_SEARCH };
// }
return async dispatch => {
dispatch({ type: FETCH_SEARCH_RESULTS });
try {
const res = await axios.post(`${window.siteUrl}api/search`, searchQueryUrl);
dispatch({ type: FETCH_SEARCH_RESULTS_SUCCESS, searchResults: res.data });
} catch (err) {
dispatch({ type: FETCH_SEARCH_RESULTS_FAILED });
}
};
};
// Helper function to craft a proper search query string
const craftSearchQueryUrl = criteria => {
const { term, articleType, selectedCategories, selectedTags } = criteria;
let categoriesString = selectedCategories.join(',');
let tagsString = selectedTags.join(',');
return `term=${term}&articleType=${articleType}&categories=${categoriesString}&tags=${tagsString}&offset=${offset}`;
};
请记住,这里的“条件”参数是我作为App.js内部参数传递的整个商店对象。您将看到,我只使用craftSearchQueryUrl函数内部所需的属性。
我提供了一个屏幕截图(标有字母),希望在其中解释在什么地方有效,在哪些地方无效。
A.)用户应该能够填写此文本字段,并且当他们按放大镜时,它应该触发Thunk。这很好用,因为每次击键时都会在商店中更新文本字段中的值,这意味着商店中的值始终是最新的,甚至用户甚至没有机会按下放大镜。
B.)默认情况下,在初始页面加载时选中“所有”复选框。如果用户单击旁边列出的其他复选框之一,则应立即导致启动搜索。这是我的问题开始发生的地方。这是我目前对此代码的要求:
export default ({ handleCheckboxChange, articleType, executeSearch }) => (
<div style={{ marginTop: '15px', marginBottom: '20px' }}>
<span className="search-title">Type: </span>
{articleTypes.map(type => (
<Checkbox
key={type}
type={type}
handleCheckboxChange={() => {
handleCheckboxChange('articleType', { type });
executeSearch();
}}
isChecked={type === articleType}
/>
))}
</div>
);
When the checkbox changes, it updates the articleType value in the store(via handleCheckboxChange) and then says to execute the search function passed down from App.js. However, the updated articleValue type is not the updated one, as I believe the search function is called before the store has a chance to update this value.
C.) The same problem from B occurs here as well. When you click one of the buttons(Category or Tag) in the "Refine by" section, this dropdown appears with multiple checkboxes that can be selected. I actually store which checkboxes become checked/unchecked in local state until the user clicks the save button. Once the save button is pressed, the newly checked/unchecked checkbox values should be updated in the store and then a new search should be initiated via the Thunk passed down from App.js.
export default ({ children, toggleDropdown, handleCheckboxChange, refineBy, executeSearch }) => {
const handleSave = () => {
handleCheckboxChange(refineBy.classification, refineBy);
toggleDropdown('close');
executeSearch(); // None of the checkbox values that were changed are reflected when the search executes
};
return (
<div className="faux-dropdown">
<button className="close-dropdown" onClick={() => toggleDropdown('close')}>
<span>X</span>
</button>
<div className="checks">
<div className="row">{children}</div>
</div>
<div className="filter-selection">
<button className="refine-save" onClick={handleSave}>
Save
</button>
</div>
</div>
);
};
It is important to note that the values used when executing the search are not the updated ones for B and C, but they are in fact updated properly inside the store.
My other idea was to maybe create a redux middleware, but honestly I could really use some expert help on this before trying anything else. The accepted solution would ideally be thorough in its explanation, and include a solution that takes best architectural practices into mind when dealing with Redux applications. Perhaps I am just doing something fundamentally wrong here.
Here is what my full store(in its initial state) looks like if it helps:
const initialState = {
term: '',
articleType: 'All',
totalJournalArticles: 0,
categories: [],
selectedCategories: [],
tags: [],
selectedTags: [],
searchResults: [],
offset: 0,
dropdownActive: false,
dropdownType: '',
isFetching: false
};
The following block in App
is the crux of the problem:
executeSearch = () => {
this.props.executeSearch(this.props.store); // This is my Thunk
};
This executeSearch
method has baked into it the store
at the point of rendering App
.
When you then do:
handleCheckboxChange('articleType', { type });
executeSearch();
by the time you get to executeSearch
your Redux store will have been synchronously updated, but that results in a new store
object which will cause App
to be re-rendered but that won't affect the store
object that executeSearch
is using.
正如其他人在评论中指出的那样,我认为处理此问题的最直接方法是使用中间件,该中间件提供一种机制来执行副作用,以响应商店更新后的还原动作。我个人将为此目的推荐Redux Saga,但我知道还有其他选择。在这种情况下,您将拥有一个传奇,可以监视应该触发搜索的任何操作,然后传奇是唯一调用的对象executeSearch
,传奇将能够将更新传递store
给executeSearch
。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句