使用 useReducer 时,道具未从父级传递给子级或孙级

问题描述 投票:0回答:1

因此,我想简化将 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 中被标记为必需,但其值未定义

reactjs react-hooks react-props
1个回答
0
投票

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>
  );
};
© www.soinside.com 2019 - 2024. All rights reserved.