Search code examples
javascriptreactjssettimeout

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.


Solution

  • 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