J'ai rencontré cet étrange problème et c'est la première fois que je le rencontre. J'ai créé un bouton qui utilise la boîte à outils Redux pour gérer la création d'applications. Selon la conception de l'interface utilisateur, le bouton doit apparaître deux fois sur la page, comme indiqué ci-dessous. Le bouton en surbrillance est le même composant.
Si j'essaie de créer une application, elle affiche deux messages toast :
J'ai remarqué que si je supprime l'un des boutons "Créer une application" et en garde un, j'essaie de créer une application, elle n'affiche qu'un message Toast, comme prévu.
Est-ce une bonne pratique idéale de créer 2 boutons distincts pour gérer une fonction ?
Voici le bouton CreateAnApp :
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) => ( )); 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 ( ); } else { return ( ); } } return (); };Create an app
Voici la page de candidature :
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 ( <>{isLoading ? ( } variant="outline" textColor="black" borderRadius="lg" fontSize={{ sm: "xs", md: "sm" }} fontWeight="normal" > Filter } fontWeight="normal" /> ) : ( {isLoading ? ( )} > ); }; export default Apps;) : apps && apps?.length > 0 ? ( apps && apps?.map((app) => { return ( ); }) ) : ( {app.environment === "Sandbox" && ( )} {app.environment === "Production" && ( Sandbox )} Production {app.product} {app.displayName} Created on {moment(app.createdAt).format("LL")} )} {apps && apps?.length > 1 && ( No apps yet Create an app to get started } bg="#002C8A" _hover={{ bg: "#002C6A" }} color="white" /> )} } 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" }} />
Et mon 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;
J'ai résolu ce problème en déplaçant la fonction de message toast du hook useEffect dans Create App vers la page App. Je viens de m'en rendre compte dans la salle de bain haha. Je ne peux pas en dire davantage car je ne le comprends pas encore complètement. Nous apprenons chaque jour
Mise à jour du hook useEffect dans le bouton "Créer une application" :
Page de l'application mise à jour :