I'm actually a beginner in react so I just want to ask if I can use useReducer and useContext for themes like (dark mode & light mode), and redux for all other data state.
The useContext hook is the React hook equivalent of the Context. ... It takes a React context object as the argument and returns the current value from the context. useReducer is an alternative version of useState for more complex state changes.
Let's see how to implement it in React by using hooks and browser's localStorage.
We will use here facebook's react-boilerplate.
Clone it first by using the command npx create-react-app dark-mode, after cloning, change the root directory to dark-mode by using cd dark-mode and to run the application npm start, use this create-react-app for more details.
Let's add some darkness 😃
Create CSS Files
// light-theme.css
html[data-theme="light"] {
--color: rgb(5, 5, 5);
--background-color: rgb(250, 250, 250);
}
// dark-theme.css
html[data-theme="dark"] {
--color: rgb(250, 250, 250);
--background-color: rgb(5, 5, 5);
}
As of now, I have added only two color variables, later you can add as many color variables for your project.
Don't hardcode color in any css files or in any inline styling, use only defined color variables.
// App.css
.App-header {
background-color:var(--background-color);
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color:var(--color);
}
I have used those color variables in App.css file.
Create DarkMode folder, add index.js and index.css files.
/ DarkMode/index.js
const DarkModeToggle = () => {
const [isDark, setIsDark] = useState(localStorage.getItem("theme") === "dark" ? true : false);
useEffect(() => {
document
.getElementsByTagName("HTML")[0]
.setAttribute("data-theme", localStorage.getItem("theme"));
},[]);
Using useState hook to store the current user theme preference, get the current user preference from localStorage.
Suppose you are running the application for first time, you won't get the user theme preference in browser's localStorage, in that case false get set to the isDark hook and applied light theme to the application.
I have used browser's localStorage to set the choosen user theme preference and update it while theme toggling.
Set HTML data-theme attribute accordingly with current user theme preference.
Note: The data-* attribute is used to store custom data private to the page or application. The data-* attribute gives us the ability to embed custom data attributes on all HTML elements.
/ handles user theme preference change
const toggleThemeChange = () => {
if (isDark === false) {
localStorage.setItem("theme", "dark");
document
.getElementsByTagName("HTML")[0]
.setAttribute("data-theme", localStorage.getItem("theme"));
setIsDark(true);
} else {
localStorage.setItem("theme", "light");
document
.getElementsByTagName("HTML")[0]
.setAttribute("data-theme", localStorage.getItem("theme"));
setIsDark(false);
}
}
This method will get triggered when we toggle the theme from light to dark or vice-versa. It will update the state isDark based on current theme choosen and simultaneously update the data-theme attribute. data-theme attribute helps application to determine which color schemes need to applied either dark html[data-theme="dark"] or light html[data-theme="light"].
// templete for theme toggle button
return (
<label className="switch">
<input
type="checkbox"
defaultChecked={isDark}
onChange={() => toggleThemeChange()}
/>
<span className="slider round" />
</label>
)
returning the html toggle element for switching the theme.
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
<DarkModeToggle />
</a>
</header>
</div>
);
}
Add this DarkModeToggle component wherever you want to place it.
Related
I want a MenuBar with no hamburger button appearing while in responsive mode. I'm using CSS module to style my components. How can I completely remove hamburger menu button using CSS module? I can't find a way to access its specific HTML tag <a class="p-menubar-button" ...> and do Display: None.
MenuBar declaration in NavBar.js -
import { Menubar } from 'primereact/menubar'
import styles from './NavBar.module.css'
const NavBar = () => {
return (
<Menubar start={start} end={end} className={styles.menubar} />
)
}
CSS in NavBar.module.css -
.menubar {
// I don't know how to access .p-menubar-button here
}
MenuBar component in plain HTML -
<div class="p-menubar p-component NavBar_menubar__ZntdZ">
<div class="p-menubar-start">...</div>
<a class="p-menubar-button" ...>...</a>
<div class="p-menubar-end">...</div>
</div>
Turns out, it is easy to solve using CSS's child selector and attribute selector. I didn't know those selectors work with CSS module.
Here is the solution in NavBar.module.css -
.menubar > a[class="p-menubar-button"] {
display: none !important;
}
I appreciate this may be a little basic here, but I'm relatively new to React and am testing the waters with various ways of applying on-the-fly styling rather than creating separate stylesheets and importing them.
I'm trying to experiment adding styles to three different elements - one via inline styles, another via a style tag, and another via a style variable - where only the inline style seems to work.
Here is my code with all 3 elements:
import React from 'react'
const App = () => {
render {
const testOneStyle = {
color: "red",
fontWeight: "bold"
};
return (
<div>
<span style={testOneStyle} className="test-one">test 1</span>
<span className="test-two">test 2</span>
<style>
.test-two {
color: red;
font-weight: bold
}
</style>
<span style={{color: "red"}} className="test-three">test 3</span>
</div>
)
}
}
export default App
Firstly, does the variable style (i.e. here) only work with class components rather than functional components?
And can someone explain why this is not rendering and how to render and apply the styles?
Thank you for any advice. Here is a StackBlitz demo: https://stackblitz.com/edit/react-tjukup
Unfortunately, <style> tags don't work in JSX the way they do in html. You are going to have to parse the the string appropriately yourself, since JSX is just javascript with syntactical sugar to convert into React.createElement() function with the right parameters. So you want to generally avoid style and head tags in JSX, but if you do, you want to use it like:
<style>
{"\
.test-two {\
color: red;\
font-weight: bold;\
}\
"}
</style>
EDIT
Also, to answer your question "does the variable style only work with class components rather than functional components?", no. The prop style is a JSX prop and works regardless of what kind of component you are using.
EDIT
And the reason why your component is not rendering, is because render() is a function that is only used in class based components. In a functional component you just directly return the JSX.
import React from "react";
const App = () => {
const testOneStyle = {
color: "red",
fontWeight: "bold"
};
return (
<div>
<span style={testOneStyle} className="test-one">
test 1 - fails
</span>
<span className="test-two">test 2 - fails</span>
<style>
{`
.test-two {
color: red;
font-weight: bold
}
`}
</style>
<span style={{ color: "red" }} className="test-three">
test 3 - works
</span>
</div>
);
};
export default App;
EDIT
As you may have observed in the snippet I have provided, you can also use strings with "`" to make it easier to enter strings in JSX
I hope this may helps you
import React from 'react'
const App = () => {
const testOneStyle = {
color: "blue",
fontWeight: "bold"
};
return (
<div>
<span style={testOneStyle} className="test-one">
test 1 - fails
</span>
<span className="test-two">
test 2 - fails
</span>
<style>
{
`.test-two {
color: green;
font-weight: bold
}`
}
</style>
<span style={{color: "red"}} className="test-three">
test 3 - works
</span>
</div>
)
}
export default App
Explaination:
You are using functional component
Class component require render method to return a JSX. Functional component can directly return JSX.
you can add style tag in your JSX but the context inside need to be string.
I am using ag-grid in react, and I want to change the direction to RTL, also use of local text. Based on the ag-grid documentation enableRtl={true} would change the style of the grid. It is true when we set it permanently, but when I want to change it programmatically, it does not change. I passed the variable through props to the component for changing the direction and translating the text. This is my code
const Grid= ({dir}) =>{
return (
<div
className="ag-theme-balham"
style={{ height: '100%', width: '100%' }}
>
<AgGridReact
defaultColDef={defaultColDef}
columnDefs={column}
rowData={rowData}
enableRtl={dir === 'rtl'}
localeTextFunc={(key, defaultValue) => {
return dir === 'rtl' ? gridLocal[key] : defaultValue;
}}
/>
</div>
);
}
before changing direction
after changing direction
As it should be
I searched for the solution, but I cannot find a way to solve it.
What should I do? What is wrong with my code?
Edit: where I used the Grid component
const MainLayout = (props) => {
const classes = useStyles();
const theme = useTheme();
const [open, setOpen] = useState(false);
const handleDrawerOpen = () => { setOpen(true) };
const handleDrawerClose = () => { setOpen(false) };
return (
<div >
<Topbar
setLang={props.setLang}
drawerOpen={handleDrawerOpen}
drawerClose={handleDrawerClose}
status={open}
handleSignOut={props.handleSignOut}
/>
<div style={{height:'48px'}}/>
<div className={classes.content}>
<Sidebar open={open} drawerClose={handleDrawerClose}/>
<Grid dir={theme.direction}/>
</div>
</div>
);
}
I have the same issue. After searching many times, I found only one solution.
I used angular not react, but, I can tell you the steps I used to solve this problem.
I followed the following steps:
I stored the value of the direction in the localstorage
Created a button to trigger an event that will change the value of the direction.
Get the value of the direction from the localstorage in the constructor
Inside the trigger, I changed the value on the localstorage for each click event.
finally, at each click event refresh the page with window.location.reload() to see the effect.
I know. It is not the perfect solution, but this is the only solution that worked for me. I think this problem is not found in the enterprise version of ag-grid.
Update:
I found a better solution. I use angular not React.
In angular, I used <ng-template> as follow:
<ng-template [ngIf]="AgGridDir === 'rtl'" [ngIfElse]="LeftGrid" #Withbutton>
<ag-grid-angular style="width: 100%; height: 450px;"
[class]="Table_Color_mode===Constants.light || Theme_dir===Constants.light?'ag-theme-alpine':'ag-theme-alpine-dark'"
[rowData]="ItemsMainCategories | async" [columnDefs]="columnDefs" (gridReady)="OnGridReady($event)"
[defaultColDef]="defaultColDef" [overlayLoadingTemplate]="overlayLoadingTemplate" [animateRows]="true"
[enableRtl]="true" [frameworkComponents]="agFrameworks" rowSelection="single"
(rowEditingStarted)="onRowEditingStarted($event)" (cellValueChanged)="UpdateItemMainCat($event)">
</ag-grid-angular>
</ng-template>
<ng-template #LeftGrid>
<ag-grid-angular style="width: 100%; height: 450px;"
[class]="Table_Color_mode===Constants.light || Theme_dir===Constants.light?'ag-theme-alpine':'ag-theme-alpine-dark'"
[rowData]="ItemsMainCategories | async" [columnDefs]="columnDefs" (gridReady)="OnGridReady($event)"
[defaultColDef]="defaultColDef" [overlayLoadingTemplate]="overlayLoadingTemplate" [animateRows]="true"
[enableRtl]="false" [frameworkComponents]="agFrameworks" rowSelection="single"
(rowEditingStarted)="onRowEditingStarted($event)" (cellValueChanged)="UpdateItemMainCat($event)">
</ag-grid-angular>
</ng-template>
<ng-template> is a tag created by Angular framework that can be used to render some components or HTML tags based on a condition.
As you can see, I used the same Ag-grid table with the same parameters. Each one with an ng-template. I used a variable called AgGridDir which is dynamically changed to rtl to ltr based on the user choice.
If the user chooses 'rtl' the first template is rendered and the [enableRtl]="true" is set to true, and if the user chooses 'ltr', then the second template will be rendered dynamically without reloading the page and the [enableRtl]="false".
I don't know about react much details, but, I want to share the idea.
I have storybook components, that I use in my project. So I need to use Card component in my project, which needs to be added some styling. So I add style using "styled" from styled-component. After I export my new CardNew and try yo use it in some pages. It works, but when I try to add onMouseLeave/onMouseEnter events to that component, it does not work.
When I try to use Card's initial version from storybook, without any styling and add event, it works.
Here is my code.
const CardStyled = styled(Card)`
width: 200px;
height: 100px;
margin-bottom:10px;
color:red;
line-height: 1.5em;
`;
export default function CardNew(props) {
return (
<CardStyled>
<Card.Body>
<p>{props.content}</p>
</Card.Body>
</CardStyled`enter code here`>
)
}
And then I use that component and try to handle any event
<CardNew onMouseEnter={() => onMouseEnter()} onMouseLeave={() => onMouseLeave()} >
</CardNew>
Can anyone explain why I can't make styling on storybook component, then use it?
Thanks.
Because your CardNew component is not HTML component. So, you should pass all props donw. Like:
export default function CardNew(props) {
return (
<CardStyled>
<Card.Body>
<p {...props}>{props.content}</p>
</Card.Body>
</CardStyled`enter code here`>
)
}
This will allow you to handle any event to element.
In App.css, I have
.theme {
color: green;
}
And I have className="theme" scattered in multiple components.
Is there a way to change the theme color from green to blue on an event?
If not, how should I design my code?
Well, You can create 2 classes named .blue-theme and .green-theme
Whenever, some event occurs,
onClick = (themeCol) => {
this.setState({theme:thmeCol})
}
render(){
return(
<button onClick={()=>onClick('blue-theme')}>Blue theme</button>
<button onClick={()=>onClick('green-theme')}>Green theme</button>
<div className={this.state.theme}> Sample </div>
)
}
You can pass the value of theme.
you can try
const themeClass = "";
if (event-as-wanted){
themeClass="theme";
}
className={themeClass}
also you can use style insted of className in same file
const theme ={
color: '';
};
style={theme}
and change it with events like
if (event-as-wanted){
theme.color = "green";
}
You can conditionally render the <style/> tag to override style definition for the class in the whole document.
class App extends React.Component {
state = {
red: true,
}
render() {
return (
<div>
<div className="foo">Foo</div>
<button onClick={() => this.setState({ red: !this.state.red })}>
Toggle Red
</button>
{this.state.red && <style>{".foo { color: red }"}</style>}
</div>
);
}
}
Keep in mind that inside JSX tags, curly brackets will be picked up by the interpreter and may break the parser. To avoid that, you should put your CSS inside a string like in the example above.
Adding a <style/> tag to CSS document will override any equally specific CSS rules that came before that. Once the condition is no longer met, the style tag will be removed and the original styling will be restored.
in react.js just set the state of color to whatever and on a click event toggle the color
class App extends React.Component {
constructor(props) {
super(props);
this.state = {color: green};
this.changeColor = this.changeColor.bind(this);
}
changeColor() {
const newcolor = this.state.color == green ? blue : green;
this.setState({ color: newcolor});
}
render() {
return (
<div class="theme" style={{color: this.statecolor}}>
<button onClick={this.changeColor}>
</button>
//put all html withing the parent DOM of element with class theme accordingly or it wont render.
</div>
);
}
Make two class, .green-theme{
color:'green'} and similarly, blue theme.
Mantain a REDUX STATE, CURRENT_THEME. Upon event fire, change the redux state accordingly and everywhere, where you want to use CURRENT_THEME, use it using mapStateToProps.
I would rather try to use almost pure CSS solution:
in App.css
#App.first-theme .theme { color:green; }
#App.second-theme .theme { color:blue; }
in App's render:
<div id="App" className={this.state.currentTheme}>
<AnotherComponent1 />
<AnotherComponent2 />
</div>
All you need to do is to change this.state.currentTheme appropriately. You can even use prop injected from the redux.
Almost all other solutions posted here have the same flaw: you have to adapt all your components to use the theme. Using this solution, you are able to change app's appearance without additional code in your components.
Trust me, injecting the same property from redux store/react context for every component will give you headaches and a lot of unnecessary code.
You should also try to avoid generating additional <style> tags - you will end up having plenty of !important and HTML, logic, and CSS in one file. What a mess! Imagine, what would happen if you would like to use SCSS in the future...