我正在尝试显示时间列表(例如07:00、07:30),但是当出现重复时间时,请在其旁边显示重复次数(例如07:30、08:00³)
当我遍历列表时,每个项目都需要有自己的状态,以便可以在每个时间旁边设置和显示计数器
此刻,我有太多的rerender,但我也不确定我的减速器是否正确
在此仓库中可以看到没有任何注释的代码: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 = []
这是我用来遍历“时间”列表并显示每个计数器旁边的逻辑,并尝试通过单击来减少计数器的逻辑。
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>
</>
)
}
答案 0 :(得分:3)
我将为您提供一些有关计数时间和降低点击价值的意见。我将解释代码中的主要问题,并提供一种不同的实现方法,使您可以继续执行业务逻辑。
counts
forEach
循环使用数组的值作为counts
对象的键。似乎您宁愿使用x.time
值,因为这是您以后使用它的方式(payload: counts[
$ {item.time} ]
)。 x
本身是一个对象。
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
保持完整状态,您可以使用其ID访问当前项目的counters
对象的计数器:
{if (index > 0 &&
item.time === availableTimes[index - 1].time &&
item.time !== availableTimes[index - 2].time) {
return (
<span> {counters[`counter${item.id}`]} </span>
)
}
还有更多的问题,并且代码非常疯狂,很难理解(例如,由于以混淆的方式混合概念)。即使您解决了上述问题,我仍然怀疑会导致某些事情达到您想要的或您曾经能够维护的水平。因此,我想出了一个不同的代码结构,它可能为您提供了一种有关如何实现它的新思路。
您不需要useReducer
,因为您的状态非常平稳。 Reducers are better suited for more complex state,但最后仍然是本地组件状态。
我不知道单击这些项目时想要实现什么,所以我减少了计数,因为我认为这就是这个问题。
以下是正在运行的以下代码的代码框: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 :(得分:1)
您在化简器中设置状态的方式与您检索它的方式不匹配。由于多次调用dispatchReducer
(对于availableTimes
中的每个元素一次),您也得到了太多的重新渲染。初始化减速器的状态时,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的答案,以了解如何更好地构造它,使其更具可读性和可维护性。