react,打开和关闭自定义下拉菜单后表单输入停止工作

IT技术 reactjs
2022-07-30 01:15:10

我创建了一个带有自定义下拉组件的表单,我花了一天时间试图找出导致这种情况的原因。

我试过使用useRef和使用defaultValueSO 答案。

“菜单名称”文本输入可以多次更改,直到我单击我的<DropDownComponent/>.

在我选择了几天并关闭弹出窗口后,我的输入变得无法使用。

可用性组件

打开和关闭弹出窗口后,我失去了与文本输入交互的能力

为什么会这样?我觉得可能<input onChange=()=>{}/>以某种方式迷失在 DOM 上?

菜单窗体.tsx

import { ChangeEvent, useEffect,  useState } from "react";
import { .......... } from '../../../styling/styled-components';
import DropDownComponent from "../../UI/DropDownComponent/DropDownComponent";
import useImgUpload from "../../../hooks/useImgUpload";
import Icon from "../../../assets/icons/Icons";

const MenuForm = () => {
  const {imgUrl, uploadFn} = useImgUpload();
  const INPUT_LIST = ["Mon", "Tue", "Wed", "Thur", "Fri", "Sat", "Sun"];
  const [nameInput, setNameInput] = useState<string>('');
  const [availableDays, setAvailableDays ] = useState<string[]>([]);

  useEffect(() => {
   console.log(availableDays);
  },[availableDays])

  const anyTimeCheckBox = (event: ChangeEvent<HTMLInputElement>) => {
    console.log(event.target.checked);
    console.log(event.target.id);
  }

  const showUploadedImg = (event: ChangeEvent<HTMLInputElement>) => {
    console.log(event);
    uploadFn(event);
  }

  const submitHandler = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    console.log(`${nameInput}, ${availableDays}`);
  }

  const selectedDayHandler = (arr:string[]) => {
    setAvailableDays( arr );
    console.log('Days Set');
  }

  const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
      setNameInput((currState:string) => {return currState = e.target.value});
  }

  console.log('Menu Form rendered');

  return(
    <MenuFormStyle onSubmit={submitHandler}>

      <MenuImgWrapper>
        <ImgInput
          type='file'
          alt='Add Image'
          accept="image/png, image/jpeg"  
          onChange={showUploadedImg}
        />
        <ImgDisplay>
          <LoadedImgCont>
            {
              imgUrl 
              ?
              <LoadedImg src={imgUrl} alt='uploaded-img'></LoadedImg>
              : 
              <>
                <NoImg src={Icon.AddImage} alt='icon-placeholder'></NoImg>
                <NoImgTxt>Add Image</NoImgTxt>
              </>
            }
          </LoadedImgCont>
        </ImgDisplay>
        
      </MenuImgWrapper>

      <MenuNameInput
        type='text' 
        placeholder= 'Menu name'
        name='menuName'
        onChange={handleInputChange}
        defaultValue={nameInput}
      />

      {/* Day + Time selections */}
      <DropDownComponent list={INPUT_LIST} setSelectedDays={selectedDayHandler}/>
      
      <TimeStartInput
        type='time'
        placeholder='Start'
      />

      <TimeEndInput 
        type='time'
        placeholder="End"
      />

      <TimeCheckBoxWpr>
        <TimeCheckBox
          type='checkbox'
          name='24/7'
          id='isAlwaysAvailable'
          onChange={anyTimeCheckBox}/>
        <label htmlFor='24/7'>Anytime</label>
      </TimeCheckBoxWpr>

      {/*Extra Settings */}
      <FieldSetMenu>
        <legend>Menu Settings:</legend>
        <div>
          <input
            type='checkbox'
            name='apples'
            id='apples'
            />
            <label htmlFor='apples'>Hide menu</label>
        </div>
      </FieldSetMenu>

      <MenuSaveButton>Save</MenuSaveButton>

    </MenuFormStyle>
  );
}

export default MenuForm;

下拉组件

import React, { useEffect, useMemo, useRef, useState } from "react";
import Icon from "../../../assets/icons/Icons";

import {
  ArrowDownIcon,
  CloseIcon,
  CustomListItem,
  DaySelectInput,
  DaySelectPopUp,
  DisplaySelectedDays,
  SelectedText,
  UList,
} from "../../../styling/styled-components";

interface IProps {
  list: string[];
  setSelectedDays: (arr: string[]) => void;
  multiSelect?: boolean;
}

interface ListProps {
  list: DropDownProps[];
  updateSelection: (e: React.MouseEvent<HTMLElement>) => void;
  closeDropDown: () => void;
}

interface DropDownProps {
  value: string;
  selected: boolean;
}

const DropDownComponent = (props: IProps) => {
  const [displayDropDown, setDisplayDropDown] = useState(false);
  const [selectedItems, setSelectedItems] = useState<string[]>([]);
  const [customListItems, setCustomListItems] = useState<DropDownProps[]>();
  const { list, setSelectedDays } = props;

  useMemo(() => {
    let buildListArray: DropDownProps[] = list.reduce(
      (previous, current) => [...previous, { value: current, selected: false }],
      [{ value: "Reset", selected: false }]
    );
    setCustomListItems((currState) => {
      return (currState = buildListArray);
    });
    console.log("DropDownComponent");
  }, []);

  useEffect(() => {
    setSelectedDays(selectedItems);
  },[selectedItems, setSelectedDays]);

  const captureSelected = (event: any) => {
    //updates state of custom drop down menu

    let dropDownItem = customListItems![event.target.value];
    dropDownItem.selected = !dropDownItem.selected;
    let updatedSelectState = dropDownItem.selected;

    if (updatedSelectState && dropDownItem.value === "Reset") {
      console.log('Reset Triggered');
      setSelectedItems([]);
      customListItems?.forEach((item) => {
        item.selected = false;
      });
      return;
    } else if (updatedSelectState && !existsInList()) {
      console.log('Added to SelectedItems');
      setSelectedItems((currState) => {
        return [...currState, dropDownItem.value];
      });
    } else if (!updatedSelectState && existsInList()) {
      console.log('Removed from selected items');
      let updatedArray = selectedItems.filter(
        (item) => item !== dropDownItem.value
      );
      setSelectedItems(updatedArray);
    }

    function existsInList(): boolean {
      return selectedItems.includes(dropDownItem.value);
    }
  };

  const displaySelection = (items: string[]): string => {
    return items.join(", ");
  };

  const toggleDropDown = () => {
    console.log(selectedItems);
    setDisplayDropDown(currState => !currState);
    console.log('toggle');
  };

  return (
    <DaySelectInput>
      <DisplaySelectedDays>
        <SelectedText>
          { 
            !selectedItems.length 
            ? 
            "Availability" 
            : 
            displaySelection(selectedItems) 
          }
        </SelectedText>

        {
          displayDropDown 
          ? 
          <CloseIcon src={Icon.Close} alt='dummy-icon'/>
          :
          <ArrowDownIcon 
            src={ Icon.ArrowDown } 
            alt='icon-down' 
            onClick={toggleDropDown}
            />
        }
        
        </DisplaySelectedDays>

        {
          displayDropDown 
          && 
          <DropDownList
              list={customListItems!}
              updateSelection={captureSelected}
              closeDropDown={toggleDropDown}
          />
        }
    </DaySelectInput>
  );
};

const DropDownList = (props: ListProps) => {
  const elRef = useRef<HTMLDivElement | null>(null);
  const { list, closeDropDown } = props;

  useEffect(() => {
    console.log('ClickOutside Effect')
    document.addEventListener("mousedown", handleClickOutside);
    return () => {
      // Unbind the event listener on clean up
      document.removeEventListener("mousedown", handleClickOutside);
    };

    function handleClickOutside(event: any) {
      event.preventDefault();
      if (elRef.current && !elRef.current?.contains(event.target)) {
        closeDropDown();
      }
    }
  }, [elRef, closeDropDown]);

  return (
    <DaySelectPopUp ref={elRef}>
      <UList>
        {list?.map((listItem: DropDownProps, index:number) => {
          return (
            <CustomListItem
              key={`li-${listItem.value}`}
              value={index}
              onClick={props.updateSelection}
              userSelected={listItem.selected}
            >
              {listItem.value}
            </CustomListItem>
          );
        })}
      </UList>
    </DaySelectPopUp>
  );
};
export default DropDownComponent;

样式化的组件文件以防您想自己尝试

import styled from 'styled-components/macro';

const primaryMain = '#003332';
const primaryLight = '#305d5b';
const primaryDark = '#000e0a';

const baseBackground = '#EBE9E9';

const secondaryMain = '#003332';
const secondaryLight = '#305d5b';
const secondaryDark = '#000e0a';

const textLight = 'white';
const textDark = 'black';

//Texts
const StandardText = styled.p` 
  color: ${textLight};
`;

//UI
const BaseCard = styled.div` 
  display: inline-flex;
  justify-content: center;
  align-items: center;
  background: ${baseBackground};
  padding: 5px; 
  margin: 5px;
`;

const GreenCard = styled(BaseCard)`
  height: 50px;
  margin: 5px 0;
  border-radius: 5px;
  background: ${primaryMain};
`

//NavigationComponent
const NavBackDrop = styled.div`
  width: 100%;
  background: ${primaryDark};
  padding: 10px 0px 5px 0px;
`;
const NavWrapper = styled.div`
  width: 100%;
  background: ${primaryMain};
  padding: 5px 0px;

  display: inline-flex;
  align-items: center;
`;

const NavLogoWrapper = styled.div` 
  width: 50px;
  height: 40px;
  background: ${primaryLight};
  margin-left: 10px;
`;

const NavLinkWrapper = styled.div` 
  width: 100%;
  display: inline-flex;
  justify-content: flex-start;
  align-items: center;

  & > a {
    min-width: 100px;
    height: fit-content;
    text-align: center;
    margin: 0px 10px;
    color: white;
    text-decoration: none;
    cursor: pointer;
  }

  & > a.active {
    background: ${primaryLight};
    padding: 0px 10px;
    border-radius: 5px;
    max-width: 100px;

    & > p {
      color: white;
      font-weight: 700;
      transform: scale(1.05);
      margin: 10px 0px;
    }
  }

`;

const ProfileImgWrapper = styled.div` 
  width: 40px;
  height: 40px;
  background: ${primaryLight};
  border-radius: 50%;
  float: right;
  margin-right: 10px;
`;

const DashBoardGrid = styled.div` 
  min-height: 80vh;
  height: fit-content;
  display: grid;
  grid-template-columns: 30vw 30vw 35vw;
  grid-gap: 10px;
  padding: 10px;
  justify-content: center;

  & > div {
    border-radius: 5px;
    box-shadow: 1px 1px 2px 1px #00000038;
  }
`;

const DashSalesGridWrapper = styled(BaseCard)` 
  grid-column: 1 / span 2;
`;

const DashMenuWrapper = styled(BaseCard)` 
  grid-column: 3 ;
  grid-row: 1 / span 2;
  display: grid;
  grid-template-rows: 50px;
  grid-template-columns: 100%;
`;

const DashKpiWrapper = styled(BaseCard)`
  grid-column: 1 / span 2 ;
  grid-row: 2 ;
`;

const MenuWrapper = styled.div` 
  width: 100vw;
  display: inline-flex;
  justify-content: space-around;
`

const Grid = styled(BaseCard)` 
  width: 45vw;
  display: grid;
  grid-template-columns: 100%;
  grid-auto-rows: minmax(50px, auto);
  align-items: flex-start;
  border-radius: 3px;
  height: fit-content;
  box-shadow: 1px 1px 2px 1px #00000054;
`;

const CreateNewButton = styled.button`
  height: 50px;
  border: 3px solid ${primaryLight};
  border-radius: 5px;
  background: white;
  cursor: pointer;
  display: inline-flex;
  justify-content: center;
  align-items: center;

  &:hover{
    transform: scale(1.01);
    box-shadow: 1px 1px 2px 1px #00000054;
    font-size: 0.9rem;

    &>img{
      transform: scale(1.2);
    }
  }
`;

const IconImg = styled.img`
  width: 30px;
  height: 30px;
  margin-right: 5px;
`;

const CustomListItem = styled.li<{userSelected:boolean}>`
  color: ${ props =>  props.userSelected ? 'white': 'none'};
  background: ${ props =>  props.userSelected ? primaryLight : 'none'};
  cursor: pointer;
  min-width: 28%;
  margin: 2px;
  border-radius: 5px;
  border: 3px solid ${primaryLight};
  font-weight: 500;
  text-align: center;

  &:first-of-type {
    width: 100%;
  }

  &:hover{
    background: white;
    color: black;
  }
`;

const DaySelectInput = styled.div` 
  grid-column: 3/ span 2;
  grid-row: 2;
  position: relative;
`;

const DaySelectPopUp = styled.div` 
  width: 100%;
  position: absolute;
  top: 100%;
  background: #d6d9d9;
  border: 2px solid ${primaryLight};
  padding: 5px;
  margin-top: 3px;
`;

const UList = styled.ul` 
    display: flex;
    flex-flow: wrap;
    list-style-type: none;
    padding: 0px;
    justify-content: flex-start;
`;

const MenuFormStyle = styled.form` 
    display: grid;
    justify-content: center;
    align-items: flex-end;
    grid-template-columns: 27% 27% 20% 20%;
    grid-gap: 5px;

    color: #444;
    border: 3px solid #305d5b;
    border-radius: 10px;
    padding: 10px;
    margin-top: 2px;
  `;

  const MenuImgWrapper = styled.div` 
    grid-column: 1/span 2;
    grid-row: 1/ span 2;
    height: 200px;
    width: 200px;
    position: relative;
  `;

  const ImgInput = styled.input` 
    width: 100%;
    height: 100%;
    opacity: 0;
    z-index: 1;
    position: absolute;
  `;

  const ImgDisplay = styled.div`
    width: 100%;
    height: 100%;
    position: absolute;
    top: 0;
    z-index: 0;
    border: 3px solid #003332;
    background: #d6d9d9;
  `;

  const MenuNameInput = styled.input` 
    grid-column: 3/span 2;
    grid-row: 1;
    min-height: 30px;
    border-radius: 5px;
    border: 3px solid ${primaryLight};
  `;

  const TimeStartInput = styled.input` 
    grid-column: 3;
    grid-row: 3;
  `;
  const TimeEndInput = styled.input` 
    grid-column: 4;
    grid-row: 3;
`;

  const TimeCheckBox = styled.input` 
    border-radius: 50%;
  `;

  const TimeCheckBoxWpr = styled.div` 
    grid-column: 4;
    grid-row: 4;
    grid-column: 2;
    grid-row: 3;
    margin-left: auto;
    width: fit-content;
  `;

  const FieldSetMenu = styled.fieldset` 
    grid-row: 5/span 2;
    grid-column: 1/span 4;
  `;

  const MenuSaveButton = styled.button` 
    grid-row: 9;
    grid-column: 4;
  `;

  const DisplaySelectedDays = styled.div` 
    width: 100%;
    border: 2px solid ${primaryLight};
    padding: 5px;
  `;

  const SelectedText = styled.span` 
    margin: 0px;
  `;

  const LoadedImg = styled.img` 
    width: 80%;
    height:auto;
  `;

  const NoImg = styled(LoadedImg)`
    width: 40px;
    margin-right: 5px;
  `;

  const NoImgTxt = styled.p` 
    font-size: 20px;
  `;

  const LoadedImgCont = styled.div`
    width: 100%;
    height: 100%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  `;

  const ArrowDownIcon = styled.img` 
    cursor: hover;
    float: right;
    width: fit-content;
  `;

  const CloseIcon = styled(ArrowDownIcon)``;



export {
  ArrowDownIcon,
  BaseCard,
  CloseIcon,
  CreateNewButton,
  CustomListItem,
  DashBoardGrid,
  DashKpiWrapper,
  DashMenuWrapper,
  DashSalesGridWrapper,
  DaySelectInput,
  DaySelectPopUp,
  DisplaySelectedDays,
  FieldSetMenu,
  GreenCard,
  Grid,
  IconImg,
  ImgInput,
  ImgDisplay,
  LoadedImg,
  LoadedImgCont,
  MenuFormStyle,
  MenuImgWrapper,
  MenuNameInput,
  MenuSaveButton,
  MenuWrapper,
  NavBackDrop,
  NavWrapper,
  NavLogoWrapper,
  NavLinkWrapper,
  NoImg,
  NoImgTxt,
  ProfileImgWrapper,
  SelectedText,
  StandardText,
  TimeCheckBoxWpr,
  TimeEndInput,
  TimeStartInput,
  TimeCheckBox,
  UList,
}
1个回答

DropDownList您注册一些点击外部侦听器时,但这发生在 auseMemo当它应该是 a 时useEffect因此,当弹出窗口关闭时,单击处理程序永远不会被删除,这可能会使preventDefault每次单击时调用的所有内容都无法选择。