Merhaba arkadaşlar, bugün başımı çok ağrıtan bir sorundan ve nasıl çözdüğümden bahsedeceğim. React'te Context API kullanarak dark/light mode (tema) geçişi yapmak oldukça popüler bir yöntem. Ben de projemde bu yapıyı kurdum, her şey harika çalışıyordu derken, sayfayı yenilediğimde veya ilk açtığımda kafayı yemiştim! Sayfa önce varsayılan light temada bir anlığına beliriyor, sonra kullanıcının tercih ettiği dark tema uygulanıyordu. İşte bu istenmeyen "flash" olayını nasıl engellediğimi anlatacağım.
Karşılaştığım Flash Sorunu
Sorunun kaynağı, tema bilgisinin (örneğin `localStorage`'dan okunmasının) React bileşen ağacı render edildikten sonra gerçekleşmesiydi. Yani, ilk render sırasında tema Context'i henüz `localStorage`'daki değeri okuyamadığı için varsayılan değeri (benim durumumda `'light'`) sağlıyordu. Birkaç milisaniye sonra `useEffect` çalışıp doğru değeri okuduğunda, tema değişiyor ve o çirkin flash efektini görüyorduk.
Çözüm: Tema Bilgisini Render'dan Önce Belirlemek
Bu sorunu çözmek için, temel fikir, ilk HTML'in sunucudan (veya ilk istemci render'ından) önce doğru tema class'ı ile gelmesini sağlamak. Yani, React'in ilk render'ı için doğru tema değerini önceden hazırlamamız gerekiyor.
İşte benim kullandığım en temiz çözüm. İlk adım, Context'imizi ve Provider'ımızı oluşturmak. Burada `theme` state'ini, `localStorage`'dan okuma işlemini bir fonksiyonla yapıyoruz.
Kritik Adım: _document.js ve İlk Render
Bu kısım, Next.js gibi bir framework kullanıyorsanız çok daha kolay. Ancak vanilla React (CRA) ile de çözebiliriz. Next.js'de, `pages/_document.js` dosyası oluşturup, sunucu tarafında çalışan bir script ile `localStorage` kontrolü yapıp, HTML etiketine doğru class'ı ekleyebiliriz.
Ama ben CRA ile nasıl yaptığımı anlatayım. Ana `index.html` dosyasındaki `<body>` etiketinden hemen sonra, `localStorage`'ı kontrol eden küçük bir inline script ekledim. Bu script, React uygulaması yüklenmeden önce çalışır ve doğru class'ı ekler.
Artık, uygulamamız ilk açıldığında, `<html>` etiketi doğru tema class'ına (`dark` veya `light`) sahip olacak. Context'imizin `getInitialTheme` fonksiyonu da aynı mantıkla çalıştığı için, ilk render'da flash olmayacak. Context'teki `useEffect` zaten aynı değeri `localStorage`'a yazacak, böylece senkronizasyon sorunu kalmayacak.
Sonuç ve Performans
Bu yöntemi uyguladıktan sonra, tema geçişleri hem kullanıcı deneyimi açısından kusursuz hem de performans açısından sorunsuz hale geldi. Flash sorunu tamamen ortadan kalktı. Context yapısı sayesinde, tema değerine uygulamanın herhangi bir yerinden `useTheme` hook'u ile erişip, kolayca değiştirebiliyorum.
Siz React'te tema yönetimi için nasıl bir yöntem izliyorsunuz? Context API dışında (Redux, Zustand) state yönetim kütüphaneleri ile benzer bir flash sorunu yaşadınız mı? Ya da Next.js'de `getServerSideProps` veya `getInitialProps` ile daha farklı bir çözüm mü uyguluyorsunuz? Yorumlarda deneyimlerinizi paylaşın, tartışalım!
Sorunun kaynağı, tema bilgisinin (örneğin `localStorage`'dan okunmasının) React bileşen ağacı render edildikten sonra gerçekleşmesiydi. Yani, ilk render sırasında tema Context'i henüz `localStorage`'daki değeri okuyamadığı için varsayılan değeri (benim durumumda `'light'`) sağlıyordu. Birkaç milisaniye sonra `useEffect` çalışıp doğru değeri okuduğunda, tema değişiyor ve o çirkin flash efektini görüyorduk.
Bu sorunu çözmek için, temel fikir, ilk HTML'in sunucudan (veya ilk istemci render'ından) önce doğru tema class'ı ile gelmesini sağlamak. Yani, React'in ilk render'ı için doğru tema değerini önceden hazırlamamız gerekiyor.
İşte benim kullandığım en temiz çözüm. İlk adım, Context'imizi ve Provider'ımızı oluşturmak. Burada `theme` state'ini, `localStorage`'dan okuma işlemini bir fonksiyonla yapıyoruz.
JavaScript:
// ThemeContext.js
import React, { createContext, useState, useEffect, useContext } from 'react';
const ThemeContext = createContext();
// localStorage'dan temayı okuyan yardımcı fonksiyon
const getInitialTheme = () => {
if (typeof window !== 'undefined' && window.localStorage) {
const storedPref = window.localStorage.getItem('color-theme');
if (typeof storedPref === 'string') {
return storedPref;
}
const userMedia = window.matchMedia('(prefers-color-scheme: dark)');
if (userMedia.matches) {
return 'dark';
}
}
// Varsayılan tema olarak 'light' döndürüyoruz.
// Eğer sistem tercihini baz almak isterseniz yukarıdaki media query'yi kullanın.
return 'light';
};
export const ThemeProvider = ({ initialTheme, children }) => {
// State'i, dışarıdan gelebilecek `initialTheme` veya yardımcı fonksiyonumuzla başlatıyoruz.
const [theme, setTheme] = useState(getInitialTheme);
const rawSetTheme = (rawTheme) => {
const root = window.document.documentElement;
const isDark = rawTheme === 'dark';
root.classList.remove(isDark ? 'light' : 'dark');
root.classList.add(rawTheme);
localStorage.setItem('color-theme', rawTheme);
};
// Theme değiştiğinde, DOM'u ve localStorage'ı güncelle.
useEffect(() => {
rawSetTheme(theme);
}, [theme]);
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
};
export const useTheme = () => useContext(ThemeContext);
Bu kısım, Next.js gibi bir framework kullanıyorsanız çok daha kolay. Ancak vanilla React (CRA) ile de çözebiliriz. Next.js'de, `pages/_document.js` dosyası oluşturup, sunucu tarafında çalışan bir script ile `localStorage` kontrolü yapıp, HTML etiketine doğru class'ı ekleyebiliriz.
Ama ben CRA ile nasıl yaptığımı anlatayım. Ana `index.html` dosyasındaki `<body>` etiketinden hemen sonra, `localStorage`'ı kontrol eden küçük bir inline script ekledim. Bu script, React uygulaması yüklenmeden önce çalışır ve doğru class'ı ekler.
HTML:
<!-- public/index.html -->
<!DOCTYPE html>
<html lang="tr">
<head>...</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!-- TEMA FLASH SORUNU ÇÖZÜMÜ -->
<script>
(function() {
// Sayfa yüklenmeden önce temayı belirle
var storedTheme = localStorage.getItem('color-theme');
var systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
var initialTheme = storedTheme || systemTheme || 'light';
document.documentElement.classList.add(initialTheme);
})();
</script>
</body>
</html>
Artık, uygulamamız ilk açıldığında, `<html>` etiketi doğru tema class'ına (`dark` veya `light`) sahip olacak. Context'imizin `getInitialTheme` fonksiyonu da aynı mantıkla çalıştığı için, ilk render'da flash olmayacak. Context'teki `useEffect` zaten aynı değeri `localStorage`'a yazacak, böylece senkronizasyon sorunu kalmayacak.
Bu yöntemi uyguladıktan sonra, tema geçişleri hem kullanıcı deneyimi açısından kusursuz hem de performans açısından sorunsuz hale geldi. Flash sorunu tamamen ortadan kalktı. Context yapısı sayesinde, tema değerine uygulamanın herhangi bir yerinden `useTheme` hook'u ile erişip, kolayca değiştirebiliyorum.
Siz React'te tema yönetimi için nasıl bir yöntem izliyorsunuz? Context API dışında (Redux, Zustand) state yönetim kütüphaneleri ile benzer bir flash sorunu yaşadınız mı? Ya da Next.js'de `getServerSideProps` veya `getInitialProps` ile daha farklı bir çözüm mü uyguluyorsunuz? Yorumlarda deneyimlerinizi paylaşın, tartışalım!