因此,我想简化将 props 传递给父组件的孙子组件,其中更改打开或关闭的按钮或模式的状态,例如:
[open, setOpen]= useState()
,可以简化为 useReducer 并从父组件传递。无论如何,我无法理解为什么它不起作用,也许我理解错误的逻辑。理想情况下,我想接近 DRY 原则,并尽可能简单地传递 props。
错误:
AddButtonModal.tsx:36 Warning: Failed prop type: The prop
打开is marked as required in
ForwardRef(Modal2), but its value is
未定义.
父组件:
import React, {useReducer, useState} from "react";
import {Ingredient} from "../../models/ingredient";
import {Box} from "@mui/material";
// Importing the 4 components for their respective part that way we have a more concise Page
import AddButtonModal from "./components/IngredientsManagementViewComponents/AddButtonModal";
import IngredientCategoryMenu from "./components/IngredientsManagementViewComponents/IngredeintCategoryMenu";
import SearchBarComponent from "./components/IngredientsManagementViewComponents/SearchBar";
import ingredientReducer, {IngredientActionTypes, initialState} from "./reducers/ingredientReducer";
import modalReducer, {ModalActionTypes, initialModalState} from "./reducers/modalReducer";
import ListOfIngredients from "./components/IngredientsManagementViewComponents/ListOfIngredients";
const IngredientsViewPage: React.FC = () => {
//states
//state of the new ingredient
const [ingredientState, dispatch] = useReducer(ingredientReducer, initialState);
console.log("ingredientState:", ingredientState);
// state for the open/close function for modals/buttons
const [modalState, modalDispatch] = useReducer(modalReducer, initialModalState);
console.log("modalState: within IVP", modalState);
console.log("initialModalState within IVP: ", initialModalState);
const handleOpenModal = () => {
console.log("Open Modal");
modalDispatch({ type: ModalActionTypes.OPEN_MODAL}) };
const handleCloseModal = () => {
console.log("Close Modal");
modalDispatch({type: ModalActionTypes.CLOSE_MODAL}) };
/*An object refering to the Ingredient model properties which can later be called to map each Number Input */
const IngredientFieldsOptions = {
carbs: {label: "Carbohydrates", value: ingredientState.carbs},
protein: {label: "Protein", value: ingredientState.protein},
sugar: {label: "Sugar", value: ingredientState.sugar},
fat:{label: "Fat", value: ingredientState.fat},
fiber: {label: "Fiber", value: ingredientState.fiber}
}
return (
<Box sx={{ display: "flex", flexDirection: "column",
bgcolor: "whitesmoke", borderRadius: "3px", borderBlockColor: "none"}}>
{/*Search field with search lens*/}
<Box sx={{display: "flex", flexDirection: "row", width: "100%"}}>
<SearchBarComponent/>
{/*Button with the + symbol, opens a modal */}
<AddButtonModal
// IngredientFieldsOptions={IngredientFieldsOptions}
open={true}
handleOpenModal={handleOpenModal}
handleCloseModal={handleCloseModal}
/>
</Box>
{/* Component with the Categories menu */}
<IngredientCategoryMenu/>
{/* Component List of Ingredients */}
<ListOfIngredients
open={modalState.isModalOpen}
handleOpenModal={handleOpenModal}
handleCloseModal={handleCloseModal}
ingredientState={ingredientState}
dispatch={dispatch}
/>
</Box>
)
};
export default IngredientsViewPage;
孩子:
import React, {useReducer, useState} from "react";
import {Box, Button, Modal, Typography} from "@mui/material";
import Add from "@mui/icons-material/Add";
import ingredientReducer, {IngredientActionTypes, initialState} from "../../reducers/ingredientReducer.ts";
import {Ingredient} from "../../../../models/ingredient.ts";
import XButton from "./XButton.tsx";
import IngredientFields from "./IngredientFields.tsx";
import DropDowns from "./DropDown.tsx";
import CheckBoxSection from "./CheckBoxSection.tsx";
import NumberInputFields from "./NumberInputFields.tsx";
import SaveButton from "./SaveButton.tsx";
import { dummy_allergens } from "../../../../dummy_data/dummy_allergens.ts";
import { dummy_ingredients } from "../../../../dummy_data/dummy_ingredients.ts";
import { number } from "yup";
//defined types of the props
interface AddButtonModalProps {
isModalOpen: boolean;
handleOpenModal: () => void;
handleCloseModal: () => void;
}
const AddButtonModal: React.FC<AddButtonModalProps> = ({isModalOpen, handleOpenModal, handleCloseModal}) => {
const [ingredientState, dispatch] = useReducer(ingredientReducer, initialState);
console.log("modalState: within ABM", isModalOpen);
return (
<Box sx={{marginBottom: 2, }}>
<Button onClick={handleOpenModal} sx={{
minWidth: 100,
height: "auto",
display: "flex",
}}
startIcon={<Add />}
id="Container-search bar & + button">
Add new
<Modal
open={isModalOpen}
onClose={handleCloseModal}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: "70vw",
bgcolor: "whitesmoke",
boxShadow: 24,
borderRadius: "3px",
p: 4,
overflowY: "auto", // Scroll for overflow content
maxHeight: "90vh",
}} id="main container"
onClick={(event) => event.stopPropagation()}
>
{/* Label & X icon button */}
<Box sx={{display: "flex", flexDirection: "row", justifyContent: "space-between", alignItems: "center"}}
id="container-title & close-icon">
<Typography id="modal-modal-title" variant="h2" component="h2" sx={{fontSize: "1.2rem"}}>
ADD INGREDIENT
</Typography>
<XButton handleCloseModal={handleCloseModal}/>
</Box>
{/* Ingredients Fields Components*/}
// name of the ingredient
<IngredientFields />
{/* Dropdowns components */}
<DropDowns />
{/* Checkboxes components */}
<CheckBoxSection/>
{/* Number Input */}
<NumberInputFields
ingredientState={ingredientState}
dispatch={dispatch}
// IngredientFieldsOptions={IngredientFieldsOptions}
/>
{/* Save Button */}
<SaveButton handleCloseModal={handleCloseModal}/>
</Box>
</Modal>
</Button>
</Box>
);
};
export default AddButtonModal;
孙子:
import React from "react";
import {Box, IconButton} from "@mui/material";
import ClearIcon from '@mui/icons-material/Clear';
//defined types of the props
interface XButtonProps {
handleCloseModal: () => void;
}
const XButton: React.FC<XButtonProps> = ({handleCloseModal}) => {
return (
<Box>
{/* Icon that closes the modal */}
<IconButton onClick={handleCloseModal} style={{ cursor: 'pointer' }}>
<ClearIcon />
</IconButton>
</Box>
);
};
export default XButton;
减速机:
// Define action types related to modal operation
export enum ModalActionTypes {
OPEN_MODAL = 'OPEN_MODAL',
CLOSE_MODAL = 'CLOSE_MODAL'
}
// set opening action
type OpenModalAction = {
type: ModalActionTypes.OPEN_MODAL
};
// set closing action
type CloseModalAction = {
type: ModalActionTypes.CLOSE_MODAL
}
// Union type for modal-related actions
type ModalAction = OpenModalAction | CloseModalAction;
// initial state of the modal
export const initialModalState = {
isModalOpen: false
};
/** reducer function for handling of modal state changes
@params {object} state: current state
@param {ModalAction} action: action to be handled
@returns {object} updated state of modal
*/
function modalReducer(state = initialModalState, action: ModalAction) {
switch (action.type) {
case ModalActionTypes.OPEN_MODAL:
return {...state, isModalOpen: true};
case ModalActionTypes.CLOSE_MODAL:
return {...state, isModalOpen: false};
default:
return state;
// throw Error("Unknown action: " + action.type);
}
}
export default modalReducer;
我用 console.log 尝试了一下,看看每个组件的状态是什么: 父组件显示:
modalState: within IVP , isModalOpen: false line 22,
modalState: within IVP , isModalOpen: false line 23,
单击应打开模式的按钮后,我可以在控制台中看到
OpenModal ln 25,
modalState: within IVP , isModalOpen: true line 22,
modalState: within IVP , isModalOpen: false line 23.
此外,我读了这篇文章,但我不确定这是否有帮助。 警告:失败的道具类型:道具打开在 Snackbar 中被标记为必需,但其值未定义
Modal
渲染的AddButtonModal
组件似乎需要open
道具。 AddButtonModal
传递了它的 isModalOpen
属性,但是 AddButtonModal
本身没有传递任何 isModalOpen
属性。
const IngredientsViewPage: React.FC = () => {
....
return (
<Box .... >
<Box sx={{ .... }}>
<SearchBarComponent />
<AddButtonModal
open={true}
handleOpenModal={handleOpenModal}
handleCloseModal={handleCloseModal}
// <-- no isModalOpen prop
/>
</Box>
....
</Box>
)
};
interface AddButtonModalProps {
isModalOpen: boolean; // <--
handleOpenModal: () => void;
handleCloseModal: () => void;
}
const AddButtonModal: React.FC<AddButtonModalProps> = ({
isModalOpen, // <-- undefined
handleOpenModal,
handleCloseModal
}) => {
....
return (
<Box sx={{marginBottom: 2 }}>
<Button .... >
Add new
<Modal
open={isModalOpen} // <-- undefined
onClose={handleCloseModal}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
....
</Modal>
</Button>
</Box>
);
};
我怀疑你想通过
isModalOpen
,例如isModalOpen={true}
或 isModalOpen={modalState.isModalOpen}
就像对 ListOfIngredients
组件所做的那样。
<AddButtonModal
isModalOpen
handleOpenModal={handleOpenModal}
handleCloseModal={handleCloseModal}
/>
<AddButtonModal
isModalOpen={modalState.isModalOpen}
handleOpenModal={handleOpenModal}
handleCloseModal={handleCloseModal}
/>
我建议还将
Modal
组件移到按钮外部,以便模式内的任何点击都不会触发按钮的 onClick
处理程序并切换任何状态,例如您可以删除 onClick
处理程序,该处理程序会停止 Box
呈现的 Modal
组件上的单击事件传播。
const AddButtonModal = ({
isModalOpen,
handleOpenModal,
handleCloseModal
}: AddButtonModalProps) => {
....
return (
<Box sx={{marginBottom: 2 }}>
<Button
onClick={handleOpenModal}
sx={{ .... }}
startIcon={<Add />}
id="Container-search bar & + button"
>
Add new
</Button>
<Modal // <-- outside button
open={isModalOpen}
onClose={handleCloseModal}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box
sx={{ .... }}
id="main container"
// <-- no onClick
>
....
</Box>
</Modal>
</Box>
);
};