React: Get DOM data from sibling elements - javascript

(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()

Related

Conditionally Rendering Markup (JSX) vs. CSS `display: none` - Which is better practice?

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)

How to get Xterm.js resize properly?

tl;dr
I've created a React wrapper to render an array of log messages into a terminal but resizing is giving a weird output (see screenshot). (There is a React-Wrapper on NPM but that wasn't working for my use-case - caused screen flickering)
I'm working on a feature for Guppy where I'm adding Xterm.js for the terminal output.
The PR can be found here.
I've added xterm because of hyperlink scanning/parsing and that is working.
But I'm stuck with getting resize to work. If I'm starting the devServer in the app and wait for some text it will display with correct letter width.
If I reduce the size I'm getting an output with an incorrect letter width.
Like in the following screenshot:
It is always looking correct in the not resized state but after resize it will get the wrong display - so this will happen for enlarging & shrinking the screen width.
The output should look similar to the following screenshot (maybe with some wrapped lines):
I think this is caused by Fit addon or the way I'm handling resizing with the resize observer but I'm not sure.
The span style of xterm letter are getting a width of NaNpx like in the following screenshot:
Is this caused by a media query I'm using? I haven't seen that yet maybe I have to temporarily disable all media queries to see if that's causing the behaviour.
What I have tried so far:
Wrapped this.xterm.fit() into a setTimeout(func, 0) but with-out an effect
Modified some of the styles I'm using but I haven't found the cause.
Code
The code I'm using can be found on Github branch feature-terminal-links but here I'd like to extract the parts I've added to get Xterm to work with React:
I created a styled-component XtermContainer as a div so I can add Xterm styles and own styling. The following code is inside render and will be our xterm.js container (innerRef will be used later in ComponentDidMount to intialize Xterm with that container):
<XtermContainer
width={width}
height={height}
innerRef={node => (this.node = node)}
/>
Init xterm in componentDidMount with the container above:
componentDidMount() {
Terminal.applyAddon(webLinks);
Terminal.applyAddon(localLinks);
Terminal.applyAddon(fit);
this.xterm = new Terminal({
convertEol: true,
fontFamily: `'Fira Mono', monospace`,
fontSize: 15,
rendererType: 'dom', // default is canvas
});
this.xterm.setOption('theme', {
background: COLORS.blue[900],
foreground: COLORS.white,
});
this.xterm.open(this.node);
this.xterm.fit();
/* ... some addon setup code here (not relevant for the problem) ... */
}
Added react-resize-observer inside of the wrapper that is also containing the terminal container so I can trigger this.xterm.fit() if the size changes (in the repo there is a setTimeout wrapper around for testing).
<ResizeObserver onResize={() => this.xterm && this.xterm.fit()} />
Using componentDidUpdate(prevProps, prevState) to update the terminal and scroll the terminal to the bottom if the component is getting new logs:
componentDidUpdate(prevProps, prevState) {
if (prevProps.task.logs !== this.state.logs) {
if (this.state.logs.length === 0) {
this.xterm.clear();
}
for (const log of this.state.logs) {
/*
We need to track what we have added to xterm - feels hacky but it's working.
`this.xterm.clear()` and re-render everything caused screen flicker that's why I decided to not use it.
Todo: Check if there is a react-xterm wrapper that is not using xterm.clear or
create a wrapper component that can render the logs array (with-out flicker).
*/
if (!this.renderedLogs[log.id]) {
this.writeln(log.text);
this.xterm.scrollToBottom();
this.renderedLogs[log.id] = true;
}
}
}
}
Ideas I have to find the cause:
Check ResizeObserver code. (see update below)
Try to find why xterm css is getting a NaN width. Is Xterm.js using the style width of the container? If yes, maybe that's not correctly set.
Update
OK, the resize obeserver is probably not needed as I'm getting the same behaviour after commenting out the <ResizeObserver/> in render. So I think it's caused by xterm.js or the css in Guppy.
I have a fix for the issue. It's now working in the above mentioned feature branch. Not sure if there is a better solution but it's working for me.
I like to explain how I have fixed the resizing issue:
The problem was the OnlyOn component that was used in DevelopmentServerPane. It always rendered two TerminalOutput components. One terminal was hidden with display: none and the other was displayed with display: inline - the style change was handled with a media query inside a styled-component.
After replacing OnlyOn with React-responsive and using the render props to check mdMin breakpoint it was working as expected. React-responsive is removing the not displayed mediaquery component from DOM so only one terminal in DOM at the same time.
I still don't know why there was a problem with the letter width but probably the two instances collided somehow. I couldn't create a minimal reproduction. I tried to recreate the issue in this Codesandbox but I have only resized one Terminal at a time and so I haven't got the issue there.
The code that fixed the problem (simplified version from the above mentioned repo):
import MediaQuery from 'react-responsive';
const BREAKPOINT_SIZES = {
sm: 900,
};
const BREAKPOINTS = {
mdMin: `(min-width: ${BREAKPOINT_SIZES.sm + 1}px)`,
};
const DevelopmentServerPane = () => (
<MediaQuery query={BREAKPOINTS['mdMin']}>
{matches =>
matches ? (
<div>{/* ... render Terminal for matching mdMin and above */}</div>
) : (
<div> {/* ... render Terminal for small screens */}</div>
)
}
</MediaQuery>
);

Looping bubbles animation

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!

How to dynamically resize a PixiJS (HTML) canvas within angular component

I've written an angular component which creates and renders (renders a single time) a Pixi canvas.
Here is the component (excuse the bad name I'm still new to angular):
import { Component, OnInit } from '#angular/core';
declare var PIXI: any;
declare var $: any;
#Component({
selector: 'pixi-component',
templateUrl: './pixi-component.component.html',
styleUrls: ['./pixi-component.component.css']
})
export class PixiComponentComponent implements OnInit {
constructor() { }
ngOnInit() {
this.generatePixiCanvas();
console.log("pixi component is created");
}
generatePixiCanvas() {
//Create the canvas and add it the DOM...
var renderer = PIXI.autoDetectRenderer(this.getParentDivWidth(), this.getParentDivHeight());
document.getElementById('pixi-canvas-container').appendChild(renderer.view);
renderer.autoResize = true;
//Create a container object called the `stage` and render it...
var stage = new PIXI.Container();
renderer.render(stage);
}
getParentDivHeight() {
return $('#pixi-canvas-container').height();
}
getParentDivWidth() {
return $('#pixi-canvas-container').width() + 1;
}
}
You can see that this component creates the pixi canvas within the ngOnInit function, but it creates it with a set height and width (returned from the stub helper functions).
What I want to do is two-fold:
On page load, create the pixi canvas so that it's size is EQUAL to the size of its parent container. Similar to what CSS {height: 100%; width: 100%} would do.
On window resize, the pixi canvas dynamically resizes with the rest of the webpage, like what a CSS flex box would do.
My original idea was to use those two stub functions getParentDivHeight and getParentDivWidth to deal with the first problem, but I was unable to find an angular-y way of doing this. I think I might be able to use JQuery?
As for the second problem, I'm not sure how to get an angular component to listen to window resize events.
How can I solve this problem?
Also, here's the HTML and CSS code for the above component, in case it helps:
<div id="pixi-canvas-container">
</div>
#pixi-canvas-container {
height: 100%;
width: 100%;
}
EDIT:
After a little bit of research, it seems that a custom directive to watch window resizes could do the trick. Would I need to write a custom directive to somehow resize the pixi canvas? The problem with this would be that the directive would need to be attached to the pixi canvas when it is created (at runtime) which may not be possible with angular.
EDIT2:
Okay, I've managed to solve problem 1 using JQuery. I've gone ahead and edited my code to reflect this. Problem 2 still remains as the pixi canvas does not respond to page resizes or resolution changes.
What has been done to solve the first problem seems to be working fine for now, so I won't address that here.
The best way to solve problem two is to attach a method to angular's window:resize event. This method will recalculate the size of the canvas' parent contain and then resize it - you can use pixi's built in .resize() method.
Here's the code you'll need:
adjustCanvasSize(){
this.renderer.resize(this.getParentDivWidth(), this.getParentDivHeight());
}
And in the HTML template:
<div (window:resize)="adjustCanvasSize()" id="pixi-canvas-container">
</div>
Easy!
Would you like to try render.autosize property?
Source:
Github source
Line: 61

Coming from jQuery, how does one do DOM animations in React.js? Such as animate a progress Bar?

I have been using React for a couple months now and I came from using a lot of jQuery for DOM animations/manipulation. The manipulations I can handle pretty well but animating things and doing shiny UI stuff seems tricky in React. Or at least, not as easy as jQuery. I don't want to pull jQuery in for animations.
Some examples I am trying to achieve:
User scrolls down to my skills section of my Personal Portfolio Page where there are some Bootstrap progress bars. How would I animate these bars to 'fill up' once the user scrolls to them. I imagine something with detecting position? I could also build my own progress bars so as to have more control.
When the user resizes my portfolio section that has a flexbox setup with images that represent my projects, I want the images to reposition themselves slowly, like a transition and not snap to their new spot.
Or something simple like have something slide in from the left/right?
I think jQuery spoiled me! I just feel like I don't have as much as control in React. I have looked at Transition Groups but that seems pretty limited.
Here is my code for the progress bar question...
import React from 'react';
export default (props) => {
const width = `${props.lvl}%`
// setup diff colors for bars
let backgroundColor = (function(lvl){
if (lvl <=35) {
return '#ffa64d'
}
else if (lvl >= 36 && lvl <= 49) {
return '#ffff66'
}
else {
return '#47d147'
}
}(props.lvl))
return (
<div>
<h4 style={{display: 'inline'}}>{props.name}</h4>
<div className="progress">
<div className='progress-bar' role="progressbar" aria-valuenow="70"
aria-valuemin="0" aria-valuemax="100" style={{width, backgroundColor}}>
</div>
</div>
</div>
)
}
I think most people have gotten their animations done through CSS transitions; it's by far the easiest way. However, I have written a custom module that provides functionality that animates elements through manipulation of inline styles stored as part of component state. You can find it here. You'll have to judge for yourself whether the hassle is worth the functionality.

Categories

Resources