how to get orientation type and lock orientation in ReactJS?
The Screen Orientation API doesn't work with ReactJS or I just don't know how to install and use it. Please guide me to do the above. Thanks.
Your use case is not that clear from your post. However, plain JavaScript (Window.matchMedia() method) along with a bit of CSS (media query for orientation) and event listener for window resize, may let you grab window orientation and store it within app state.
Following code demonstrates that concept (no live-demo due to stacksnippets limitations, so you may try that out at stackblitz):
import React, { useState, useEffect } from 'react'
import { render } from 'react-dom'
const rootNode = document.getElementById('root')
const App = () => {
const isLandscape = () => window.matchMedia('(orientation:landscape)').matches,
[orientation, setOrientation] = useState(isLandscape() ? 'landscape' : 'portrait'),
onWindowResize = () => {
clearTimeout(window.resizeLag)
window.resizeLag = setTimeout(() => {
delete window.resizeLag
setOrientation(isLandscape() ? 'landscape' : 'portrait')
}, 200)
}
useEffect(() => (
onWindowResize(),
window.addEventListener('resize', onWindowResize),
() => window.removeEventListener('resize', onWindowResize)
),[])
return (
<div>{orientation}</div>
)
}
render (
<App />,
rootNode
)
p.s. while above is pretty viable, it may become even more comprehensive in a short while as window.screen.orientation (working draft currently) will make it to the standard, one more improvement may seem obvious (listen for orientationchange event, rather than resize), but former doesn't seem to me working so smoothly for desktop version of the app
Related
Simple question. How can I react to changes in the window size in solid-js? I need to do some computations with document.body.clientWidth, but I can't get anything to re-execute when that changes. I've tried using on(), memos, functions that are not memos, and the value directly. Nothing's working. There's got to be something simple that I'm missing.
Solid components can react to signal changes only and reaction is limited to the tracking scope meaning you can not detect changes outside createComputed or one of its variations.
Now to answer your question, you are trying to react changes that occur outside solid's realm, an outside object that exists and changes on its own. So you need to set a listener on that object, in your case resize event on the window object, and update your signal whenever you receive a resize event:
import { createSignal, onCleanup, onMount } from 'solid-js';
import { render } from 'solid-js/web';
export const App = () => {
const [rect, setRect] = createSignal({
height: window.innerHeight,
width: window.innerWidth
});
const handler = (event: Event) => {
setRect({ height: window.innerHeight, width: window.innerWidth });
};
onMount(() => {
window.addEventListener('resize', handler);
});
onCleanup(() => {
window.removeEventListener('resize', handler);
})
return (
<div>Window Dimensions: {JSON.stringify(rect())}</div>
);
};
render(() => <App />, document.body);
Here, we did set listener when component mounts and cleaned up when the component unmounts.
Here you can find a live demo. Try resizing the output frame: https://playground.solidjs.com/anonymous/66ab1288-732e-4847-a0a4-86b8d24af55e
So, I've basically tried everything with this one. I ran out of solutions or options. Thing is, I have a button. When you click on it your camera will open and you will see some filters that you can apply to your face. I am new to React. Made it work without the iframe to test the API first, but it's not working anymore inside this iframe. The react component needs to be inside this iframe. The code can be found here with what I did so far/tried: https://codesandbox.io/s/cool-fog-3k5si5?file=/src/components/button/button.jsx
The problem is that when I click the button, the canvas disappears from the page and I get this error in the console:
The DeepAR API fails initialization because the canvas is no longer on the page and it crashes. I really don't know what to search for as I considered this to be a react render error and I tried different ways to write the react code (functional/class). If you have any ideas or suggestions, please help. Thank you in advance.
Your use of useEffect in your Modal and App Component is incorrect.
To remind you, useEffect accepts a function which runs after the render is committed to the screen.
If the function returns a function (which is your case), this function is the "clean up" function which is run before the component is removed from the UI.
So what is happening is that your useEffect code is run only when your components are being unmounted.
Since we are not concerned with any clean up at this stage, a quick solution for you is to move the clean up expressions to the main effect function as follows:
useEffect(() => {
fetch(
"https://cors-anywhere.herokuapp.com/https://staging1.farmec.ro/rest/V1/farmec/deeparProducts/"
)
.then((response) => response.json())
.then((productsJson) => setProducts(productsJson));
}, []);
The same goes for your Modal component :
useEffect(() => {
let initializedDeepAR = new DeepAR({
licenseKey:
"6fda241c565744899d3ea574dc08a18ce3860d219aeb6de4b2d23437d7b6dcfcd79941dffe0e57f0",
libPath: DeepAR,
deeparWasmPath: deeparWasm,
canvas: canvas.current,
segmentationConfig: {
modelPath: segmentationMode
},
callbacks: {
onInitialize: () => {
// let filterName = colors[0].filterData[0]['Filter Binary Path'].match(new RegExp("[^/]+(?=\\.[^/.]*$)"))[0];
setDeepAR(initializedDeepAR);
initializedDeepAR.startVideo(true);
// initializedDeepAR.switchEffect(0, 'slot', `https://staging1.farmec.ro/media/deepArFilters/${filterName}.bin`);
}
}
});
/*#TODO: replace paths with server local path*/
initializedDeepAR.downloadFaceTrackingModel(models);
}, []);
With one additional fix concerning your use of useRef.
To target the element behind the useRef, you must use the .current property.
Finally, your Frame component is using useState to manage the mounting of the iframe. I would suggest using the useRef hook with a useState for your mountNode as follows:
export const Frame = ({
children,
styleSelector,
title,
...props
}) => {
const contentRef = useRef(null)
const [mountNode, setMountNode] = useState()
useEffect(() => {
setMountNode(contentRef.current.contentWindow.document.body)
}, [])
useEffect(() => {
const win = contentRef.current.contentWindow
const linkEls = win.parent.document.querySelectorAll(
styleSelector
)
if (linkEls.length) {
linkEls.forEach((el) => {
win.document.head.appendChild(el)
})
}
}, [styleSelector])
return (
<iframe title={title} {...props} ref={contentRef}>
{mountNode && createPortal(children, mountNode)}
</iframe>
)
}
I am trying to apply a parallax effect to an .svg image by using useRef() to grab bubblesRef and translateY() onScroll.
The parallax works but when I navigate to the next page I receive error "TypeError: Cannot read property 'style' of null". I think it is because the addEventListener is still listening and trying to useRef() on bubblesRef while navigating to the next page. So I added the cleanup function in useEffect() but that doesn't seem to fix it.
Any help is appreciated. Thanks!
p.s. If anyone can share their approach to a simple parallax effect like this that would be great too. This is the only approach I've figured that won't rerender everything else on the page onScroll.
const HomePage = () => {
const [loadedPosts, setLoadedPosts] = useState([]);
const { sendRequest } = useHttpClient();
console.log("loadedPosts homePage", loadedPosts);
const bubblesRef = useRef();
useEffect(() => {
if (loadedPosts.length === 0) {
//api call
}
}, [sendRequest, loadedPosts]);
useEffect(() => {
const parallax = () => {
let scrolledValue = window.scrollY / 3.5;
bubblesRef.current.style.transform = `translateY(
-${scrolledValue + "px"}
)`;
console.log("scrolling...", scrolledValue);
};
window.addEventListener("scroll", parallax);
return () => window.removeEventListener("scroll", parallax);
}, []);
return (
<HomePageContainer>
<Header />
<SectionOne posts={loadedPosts} />
<SectionTwo />
<BubbleBlobs className="bubbleBlobs" ref={bubblesRef} />
<BlobTop className="backBlobBottom" preserveAspectRatio="none" />
</HomePageContainer>
);
};
export default HomePage;
You definitely need the cleanup function any time you add a listener to the window, or the handler (and thus the component instance itself) will live on forever. However, since React runs those cleanup hooks asynchronously, it might not happen until after other window events. The value of the ref is set to null when the component unmounts, so you need to check that it is still defined before using the value.
useEffect(() => {
const handler = () => {
if (ref.current) {
// perform update
}
}
window.addEventListener('scroll', handler)
return () => window.removeEventListener('scroll', handler)
}, [])
When you call useEffect, your reference has not been instantiated, so the error message appears, in your useEffect dependency array, insert your ref and before running the code in useEffect, make sure your current reference is defined.
I want to detect screen orientation changes using the event orientationchange, which is supported by all major mobile browsers.
I add the event listener in componentDidMount and set the state from inside the event callback.
However, I see that when the event first fires as a result of change from portrait to landscape the state is not updated to landscape. Then when I change the orientation from landscape back to portrait, the state says the orientation is landscape. After this each time I change the orientation the state always says the opposite of the actual orientation. I'm not sure if I should be using some lifecycle react method or my detection is not good.
I tested the code using Chrome developer tools Toggle device toolbar.
This is my code:
import React from 'react';
class AppInfo extends React.Component {
state = {
screenOrientation: 'portrait'
}
isPortraitMode = () => {
console.log(this.state);
const { screenOrientation } = this.state;
return screenOrientation === 'portrait';
}
setScreenOrientation = () => {
if (window.matchMedia("(orientation: portrait)").matches) {
console.log('orientation: portrait');
this.setState({
screenOrientation: 'portrait'
});
}
if (window.matchMedia("(orientation: landscape)").matches) {
console.log('orientation: landscape');
this.setState({
screenOrientation: 'landscape'
});
}
}
componentDidMount() {
window.addEventListener('orientationchange', this.setScreenOrientation);
}
render() {
console.log(`orientation: from render: isPortraitMode = ${this.isPortraitMode()}`);
<div>
Hello
</div>
}
}
export default AppInfo;
import { useWindowDimensions } from 'react-native';
// In your component:
const { height, width } = useWindowDimensions();
const isPortrait = height > width;
You could try triggering it with the "resize" event instead, as some devices don't provided the orientationchange event, but do fire the window's resize event.
window.addEventListener("resize", this.setScreenOrientation);
orientationchange has been deprecated and it might or might not work in some browsers. Best bet now is to use one of the following options:
window.screen.orientation.addEventListener('change', function(e) { ... })
window.screen.orientation.onchange = function(e) { ... }
This way you can put whatever logic you want in the callback part of the listener, react or not.
Here is an implementation using hooks for react that tracks screen orientation using a custom hook from GitHub for obtaining and tracking orientation. It creates the event listener every time the component is mounted using useEffect and it shuts off the listener everytime the component is dismounted:
import {useState, useEffect} from 'react'
const getOrientation = () =>
window.screen.orientation.type
const useScreenOrientation = () => {
const [orientation, setOrientation] =
useState(getOrientation())
const updateOrientation = event => {
setOrientation(getOrientation())
}
useEffect(() => {
window.addEventListener(
'orientationchange',
updateOrientation
)
return () => {
window.removeEventListener(
'orientationchange',
updateOrientation
)
}
}, [])
return orientation
}
export default useScreenOrientation
In my Android mobile the two values for orientation are: 'portrait-primary' and 'landscape-secondary'. In my Ubuntu system it is: 'landscape-primary'. In my Windows 10 Surface Book 2 laptop mode: 'protrait-primary'. Doesn't handle the switch to tablet well. And it doesn't detect the orientation change at all.
All this is under Chrome v 99.0.4844.84 (official build) (64 bit). The Mac OS Monterey v 12.2.1 reports 'landscape-primary' same version of Chrome.
Your program works just fine, you just need to log it like this:
this.setState({screenOrientation: 'landscape'}, console.log('orientation: landscape'))
The reason behind it is that call to setState isn't synchronous but setState(updater, callback) is an async function and it takes the callback as second argument.
What is the correct way of dealing with scroll position in React? I really like smooth scrolling because of better UX. Since manipulating the DOM in React is an anti-pattern I ran into problem: how to scroll smoothly to some position/element? I usually change scrollTop value of an element, but this is a manipulation on the DOM, which is not allowed.
JSBIN
code:
import React from 'react';
import ReactDOM from 'react-dom';
class App extends React.Component {
handleClick = e => {
for (let i = 1; i <= 100; i++) {
setTimeout(() => (this.node.scrollTop = i), i * 2);
}
};
render() {
const someArrayToMap = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
return (
<div ref={node => this.node = node} style={{overflow: 'auto', height: '100vh'}}>
<button onClick={this.handleClick}>CLICK TO SCROLL</button>
{
[...someArrayToMap,
...someArrayToMap,
...someArrayToMap,
...someArrayToMap,
...someArrayToMap,
...someArrayToMap,
...someArrayToMap].map((e, i) => <div key={i}>some text here</div>)
}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
How to achieve this in a React way?
You can just use refs and the scrollIntoView method (with behavior: 'smooth' for smooth scrolling). It's only a few lines of code and doesn't require a package.
Say this is what you want to scroll to
<p ref={this.myRef} className="scrollToHere">[1] ...</p>
And some sort of button
<button onClick={() => {this.scroll(this.myRef)}} className="footnote">[1]</button>
to call the scroll method
class App extends Component {
constructor() {
super()
this.myRef = React.createRef();
scroll(ref) {
ref.current.scrollIntoView({behavior: 'smooth'})
}
}
EDIT: Because this method is not yet supported by all browsers (overview of browser support), you might want to use a polyfill.
window.scroll({top: 0, left: 0, behavior: 'smooth' }) works for me.
You also need to check for browser's compability
Or use polyfill
Edit: For the completeness' sake, here is how to dynamically polyfill with webpack.
if (!('scrollBehavior' in document.documentElement.style)) {
//safari does not support smooth scroll
(async () => {
const {default: smoothScroll} = await import(
/* webpackChunkName: 'polyfill-modern' */
'smoothscroll-polyfill'
)
smoothScroll.polyfill()
})()
}
By this dynamic polyfill, the package is loaded via ajax unless the browser supports the smooth scroll.
polyfill-modern is an arbitrary chunk name, which hints webpack compiler to combine packages together, in order to reduce the number of requests to the server.
The Simplest way to do: -
window.scrollTo({top: 0, left: 0, behavior: 'smooth' });
This simple JavaScript code is working with all browser.
Here's a small, no-dependancy solution using hooks
const useSmoothScrollTo = id => {
const ref = useRef(null)
useEffect(() => {
const listener = e => {
if (ref.current && location.hash === id) {
ref.current.scrollIntoView({behavior: 'smooth'})
}
}
window.addEventListener('hashchange', listener, true)
return () => {
window.removeEventListener('hashchange', listener)
}
}, [])
return {
'data-anchor-id': id,
ref
}
}
You use it like this:
export const FeaturesSection = () => {
const bind = useSmoothScrollTo('#features')
return (
<section {...bind} className={classes.features}>
...
</section>
)
}
Then anywhere else in your app you only need to do
Go to Features
Obviously, same caveats as above apply to .scrollIntoView({behavior: 'smooth'})
A couple good packages out there already that can handle this for you:
https://github.com/fisshy/react-scroll - Demo
https://www.npmjs.com/package/react-scroll-to-component
Simple scroll to component
Hope this helps!
There are several libraries for scrolling to anchors in React. The one to choose depend on the features you're seeking and the existing setup of your page.
React Scrollable Anchor is a lightweight library that's specifically for scrolling to anchors that are mapped to URL hashes. It also updates the URL hash based on currently focused section. [Full disclosure: I'm the author of this library]
React Scroll, mentioned in another answer, is a more fully featured library for scrolling to anchors, without any reflection of location in the URL.
You can also hook up something like React Router Hash Link Scroll if you're already using React Router, which will then also tie into your URL hash.
I really enjoy the react-router website in the API section, they seem to use this scrollToDoc component that is a really sweet translation of a typical VanillaJS smooth-scroll function into React which depends on react-motion!
Simple hook:
function useScrollTo(): [string, () => void] {
const id = useId();
const handleScroll = useCallback(() => {
const element = document.getElementById(id);
if (element) {
element.scrollIntoView({ behavior: 'smooth' });
}
}, [id]);
return [id, handleScroll];
}
And usage:
function App() {
const [section2, scrollToSection2] = useScrollTo();
return (
<>
<button onClick={scrollToSection2}>Scroll</button>
<div id={section2}>Section 2</div>
</>
)
}