Как динамически использовать useReducer при зацикливании списка? - PullRequest
1 голос
/ 16 апреля 2020

Я пытаюсь показать список времен (например, 07:00, 07:30), но когда появляется повторное время, показывать количество повторений рядом с ним (например, 07:30, 08: 00³)

Поскольку я зацикливаюсь на списке, каждому элементу нужно свое собственное состояние, чтобы счетчик можно было устанавливать и отображать рядом с каждым временем

В настоящий момент у меня проблемы со слишком большим количеством повторных обращений, но я также не уверен, что мой редуктор правильный

Код без комментариев можно увидеть в этом репо: 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

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)

У меня есть полезная функция для подсчета количества повторений времени

  //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 = []

И это логи c, которые я использую для l oop над списком Times и покажите каждому счетчик рядом с ним, а также попробуйте уменьшить счетчик одним кликом.

  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>
    </>
  )
}

Ответы [ 2 ]

3 голосов
/ 19 апреля 2020

Я дам вам некоторые замечания, касающиеся подсчета времени и уменьшения их значений при нажатии. Я объясняю основные проблемы в вашем коде и предоставляю иной подход к реализации, который позволяет вам продолжить работу с бизнес-логикой c.

1. правильный доступ к counts

forEach l oop использует значения массива в качестве ключей для объекта counts. Кажется, что вы скорее хотите использовать значение x.time, потому что именно так вы позже получите к нему доступ (payload: counts[ $ {item.time} ]). x сам по себе является объектом.

2. правильное использование useReducer

useReducer дает вам объект состояния в первом элементе возвращаемого массива. Вы немедленно разлагаете его, используя { counter }. Значение этой переменной счетчика является начальным значением (''). Ваш редуктор устанавливает значения в объекте состояния с ключами в виде counter${action.id}, поэтому разложенная переменная counter не изменится.

Я думаю, вы хотите что-то вроде этого:

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 удерживает полное состояние, вы можете получить доступ к счетчику вашего объекта counters для текущего элемента, используя его идентификатор:

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

3. общая структура кода

Есть больше проблем, и код довольно сумасшедший и его очень трудно понять (например, из-за смешанных понятий в путанице). Даже если вы исправите упомянутые наблюдения, я сомневаюсь, что это приведет к тому, что вы делаете то, что хотите, или что вы когда-либо сможете поддерживать. Поэтому я придумал другую структуру кода, которая могла бы дать вам новое представление о том, как ее реализовать.

Вам не нужно useReducer, потому что ваше состояние довольно плоское. Редукторы лучше подходят для более сложного состояния , но, в конце концов, это все еще локальное состояние компонента.

Я не знаю, чего именно вы хотите достичь при нажатии на элементы, поэтому я просто уменьшите счет, потому что я думаю, что это то, о чем этот вопрос.

Вот коды и поле следующего кода в действии: 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;
1 голос
/ 19 апреля 2020

То, как вы устанавливаете свое состояние в вашем редукторе, не совпадает с тем, как вы его восстанавливаете. Вы также получаете слишком много повторных вызовов, потому что вы звоните dispatchReducer несколько раз (один раз для каждого элемента в availableTimes). Все логики c в displayAvailableTimes должны происходить при инициализации состояния редуктора.

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

  const counts = {}
  extractedTimesOnly.forEach(x => {
    counts[x] = (counts[x] || 0) + 1
  })

  const init = (initialState) => availableTimes.reduce((accum, item, index) => ({
      ...accum,
      `counter${item.id}`: counts[`${item.time}`]
  }), initialState);

  let [state, dispatchReducer] = useReducer(reducer, {
    counter: '',
  }, init)
const displayAvailableTimes = availableTimes.map((item, index) => {
  if (index > 0 &&
    item.time === availableTimes[index - 1].time &&
    item.time !== availableTimes[index - 2].time) { //An array out of bounds error could happen here, FYI
    return (
      <span> {state[`counter${item.id}`]} </span>
    )
  } else if (item.time > currentTime - 10 && !timeAlreadyDisplayed[item.time]) {
    timeAlreadyDisplayed[item.time] = true
      return (
        <li
          key={item.id}
          id={item.id}
          onClick={() => {
            state[`counter${item.id}`] > 1 ? dispatchReducer({
              type: 'SET_COUNTER',
              id: item.id,
              payload: state[`counter${item.id}`] - 1
            }) :
              allBookedTimes.includes(item.time) && item.day === 'today'
                ? void 0  //did you miss a colon here?
                timesUnavailable.includes(item)
                  ? removeFromTimesUnavailable(item)
                  : addToTimesUnavailable(item)
          }}>
          {item.time}
        </li>
      )
    } 
});

Это решит проблемы, с которыми вы сталкиваетесь сейчас. Однако вам действительно не нужен редуктор, если это все, для чего вы его используете. Обратитесь к ответу Stuck, чтобы увидеть, как его лучше структурировать, чтобы он был более читабельным и понятным.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...