简体   繁体   English

反应检测点击外部组件清除我的 state

[英]React detect click outside component clearing my state

I have an outsideAlerter component that functions elsewhere on my site.我有一个 outsideAlerter 组件,可以在我网站的其他地方运行。 I am now using it on a repeatable component and for some reason it is clearing my state effectively breaking my desired outcome.我现在在可重复组件上使用它,由于某种原因,它正在清除我的 state,有效地破坏了我想要的结果。

below is my wrapper component that detects if you click outside of its children下面是我的包装器组件,它检测您是否在其子项之外单击

import React, { useRef, useEffect } from "react";

/**
 * Hook that alerts clicks outside of the passed ref
 */
function useOutsideAlerter(ref, onClickOutside) {
    useEffect(() => {
        /**
         * Alert if clicked on outside of element
         */
        function handleClickOutside(event) {
            if (ref.current && !ref.current.contains(event.target)) {
              //console.log(onClickOutside);
              onClickOutside();
            }
        }

        // Bind the event listener
        document.addEventListener("mousedown", handleClickOutside);
        return () => {
            // Unbind the event listener on clean up
            document.removeEventListener("mousedown", handleClickOutside);
        };
    }, []);
}

/**
 * Component that alerts if you click outside of it
 */
export default function OutsideAlerter(props) {
    const wrapperRef = useRef(null);
    useOutsideAlerter(wrapperRef, props.onClickOutside);

    return <div ref={wrapperRef}>{props.children}</div>;
}

Below is my controller component, it handles state下面是我的 controller 组件,它处理 state

const TableFilterDropdownController = ({style, rows, colKey, activeFilters, addActiveFilter}) => {
    const [tableFilterState, setTableFilterState] = useState(
        {
            state: INACTIVE,
            iconColor: "black",
            filter: "",
            filteredRows: [...rows],
            localActiveFilters: []
        }
    );

    useEffect(() => {
        let state = tableFilterState.state;
        let localActiveFilters = tableFilterState.localActiveFilters;
        if (state === INACTIVE && localActiveFilters.length > 0) {
            setTableFilterState({...tableFilterState, state: ACTIVE})
        }
    }, [tableFilterState.state])
    //filter out repeats and rows that don't match input
    useEffect(() => {
        let filter = tableFilterState.filter
        if (filter !== "") {
            let tempFilteredRows = []; 
            rows.map(row => {
                if (row[colKey].toLowerCase().includes(filter.toLowerCase()) && 
                !tempFilteredRows.includes(row[colKey])) {
                    tempFilteredRows.push(row[colKey]);
                }
            })
            setTableFilterState({...tableFilterState, filteredRows: tempFilteredRows})
        }
        else {
            let tempFilteredRows = []; 
            rows.map(row => {
                if (!tempFilteredRows.includes(row[colKey])) {
                    tempFilteredRows.push(row[colKey]);
                }
            })
            setTableFilterState({...tableFilterState, filteredRows: tempFilteredRows});
        }
    }, [tableFilterState.filter, rows])

    const onClick = () => {
        if (tableFilterState.state === DROP_DOWN) {
            console.log(tableFilterState)
            if (tableFilterState.localActiveFilters.length > 0) {
                //setState(ACTIVE)
                setTableFilterState({...tableFilterState, state: ACTIVE});
            }
            else {
                //setState(INACTIVE)
                setTableFilterState({...tableFilterState, state: INACTIVE});
            }
        }
        else {
            //setState(DROP_DOWN)
            setTableFilterState({...tableFilterState, state: DROP_DOWN});
        }
    }
    //something here is breaking it and resetting on click outside

    const onClickOutside = () => {
        setTableFilterState({...tableFilterState, state: INACTIVE});
    }

    let addLocalActiveFilter = (filter) => {
        let newActiveFilters = [...tableFilterState.localActiveFilters];
        const index = newActiveFilters.indexOf(filter);
        if (index > -1) {
            newActiveFilters.splice(index, 1);
        } else {
            newActiveFilters.push(filter);
        }
        setTableFilterState({...tableFilterState, localActiveFilters: newActiveFilters});
    }
    

    return (
      <TableFilterDropdown 
        style={style}
        color={tableFilterState.iconColor}
        state={tableFilterState.state}
        onClick={onClick}
        onClickOutside={onClickOutside}
        dropLeft={true}
        filter={tableFilterState.filter}
        setFilter={e => setTableFilterState({...tableFilterState, filter: e.target.value})}

      >
      
        {tableFilterState.filteredRows.map((item, index) => {
            return (
              <CheckboxInput 
                value={item} 
                label={item} 
                key={index} 
                onChange={e => {
                  addActiveFilter(e.target.value);
                  addLocalActiveFilter(e.target.value)
                  }} 
                isChecked={tableFilterState.localActiveFilters.includes(item)}
              />
            );
        })}
      </TableFilterDropdown>
    );
}

export default TableFilterDropdownController;

And lastly below is the UI component最后是 UI 组件

const TableFilterDropdown = ({style, state, color, children, onClick, onClickOutside, dropLeft, filter, setFilter}) => {
    
    useEffect(() => {
        console.log("state change")
        console.log(state);
    }, [state])

    return (
    <div 
      className={`sm:relative inline-block ${style}`} 
    >
      <OutsideAlerter onClickOutside={onClickOutside}>
        <IconButton 
          type="button" 
          style={`relative text-2xl`}
          onClick={onClick}
        >
         <IconContext.Provider value={{color: color}}>
            <div>
                {state === DROP_DOWN ?
                <AiFillCloseCircle /> :
                state === ACTIVE ?
                <AiFillFilter /> :
                <AiOutlineFilter />
                }
            </div>
          </IconContext.Provider>
        </IconButton>

        {state === DROP_DOWN ? 
          <div className={`flex flex-col left-0 w-screen sm:w-32 max-h-40 overflow-auto ${dropLeft ? "sm:origin-top-left sm:left-[-2.5rem]" : "sm:origin-top-right sm:right-0"} absolute mt-2 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none z-10`} role="menu" aria-orientation="vertical" aria-labelledby="menu-button">
            <SearchBar label={"Search"} placeholder={"Search"} value={filter} onChange={setFilter} />
              {children}
            
          </div>
          : null}
      </OutsideAlerter>
    </div>
  );

For some reason whenever you click outside the component the tableFilterState gets set to出于某种原因,每当您在组件外部单击时,tableFilterState 都会设置为

{
                state: INACTIVE,
                iconColor: "black",
                filter: "",
                filteredRows: [],
                localActiveFilters: []
            }

Which is not intentional, the tableFilterState should stay the same, only state should change.这不是故意的,tableFilterState 应该保持不变,只有 state 应该改变。 I can't figure this out so please help我无法弄清楚所以请帮忙

When you call useOutsideAlerter and pass onClickOutside handler it captures tableFilterState value and use it in a subsequent calls.当您调用useOutsideAlerter并传递onClickOutside处理程序时,它会捕获tableFilterState值并在后续调用中使用它。 This is a stale state.这是一个陈旧的 state。 You could try this approach or use refs as described in docs :您可以尝试这种方法或使用文档中描述的 refs :

const onClickOutside = () => {
  setTableFilterState(tableFilterState => ({
    ...tableFilterState,
    state: INACTIVE,
  }));
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM