Babidi
Babidi

Reputation: 31

How to prevent individual component timeouts from overlapping?

I am trying to handle individual promises in a React app, and list their current state in a list on the App. I have two components :

  1. InstructionList : has the job to show and update a list of <Instruction/> components based on InstructionListContext
  2. Instruction : has the job of keeping track of a given promise. When the promise is resolved, it waits 5 seconds and updates InstructionListContext to disappear from the list.

My problem is the following : One instruction behaves perfectly fine, but when I add another instruction in the list, timeouts seem to overlap, leading instructions that are still pending to disappear from the list as well ! Do you see anything wrong in my way of handling it ?

Here's the code for reference :

import { useContext } from "react";
import InstructionListContext from "../../const/contexts/instructionListContext";
import { instruction } from "../../const/models/instruction";
import Instruction from "./instruction";
import './instructionList.scss';



export default function InstructionList() {
    const {instructionList} = useContext(InstructionListContext)

    return (
        <div id="instruction-list">
            <h1>Instructions en cours</h1>
            {instructionList.map((instruction:instruction) => {
                return <Instruction key={instruction.id} instruction={instruction} />
            })}
        </div>
    )
}
import { useContext, useEffect, useState } from "react"
import failure from "../../assets/picto/failure.svg"
import loading from "../../assets/picto/loader.svg"
import success from "../../assets/picto/success.svg"
import { instruction, InstructionActionTypes } from "../../const/models/instruction"
import { message, messageType } from "../../const/models/message"
import InstructionListContext from "../../const/contexts/instructionListContext"


interface IProps {
    instruction: instruction,
}

enum instructionStates {
    success = 1,
    failure = 2,
    pending = 3
}

export default function Instruction({ instruction }: IProps) {
    const [state, setState] = useState<instructionStates>(instructionStates.pending)
    const [statePic, setStatePic] = useState(generateStatePic(state))
    const [actionLabel] = useState(generateActionLabel(instruction.action))
    const {instructionList, setInstructionList} = useContext(InstructionListContext)

    useEffect(() => {
        instruction.promise.then((message:message) => {
            message.type === messageType.confirm ? setState(instructionStates.success) : setState(instructionStates.failure)
        })
    }, [])

    useEffect(() => {
        setStatePic(generateStatePic(state))
        let timeout:NodeJS.Timeout 
        if (state === instructionStates.success || state === instructionStates.failure) {
            if (instruction.cleanup) {
                instruction.cleanup()
            }
            timeout = setTimeout(() => {
                    const filtered = instructionList.filter(x => x.id !== instruction.id)
                    setInstructionList([...filtered])
            }, 5000)
        }

        return () => {
            clearTimeout(timeout)
        }
    }, [state])

    return (
        <div className="instruction">
            <p>{statePic}</p><p>{actionLabel}</p><p>dans {instruction.parentId}</p>
        </div>
    )
}



const generateActionLabel = (instructionType: InstructionActionTypes): JSX.Element => {
    let source: string = "ND"
    switch (instructionType) {
        case InstructionActionTypes.elementCreation:
            source = "Nouv. Élém."
            break
        case InstructionActionTypes.elementUpdate:
            source = "Mod. Élém."
            break
        case InstructionActionTypes.elementDeletion:
            source = "Suppr. Élém."
            break
        case InstructionActionTypes.subelementCreation:
            source = "Nouv. Sous-élém."
            break
        case InstructionActionTypes.subelementUpdate:
            source = "Mod. Sous-élém."
            break
        case InstructionActionTypes.subelementDeletion:
            source = "Suppr. Sous-élém."
            break
    }
    return <p>{source}</p>
}

const generateStatePic = (instructionState: instructionStates): JSX.Element => {
    let source: string = loading
    switch (instructionState) {
        case instructionStates.pending:
            source = loading
            break
        case instructionStates.failure:
            source = failure
            break
        case instructionStates.success:
            source = success
            break
    }
    return <img src={source}/>
}

Before the given version of my code, I didn't give a key prop to my Instruction component in the .map(), which I thought could be the source of my problem. (spoiler, it was not) I also tried passing the timeout from instruction list instead of creating it in <Instruction/>, but it did not work either...

Edit after Jesus Diaz Rivero's comment: Here is the code of InstructionListContext.tsx :

import { createContext } from "react";
import { instruction } from "../models/instruction";

interface IInstructionListContext {
    instructionList: instruction[],
    setInstructionList: (instruction: instruction[]) => void,
}

const InstructionListContext = createContext<IInstructionListContext>({
    instructionList:[],
    setInstructionList:()=>{},
})

export default InstructionListContext

And here is my instruction.ts type declaration :

import { message } from "./message"

export enum InstructionActionTypes {
    elementCreation = 1,
    elementDeletion = 2,
    elementUpdate = 3,
    subelementCreation = 4,
    subelementDeletion = 5,
    subelementUpdate = 6}

export type instruction = {
    id: string
    promise:Promise<message>
    action:InstructionActionTypes
    parentId:string
    cleanup?:()=>any
}

Upvotes: 0

Views: 43

Answers (0)

Related Questions