suchen

Heim  >  Fragen und Antworten  >  Hauptteil

Beim Importieren von zwei Komponenten auf einer Seite erscheint die Toastmeldung zweimal

Ich bin auf dieses seltsame Problem gestoßen und dies ist das erste Mal, dass ich darauf gestoßen bin. Ich habe eine Schaltfläche erstellt, die das Redux-Toolkit für die Anwendungserstellung verwendet. Gemäß dem UI-Design sollte die Schaltfläche wie unten gezeigt zweimal auf der Seite erscheinen. Die hervorgehobene Schaltfläche ist dieselbe Komponente.

Wenn ich versuche, eine App zu erstellen, werden zwei Toastmeldungen angezeigt:

Mir ist aufgefallen, dass, wenn ich eine der Schaltflächen „App erstellen“ entferne und eine behalte, beim Versuch, eine App zu erstellen, wie erwartet nur eine Toast-Nachricht angezeigt wird.

Ist es eine ideale Best Practice, zwei separate Schaltflächen für eine Funktion zu erstellen?

Dies ist die Schaltfläche „AnApp erstellen“:

import React, { useState, useEffect } from "react";
import { Box, Button, Checkbox, FormControl, FormLabel, Flex, Input, useDisclosure, Modal, ModalOverlay, ModalContent, ModalHeader, Spinner, Text, ModalBody, ModalCloseButton, Wrap, Select, Textarea } from "@chakra-ui/react";
import { Select as Select1 } from "chakra-react-select";
import { useToast } from "@chakra-ui/react";
import { useDropzone } from "react-dropzone";
import "./style.css";
import { AiOutlineCloudUpload } from "react-icons/ai";
import { useDispatch, useSelector } from "react-redux";
import { createApp, reset } from "../../features/apps/appSlice";

export const CreateAnApp = (props) => {

  const { isOpen, onOpen, onClose } = useDisclosure();

  const { variant, bg, textColor, fontSize, fontWeight, leftIcon, hover, children, ...rest } = props;

  const { isAppLoading, isError, isAppSuccess, message } = useSelector(
    (state) => state.app
  );

  const toast = useToast();

  const [formData, setFormData] = useState({
    name: "",
    displayName: "",
    reason: "",
    product: "",
    environment: "",
  });
  const { name, displayName, reason, product, environment } = formData;

  const [icon, setIcon] = useState([]);
  const { getRootProps, getInputProps } = useDropzone({
    accept: "image/*",
    onDrop: (acceptedFiles) => {
      setIcon(
        acceptedFiles.map((file) =>
          Object.assign(file, {
            preview: URL.createObjectURL(file),
          })
        )
      );
    },
  });

  // const [product, setProduct] = useState([]);
  const [scopes, setScopes] = useState([]);
  const [institutionScope, setInstitutionScope] = useState([]);
  // const [environment, setEnvironment] = useState([]);

  const images = icon.map((file) => (
    <img
      key={file.name}
      src={file.preview}
      alt="image"
      style={{ width: "50%", height: "50%" }}
    />
  ));

  const onChange = (e) => {
    setFormData((prevState) => ({
      ...prevState,
      [e.target.name]: e.target.value,
    }));
  };

  const onCheckBoxChange = (event) => {
    if (event.target.checked) {
        setFormData((prevState) => ({
            ...prevState,
            displayName: prevState.name,
        }));
    }
}

  // handle onChange event of the dropdown
  const handleScopes = (e) => {
    setScopes(Array.isArray(e) ? e.map((x) => x.value) : []);
  };
  const handleInstitutionScope = (e) => {
    setInstitutionScope(Array.isArray(e) ? e.map((x) => x.value) : []);
  };
  // const handleEnvironment = (e) => {
  //   setEnvironment(Array.isArray(e) ? e.map((x) => x.value) : []);
  // };

  const scopesOptions = [
    {
      label: "Transactions",
      value: "Transactions",
    },
    {
      label: "Accounts",
      value: "Accounts",
    },
  ];

  const institutionScopeOptions = [
    {
      label: "Neobanks",
      value: "Neobanks",
    },
    {
      label: "DeFi/CeFi",
      value: "DeFi/CeFi",
    },
    {
      label: "Personal finance",
      value: "Personal finance",
    },
    {
      label: "Investments",
      value: "Investments",
    },
    {
      label: "Wallets",
      value: "Wallets",
    },
  ];

  const dispatch = useDispatch();

  useEffect(() => {

    if (isError) {
      toast({
        title: "Error",
        description: message,
        status: "error",
        position: "top-right",
        duration: 5000,
        isClosable: true,
      });
      dispatch(reset());
    }

    if (isAppSuccess) {
      toast({
        title: "App created",
        description: "Refreshing page",
        status: "success",
        position: "top-right",
        duration: 5000,
        isClosable: true,
      });
      dispatch(reset());
      onClose();
    }
  }, [isAppSuccess, reset]);

  const onSubmit = async (e) => {
    e.preventDefault();

    const appData = {
      name,
      displayName,
      product,
      // icon,
      scopes,
      reason,
      institutionScope,
      environment,
    };
    dispatch(createApp(appData));
  };

  function SubmitButton() {
    if (
      name?.length &&
      displayName?.length &&
      scopes?.length &&
      environment?.length &&
      reason?.length > 8 &&
      institutionScope?.length > 0
    ) {
      return (
        <Button
          fontSize={{ sm: "12px", md: "14px" }}
          type="submit"
          borderRadius="md"
          color="white"
          bg="#002C8A"
          _hover={{ bg: "#002C6A" }}
          width={{ sm: "300px", md: "400px" }}
        >
          {isAppLoading ? <Spinner /> : "Create app"}
        </Button>
      );
    } else {
      return (
        <Button
          fontSize={{ sm: "12px", md: "14px" }}
          type="submit"
          borderRadius="md"
          color="white"
          bg="#002C8A"
          _hover={{ bg: "#002C6A" }}
          width={{ sm: "300px", md: "400px" }}
          isDisabled
        >
          {isAppLoading ? <Spinner /> : "Create app"}
        </Button>
      );
    }
  }

  return (
    <div>
      <Button
        {...rest}
        leftIcon={leftIcon}
        onClick={onOpen}
        bg={bg}
        textColor={textColor}
        borderRadius="lg"
        variant="solid"
        fontSize={fontSize}
        _hover={_hover}
        fontWeight={fontWeight}
      >
        Create an app
      </Button>

      <Modal
        size="lg"
        closeOnOverlayClick={false}
        isOpen={isOpen}
        onClose={onClose}
      >
        <ModalOverlay />
        <ModalContent mt={1}>
          <ModalHeader textAlign="center" fontSize="md" color="#002c8a">
            Create an app
          </ModalHeader>
          <ModalCloseButton />
          <form onSubmit={onSubmit}>
            <ModalBody pb={6}>
              <Flex flexDirection={{ sm: "column", md: "row" }}>
                <Box>
                  <Box mb={6}>
                    <FormControl>
                      <FormLabel fontSize="sm" fontWeight="semibold">
                        Add a logo to personalize your app
                      </FormLabel>
                      <Box
                        width={{ sm: "340px", md: "450px" }}
                        className="dropArea"
                        {...getRootProps()}
                      >
                        <input {...getInputProps()} />
                        <Flex
                          className="text"
                          width={{ sm: "340px", md: "450px" }}
                        >
                          {images?.length > 0 && (
                            <>
                              <div>{images}</div>
                            </>
                          )}
                          {images?.length === 0 && (
                            <>
                              <Box>
                                <AiOutlineCloudUpload size={30} />
                              </Box>
                              <Box>
                                <Text fontSize="sm">
                                  Drop app icon here or{" "}
                                  <Button
                                    variant="link"
                                    fontSize="sm"
                                    color="#002c8a"
                                  >
                                    browse
                                  </Button>
                                </Text>
                              </Box>
                            </>
                          )}
                        </Flex>
                      </Box>
                    </FormControl>
                  </Box>
                  <Flex mt={6}>
                    <Box>
                      <FormControl>
                        <FormLabel fontSize="sm" fontWeight="semibold">
                          App Name
                        </FormLabel>
                        <Input
                          fontSize="14"
                          width={{ sm: "165px", md: "225px" }}
                          name="name"
                          type="name"
                          value={name}
                          onChange={onChange}
                        />
                      </FormControl>
                      <Checkbox mt={1} onChange={onCheckBoxChange} size='sm'  css={`
                      > span:first-of-type {
                        box-shadow: unset;
                      }
                    `}><Text fontSize="10.9px">Use as display name</Text></Checkbox>
                    </Box>
                    <Box ml={2}>
                      <FormControl>
                        <FormLabel fontSize="sm" fontWeight="semibold">
                          Display Name
                        </FormLabel>
                        <Input
                          fontSize="14"
                          width={{ sm: "165px", md: "225px" }}
                          name="displayName"
                          type="name"
                          value={displayName}
                          onChange={onChange}
                        />
                      </FormControl>
                    </Box>
                  </Flex>
                  <Box mt={3}>
                    <FormLabel fontSize="sm" fontWeight="semibold">
                      Product
                    </FormLabel>
                    <Select
                      name="product"
                      placeholder=" "
                      fontSize="14"
                      value={product}
                      onChange={onChange}
                    >
                      <option value="Connect">Connect</option>
                      <option value="Directpay">Directpay</option>
                    </Select>
                  </Box>
                  <Box w="100%" mt={3}>
                    <FormLabel fontSize="sm" fontWeight="semibold">
                      Account
                    </FormLabel>
                    <Select1
                      useBasicStyles
                      isMulti
                      name="scopes"
                      colorScheme="blue"
                      placeholder=" "
                      options={scopesOptions}
                      closeMenuOnSelect={false}
                      value={scopesOptions?.filter((obj) =>
                        scopes?.includes(obj.value)
                      )} // set selected values
                      onChange={handleScopes}
                    />
                  </Box>
                  <Box w="100%" mt={3}>
                    <FormLabel fontSize="sm" fontWeight="semibold">
                      Institution
                    </FormLabel>
                    <Select1
                      useBasicStyles
                      isMulti
                      name="institution"
                      colorScheme="blue"
                      placeholder=" "
                      _placeholder={{ color: "red" }}
                      options={institutionScopeOptions}
                      closeMenuOnSelect={false}
                      value={institutionScopeOptions?.filter((obj) =>
                        institutionScope?.includes(obj.value)
                      )} // set selected values
                      onChange={handleInstitutionScope}
                    />
                  </Box>
                  <Box w="100%" mt={3}>
                  <FormLabel fontSize="sm" fontWeight="semibold">
                      Environment
                    </FormLabel>
                    <Select
                      name="environment"
                      placeholder=" "
                      fontSize="14"
                      color="black"
                      value={environment}
                      onChange={onChange}
                    >
                      <option value="Sandbox">Sandbox</option>
                      <option value="Production">Production</option></Select>
                    {/*<FormLabel fontSize="sm" fontWeight="semibold">
                      Environment
                    </FormLabel>
                    <Select1
                      useBasicStyles
                      name="environment"
                      isMulti
                      placeholder=" "
                      options={environmentOptions}
                      closeMenuOnSelect={true}
                      color="black"
                      value={environmentOptions?.filter((obj) =>
                        environment?.includes(obj.value)
                      )} // set selected values
                      onChange={handleEnvironment}
                      />*/}
                  </Box>

                  <Box w="100%" mt={4}>
                    <Textarea
                      placeholder="Reason for data access"
                      fontSize="sm"
                      value={reason}
                      name="reason"
                      type="string"
                      onChange={onChange}
                      colorScheme="blue"
                    />
                  </Box>
                </Box>
              </Flex>
            </ModalBody>

            <Wrap mb={6} justify="center">
              <SubmitButton />
            </Wrap>
          </form>
        </ModalContent>
      </Modal>
    </div>
  );
};

Dies ist die Bewerbungsseite:

import {
  Box,
  Button,
  Flex,
  Spacer,
  Center,
  Skeleton,
  SkeletonCircle,
  SkeletonText,
  Text,
  VStack,
  Image,
  Spinner,
  SimpleGrid,
  HStack,
  Avatar,
  Stack,
  Select,
  Hide,
  Tag,
} from "@chakra-ui/react";
import React, { useState, useEffect } from "react";
import { MdFilterList } from "react-icons/md";
import { IoIosApps } from "react-icons/io";
import { ArrowLeftIcon, ArrowRightIcon, SpinnerIcon } from "@chakra-ui/icons";
import { CreateAnApp } from "../../../../components/Buttons/CreateAnApp";
import { useDispatch, useSelector } from "react-redux";
import { getAllApps } from "../../../../features/apps/appSlice";
import moment from "moment";
import { Link, useNavigate } from "react-router-dom";
import Card from "#components/Card/Card";
import CardBody from "#components/Card/CardBody";
import transaction_blue from "#assets/svg/transaction_blue.svg";
import { BsPlusCircleFill } from "react-icons/bs";
import useLocalStorage from "use-local-storage";

const Apps = () => {
  const dispatch = useDispatch();

  const { apps, isLoading, isAppSuccess, meta } = useSelector(
    (state) => state.app
  );
  
  const [mode] = useLocalStorage("apiEnv", false);

  const [loading, setLoading] = useState(true);
  useEffect(() => {
    setTimeout(() => {
      setLoading(false);
    }, 2000);
  }, [loading]);

  const fetchApps = () => {
    dispatch(getAllApps());
  };

  useEffect(() => {
    fetchApps();
  }, [isAppSuccess]);

  return (
    <>
        <Flex alignItems="center" mt={-3} ml={-4} p="5px" mb="10px">
          <Spacer />
          <Flex>
            <Box>
              <Skeleton borderRadius="lg" isLoaded={!loading}>
                <Button
                  leftIcon={<MdFilterList size={20} />}
                  variant="outline"
                  textColor="black"
                  borderRadius="lg"
                  fontSize={{ sm: "xs", md: "sm" }}
                  fontWeight="normal"
                >
                  Filter
                </Button>
              </Skeleton>
            </Box>
            <Box ml={4}>
              <Skeleton borderRadius="lg" isLoaded={!loading}>
                <CreateAnApp
                  bg="#002C8A"
                  textColor="white"
                  fontSize={{ sm: "xs", md: "sm" }}
                  _hover={{ bg: "#002C6A" }}
                  leftIcon={<BsPlusCircleFill size={16} />}
                  fontWeight="normal"
                />
              </Skeleton>
            </Box>
          </Flex>
        </Flex>
        {isLoading ? (
          <Center>
            <Spinner mt={20} />
          </Center>
        ) : (
          <SimpleGrid mt={10} minChildWidth="360px" spacing="40px">
            {isLoading ? (
              <Center>
                <Spinner mt={20} />
              </Center>
            ) : apps && apps?.length > 0 ? (
              apps &&
              apps?.map((app) => {
                return (
                  <Skeleton borderRadius="lg" isLoaded={!loading}>
                    <Box
                      _hover={{ bg: "white" }}
                      h="150px"
                      as="button"
                      shadow="lg"
                      p={2}
                      w={{ sm: "85%", md: "350px" }}
                      bg="#f5f5f5"
                      borderRadius="lg"
                    >
                      <Link to={`/admin/viewapp/${app.uid}`}>
                        <Box>
                          {app.environment === "Sandbox" && (
                            <Box align="right" mt={-4}>
                              <Tag
                                variant="solid"
                                borderRadius="10px"
                                size="sm"
                                colorScheme="orange"
                                fontSize="xs"
                                textTransform="uppercase"
                              >
                                Sandbox
                              </Tag>
                            </Box>
                          )}

                          {app.environment === "Production" && (
                            <Box align="right" mt={-4}>
                              <Tag
                                variant="solid"
                                borderRadius="10px"
                                colorScheme="green"
                                size="sm"
                                fontSize="xs"
                                textTransform="uppercase"
                              >
                                Production
                              </Tag>
                            </Box>
                          )}
                          <HStack>
                            <Box mt={3} ml={6}>
                              <Avatar
                                bg="black"
                                color="white"
                                name={app.name}
                              />
                            </Box>
                            <Box>
                              <Stack ml={2}>
                                <Box mt={-1}>
                                  <Text
                                    color="orange"
                                    textTransform="uppercase"
                                    fontSize="12px"
                                  >
                                    {app.product}
                                  </Text>
                                </Box>
                                <Box>
                                  <Text
                                    fontSize={{ sm: "sm", md: "lg" }}
                                    fontWeight="bold"
                                  >
                                    <SkeletonText isLoaded={!loading}>
                                      {app.displayName}
                                    </SkeletonText>
                                  </Text>
                                </Box>
                              </Stack>
                            </Box>
                          </HStack>
                          <Text fontSize="sm">
                            Created on {moment(app.createdAt).format("LL")}
                          </Text>
                        </Box>
                      </Link>
                    </Box>
                  </Skeleton>
                );
              })
            ) : (
              <Center ml={{ sm: "0", md: -32 }}>
                <VStack spacing={4} align="stretch">
                  <Box>
                    <Center>
                      <SkeletonCircle isLoaded={!loading}>
                        <IoIosApps size={45} />
                      </SkeletonCircle>
                    </Center>
                  </Box>
                  <Box>
                    <Text fontSize="30px" fontWeight={700}>
                      <SkeletonText isLoaded={!loading}>
                        No apps yet
                      </SkeletonText>
                    </Text>
                    <Text mb={4}>
                      <SkeletonText noOfLines={1} mt={2} isLoaded={!loading}>
                        Create an app to get started
                      </SkeletonText>
                    </Text>
                    <Skeleton borderRadius="lg" isLoaded={!loading}>
                      <CreateAnApp
                        w="200px"
                        h="50px"
                        leftIcon={
                          <BsPlusCircleFill
                            className="bg-[#002C8A] hover: none text-white"
                            size={30}
                          />
                        }
                        bg="#002C8A"
                        _hover={{ bg: "#002C6A" }}
                        color="white"
                      />
                    </Skeleton>
                  </Box>
                  <Box></Box>
                </VStack>
              </Center>
            )}

            {apps && apps?.length > 1 && (
              <Skeleton borderRadius="lg" isLoaded={!loading}>
                <CreateAnApp
                  h="150px"
                  ml={{ sm: 0, md: -20 }}
                  shadow="lg"
                  leftIcon={
                    <BsPlusCircleFill
                      className="bg-[#f5f5f5] text-blue-800"
                      size={30}
                    />
                  }
                  bg="#f5f5f5"
                  textColor="black"
                  border="2px"
                  borderColor="gray.400"
                  borderStyle="dashed"
                  fontSize={{ sm: "sm", md: "2xl" }}
                  fontWeight="bold"
                  p={2}
                  w={{ sm: "85%", md: "350px" }}
                />
              </Skeleton>
            )}
          </SimpleGrid>
        )}
      </Box>
    </>
  );
};

export default Apps;

Und mein AppSlice:

import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import appService from "./appService";

const initialState = {
  apps: [],
  app: [],
  isLoading: false,
  isAppLoading: false,
  isError: false,
  isAppSuccess: false,
  isSuccess: false,
  message: "",
};

// Create new app
export const createApp = createAsyncThunk(
  "app/createApp",
  async (appData, thunkAPI) => {
    try {
      const token = sessionStorage.getItem("token");
      return await appService.createApp(appData, token);
    } catch (error) {
      const message =
        (error.response &&
          error.response.data &&
          error.response.data.message) ||
        error.message ||
        error.toString();
      return thunkAPI.rejectWithValue(message);
    }
  }
);

// Get all apps
export const getAllApps = createAsyncThunk(
  "app/getAllApps",
  async (_, thunkAPI) => {
    try {
      const token = sessionStorage.getItem("token");
      return await appService.getAllApps(token);
    } catch (error) {
      const message =
        (error.response &&
          error.response.data &&
          error.response.data.message) ||
        error.message ||
        error.toString();
      return thunkAPI.rejectWithValue(message);
    }
  }
);

export const appSlice = createSlice({
  name: "app",
  initialState,
  reducers: {
    reset: (state) => {
      (state.isLoading = false),
      (state.isAppSuccess= false),
      (state.isAppLoading = false),
        (state.isSuccess = false),
        (state.isError = false),
        (state.message = "");
    },
  },

  extraReducers: (builder) => {
    builder
      .addCase(createApp.pending, (state) => {
        state.isAppLoading = true;
        state.isError = false;
      })
      .addCase(createApp.fulfilled, (state, action) => {
        state.isAppLoading = false;
        state.isAppSuccess = true;
        state.app = action.payload;
      })
      .addCase(createApp.rejected, (state, action) => {
        state.isAppLoading = false;
        state.isError = true;
        state.message = action.payload;
      })

      .addCase(getAllApps.pending, (state) => {
        state.isLoading = true;
        state.isError = false;
      })
      .addCase(getAllApps.fulfilled, (state, action) => {
        state.isLoading = false;
        state.apps = action.payload.payload.data;
      })
      .addCase(getAllApps.rejected, (state, action) => {
        state.isLoading = false;
        state.isError = true;
        state.message = action.payload;
      })
  },
});

export const { reset } = appSlice.actions;
export default appSlice.reducer;

P粉674999420P粉674999420471 Tage vor651

Antworte allen(1)Ich werde antworten

  • P粉282627613

    P粉2826276132023-09-15 00:34:43

    我通过将 toast 消息函数从“创建应用程序”中的 useEffect 挂钩移动到“应用程序”页面来修复此问题。刚刚在厕所里想出来的哈哈。我无法对此进行更多阐述,因为我还没有完全理解它。我们每天都在学习

    更新了“创建应用程序”按钮中的 useEffect 挂钩:

    useEffect(() => {
    
        if (isError) {
          dispatch(reset());
        }
    
        if (isAppSuccess) {
          dispatch(reset());
          onClose();
        }
      }, [isAppSuccess]);

    更新的应用程序页面:

    useEffect(() => {
    
        if (isError) {
          toast({
            title: "Error",
            description: message,
            status: "error",
            position: "top-right",
            duration: 5000,
            isClosable: true,
          });
        }
    
        if (isAppSuccess) {
          toast({
            title: "App created",
            description: "Refreshing page",
            status: "success",
            position: "top-right",
            duration: 5000,
            isClosable: true,
          });
        }
      }, [isAppSuccess]);

    Antwort
    0
  • StornierenAntwort