Daniel M
Daniel M

Reputation: 103

React setTimeout after 3 seconds on Error

Im trying to make the error remove itself from an State array after 3 seconds, and it kinda of works, but for some reason it jumps back and acts weird/removes multiple on 1 timeout finished. (seen in the Error Functional Component).

Im using React context for my State Management, and would appreciate any help i could get.

i would appreciate any help

You can view the bug live here: https://codesandbox.io/s/flamboyant-jackson-soutv?file=/src/components/Errors.jsx:558-567

Errors.jsx

import { SiteContext } from "../context/SiteContext";

const Error = props => {
  const [siteSettings, setSiteSettings] = useContext(SiteContext);
  let errors = siteSettings.errors;
  console.log("Errors", errors);
  let filteredAry = errors.filter(function(obj) {
    return obj.id !== props.id;
  });
  //removed last id , and filteredArray is where we store the new array
  console.log("Filtered array", filteredAry);
  //after 3 seconds we should update the old array with the new array
  useEffect(() => {
    setTimeout(() => {
      setSiteSettings(siteSettings => ({
        ...siteSettings,
        errors: filteredAry
      }));
    }, 3000);
  }, []);

  return <div className="error">{props.error}</div>;
};

const Errors = () => {
  const [site, setSiteSettings] = useContext(SiteContext);
  const addError = () => {
    //find the latest id
    let max = 0;
    if (site.errors.length > 0) {
      max = Math.max.apply(null, site.errors.map(item => item.id)) + 1;
    } else {
      max = 1;
    }
    console.log("Highest id: " + max);
    //add new error to the State,
    setSiteSettings(site => ({
      ...site,
      errors: [...site.errors, { message: "Some error", id: max }]
    }));
  };
  return (
    <div id="errors">
      Error page
      {site.errors.map((error, i) => {
        return <Error id={i} error={error.message} />;
      })}
      <button onClick={addError}>Add error</button>
    </div>
  );
};

export default Errors;

App.js

import "./styles.css";
import { SiteProvider } from "./context/SiteContext";
import Errors from "./components/Errors";

export default function App() {
  return (
    <SiteProvider>
      <div className="App">
        Errors : <Errors />
      </div>
    </SiteProvider>
  );
}

SiteContext.jsx


export const SiteContext = createContext();

export const SiteProvider = props => {
  const [siteSetting, setSiteSetting] = useState({
    errors: []
  });

  return (
    <SiteContext.Provider value={[siteSetting, setSiteSetting]}>
      {props.children}
    </SiteContext.Provider>
  );
};

Best Regards, Daniel.

Upvotes: 1

Views: 2585

Answers (2)

xdeepakv
xdeepakv

Reputation: 8125

you have to clear timeout on unmount. Since you are not cleary, so every time new timeout declare.

useEffect(() => {
    const id = setTimeout(() => {
      setSiteSettings(siteSettings => ({
        ...siteSettings,
        errors: filteredAry
      }));
    }, 3000);
    return () => clearTimeout(id);
  }, []);

You can create it like service, message which are old can be deleted, based on time stamp:

Here id is timestamp when message was created.

Snippet:

import React, { useState, useEffect } from "react";
import "./styles.css";
const Errors = () => {
  const [errors, setErrors] = useState([]);
  const [counter, setCounter] = useState(0);
  useEffect(() => {
    const timerId = setInterval(() => {
      if (counter < 10) {
        const id = new Date().getTime();
        // stop generating
        setErrors(ee => ee.concat({ id, message: "Error: " + new Date() }));
      }

      setCounter(counter + 1);
    }, 1000);
    return clearInterval.bind(this, timerId);
  }, [counter]);
  /// clear error
  useEffect(() => {
    const timerId = setInterval(() => {
      setErrors(ee => {
        return ee.filter(({ id }) => new Date().getTime() - id < 3000);
      });
    }, 2000);
    return clearInterval.bind(this, timerId);
  }, []);
  return (
    <ul>
      {errors.map(({ id, message }, i) => {
        return <li key={id}>{message}</li>;
      })}
    </ul>
  );
};
export default function App() {
  return (
    <div className="App">
      <Errors />
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}

Please check a rough sample:

https://codesandbox.io/s/quiet-dream-mzcmm?file=/src/App.js:0-1084

Upvotes: 1

Vivek Doshi
Vivek Doshi

Reputation: 58553

There are 2 main issue with your code :

First : Not updating latest state, and updating diff old state

 let errors = siteSettings.errors;
  console.log("Errors", errors);
  // Will provide the old errors, not the updated once
  // so each time you add new error, you will get diff filtered array
  // for 1 you get 0
  // for 2 you get 1
  // ....
  let filteredAry = errors.filter(function(obj) {
    return obj.id !== props.id;
  });
  useEffect(() => {
    setTimeout(() => {
      setSiteSettings(siteSettings => ({
        ...siteSettings,
        errors: filteredAry
      }));
    }, 3000);
  }, []);

Second : passing index i, instead of error.id

// here you are passing index, instead of error.id
<Error id={i} error={error.message} />;

Solution to first issue is this DO READ :

  useEffect(() => {
    const id = setTimeout(() => {
      setSiteSettings(siteSettings => {
        // get latest copy of state `siteSettings `
        // and work on that
        let filteredAry = siteSettings.errors.filter(function(obj) {
          return obj.id !== props.id;
        });        
        return {
          ...siteSettings,
          errors: filteredAry
      }});
    }, 3000);
    return () => clearTimeout(id);
  }, [props.id]);

WORKING DEMO :

Edit so-remove-incremental-error

Upvotes: 1

Related Questions