使用Redux Thunk在React Redux应用程序中保持异步调用的DRY

还有祖兹维奇

问题

我目前在如何执行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.

Other Solutions

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.

Misc

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

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,传奇将能够将更新传递storeexecuteSearch

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

使用 react native 和 redux 进行异步调用,thunk

如何在Redux React应用程序中处理异步调用

从React Redux Thunk返回承诺

如何在带有thunk的react-redux挂钩中进行异步调用?

如何在React上正确使用redux-thunk?

派遣Redux Thunk重新渲染React组件

Redux-thunk 异步调用和状态

为什么我的redux应用程序不能在redux-thunk中缓存异步api调用?

使用React Redux进行Firebase身份验证Redux-Thunk

使用自定义异步中间件进行分派 - Redux Thunk - react Router

Redux Thunk vs在React组件中进行api调用

react-redux 中的 API 调用与 thunk 不起作用

在使用Redux和redux-thunk的React应用程序中处理加载状态,在一次获取数据后返回组件

使用 redux thunk 进行异步 api 获取

如何测试分派 Redux / Thunk 操作的 React 组件

React / TypeScript / Redux / Thunk操作未调度,状态未更新

如何在react-redux-thunk中使用reducer编写动作的集成测试

使用React,Jest,Redux和Thunk进行测试中的无限循环

如何使用thunk和useDispatch(react-redux挂钩)从操作中返回承诺?

当使用redux-thunk的异步api调用导致redux道具更改时,不会触发UseEffect

在异步调用axios API之后使用带有React Hook和Redux的SetState

关于不使用中间件的Redux异步(redux-thunk,redux-saga ...)

redux,react-redux,redux-thunk有什么区别?

如何使用redux-thunk`bindActionCreators`

如何在Redux Toolkit的createSlice中使用Redux-Thunk?

什么时候使用redux saga和redux thunk?

使用redux-thunk和redux-observable

相當於在 Redux thunk 中使用 Redux Saga 的 yield

如何使用 Redux Thunk 和 Redux Toolkit 进行发布请求