Reputation: 1970
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 };
Upvotes: 1
Views: 122
Reputation: 202761
You don't reset your display
state in App.js
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>
);
}
Upvotes: 1
Reputation: 2112
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>
);
There are other ways to structure thee components but for your example, this would be one of the simplest solutions.
Upvotes: 1