use-dark-mode
use-dark-mode copied to clipboard
Works on development but fails on production in Gatsby
I've been trying to fix this issue but couldn't. I'm using use-dark-mode with gatsby and while developing the application it works fine, but when I run build of the application it shows wrong behaviour, On page refresh the icon changes from the previous value even though the mode (dark/light persists)
I've added the your custom js in my html but doesn't seem to work.
Here's my implimentation
export default function Navbar({ title }) {
const darkMode = useDarkMode()
return (
<nav className="nav">
<Container>
<div className="nav__content">
<Link to="/" className="nav__brand">
{darkMode.value ? "Dark" : "Light"}
</Link>
<div className="nav__right">
{darkMode.value === true ? (
<Sun disable={darkMode.disable} />
) : (
<Moon enable={darkMode.enable} />
)}
</div>
</div>
</Container>
</nav>
)
}
This is how I style elements
body.light-mode nav {
background: rgb(153, 153, 153);
color: black;
}
body.dark-mode nav {
background: $dark-mode-section;
}
Thanks for your help
Had the same problem.
Turned out to be an issue with Gatsby and how React SSR works. See discussion here: https://github.com/gatsbyjs/gatsby/issues/17914
TLDR:
You can encounter it if server-rendered (or built) content is different from client content on initial load. Gatsby re-hydrates the elements on the initial load, but this is not always enough and some components will show the server state.
The proposed solution of forcing re-render worked for me (two-pass rendering):
class Layout extends React.Component {
constructor(props) {
super(props)
this.state = { isClient: false }
}
render() {
// can be `div` or anything else, I tried to keep this generic
return <React.Fragment key={this.state.isClient}>
{/*...*/}
</React.Fragment/>
}
componentDidMount() {
this.setState({ isClient: true })
}
}
For my use case, conditionally rendering the toggle element worked the best:
export const ThemeToggle = () => {
const darkMode = useDarkMode(false)
const [isClient, setIsClient] = useState(false)
useEffect(() => {
setIsClient(true)
}, [])
return (
<Container>
{isClient && (
<Switch
checked={darkMode.value}
onChange={darkMode.toggle}
/>
)}
</Container>
)
}
It's not perfect since the user sees the render, but it's good for now :)
@ognus Thank you so much for this!
FWIW, using a post body script in Gatsby SSR, to update the input state, prevents the users from seeing the render every time.
Here is the whole code: https://github.com/astefanutti/website/commit/f315e934b8729c0a431d52e74f82abcb140805f6.