DEV Community

Cover image for Toggle Dark Mode in React

Toggle Dark Mode in React

Abbey Perini on March 05, 2021

When I rebuilt my portfolio site, I knew I wanted to have some fun with the design, and a dark and light mode fit the bill. I enjoyed a lot of the ...
Collapse
 
kirkcodes profile image
Kirk Shillingford

I will 100% be referencing this article multiple times. Thank you for putting this together.

Collapse
 
overtureweb profile image
Overture Web

This was super helpful, I love it! Thanks for taking the time to go through it. I found a place to simplify, rather than use a ternary operator that renders one of two input elements you could use an equality check expression as the checkbox input’s “checked” property.

<input type="checkbox" id="toggle" className="toggle--checkbox" onClick={handleOnClick} checked={togClass === “light”} />
Enter fullscreen mode Exit fullscreen mode
Collapse
 
abbeyperini profile image
Abbey Perini • Edited

Ah, but the reason "I couldn’t get defaultChecked to work how I wanted, so I replaced the unchecked with this conditional rendering ternary operator (conditional operator):" is the togClass doesn't just depend on whether the checkbox is checked. It also depends on the theme set in localStorage. Otherwise, the user could have light mode enabled by default or have set the theme to light mode and reloaded. Then the "checked=false" state will not match the current set theme and you'll see a moon on a light background. I was trying to tell the element to be checked or unchecked using a conditional in the defaultChecked property in the element, which made React yell about an uncontrolled element. The ternary operator renders 1 element either in the checked or unchecked state, which will not cause React to yell about it being uncontrolled.

Collapse
 
abbeyperini profile image
Abbey Perini

You were totally right! I just didn't understand what you were saying. You can see the updated logic here: dev.to/abbeyperini/an-accessible-d...

Collapse
 
urielbitton profile image
Uriel Bitton

nice, comprehensive, and well written article!

Collapse
 
abbeyperini profile image
Abbey Perini

Thank you!

Collapse
 
abbeyperini profile image
Abbey Perini

Hmm. I chose not to use Redux for this project.

The only time I used local state was to get the toggle to load with the correct side. You could put that in global state, but I don't see an advantage to that - especially since you could probably get that to work with just JS. 🤔

Collapse
 
passenger89 profile image
William Nicholson

Nicely done. I couldn't help but notice you are using hooks inside conditional statements. Isn't that not recommended in rules of hooks?

Collapse
 
abbeyperini profile image
Abbey Perini

According to the link you provided and my experience working with the ESLint rule, this code meets those recommendations. The order in which the hooks are called will not change from one render to another in these components.

The ones in useEffect() match an example in the link you provided.

The click handler will not be called on render. Plus, I even have conditions for all the possible values of the variable I'm checking against. This is a common design.

Collapse
 
ruppysuppy profile image
Tapajyoti Bose

Great job! Really liked the animation & stars and the cloud

Collapse
 
viragdesai profile image
Virag Dilip Desai

Very well written and explained. Thank you!

Collapse
 
abbeyperini profile image
Abbey Perini

Thanks for reading!

Collapse
 
mnunezdm profile image
Miguel Núñez Díaz-Montes

Why are you doing this?

localStorage.getItem('theme') && localStorage.getItem('theme') === 'theme-dark'

And not directly this?

localStorage.getItem('theme') === 'theme-dark'

Collapse
 
abbeyperini profile image
Abbey Perini

"if (localStorage.getItem('theme') && localStorage.getItem('theme') === 'theme-dark')" in keepTheme() is checking if a theme is stored in localStorage before comparing it to a string. It's in the main container's useEffect() and was throwing an error if it didn't exist when written like you suggest. The handleOnClick() in the Toggle component is written the way you suggest because there's always something in localStorage by the time it gets used.

Collapse
 
abbeyperini profile image
Abbey Perini • Edited

Now it occurs to me I could totally refactor it to

if (localStorage.getItem('theme')) {if (localStorage.getItem('theme') === 'theme-dark') {setTheme('theme-dark'); } else if (localStorage.getItem('theme') === 'theme-light') {setTheme('theme-light')

} else {
setTheme('theme-dark')
}
}

ETA: the site and blog have been updated with this refactor

Collapse
 
lindiwe09 profile image
Lindie

Nice and well-written article but any idea on how l can implement that using Tailwind CSS

Collapse
 
abbeyperini profile image
Abbey Perini

No, but if I figure it out on the upcoming project I'm going to learn Tailwind for, I'll let you know.

Collapse
 
lindiwe09 profile image
Lindie

Alrighty