遍历列表时如何动态使用useReducer? [英] How to dynamically use useReducer when looping over a list?

查看:100
本文介绍了遍历列表时如何动态使用useReducer?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试显示时间列表(例如07:00、07:30),但是当出现重复时间时,请在其旁边显示重复次数(例如07:30、08:00³)

I am trying to show a list of Times (e.g. 07:00, 07:30) but when a repeated time appears, show the number of repetitons by its side (e.g. 07:30, 08:00³)

当我遍历列表时,每个项目都需要有自己的状态,以便可以在每个时间旁边设置和显示计数器

As I am looping over a list, each item will need its own state so that the counter can be set and displayed next to each Time

此刻,我有太多的rerender,但是我也不确定我的reducer是否正确

At the moment, I am having trouble with too many rerenders, but I am also not sure if my reducer is correct

在此仓库中可以看到没有任何注释的代码:

The code without any comments can be seen in this repo: https://github.com/charles7771/decrease-number-wont-work/blob/master/index.js

const TimeGrid = () => {

  const reducer = (state, action) => {
    switch(action.type) {
      case 'SET_COUNTER':
        return {
          ...state,
          [`counter${action.id}`]: action.payload
        }
        default:
          return state
    }
  }

  //not sure if this bit is correct
  let [{ counter }, dispatchReducer] = useReducer(reducer, {
    counter: '',
  })

我的上下文导入和allBookedTimes

My Context import and allBookedTimes

const { theme, timesUnavailable, 
        removeFromTimesUnavailable, 
        addToTimesUnavailable } = useContext(Context)

const allBookedTimes = allBookings.map(element => element.time)
//below, both have been mapped out of a JSON file
const extractedTimesOnly = availableTimesJSON.map(item => item.time)
const availableTimes = availableTimesJSON.map(item => item)

我有有用的功能来计算重复一次的次数

I have useful function to count the number of times a Time is repeated

  //used to count instances. (e.g. 22:30: 3, 23:00: 1)
  const counts = {}
  extractedTimesOnly.forEach(x => {
    counts[x] = (counts[x] || 0) + 1
  })

  //used to not repeat a Time
  const timeAlreadyDisplayed = []

这是我用来遍历时间"列表并显示每个计数器旁边的逻辑,并尝试通过单击来减少计数器的逻辑.

And this is the logic I am using to loop over a list of Times and show each one with their counter by its side, as well as try and reduce the counter with a click.

  const displayAvailableTimes = availableTimes.map((item, index) => {
    //tries to set the value of counter0 (or counter1, ... counterN) 
    //to the number of instances it appears,
    //too many rerenders occurs...
    dispatchReducer({
      type: 'SET_COUNTER',
      id: item.id,
      payload: counts[`${item.time}`] //doesn't seem to be working. tried logging it and it shows nothing
    })

    //counter = counts[`${item.time}`] -----> works, but then I am not doing this through the dispatcher

    //maybe this logic could be flawed too?
    if (index > 0 &&
      item.time === availableTimes[index - 1].time &&
      item.time !== availableTimes[index - 2].time) {
      return (
          //tries to show the counter below
        <span> {counter} </span>
      )
    }
    else if (item.time > currentTime - 10 && !timeAlreadyDisplayed[item.time]) {
      timeAlreadyDisplayed[item.time] = true
      return (
        <li
          key={item.id}
          id={item.id}
          onClick={() => {
            //tries to decrease the counter, I don't think it works
            counter > 1 ? dispatchReducer({
              type: 'SET_COUNTER',
              id: item.id,
              payload: counter - 1
            }) :
              allBookedTimes.includes(item.time) && item.day === 'today'
                ? void 0
                timesUnavailable.includes(item)
                  ? removeFromTimesUnavailable(item)
                  : addToTimesUnavailable(item)
          }}>
          {item.time}
        </li>
      )
    } 
    return null
  })

  return (
    <>
      <ul>{displayAvailableTimes}</ul>
    </>
  )
}

推荐答案

我将为您提供一些有关计数时间和降低点击价值的观察.我将解释代码中的主要问题,并提供一种不同的实现方法,使您可以继续执行业务逻辑.

I will give you some observations with regards to counting the times and decreasing their values on click. I explain the main problems in your code and provide a differnt approach of implementation that allows you to continue with your business logic.

forEach循环使用数组的值作为counts对象的键.似乎您更想使用x.time值,因为这是您以后访问它的方式(payload: counts[ $ {item.time} ]). x本身是一个对象.

The forEach loop uses the values of the array as keys for the counts object. It seems that you rather want to use the x.time value, because this is how you later access it (payload: counts[${item.time}]). x itself is an object.

useReducer在返回的数组的第一项中为您提供一个状态对象.您立即使用{ counter }对其进行分解.该计数器变量的值是初始值('').减速器使用counter${action.id}形式的键在状态对象中设置值,因此分解后的counter变量将保持不变.

useReducer gives you a state object in the first item of the returned array. You immediately decompose it using { counter }. The value of that counter variable is the initial value (''). Your reducer sets values in the state object with keys in the form of counter${action.id}, so the decomposed counter variable will not change.

我认为您想要这样的东西:

I think you want something like this:

const [counters, dispatchReducer] = useReducer(reducer, {}); // not decomposed, the counters variable holds the full state of all counters you add using your `SET_COUNTER` action.

稍后,当您尝试渲染计数器时,您当前执行的是{ counter },该值始终为空(''),因为这仍然表示您的原始初始状态.现在,在counters保持完整状态的情况下,您可以使用其ID访问当前项目的counters对象的计数器:

Later, when you try to render your counters you currently do { counter } which is always empty ('') as this still refers to your original inital state. Now with the counters holding the full state you can access the counter of your counters object for the current item by using its id:

    {if (index > 0 &&
      item.time === availableTimes[index - 1].time &&
      item.time !== availableTimes[index - 2].time) {
      return (
        <span> {counters[`counter${item.id}`]} </span>
      )
    }

3.通用代码结构

还有更多问题,并且代码太疯狂了,很难理解(例如,由于以混淆的方式混合概念).即使您修正了上述观察结果,我也怀疑会导致某些事情达到您想要的或您曾经能够维护的水平.因此,我想出了一种不同的代码结构,该结构可能会为您提供一种有关如何实现它的新思路.

3. general code structure

There are more issues and the code is pretty crazy and very hard to comprehend (e.g. because of mixing concepts in confusing ways). Even if you fix the said observations, I doubt it will result in something that does what you want or that you ever are able to maintain. So I came up with a different code structure that might give you a new way of thinking on how to implement it.

您不需要useReducer ,因为您的状态相当平坦. Reducer更适合于更复杂的状态,但最终它仍然是本地组件状态.

You do not need useReducer because your state is pretty flat. Reducers are better suited for more complex state, but in the end it still is local component state.

我不知道您在单击这些项目时到底想要实现什么,所以我只是减少了计数,因为我认为这就是这个问题.

I do not know what exactly you want to achieve when clicking on the items, so I just reduce the count, because I think that is what this question is about.

以下是正在运行的以下代码的代码框: https://codesandbox.io/s/relaxed-roentgen-xeqfi?file=/src/App.js

Here is a codesandbox of the following code in action: https://codesandbox.io/s/relaxed-roentgen-xeqfi?file=/src/App.js

import React, { useCallback, useEffect, useState } from "react";

const availableTimes = [
  { time: "07:30" },
  { time: "08:00" },
  { time: "08:00" },
  { time: "08:00" },
  { time: "09:30" },
  { time: "10:00" }
];

const CounterApp = () => {
  const [counts, setCounts] = useState({});
  useEffect(() => {
    const counts = {};
    availableTimes.forEach(x => {
      counts[x.time] = (counts[x.time] || 0) + 1;
    });
    setCounts(counts);
  }, []);

  const onClick = useCallback(time => {
    // Your logic on what to do on click goes here
    // Fore example, I only reduce the count of the given time.
    setCounts(prev => ({
      ...prev,
      [time]: prev[time] - 1,
    }));
  }, []);

  return (
    <div>
      <h2>Counts:</h2>
      <ul>
        {Object.keys(counts).map(time => (
          <li key={time} onClick={() => onClick(time)}>
            {time} ({counts[time]})
          </li>
        ))}
      </ul>
    </div>
  );
};

export default CounterApp;

这篇关于遍历列表时如何动态使用useReducer?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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