Say I've got a component with identical content, but presented two totally different markup structures depending on the device (desktop viewports or mobile viewports).
In this situation when the viewport is below or above certain width or breakpoint (for this example 768px), I want to show one over the other.
A common situation for something like this might be the Navigation, where at Desktop views you have a simple navigation bar in the header of the page, whilst at Mobile views you have a more complex navigation menu that slides in and out:
import React from 'react';
import './Navigation.scss';
const Navigation = () => {
return (
<div className="navigation">
<div className="mobile-navigation-container">
<MobileNavigation />
</div>
<div className="desktop-navigation-container">
<DesktopNavigation />
</div>
</div>
);
};
Solution 1:
A simple solution to achieve this is to use CSS:
.navigation {
.mobile-navigation-container {
#media (min-width: 768px){
display: none;
}
}
.desktop-navigation-container {
#media (max-width: 767px){
display: none;
}
}
}
However, the issue here is that I still have both views in the DOM, even though one is not visible.
Solution #2:
Alternatively, I can use a resize listener and piece of state in my JSX component to conditionally render the correct component depending on the viewport width I can calculate using window.innerWidth:
import React, { Component } from 'react';
const isClient = typeof window !== 'undefined';
class Navigation extends Component {
state = {
viewportWidth: 0,
}
componentDidMount() {
if (isClient) {
this.updateWindowDimensions();
window.addEventListener('resize', this.updateWindowDimensions);
}
}
componentWillUnmount() {
if (isClient) window.removeEventListener('resize', this.updateWindowDimensions);
}
updateWindowDimensions = () => {
this.setState({ viewportWidth: window.innerWidth });
}
render() {
const { viewportWidth } = this.state;
return (
<div className="navigation">
{viewportWidth <= 768 && (
<div className="mobile-navigation-container">
<MobileNavigation />
</div>
)}
{viewportWidth > 768 && (
<div className="desktop-navigation-container">
<DesktopNavigation />
</div>
)}
</div>
);
}
This solves the issue of having duplicate content on the DOM. Which I'd guess is better for Search Engine Crawlers.
However, this somewhat makes my JSX more complicated, and I have the feeling that the CSS breakpoint is cleaner, smoother implementation in terms of performance, rather than using a JavaScript resize listener (though I can't find solid sources to advise one over the other).
My question is which of these two implementations is better practice and why?
The second approach Solution #2 is very good as compared to Solution #1. Because #1 has unnecessary and unwanted elements in DOM Object which is also confusing for react. Although it is not a good practice in any other languages as well. But in #2 you are not rendering unwanted contents this will improve smooth running of your code and debugging and designing is also easy in this approach.
Definitely the 2nd one even though it involves more lines of code, the overall performance outcome is much better because you don’t have extra pieces of DOM elements laying unnecessarily here and there in the page.
What’s more important is the flexibility provided by solution 2, what if you have to change the underlying markup on different screens in the future? (ex. hide some columns in smaller screens)
Related
I'm learning Angular and there is something I dont really understand. My Application works fine on all browser (Chrome, Firefox, Brave, Opera, mobile version also) except Safari (desktop and mobile). My app launches great except for my script from the assets folder. ("src/assets").
angular.json
...
"scripts": ["src/assets/js/layout.js"]
...
layout.js
'use strict';
var layoutInit = function layoutInit(){
const header = document.querySelector("header")// return null on safari;
const main = document.querySelector("main") // return null on safari;
const footer = document.querySelector("footer") // return null on safari;
function resize(){
main.style.minHeight = window.innerHeight - (footer?.offsetHeight ?? 0) + "px";
main.style.paddingTop = (header?.offsetHeight ?? 0) + "px";
}
window.onresize = resize;
resize();
}
if(document.readyState === 'loading' || document.readyState === 'interactive'){
document.addEventListener('DOMContentLoaded', layoutInit)
}else{
layoutInit()
}
Safari version : 15.6.1
Angular : 15
Does anyone experienced this issue ?
First off: I can't answer the question. My guess is something to do with how/when Safari loads scripts?
However, I'd rather suggest alternatives as a better practice over what you currently have (unless you have it for a particularly good reason?); It is odd to have a lovely Angular SPA that can do everything you want, but then use an outside script to just set out the page layout.
Move that into the app.
Assuming you have a basic app.component.html that looks suspiciously like this currently:
<div id="header">...</div>
<div id="main">
<router-outlet></router-outlet>
</div>
<div id="footer>...</div>
Add in some view child usage:
<div id="header" #header>...</div>
<div id="main" #main>
<router-outlet></router-outlet>
</div>
<div id="footer" #footer>...</div>
So they can be accessed in the app.component.ts:
#ViewChild('header') public header: ElementRef;
#ViewChild('main') public main: ElementRef;
#ViewChild('footer') public footer: ElementRef;
And then handle your resize in there too:
#HostListener('window:resize'. []) public onResize() {
this.resizeLayout();
}
private resizeLayout(): void {
if (!this.main) return; // make sure it all exists first
this.main.style.minHeight = window.innerHeight - (this.footer?.offsetHeight ?? 0) + "px";
this.main.style.paddingTop = (this.header?.offsetHeight ?? 0) + "px";
}
Something like that; might look a little different but that's a you problem not a me problem.
Probably want to put in either an ngOnInit or likely an ngAfterViewInit method to call that so it's all set up on first load (otherwise it'll ONLY be triggered on window resizing)...
public ngAfterViewInit(): void {
this.resizeLayout();
}
That gets rid of your additional script and keeps it fully contained inside the app itself - triggered once when the app (view) is first initialised, and then subsequently after any window resizing is done.
That's a lot of effort for something that CSS can do, though.
Use CSS - flex
My preference is to use Angular flex layout rather than raw CSS because 1) it's easier and 2) I personally like to keep base page structural details with the base page itself (the HTML).
Because of that, my memory for base CSS flex details is pretty poor, so I'll give it to you in AFL form in the template:
<div id="app-container"
fxFlexFill
fxLayout="column"
fxLayoutAlign="stretch stretch">
<div id="header" fxFlex="100px">...</div>
<div id="main" fxFlex>
<router-outlet></router-outlet>
</div>
<div id="footer fxFlex="50px">...</div>
</div>
So long as you appropriately set up your html/body styling to flex layout, this will:
fxFlexFill: cause the app-container to fill the available space (e.g. if you had body { width: 100vw; height: 100vh; } then app-container now fills the whole of the window)
fxLayout="column": cause the child elements to be rendered down the page (row for across)
fxLayoutAlign="stretch stretch": cause the child elements to stretch up and down as well as across
Followed by the children:
fxFlex="100px": limit the header div to only 'stretch' to 100px
fxFlex: allow the main div to stretch as much as possible
fxFlex="50px": limit the footer div to only 'stretch' to 50px
There's some additional things needed to make it work a little nicer and work exactly how you want it to (e.g. #main { overflow: auto; }) but that gets you 90% of the way there with the layout you want and there's no actual scripting involved, just CSS.
(I am new to React and TypeScript so apologies ahead of time)
I am trying to build a bar that I can fill between 2 elements and I think the best way might be to figure out where those two elements are after the component is built and then do some logic in order to size the bar properly.
For reference here is what im trying to build:
How / is it possible to get the DOM using something like getBoundingClientRect() in order to know how big and where the bar should start and stop?
Here is the component set up so far:
import React from "react";
import './CardLineItem.css';
import CardLineItemImage from "./CardLineItemImage";
import { FaChevronRight } from 'react-icons/fa';
import CardLineItemProgressBar from "./CardLineItemProgressBar";
export default class CardLineItem extends React.Component {
render() {
return (
<div className="CardLineItem">
<CardLineItemImage cardUrl="platinum-card.png" altText="platinum-card"/>
<FaChevronRight className="CardLineItemChevron" size="25%"/>
<CardLineItemProgressBar />
</div>
)
}
}
I looked into componentDidMount and this.children.toArray but that never returns anything. Suggestions on how I could best do this? CardLineItemProgressBar is the element I need to be flexible depending on the browser size.
Thanks!
Rather than reading the size and position of the other items, and making adjustments before the DOM paints, it's much better for performance to rely on CSS to do something like this.
I would highly recommend seeing if flex or grid can fulfill what you're trying to do here.
By bringing the progress bar component between the two other components in the JSX tree like this ...
<div className="CardLineItem">
<CardLineItemImage cardUrl="platinum-card.png" altText="platinum-card"/>
<CardLineItemProgressBar className="CardLineItemProgress" />
<FaChevronRight className="CardLineItemChevron" size="25%"/>
</div>
... you can add the flex style to the .CardLineItem and a .CardLineItemProgress class to have your progress bar grow accordingly to fill the space.
.CardLineItem {
// ... your other styles
display: flex;
gap: 10px;
}
.CardLineItemProgress {
flex-grow: 1;
align-self: flex-end;
}
That said, if you really would prefer to add JS calculations to manually calculate and set the size, getSnapshotBeforeUpdate() should be what you're looking for.
And if you ever switch to React functional components with hooks (standardized after React 16.8), then the hook you'd be looking for is useLayoutEffect()
Heres my component code
constructor(props) {
super(props);
this.updateView = this.updateView.bind(this);
this.state = {
screen_width: ''
}
}
componentDidMount() {
window.addEventListener("resize", this.updateView);
this.setState({
screen_width: window.innerWidth
})
}
componentDidUpdate(prevProps) {
}
updateView() {
this.setState({
screen_width: window.innerWidth
})
}
displayHome() {
console.log(this.state.screen_width);
if (this.state.screen_width >= 768) {
return (
<React.Fragment>
<HomeDesktop/>
</React.Fragment>
)
}
else {
return (
<React.Fragment>
<HomeMobile/>
</React.Fragment>
)
}
}
render() {
return (
{this.displayHome()}
)
}
My problem is that on mobile devices, its supposed to display HomeMobile initially, but it just doesn't do that. The space that HomeMobile is supposed to take up is just blank. A picture is supposed to come up, but currently ONLY after scrolling does the picture show up. Initially I thought this was based on css positioning, but when I just put within render, the picture shows up as normal. This means for some reason displayHome() is not displaying the mobile component properly on the initial render.
My code works fine on desktop, if you are above a certain width, it will show the desktop view, and under a certain width it will show the mobile view. On mobile devices, the Home components just straight up don't show up.
Has anyone ever dealt with a similar issue with mobile views on the initial render? How do I get HomeMobile to show up on the initial render?
I managed to find out what was happening. Even though the screen was small, it wasn't considered sub 768 pixels for window.innerWidth;
I changed the code to this:
displayHome() {
console.log(this.state.screen_width);
return (
<React.Fragment>
{window.screen.width >= 768 ? <HomeDesktop/> : <HomeMobile/>}
</React.Fragment>
)
}
Now the mobile part renders on the initial render. I dont know exactly why window.innerWidth and window.screen.width are different but here we are.
In mobile physical resolution can be 1920x1080 but for example 3 physical pixels can be counted as 1, so everything will not look really small, so can be tricky.
https://www.programmersought.com/article/94365499679/
Related window.innerWidth in Chrome's device mode
Better to use CSS media queries for this task, implementation in JS can change and can be differences in different browsers.
I apologize is this is a very simple task this is really my first time transitioning to react.
I have an existing application built with js/css/html that I am attempting to shift over to react. It has a full length horizontal side panel that is open by default, when the window is shrunk beyond a point it collapses with a hamburger icon, and expands again when the window is resized larger. Fairly easily done with media queries in css.
A perfect example is https://purecss.io/layouts/side-menu/ (note the side menu) This is exactly what the current app does.
I'm struggling to do this in react. I can build a side panel that is collapsible (https://reactjsexample.com/react-side-nav-component/) and mofify it for my needs, but I cannot figure out how to set it up so it collapses and expands by itself. I understand I can set it up react to use media queries, however I figured there was likely a more efficient way.
Any advice of good libraries to use, or examples would be greatly appreciated.
You could do something like this:
const Component = props => {
const [windowWidth, setWindowWidth] = useState(0)
useEffect(() => {
window.addEventListener('resize', updateWindowDimensions)
updateWindowDimensions()
return () => window.removeEventListener('resize', updateWindowDimensions)
}, [])
useEffect(() => {
if (windowWidth < 500) {
closeModal()
return
}
openModal()
}, [windowWidth])
updateWindowDimensions() {
setWindowWidth(window.innerWidth)
}
}
I'm working on a react website but new to the animation area.
I'm trying to accomplish the effect like the hero section of this page: https://stripe.com/us/customers, where there's an infinite loop of circles scrolling from right to left, each with different images and sizes.
How should I get started with this infinite loop of objects animation using React? Is there some library that I can use, or is there a react code snippet sample that I can learn from?
you may be able to accomplish this using purely CSS (check out these crazy pure CSS animation examples that you can fork https://envato.com/blog/pure-css-animation-snippets/)
But the ReactJS approach would be to create a component like.. lets say FloatingIcon
import React from 'react';
class FloatingIcon extends React.Component {
constructor(props) {
super(props);
this.state{
horizontalPosition: "0px",
verticalPosition: "0px",
imgRef: "http://blah.com/asdf",
backgroundColor: "#000000"
}
}
changePosition(horizontalPosition, verticalPosition) {
this.setState({
horizontalPosition,
verticalPosition
});
}
render() {
return (
<div>
<img
href={this.state.imgRef}
style={
{translate(this.state.horizontalPosition,
this.state.verticalPosition)},
backgroungColor:{this.state.backgroundColor}}>
</img>
</div>
);
}
}
export default FloatingIcon;
each floating icon has an image, background-color, and position in it's state. Create as many as you need for your page and store them in an array. You can change the position using the changePosition function that sets the state and updates causing the DOM to render again. Getting it to float all pretty will take some work, but if you calculate correctly and create a good position change. This will work in a React technical sense and this is a React like design for such a problem creating components to accomplish these tasks using single responsibility principles. Let me know how it goes. Hope this helps friend.
Cheers!