I have added a simple dark mode switch to my website https://atlasstic.com/ and everything works just fine in desktop.
In Mobile however the switch happens (only in preview mode, not on an actual mobile), but I need to refresh the page to see the change:
`
<script type="text/javascript">
const themeToggle = document.querySelector("#theme-toggle");
themeToggle.addEventListener("click", () => {
document.body.classList.contains("light-theme")
? enableDarkTheme()
: enableLightTheme();
});
function enableDarkTheme(){
document.body.classList.remove("light-theme");
document.body.classList.add("dark-theme");
themeToggle.setAttribute("aria-label", "Switch to Light Theme");
ips.utils.cookie.set( 'theme-toggle', 'dark-theme', true );
}
function enableLightTheme(){
document.body.classList.remove("dark-theme");
document.body.classList.add("light-theme");
themeToggle.setAttribute("aria-label", "Switch to Dark Theme");
ips.utils.cookie.set( 'theme-toggle', 'light-theme', true );
}
function themeToggleChoice(){
var themeToggleChoiceCookie = ips.utils.cookie.get( 'theme-toggle' );
if(themeToggleChoiceCookie == ''){
ips.utils.cookie.set( 'theme-toggle', 'light-theme', true );
}
else if(themeToggleChoiceCookie == 'light-theme'){
ips.utils.cookie.set( 'theme-toggle', 'dark-theme', true );
}
else if(themeToggleChoiceCookie == 'dark-theme'){
ips.utils.cookie.set( 'theme-toggle', 'light-theme', true );
}
}
</script>
`
I assume there is something in the mobile vie that need something to update the dom that is different than on desktop, so I need some help to figure out what :)
Related
I need to conditionally render components based on screen size.
I use nextjs and getInitialProps for data fetching, the page is server-side rendered. I want to detect device screen size on the client-side, so I implement a customized hook to do it.
useWindowSize.js
import { useEffect, useState } from 'react';
export default function useWindowSize() {
const [windowSize, setWindowSize] = useState({
width: typeof window === 'undefined' ? 1200 : window.innerWidth, // default width 1200
});
useEffect(() => {
// Handler to call on window resize
function handleResize() {
// Set window width/height to state
setWindowSize({
width: window.innerWidth,
//height: window.innerHeight,
});
}
// Add event listener
window.addEventListener('resize', handleResize);
// Call handler right away so state gets updated with initial window size
handleResize();
// Remove event listener on cleanup
return () => window.removeEventListener('resize', handleResize);
}, []); // Empty array ensures that effect is only run on mount
return windowSize.width <= 600;
}
then I use this hook to detect window size and conditional render components
export default function IndexPage() {
const isMobile = useWindowSize();
if (typeof window !== "undefined") {
// if you are running it on codesanbox, I don't know why log is not printed
console.log("client side re-render");
}
return (
<div>
{isMobile ? (
<div
style={{
color: "red",
fontSize: 40
}}
>
mobile
</div>
) : (
<div
style={{
color: "blue",
fontSize: 20
}}
>
desktop
</div>
)}
</div>
);
}
IndexPage.getInitialProps = () => {
return {
a: 1
};
};
when I load the page on mobile browser, you will see
text mobile is applied wrong CSS style. video demo: https://share.getcloudapp.com/nOuk08L0
how to reproduce:
https://codesandbox.io/s/thirsty-khayyam-npqpt
Can someone please help me out. Thank you in advance!
This is an issue that is related to how React patch up DOM from SSR. When there is a mismatch between client-side and server-side rendering, React will only patch/sync the text context for the node. The DOM attribute will not be automatically updated. In your case, the SSR result has the desktop style because there is no window object, and client side has the mobile result. After the mismatch, React update the text node from 'desktop' to mobile but not the style attributes.
In my opinion, you can use two different approaches. You can use Media Query to style your component based on the screen width instead of the hook. If you are doing SSR, not SSG, you can use user agent req.headers["user-agent"] to detect the device your device is being viewed on.
For the first approach, you might need to render more DOM node you might need to. For the second approach, you won't be able to know the actual viewport size, which can cause visual issue. You might be able to combine both approach to produce a good viewing experience for your user.
Reference
https://github.com/facebook/react/issues/11128#issuecomment-334882514
Thanks for #Andrew Zheng's detailed explanation! Today I learned.
I know that I can style the layout by using pure CSS media query, but my use case needs a variable like isMobile to
if (isMobile) {
doSomethingOnlyOnMobileWeb();
} else {
doSomethingOnlyForDesktopWeb();
}
So I combined two approaches you provided, and modify my hook this way:
export default function useWindowSize(userAgent) {
let isMobile = Boolean(
userAgent &&
userAgent.match(
/Android|BlackBerry|iPhone|iPod|Opera Mini|IEMobile|WPDesktop/i
)
);
const [windowSize, setWindowSize] = useState({
width: isServer
? isMobile
? BREAKPOINT_SMALL
: BREAKPOINT_LARGE
: window.innerWidth,
});
useEffect(() => {
// Handler to call on window resize
function handleResize() {
// Set window width/height to state
setWindowSize({
width: window.innerWidth,
//height: window.innerHeight,
});
}
// Add event listener
window.addEventListener('resize', handleResize);
// Call handler right away so state gets updated with initial window size
handleResize();
// Remove event listener on cleanup
return () => window.removeEventListener('resize', handleResize);
}, []); // Empty array ensures that effect is only run on mount
return windowSize.width <= BREAKPOINT_SMALL;
}
diff: passing user-agent string to useWindowSize for server-side detection and use window.innerWidth for client-side detection. There won't be a mismatch between server and client.
I'm trying to include Waypoints (http://imakewebthings.com/waypoints/) in my WordPress theme without using their plugin (premium plugin).
So, I downloaded the necessary files and enqueued them:
// Waypoints
wp_enqueue_script( 'waypoints-main', get_template_directory_uri() . '/inc/waypoint/jquery.waypoints.min.js', array( 'jquery' ), false, true );
wp_enqueue_script( 'waypoints-animations', get_template_directory_uri() . '/js/waypoints-animations.js', array( 'jquery', 'waypoints-main' ), false, true );
This is what I have in my waypoints-animations.js file:
(function($){
var $div1 = $('.new-arrivals');
$div1.waypoint(function(direction){
if (direction === 'down'){
$div1.addClass('animate');
} else{
$div1.removeClass('animate');
}
},
{offset: '60%'}
);
})(window.jQuery)
My CSS says this:
.new-arrivals {
clear: both;
padding: 40px 0 0 0;
margin-bottom: -40px;
transition: 1s;
opacity: 0;
}
.animate {
opacity: 1;
}
I don't have errors in my console. But I noticed that the page is loading with the direction set to 'down', causing the div with the class .new-arrivals to display by default (opacity 1). I want the opposite to happen.
Once I managed to make the code function, but that only happened when the developer's tool was opened. I couldn't understand why that happened...
Can you please help me?
Just managed to find the mistake.
I needed to add the scroll event. Like this:
(function ($) {
var $div1 = $('.new-arrivals');
$(window).on('scroll', function () {
$div1.waypoint(function (direction) {
if (direction === 'down') {
$div1.addClass('animate');
} else {
$div1.removeClass('animate');
}
}, {
offset: '60%'
});
});
})(window.jQuery)
I have following React code
Code
What I would like is to when I hover my "E-commerce" picture App component background should change on "E-commerce" picture background.
So respectively and for other pictures.
I will be very grateful if you help me solve this problem.
Context, according to the React docs, should be used only for truly global state like current user or theme. Using context for components makes them less reusable.
updated code
Your component tree is App -> SolutionBox -> SolutionItem.
You want to "react" to an event in SolutionItem in App but there is SolutionBox inbetween them so you have to thread the event thru SolutionBox to App.
Step 1
Add a prop to SolutionItem called on OnHover, this will be a function call back that any parent component can use to react to changes.
function SolutionsSectionBoxItem({ solutionIMG, onHover }) {
let callOnHover = state => {
if (_.isFunction(onHover)) {
onHover(state);
}
};
return (
<div className="solutions-section-item-box">
<img
src={solutionIMG}
alt=""
onMouseEnter={() => {
callOnHover(true);
}}
onMouseLeave={() => {
callOnHover(false);
}}
className="solutions-section-item-img"
/>
</div>
);
}
Step 2
Add a prop to SolutionBoxItem called on BGChanged, this will again be a function call back that will be called when any solutionitem onhover happens. This function will take a menuName string and pass either the current menu name or default.
function SolutionsSectionBox({ onBGChanged }) {
let callBGChanged = menuName => {
if (_.isFunction(onBGChanged)) {
onBGChanged(menuName);
}
};
return (
<div className="solutions-section-box-box">
<SolutionItem
solutionIMG={Ecommerce}
onHover={state => {
callBGChanged(state === true ? "Ecommerce" : "default");
}}
/>
<SolutionItem
solutionIMG={SalesMarketing}
onHover={state => {
callBGChanged(state === true ? "SalesMarketing" : "default");
}}
/>
<SolutionItem
solutionIMG={Analytics}
onHover={state => {
callBGChanged(state === true ? "Analytics" : "default");
}}
/>
<SolutionItem
solutionIMG={Middleware}
onHover={state => {
callBGChanged(state === true ? "Middleware" : "default");
}}
/>
</div>
);
}
Step 3
In the App component listen for the changes. In here we now set state when ever the mouse enters or leaves a solution item. From here you have to change the background, you are using css to control the background url, this will be harder since you now need css class for each background type. You could use the bgImage state value to change the name of the extra css className like 'AppSalesMarketing', 'AppEcommerce', etc.
export default function App() {
const [bgImage, setbgImage] = useState(E);
const onBGChanged = menuName => {
setbgImage(menuName);
};
return (
<div className={`App ${bgImage === "default" ? "" : `App${bgImage}`}`}>
<SolutionBox onBGChanged={onBGChanged} />
</div>
);
}
In CSS
Leave the original App class but based on the bgImage value add an additional one using the name of the bgImage + App like below to cascade down the updated background-image value.
.AppEcommerce {
background-image: url(https://placekitten.com/600/600);
}
.AppSalesMarketing {
background-image: url(https://placekitten.com/500/800);
}
.AppAnalytics {
background-image: url(https://placekitten.com/800/500);
}
.AppMiddleware {
background-image: url(https://placekitten.com/700/700);
}
Extra
I added lodash to test that the incoming props are functions before I call them, it is good to do defensive programming because you never know who may use your component in the future.
let callBGChanged = menuName => {
if (_.isFunction(onBGChanged)) {
onBGChanged(menuName);
}
};
Two ways to solve the problem. One is passing down a function to update state, the other is to useContext. In this case it makes sense to use context because you are passing down a function through multiple components that do not care about the function.
First thing to do is make the background image dynamic in the div's style and use context:
// Put this outside the component
export const BackgroundContext = React.createContext(null);
// -- snip
const [backgroundImage, setBackgroundImage] = useState(Ecommerce);
const updateBackgroundImage = newImage => setBackgroundImage(newImage);
// -- snip
<BackgroundContext.Provider value={updateBackgroundImage}>
<div className="App" style={{ backgroundImage: `url(${backgroundImage})` }}>
{/* -- snip */}
</BackgroundContext.Provider>
Now in your SolutionsSectionBoxItem component you can import the background context:
import BackgroundContext from "../App";
Then using that context and react's mouseover api, update the selected background image:
const setBackgroundImage = useContext(BackgroundContext);
// -- snip
<img onMouseOver={() => setBackgroundImage(solutionIMG)} {/* -- snip -- */} />
You can read more here: https://reactjs.org/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down
I use a Popup to show an error message on an input if validation fails.
<Popup
trigger={InputComponent}
open={state.error}
content={errorMessage}
/>
This works fine, but the annoying part is that when I focus the element an empty popup appears. I can't disable this behaviour for as far as I know.
I've tried adding on={null} and on="none", but all this does not work.
Any ideas? It would be nice to disable triggering the popup, but to allow it to be visible on state value only.
If anyone facing the same issue, the easiest fix would be to add a custom popup style to your popup tag and define opacity with the state as below.
const style = {
opacity: this.state.isOpen ? "1" : "0"
}
<Popup style={style} trigger={<Button icon='add' />} content='Add users to your feed'/>
The usage is very similar to one of the cases mentioned in the docs: https://react.semantic-ui.com/modules/popup#popup-example-controlled
Make sure state.error returns bool type and not string bool and finally, check you are able to close it after the popup opens using onOpen handler as an added measure to make sure you are able to atleast control the component's state.
Finally, as a hack, you can add a {{display: "none"}} through Popup's style prop when this.state.error === true
An example usage from SUI docs of a Popup that automatically after 2.5 seconds:
import React from 'react'
import { Button, Grid, Header, Popup } from 'semantic-ui-react'
const timeoutLength = 2500
class PopupExampleControlled extends React.Component {
state = { isOpen: false }
handleOpen = () => {
this.setState({ isOpen: true })
this.timeout = setTimeout(() => {
this.setState({ isOpen: false })
}, timeoutLength)
}
handleClose = () => {
this.setState({ isOpen: false })
clearTimeout(this.timeout)
}
render() {
return (
<Grid>
<Grid.Column width={8}>
<Popup
trigger={<Button content='Open controlled popup' />}
content={`This message will self-destruct in ${timeoutLength / 1000} seconds!`}
on='click'
open={this.state.isOpen}
onClose={this.handleClose}
onOpen={this.handleOpen}
position='top right'
/>
</Grid.Column>
<Grid.Column width={8}>
<Header>State</Header>
<pre>{JSON.stringify(this.state, null, 2)}</pre>
</Grid.Column>
</Grid>
)
}
}
export default PopupExampleControlled
Currently upgrading from a jquery / php webapp to react. All is going well and I understand the concepts behind react etc. The only issue I can't find a workaround is how to dynamically add / delete a class based on where the user clicks. I need this because I have a few dropdowns which trigger when the user click it and need to be hidden if they click somewhere else.
From: Give a class of "selected" when clicked on another <a> then remove the class if it's the same <a>
I've taken this example as it is simple, Jquery solution to the problem:
var h2 = $("h2 a");
h2.on("click", function() {
if ($(this).is(".selected")) {
$(this).removeClass("selected");
} else {
h2.removeClass("selected")
.filter(this).addClass("selected")
}
});
How to mimic the same functionality in react (and / or redux)?
Image to further clarify
The thing is, you want to toggle some data on click event and change the classname of a html element accordingly.
Your data that 'selected' class based on could come from anywhere, from parent or component state. You would do something like this :
<div className={ myData ? 'selected' : '' } ></div>
But there is a better way to display classname changes with a library called classnames. The same thing is accomplished as :
<div className={ classNames({ 'selected' : myData }) } ></div>
Until now, we have seen how to display changes on render function. Second thing you need is to listen to click events and fire the function that will eventually toggle the data that controls the 'selected' classname, in my example 'myData'.
Here is a working example, there might be various ways to accomplish this. But I strongly recommend using classnames library to toggle classnames.
The workaround about removing class when another element ( apart from li elements we observe ) clicked could be solved by a click event listener.
For instance :
import React, { Component } from 'react'
import classNames from 'classnames'
class DropDown extends Component {
constructor(props){
super(props)
this.state = {
activeSelected : ''
}
this.handleClick = this.handleClick.bind(this)
}
componentDidMount(){
global.document.addEventListener( 'click', this.handleClick, false )
}
componentWillUnmount(){
global.document.removeEventListener( 'click', this.handleClick, false )
}
handleClick(event){
if( event.target.className.includes('not-changing-css-class') &&
this.state.activeSelected !== ''
) this.setState( { activeSelected : '' } )
}
render(){
let { activeSelected } = this.state
return (
<ul>
<li
className={ classNames({
'not-changing-css-class' : true,
'selected' : activeSelected === 'item1'
}) }
onClick={ event => this.setState({ activeSelected : activeSelected === 'item1' ? '' : 'item1' }) }
>
Item 1
</li>
<li
className={ classNames({
'not-changing-css-class' : true,
'selected' : activeSelected === 'item2'
}) }
onClick={ event => this.setState({ activeSelected : activeSelected === 'item2' ? '' : 'item2' }) }
>
Item 2
</li>
<li
className={ classNames({
'not-changing-css-class' : true,
'selected' : activeSelected === 'item3'
}) }
onClick={ event => this.setState({ activeSelected : activeSelected === 'item3' ? '' : 'item3' }) }
>
Item 3
</li>
</ul>
)
}
}
You can hold the selected element index (or ID if you use IDs) in the component state. You can use redux store if you think this state will be relevant to any other component in your app, but starting with state is simpler.
Once you have this state. You can check within your render() function whether a link is selected or not, by comparing with the component state. You would also update the selectedIndex whent the links are clicked.
A simple example to render the links could be as follows. Note that you can extract parts of these to be functions instead of using expressions in JSX.
```
allLinks.map( (link, index) =>
<a href={link.target} className={this.state.selectedIndex === index ? 'selected' : null}/>
)
```