use-dark-mode icon indicating copy to clipboard operation
use-dark-mode copied to clipboard

Works on development but fails on production in Gatsby

Open abhagsain opened this issue 5 years ago • 3 comments

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)

darkmode 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

abhagsain avatar Jan 19 '20 03:01 abhagsain

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 avatar Jul 12 '20 01:07 ognus

@ognus Thank you so much for this!

kelvindecosta avatar Jan 25 '21 23:01 kelvindecosta

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.

astefanutti avatar Jan 26 '21 07:01 astefanutti