我创建了一个带有自定义下拉组件的表单,我花了一天时间试图找出导致这种情况的原因。
我试过使用useRef
和使用defaultValue
SO 答案。
“菜单名称”文本输入可以多次更改,直到我单击我的<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,
}