useState 使动态样式应用较晚

useState makes dynamic styling apply late

提问人:in43sh 提问时间:10/21/2023 最后编辑:in43sh 更新时间:10/23/2023 访问量:67

问:

我正在开发Nextjs应用程序。这是我在客户端组件中的内容:

const [view, setView] = useState<string[]>(["all"]);

const story = stories?.holidays;
const joke = stories?.jokes;
const idiom = stories?.idioms;
const factoid = stories?.culturalTips;

const handleClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
    const target = e.currentTarget as HTMLButtonElement;
    const value = target.innerText.toLowerCase();

    setView((prevState) => {
      if (value === "all") {
        return ["all"];
      } else {
        // If any other filter is clicked while "all" is selected, deselect "all"
        if (prevState.includes("all")) {
          return [value];
        } else {
          // For other filters, toggle their selection status
          if (prevState.includes(value)) {
            const filteredView = prevState.filter(v => v !== value);
            //Checks if no filter is selected, if so selects "all"
            return filteredView.length === 0 ? ["all"] : filteredView;
          } else {
            return [...prevState, value];
          }
        }
      }
    });
  };

return (
    <Flex>
      <Button
        justifyContent={'center'}
        backgroundColor={view.includes('all') ? 'gray.200' : 'orange.50'}
        textColor={view.includes('all') ? 'black' : 'white'}
        disabled={isLoading}
        onClick={(e) => handleClick(e)}
      >
        <Text fontSize={'sm'}>All</Text>
      </Button>
      <Button
        justifyContent={'center'}
        backgroundColor={view.includes('story') ? 'gray.200' : 'orange.50'}
        textColor={view.includes('story') ? 'black' : 'white'}
        disabled={isLoading}
        onClick={(e) => handleClick(e)}
      >
      // the rest of the buttons
    </Flex>
    {factoid &&
    story &&
    idiom &&
    joke &&
    (view.includes('all') || view.includes('story')) &&
    story.map((holidayValue, idx) => (
    <Box
      border={'1px solid #E2E2E2'}
      borderRadius={'10px'}
      padding={'20px'}
      my={'20px'}
      boxShadow="lg"
      key={idx}
    >
      <Image
        width={40}
        height={40}
        className="stories-icon"
        src={storyImg}
        alt="story"
      />
      <Text>{holidayValue}</Text>
    </Box>
    ))}
    // the same 3 more times for the joke, idiom, factoid
)

目标是能够同时应用多个过滤器。如果未应用任何筛选器,则选择“全部”。如果未应用任何筛选器,则按钮将变为原始状态。按钮样式是动态的,取决于视图状态。问题是它比应该的应用晚了一步。我认为这是因为 的异步性质,但我不知道如何解决它。我正在使用 Chakra UI,但我认为它不会引起任何问题。示例在下面的 GIF 上:useState

illustration of the issue

reactjs 异步 next.js react-hooks

评论

1赞 tao 10/21/2023
“it's applied one step late than it should” 是什么意思?换句话说,目前尚不清楚这里所需的功能是什么。你能澄清一下吗?
1赞 Mike 'Pomax' Kamermans 10/21/2023
请注意,onClick 仅在发布启动。您可能至少希望挂接到触摸开始/鼠标按下事件。
1赞 tao 10/21/2023
这是否按预期工作?这个想法是你永远不应该包含在activeFilters中(或你在示例中所说的任何内容)。 是派生状态。它的值由(在我的示例中)确定。'all''all'filters.length === activeFilters.length
1赞 Ori Drori 10/21/2023
请注意,当您遇到问题时,您会获得带有灰色背景的白色文本,尽管它应该是橙色上的白色或灰色上的黑色。这意味着某些内容会覆盖背景。尝试从按钮中删除,然后检查它。disabled={isLoading}
1赞 tao 10/21/2023
理解,使用描述的逻辑更新了示例。

答:

1赞 tao 10/23/2023 #1

我建议任何时候都不要在活动过滤器数组中添加过滤器。筛选器(表示“所有项目”,又名“无筛选器”)是从 派生自 的状态。'all''all'!activeFilters.length

下面是一个具有所需功能的组件(codesandbox 链接):

import * as React from 'react'

export const FilterButtons: React.FC<object> = () => {
  const [activeFilters, setActiveFilters] = React.useState<string[]>([])

  const filters = ['one', 'two', 'three', 'four']
  const all = !activeFilters.length

  const toggleFilter = (filter?: string) =>
    setActiveFilters((prevState) =>
      filter
        ? prevState.includes(filter)
          ? prevState.filter((f) => f !== filter)
          : filters.length - prevState.length !== 1
          ? [...prevState, filter]
          : []
        : []
    )

  return (
    <>
      {filters.map((filter) => (
        <button
          key={filter}
          onClick={() => toggleFilter(filter)}
          className={activeFilters.includes(filter) ? 'active' : ''}
        >
          {filter}
        </button>
      ))}
      <button
        className={all ? 'active' : ''}
        onClick={() => toggleFilter()}
        disabled={all}
      >
        all
      </button>
    </>
  )
}

请注意,我使用的唯一状态是 ,因为其他任何变化都没有改变。activeFilters

如果里面的嵌套三元难以阅读,这里有一个更明确的版本:toggleFilter

function toggleFilter(filter?: string) {
  setActiveFilters((prevState) => {
    if (filter) {
      if (prevState.includes(filter)) {
        // remove filter
        return prevState.filter((f) => f !== filter)
      } else if (filters.length - prevState.length !== 1) {
        // add filter
        return [...prevState, filter]
      }
    }
    // remove all filters
    return []
  })
}