简体   繁体   中英

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 };

It's true that the useSnackbar hook has a default value of true for showSnackbar and it does get changed to false after 3 seconds but display in App.js stays true which means that even after the timeout .

Since display never gets set to false , Snackbar never gets unmounted so the state of useSnackbar never gets reinitialized.

My suggestion would be to change the structure of how useSnackbar is being used.

I'd move useSnackbar into App.js and set the snackbar open states there.

function App() {
  const { showSnackbar, setSnackbar } = useSnackbar();

  return (
    <div>
      <button onClick={() => setSnackbar(true)}>Click me</button>
      {showSnackbar && <Snackbar message="hello" />}
    </div>
  );
}
const useSnackbar = () => {
  const [showSnackbar, setSnackbar] = useState(false);

  // rest of code
};
const Snackbar = ({ message }) => (
  <div>
    <p>{message}</p>
  </div>
);

Code Sandbox Demo

There are other ways to structure thee components but for your example, this would be one of the simplest solutions.

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>
  );
}

编辑 default-value-in-usestate-not-getting-re-initialised

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM