Search code examples
javascriptreactjsreact-hookssetstateuse-state

Default value in useState not getting re-initialised


I have a snack bar component, which displays a toast message for a few seconds and then disappears. I have an App component which contains a button, and on click of that button, I want to control the snack bar component. On the first click, the snack bar appears fine and disappears after the time specified is over. But when I click it again, the snack bar doesn't appear. I am initializing the show state to true every time, still, the snack bar isn't appearing. Please tell me where I am going wrong and how to rectify this. Below are the files. I am using a custom hook to control the behavior of snack bar's appearance.

App.js

import React, { useState } from "react";
import { Snackbar } from "./Snackbar";

function App() {
  const [display, setDisplay] = useState(false);
  return (
    <div>
      <button onClick={() => setDisplay(true)}>Click me</button>
      {display && <Snackbar message="hello" />}
    </div>
  );
}

export default App;

Snackbar.js

import React from "react";
import { useSnackbar } from "./useSnackbar";

const Snackbar = ({ message }) => {
  const { showSnackbar } = useSnackbar();
  return (
    showSnackbar && (
      <div>
        <p>{message}</p>
      </div>
    )
  );
};

export { Snackbar };

useSnackbar.js

import { useState, useEffect } from 'react';

const useSnackbar = () => {
  const [showSnackbar, setSnackbar] = useState(true);
  const [snackbarMessage, setSnackbarMessage] = useState('');

  useEffect(() => {
    const timer = setTimeout(() => {
      setSnackbar(false);
      setSnackbarMessage('');
    }, 3000);

    return () => {
      clearTimeout(timer);
    };
  }, [showSnackbar]);

  return {
    showSnackbar,
    setSnackbar,
    snackbarMessage,
    setSnackbarMessage
  };
};

export { useSnackbar };

Solution

  • You don't reset your display state in App.js

    Solution

    Pass a reset state callback function to Snackbar to pass to the useSnackbar hook.

    const useSnackbar = (onClose) => { // <-- callback function
      const [showSnackbar, setSnackbar] = useState(true);
      const [snackbarMessage, setSnackbarMessage] = useState('');
    
      useEffect(() => {
        const timer = setTimeout(() => {
          setSnackbar(false);
          setSnackbarMessage('');
          onClose && onClose(); // <-- invoke when timer expired
        }, 3000);
    
        return () => {
          clearTimeout(timer);
          onClose && onClose(); // <-- edge case if component unmounts before expire
        };
      }, [onClose, showSnackbar]);
    
      return {
        showSnackbar,
        setSnackbar,
        snackbarMessage,
        setSnackbarMessage
      };
    };
    
    const Snackbar = ({ message, onClose }) => {
      const { showSnackbar } = useSnackbar(onClose); // <-- pass callback to hook
      return (
        showSnackbar && (
          <div>
            <p>{message}</p>
          </div>
        )
      );
    };
    
    export default function App() {
      const [display, setDisplay] = useState(false);
    
      return (
        <div className="App">
          <h1>Hello CodeSandbox</h1>
          <h2>Start editing to see some magic happen!</h2>
    
          <div>
          <button onClick={() => setDisplay(true)}>Click me</button>
          {display && <Snackbar message="hello" onClose={() => setDisplay(false)} />} // <-- pass reset callback
        </div>
        </div>
      );
    }
    

    Edit default-value-in-usestate-not-getting-re-initialised