first i have root component which handles the component tree ,
RootApp.jsx
import { Suspense } from 'react';
import { HashRouter as Router } from 'react-router-dom';
import { Provider } from 'react-redux';
import store from '@/redux/store';
import PageLoader from '@/components/PageLoader';
import '@/style/app.css';
import '@/style/index.css';
import '@/style/tailwind.css'
import ERP_SODEOs from '@/apps/IdurarOs';
import { ThemeProvider } from '@/context/ThemeContext/ThemeContext'; // Import ThemeProvider
export default function RoutApp() {
return (
<Router>
<Provider store={store}>
<ThemeProvider>
<Suspense fallback={<PageLoader />}>
<ERP_SODEOs />
</Suspense>
</ThemeProvider>
</Provider>
</Router>
);
}
in <ERP_SODEOs> there are multiple component , the main component handling the the child component is ErpApp.jsx :
return (
<Layout hasSider>
<Navigation onPathChange={handlePathChange} />
{isMobile ? (
<Layout style={{ marginLeft: 0 }}>
<HeaderContent />
<Content
style={{
margin: '40px auto 30px',
overflow: 'initial',
width: '100%',
padding: '0 25px',
maxWidth: 'none',
}}
>
<AppRouter />
</Content>
</Layout>
) : (
<Layout style={{ marginLeft: isNavMenuClose ? 100 : 220 }}>
<HeaderContent currentPath={currentPath} />
<Content
style={{
margin: '30px auto 30px',
overflow: 'initial',
width: '100%',
padding: '0px 10px 0px 0px',
maxWidth: isNavMenuClose ? 1700 : 1600,
}}
>
<AppRouter />
</Content>
</Layout>
)}
</Layout>
);
}
i am trying to implement dark mode inside my with my context api component which is not working HeaderContent.jsx :
export default function HeaderContent() {
const currentAdmin = useSelector(selectCurrentAdmin);
const translate = useLanguage();
const [hasPhotoprofile, setHasPhotoprofile] = useState(false);
const [activeKey, setActiveKey] = useState(null);
const [isScrolled, setIsScrolled] = useState(false);
const isAdmin = currentAdmin?.role === 'admin';
const { theme, toggleTheme } = useContext(ThemeContext);
useEffect(() => {
function handleScroll() {
const scrollPosition = window.scrollY;
setIsScrolled(scrollPosition > 0);
}
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
useEffect(() => {
async function fetchData() {
try {
const result = await checkImage(BASE_URL + currentAdmin?.photo);
setHasPhotoprofile(result);
} catch (error) {
console.error('Error checking image:', error);
}
}
fetchData();
}, [currentAdmin]);
const srcImgProfile = hasPhotoprofile ? BASE_URL + currentAdmin?.photo : null;
const ProfileDropdown = () => {
const navigate = useNavigate();
return (
<div className="profileDropdown" onClick={() => navigate('/profile')}>
<Avatar
size="large"
className="last"
src={srcImgProfile}
style={{ color: '#f56a00', backgroundColor: !hasPhotoprofile ? '#fde3cf' : '#f9fafc' }}
>
{currentAdmin?.fullname && currentAdmin?.fullname.charAt(0).toUpperCase()}
</Avatar>
<div className="profileDropdownInfo">
<p className='capitalize font-thin text-sm text-blue-600'>
{currentAdmin?.fullname}
</p>
<p className='font-mono text-[11px] text-sm text-red-600 font-thin'>{currentAdmin?.username}</p>
</div>
</div>
);
};
const DropdownMenu = ({ text }) => <span>{text}</span>;
const items = isAdmin
? [
{
label: <ProfileDropdown className="headerDropDownMenu" />,
key: 'ProfileDropdown',
},
{ type: 'divider' },
{
icon: <SettingOutlined />,
key: 'settingProfile',
label: (
<Link to={'/profile'}>
<DropdownMenu text={translate('profile_settings')} />
</Link>
),
},
{
icon: <SettingOutlined />,
key: 'settingApp',
label: <Link to={'/settings'}>{translate('app_settings')}</Link>,
},
{ type: 'divider' },
{
icon: <LogoutOutlined />,
key: 'logout',
label: <Link to={'/logout'}>{translate('logout')}</Link>,
}
]
: [
{
icon: <SettingOutlined />,
key: 'settingProfile',
label: (
<Link to={'/profile'}>
<DropdownMenu text={translate('update_password')} />
</Link>
),
},
{
icon: <LogoutOutlined />,
key: 'logout',
label: <Link to={'/logout'}>{translate('logout')}</Link>,
}
];
const darkModeIcons = [
{
icon: <BsMoonStars />,
key: 'dark',
label: 'Dark',
onClick: () => toggleTheme('dark'),
},
{
icon: <GrSun />,
key: 'light',
label: 'Light',
onClick: () => toggleTheme('light'),
},
{
icon: <MdOutlineComputer />,
key: 'system',
label: 'System',
onClick: () => toggleTheme('system'),
},
];
const darkModeDropdown = (
<Menu className='w-36' selectedKeys={[activeKey]}>
{darkModeIcons.map((item) => (
<Menu.Item
key={item.key}
onClick={() => handleItemClick(item.key)}
style={{ display: 'flex', alignItems: 'center' }}
>
<div className='flex items-center gap-2.5'>
{item.icon} <span>{item.label}</span>
</div>
</Menu.Item>
))}
</Menu>
);
const handleItemClick = (key) => {
setActiveKey(key);
};
return (
<Header
className={`sticky top-0 z-50 ${isScrolled ? 'scrolled' : 'bg-white w-[100%] scroll-smooth'} ${theme === 'dark' ? 'bg-gray-900' : 'bg-white'}`}
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<div className={`text-[15px] font-thin ${theme === 'dark' ? 'text-white' : 'text-black'}`}>
<span className='uppercase'>Dashboard</span>
</div>
<div className='flex flex-row-reverse gap-3 items-center'>
<Dropdown
overlay={<Menu items={items} />}
trigger={['click']}
placement="bottomRight"
style={{ width: '280px', float: 'right' }}
>
<Avatar
className="last"
src={srcImgProfile}
style={{
color: '#f56a00',
backgroundColor: !hasPhotoprofile ? '#fde3cf' : '#f9fafc',
float: 'right',
}}
size="large"
>
{currentAdmin?.fullname && currentAdmin?.fullname.charAt(0).toUpperCase()}
</Avatar>
</Dropdown>
<Dropdown overlay={darkModeDropdown} trigger={['click']} placement='bottomRight'>
<div>
<BsMoonStars className={`text-[20px] cursor-pointer ${theme === 'dark' ? 'text-white' : 'text-black'}`} />
</div>
</Dropdown>
<SelectLanguage />
</div>
</Header>
);
}
as global css files are imported in the root file i also tried to add custom css in that global css file but its not working.
there is dropdown button in the headercontent file in which there are 3 options to change the theme of the application , when user click the button dark it should change the color of the header i also want to use context created by me used in other components as well, i also want to add that i have already changed the tsconfig file with darkMode: 'class'
To implement dark mode functionality using Context API and ensure it works across your application, including in the HeaderContent
component, you need to follow a few steps to correctly set up and use your ThemeContext
. Here’s a detailed approach to help you troubleshoot and implement the functionality:
Step 1: Create the Theme Context
First, create a ThemeContext
with a provider and a custom hook for easier usage.
ThemeContext.js
import React, { createContext, useState, useContext, useEffect } from 'react';
const ThemeContext = createContext();
export const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState(() => {
// Retrieve the stored theme or default to 'light'
const savedTheme = localStorage.getItem('theme');
return savedTheme ? savedTheme : 'light';
});
const toggleTheme = (newTheme) => {
setTheme(newTheme);
localStorage.setItem('theme', newTheme);
};
useEffect(() => {
// Apply the theme to the body class
document.body.className = theme;
}, [theme]);
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
export const useTheme = () => useContext(ThemeContext);
Step 2: Wrap your application with the Theme Provider
Ensure that your ThemeProvider
wraps your application so that the context is available to all components.
RootApp.jsx
import { Suspense } from 'react';
import { HashRouter as Router } from 'react-router-dom';
import { Provider } from 'react-redux';
import store from '@/redux/store';
import PageLoader from '@/components/PageLoader';
import '@/style/app.css';
import '@/style/index.css';
import '@/style/tailwind.css';
import ERP_SODEOs from '@/apps/IdurarOs';
import { ThemeProvider } from '@/context/ThemeContext/ThemeContext'; // Import ThemeProvider
export default function RootApp() {
return (
<Router>
<Provider store={store}>
<ThemeProvider>
<Suspense fallback={<PageLoader />}>
<ERP_SODEOs />
</Suspense>
</ThemeProvider>
</Provider>
</Router>
);
}
Step 3: Use the Theme Context in HeaderContent
Now you can use the useTheme
hook to access and modify the theme within your HeaderContent
component.
HeaderContent.jsx
import React, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { Link, useNavigate } from 'react-router-dom';
import { Avatar, Dropdown, Menu, Layout } from 'antd';
import { SettingOutlined, LogoutOutlined } from '@ant-design/icons';
import { BsMoonStars } from 'react-icons/bs';
import { GrSun } from 'react-icons/gr';
import { MdOutlineComputer } from 'react-icons/md';
import { useTheme } from '@/context/ThemeContext/ThemeContext'; // Import useTheme
import { selectCurrentAdmin } from '@/redux/selectors';
import SelectLanguage from '@/components/SelectLanguage';
import checkImage from '@/utils/checkImage';
const { Header, Content } = Layout;
export default function HeaderContent() {
const currentAdmin = useSelector(selectCurrentAdmin);
const [hasPhotoprofile, setHasPhotoprofile] = useState(false);
const [activeKey, setActiveKey] = useState(null);
const [isScrolled, setIsScrolled] = useState(false);
const { theme, toggleTheme } = useTheme(); // Use the custom hook to access theme context
useEffect(() => {
function handleScroll() {
const scrollPosition = window.scrollY;
setIsScrolled(scrollPosition > 0);
}
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
useEffect(() => {
async function fetchData() {
try {
const result = await checkImage(BASE_URL + currentAdmin?.photo);
setHasPhotoprofile(result);
} catch (error) {
console.error('Error checking image:', error);
}
}
fetchData();
}, [currentAdmin]);
const srcImgProfile = hasPhotoprofile ? BASE_URL + currentAdmin?.photo : null;
const ProfileDropdown = () => {
const navigate = useNavigate();
return (
<div className="profileDropdown" onClick={() => navigate('/profile')}>
<Avatar
size="large"
className="last"
src={srcImgProfile}
style={{ color: '#f56a00', backgroundColor: !hasPhotoprofile ? '#fde3cf' : '#f9fafc' }}
>
{currentAdmin?.fullname && currentAdmin?.fullname.charAt(0).toUpperCase()}
</Avatar>
<div className="profileDropdownInfo">
<p className='capitalize font-thin text-sm text-blue-600'>
{currentAdmin?.fullname}
</p>
<p className='font-mono text-[11px] text-sm text-red-600 font-thin'>{currentAdmin?.username}</p>
</div>
</div>
);
};
const DropdownMenu = ({ text }) => <span>{text}</span>;
const items = currentAdmin?.role === 'admin'
? [
{
label: <ProfileDropdown className="headerDropDownMenu" />,
key: 'ProfileDropdown',
},
{ type: 'divider' },
{
icon: <SettingOutlined />,
key: 'settingProfile',
label: (
<Link to={'/profile'}>
<DropdownMenu text={translate('profile_settings')} />
</Link>
),
},
{
icon: <SettingOutlined />,
key: 'settingApp',
label: <Link to={'/settings'}>{translate('app_settings')}</Link>,
},
{ type: 'divider' },
{
icon: <LogoutOutlined />,
key: 'logout',
label: <Link to={'/logout'}>{translate('logout')}</Link>,
}
]
: [
{
icon: <SettingOutlined />,
key: 'settingProfile',
label: (
<Link to={'/profile'}>
<DropdownMenu text={translate('update_password')} />
</Link>
),
},
{
icon: <LogoutOutlined />,
key: 'logout',
label: <Link to={'/logout'}>{translate('logout')}</Link>,
}
];
const darkModeIcons = [
{
icon: <BsMoonStars />,
key: 'dark',
label: 'Dark',
onClick: () => toggleTheme('dark'),
},
{
icon: <GrSun />,
key: 'light',
label: 'Light',
onClick: () => toggleTheme('light'),
},
{
icon: <MdOutlineComputer />,
key: 'system',
label: 'System',
onClick: () => toggleTheme('system'),
},
];
const darkModeDropdown = (
<Menu className='w-36' selectedKeys={[activeKey]}>
{darkModeIcons.map((item) => (
<Menu.Item
key={item.key}
onClick={() => handleItemClick(item.key)}
style={{ display: 'flex', alignItems: 'center' }}
>
<div className='flex items-center gap-2.5'>
{item.icon} <span>{item.label}</span>
</div>
</Menu.Item>
))}
</Menu>
);
const handleItemClick = (key) => {
setActiveKey(key);
};
return (
<Header
className={`sticky top-0 z-50 ${isScrolled ? 'scrolled' : 'bg-white w-[100%] scroll-smooth'} ${theme === 'dark' ? 'bg-gray-900' : 'bg-white'}`}
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<div className={`text-[15px] font-thin ${theme === 'dark' ? 'text-white' : 'text-black'}`}>
<span className='uppercase'>Dashboard</span>
</div>
<div className='flex flex-row-reverse gap-3 items-center'>
<Dropdown
overlay={<Menu items={items} />}
trigger={['click']}
placement="bottomRight"
style={{ width: '280px', float: 'right' }}
>
<Avatar
className="last"
src={srcImgProfile}
style={{
color: '#f56a00',
backgroundColor: !hasPhotoprofile ? '#fde3cf' : '#f9fafc',
float: 'right',
}}
size="large"
>
{currentAdmin?.fullname && currentAdmin?.fullname.charAt(0).toUpperCase()}
</Avatar>
</Dropdown>
<Dropdown overlay={darkModeDropdown} trigger={['click']} placement='bottomRight'>
<div>
<BsMoonStars className={`text-[20px] cursor-pointer ${theme === 'dark' ? 'text-white' : 'text-black'}`} />
</div>
</Dropdown>
<SelectLanguage />
</div>
</Header>
);
}
Step 4: Add Styles for Dark Mode Finally, ensure that your CSS has the necessary styles for the dark mode and light mode. You can use a CSS class on the body element to handle this.
body.light {
background-color: #ffffff;
color: #000000;
}
body.dark {
background-color: #1a1a1a;
color: #ffffff;
}
In this approach, the toggleTheme
function sets the theme
state and updates the local storage. The useEffect
hook applies the theme by changing the body
class. The HeaderContent
component uses the useTheme
hook to access the current theme and apply styles accordingly.
This should ensure that your dark mode functionality works as expected across your application.