Get dimensions of image with React - javascript

I need to get the dimensions of an image with React. I found a library called react-measure that computes measurements of React components. It works, but I can't get it to fire when the image has loaded. I need to get it to fire when the image loads so I get accurate dimensions and not 0 x 157 or something like that.
I tried using the onLoad Image Event to detect when the image has loaded, but I didn't get satisfactory results. Essentially what I've done is when the image has loaded (handleImageLoaded() has been called), change the hasLoaded state property to true. I know for a fact that hasLoaded has been changed to true because it says so: Image Loaded: true.
What I noticed is that I can calculate the dimensions for only images that have been cached...
Here is a demo video: cl.ly/250M2g3X1k21
Is there a better, more concise way of retrieving dimensions properly with React?
Here is the code:
import React, {Component} from 'react';
import Measure from '../src/react-measure';
class AtomicImage extends Component {
constructor() {
super();
this.state = {
hasLoaded: false,
dimensions: {}
};
this.onMeasure = this.onMeasure.bind(this);
this.handleImageLoaded = this.handleImageLoaded.bind(this);
}
onMeasure(dimensions) {
this.setState({dimensions});
}
handleImageLoaded() {
this.setState({hasLoaded: true});
}
render() {
const {src} = this.props;
const {hasLoaded, dimensions} = this.state;
const {width, height} = dimensions;
return(
<div>
<p>Dimensions: {width} x {height}</p>
<p>Image Loaded: {hasLoaded ? 'true' : 'false'}</p>
<Measure onMeasure={this.onMeasure} shouldMeasure={hasLoaded === true}>
<div style={{display: 'inline-block'}}>
<img src={src} onLoad={this.handleImageLoaded}/>
</div>
</Measure>
</div>
);
}
}
export default AtomicImage;
Here is the parent code. It's not really important—just passes src to the AtomicImage element:
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import AtomicImage from './AtomicImage';
class App extends Component {
constructor() {
super();
this.state = {src: ''}
this.handleOnChange = this.handleOnChange.bind(this);
}
handleOnChange(e) {
this.setState({src: e.target.value});
}
render() {
const {src} = this.state;
return (
<div>
<div>
<input onChange={this.handleOnChange} type="text"/>
</div>
<AtomicImage src={src} />
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'));

way of retrieving dimensions
You can achieve your goal just by js: through offsetHeight, offsetWidth.
In order to get the img's dimensions, img must be visible. You can't get dimensions from a cached img.
example: https://jsbin.com/jibujocawi/1/edit?js,output
class AtomicImage extends Component {
constructor(props) {
super(props);
this.state = {dimensions: {}};
this.onImgLoad = this.onImgLoad.bind(this);
}
onImgLoad({target:img}) {
this.setState({dimensions:{height:img.offsetHeight,
width:img.offsetWidth}});
}
render(){
const {src} = this.props;
const {width, height} = this.state.dimensions;
return (<div>
dimensions width{width}, height{height}
<img onLoad={this.onImgLoad} src={src}/>
</div>
);
}
}

The accepted answer is wrong for me. I'm getting the correct result with this for the onImgLoad() method:
noteImgSize(img){
this.imgOrigH=img.nativeEvent.srcElement.naturalHeight
this.imgOrigW=img.nativeEvent.srcElement.naturalWidth
this.imgOrigRatio=this.imgOrigH/this.imgOrigW
}
Side Note, unrelated to this:
Use Mobx and never have to call setState() ever again!

Related

How to add css class based on function in React / Next js?

I'm using Next js and react visibility sensor to let me know when a div is visible on screen.
Code kinda looks like:
import VisibilitySensor from "react-visibility-sensor";
function onChange(isVisible) {
let colorstate = isVisible ? "test" : "test dark";
console.log(colorstate)
}
export default function Home() {
return (
<VisibilitySensor onChange={onChange}>
<div className={colorstate}>this is a test div.</div>
</VisibilitySensor>
);
}
Changing the div className to the {colorstate} variable doesn't work (returns undefined).
I'm fairly new to React and I tried various answers online using "this.state" methods which all didn't work.
Right now the onChange function works fine and prints the correct class name in the log, I just don't know how to associate it with the div.
Thanks.
You can use useState hook, this is how it would look like with initial className of 'test dark'
import VisibilitySensor from "react-visibility-sensor";
import {useState} from 'react'
export default function Home() {
const [colorState, setColorState] = useState('test dark')
const onChange = (isVisible) => {
isVisible ? setColorState("test") : setColorState("test dark");
}
return (
<VisibilitySensor onChange={onChange}>
<div className={colorState}>this is a test div.</div>
</VisibilitySensor>
);
}
seems your colorState variable is visible only through the onChange.
class Home extends React.Component{
constructor(props){
super(props);
this.state =
{
dark: true
}
}
test = () => {
this.setState(
{
dark: !this.state.dark
}
)
}
render(){
return(
<div className={this.state.dark ? "dark" : "white"} onClick={this.test}>
test
</div>
);
}
}
should work

How to get two or more components to listen to eachother and turn off when others turn on

I have three components in my React app, all set to grey but can turn a different color when clicked, and the idea is to have the other components change back to grey whenever one of the components turns from grey to it's chosen color. Another way to phrase it is that I want only one components to show it's color at a time.
Here is the index.js for my page.
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import Redlight from "./Components/Redlight";
import Yellowlight from "./Components/Yellowlight";
import Greenlight from "./Components/Greenlight";
// probably add "isActive" line here? need to
// figure out how to get the components to listen
// to eachother
function App() {
return (
<div className="App">
<Redlight />
<Yellowlight />
<Greenlight />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
And here is one of the components. All the components are written the exact same way, so consider this one to count as all three besides the obvious differences of color.
import React from "react";
class Redlight extends React.Component {
state = {
className: "Offlight"
};
handleClick = e => {
e.preventDefault();
console.log("The red light was clicked.");
const currentState = this.state.className;
this.setState({
className: !currentState
});
};
render() {
return (
<div
className={this.state.className ? "Offlight" : "Redlight"}
onClick={this.handleClick}
/>
);
}
}
export default Redlight;
So far all the components show up and change color on click, but it's the matter of getting them all to listen to each other that is really hanging me up.
Try moving your state to your root component, which in your case is App, then make App stateful and Redlight, Bluelight, and Greenlight stateless.
If you turn your App component into a stateful component, you can pass the state of the lights down to the children component while at the same time you can manage their states in the parent's component level.
For example, for your App.js, do something like:
import React, { Component } from "react";
import Redlight from "./Redlight";
import Yellowlight from "./Yellowlight";
import Greenlight from "./Greenlight";
import "./App.css";
class App extends Component {
state = {
red: "Offlight",
yellow: "Offlight",
green: "Offlight"
};
clickHandler = (light, e) => {
e.preventDefault();
console.log("The " + light + " light was clicked.");
const currentState = this.state[light];
const newState = {
red: "Offlight",
yellow: "Offlight",
green: "Offlight"
};
this.setState({
...newState,
[light]: !currentState
});
};
render() {
return (
<div className="App">
<Redlight light={this.state.red} clicked={this.clickHandler} />
<Yellowlight light={this.state.yellow} clicked={this.clickHandler} />
<Greenlight light={this.state.green} clicked={this.clickHandler} />
</div>
);
}
}
export default App;
If you see, the state in the parent is controlling the class name for the lights, and the clickHandler turns all of them off, them turns the clicked one on.
Your children components can be cleaner, like this:
Yellowlight.js
import React from "react";
class Yellowlight extends React.Component {
render() {
return (
<div
className={this.props.light ? "Offlight" : "Yellowlight"}
onClick={(e) => this.props.clicked('yellow', e)}
/>
);
}
}
export default Yellowlight;
Redlight.js
import React from "react";
class Redlight extends React.Component {
render() {
return (
<div
className={this.props.light ? "Offlight" : "Redlight"}
onClick={(e) => this.props.clicked('red', e)}
/>
);
}
}
export default Redlight;
Greenlight.js:
import React from "react";
class Greenlight extends React.Component {
render() {
return (
<div
className={this.props.light ? "Offlight" : "Greenlight"}
onClick={(e) => this.props.clicked('green', e)}
/>
);
}
}
export default Greenlight;
You can check the final code in this sandbox where I tried to replicate your problem: https://codesandbox.io/s/friendly-haibt-i4nck
I suppose you are looking for something like this
What you need, is to lift up the state in order to hold the selected color/light into it. For this example, I'm using the state hook instead of the class component approach. I've also created one <Light /> component as they share the same logic.
You can find inline comments of what is going on for each of the steps.
Let me know if you find any trouble reading or implementing it.
What you want to do is to lift the state up to a common ancestor. You can create a common ancestor to host the information that all 3 light components need. You can pass a boolean isLightOn to tell the component to switch on or off. You will also pass an event handler to allow a light component to set the light color.
const LightContainer = () => {
const [lightColor, setLightColor] = useState('');
const RED_COLOR = 'RED';
const YELLOW_COLOR = 'YELLOW';
const GREEN_COLOR = 'GREEN';
return (
<div>
<Light className="Redlight" isLightOn={lightColor === RED_COLOR} onLightSwitch={() => { setLightColor(RED_COLOR); }} />
<Light className="Yellowlight" isLightOn={lightColor === YELLOW_COLOR} onLightSwitch={() => { setLightColor(YELLOW_COLOR); }} />
<Light className="Greenlight" isLightOn={lightColor === GREEN_COLOR} onLightSwitch={() => { setLightColor(GREEN_COLOR); }} />
</div>
);
};
On your light components, we can also make it more generic since we move most of the logic to the parent component.
const Light = ({ isLightOn, onLightSwitch, className }) => {
return (
<div
className={isLightOn ? 'Offlight' : className}
onClick={onLightSwitch}
/>
);
};

How can i render a React JS component directly & without a Target container

I would like to render a component directly without a Target container, i need to get the content of the component right in the place where i put the generated JS file.
Here is my index.js file, i have removed the "document.getElementById('target-container')" is there a way to render this component without a target container or a way to append a target container without inserting it in the HTML template file.
var React = require('react');
var ReactDOM = require('react-dom');
import axios from 'axios';
import '../node_modules/cleanslate/cleanslate.css';
import './style.scss';
class Widget extends React.Component {
constructor(props){
super(props);
this.state = {}
}
componentDidMount() {
let id = this.props.id;
axios.get(`https://api.com/${id}`)
.then((res) => {
const brand = res.data;
this.setState({
rating: brand.rating,
logo : brand.logo,
name: brand.name,
stars: brand.stars,
url: brand.url
});
});
}
render() {
return (
<div className="cleanslate">
<a href={this.state.url} target="_blank">
<img src="https://img/.svg" className="" alt="" />
<div className="rating-box">
<img src={this.state.logo} className="logo" alt={this.state.name} />
<span className="note">{this.state.rating}/10</span>
<div id="Selector" className={`selected-${this.state.stars}`}></div>
</div>
</a>
</div>
)
}
}
ReactDOM.render(
<Widget id="7182" />
)
Here is an example (https://github.com/seriousben/embeddable-react-widget) of appending the component in another one :
import React from 'react';
import ReactDOM from 'react-dom';
import Widget from '../components/widget';
import '../../vendor/cleanslate.css';
export default class EmbeddableWidget {
static el;
static mount({ parentElement = null, ...props } = {}) {
const component = <Widget {...props} />;
function doRender() {
if (EmbeddableWidget.el) {
throw new Error('EmbeddableWidget is already mounted, unmount first');
}
const el = document.createElement('div');
el.setAttribute('class', 'cleanslate');
if (parentElement) {
document.querySelector(parentElement).appendChild(el);
} else {
document.body.appendChild(el);
}
ReactDOM.render(
component,
el,
);
EmbeddableWidget.el = el;
}
if (document.readyState === 'complete') {
doRender();
} else {
window.addEventListener('load', () => {
doRender();
});
}
}
static unmount() {
if (!EmbeddableWidget.el) {
throw new Error('EmbeddableWidget is not mounted, mount first');
}
ReactDOM.unmountComponentAtNode(EmbeddableWidget.el);
EmbeddableWidget.el.parentNode.removeChild(EmbeddableWidget.el);
EmbeddableWidget.el = null;
}
}
You could generate a container for your app with JS before calling ReactDOM.render (for instance with appendChild as described here) and then call ReactDOM.render passing just generated element as container.
UPD:
Even though it feels strange, you actually can get the script tag of your bundle before ReactDOM.render is called.
Knowing this, you could do something like:
// Create a container dynamically
const appContainer = document.createElement('div');
// Get all <script>s on the page and put them into an array.
const scriptTags = Array.from(document.scripts);
// Filter scripts to find the one we need.
const targetScript = scriptTags.filter(
scriptTag => scriptTag.src === 'https://example.com/bundle.js'
);
// Uh oh, we got either too many or too few,
// it might be bad, better stop right here.
if (targetScript.length !== 1) {
return;
}
// Inserts app container before the script.
document.body.insertBefore(appContainer, targetScript[0]);
// Renders things inside the container
ReactDOM.render(
<MyComponent />,
appContainer
);

ReactJS how to render a component only when scroll down and reach it on the page?

I have a react component Data which includes several charts components; BarChart LineChart ...etc.
When Data component starts rendering, it takes a while till receiving the data required for each chart from APIs, then it starts to respond and render all the charts components.
What I need is, to start rendering each chart only when I scroll down and reach it on the page.
Is there any way could help me achieving this??
You have at least three options how to do that:
Track if component is in viewport (visible to user). And then render it. You can use this HOC https://github.com/roderickhsiao/react-in-viewport
Track ‘y’ scroll position explicitly with https://react-fns.netlify.com/docs/en/api.html#scroll
Write your own HOC using Intersection Observer API https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
To render component you may need another HOC, which will return Chart component or ‘null’ based on props it receives.
I have tried many libraries but couldn't find something that best suited my needs so i wrote a custom hook for that, I hope it helps
import { useState, useEffect } from "react";
const OPTIONS = {
root: null,
rootMargin: "0px 0px 0px 0px",
threshold: 0,
};
const useIsVisible = (elementRef) => {
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
if (elementRef.current) {
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setIsVisible(true);
observer.unobserve(elementRef.current);
}
});
}, OPTIONS);
observer.observe(elementRef.current);
}
}, [elementRef]);
return isVisible;
};
export default useIsVisible;
and then you can use the hook as follows :
import React, { useRef } from "react";
import useVisible from "../../hooks/useIsVisible";
function Deals() {
const elemRef = useRef();
const isVisible = useVisible(elemRef);
return (
<div ref={elemRef}>hello {isVisible && console.log("visible")}</div>
)}
I think the easiest way to do this in React is using react-intersection-observer.
Example:
import { useInView } from 'react-intersection-observer';
const Component = () => {
const { ref, inView, entry } = useInView({
/* Optional options */
threshold: 0,
});
useEffect(()=>{
//do something here when inView is true
}, [inView])
return (
<div ref={ref}>
<h2>{`Header inside viewport ${inView}.`}</h2>
</div>
);
};
I also reccommend using triggerOnce: true in the options object so the effect only happens the first time the user scrolls to it.
you can check window scroll position and if the scroll position is near your div - show it.
To do that you can use simple react render conditions.
import React, {Component} from 'react';
import PropTypes from 'prop-types';
class MyComponent extends Component {
constructor(props){
super(props);
this.state = {
elementToScroll1: false,
elementToScroll2: false,
}
this.firstElement = React.createRef();
this.secondElement = React.createRef();
}
componentDidMount() {
window.addEventListener('scroll', this.handleScroll);
}
componentWillUnmount() {
window.removeEventListener('scroll', this.handleScroll);
}
handleScroll(e){
//check if scroll position is near to your elements and set state {elementToScroll1: true}
//check if scroll position is under to your elements and set state {elementToScroll1: false}
}
render() {
return (
<div>
<div ref={this.firstElement} className={`elementToScroll1`}>
{this.state.elementToScroll1 && <div>First element</div>}
</div>
<div ref={this.secondElement} className={`elementToScroll2`}>
{this.state.elementToScroll2 && <div>Second element</div>}
</div>
</div>
);
}
}
MyComponent.propTypes = {};
export default MyComponent;
this may help you, it's just a quick solution. It will generate you some rerender actions, so be aware.

When importing styles into a React component, how do I assign a className that updates based off state?

I'm using style-loader to inject css modularly into my components ({style.exampleClassName}).
I want to display a loader for a set amount of time then display an image (at least 16 of these components in a grid pattern).
My current component looks like this:
// Child Component
/**
*
* Sets up props for the child (icon)
*
*/
import React, { Component } from 'react';
import styles from './styles.css';
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {
hidden : "shown",
loading: "loading"
};
}
componentDidMount() {
const self = this;
const wait = this.props.wait;
console.log('mounted');
setTimeout(() => {
console.log('timeout working ' + wait);
self.setState({
hidden: "hidden",
loading: "loaded"
});
}, wait);
}
render() {
const hidden = `styles.${this.state.hidden}`;
const loading = `styles.${this.state.loading}`;
return (
<div>
<link rel="stylesheet" type="text/css" href="./app/components/socialgrid/styles.css" />
<div className={this.state.hidden}>
<p>Loading...</p>
</div>
<div className={this.state.loading}>
<p>Child - {this.props.wait}ms</p>
</div>
</div>
)
}
};
export default Child;
// Parent
/**
*
* Individual Icon Component
*
*/
import React, { Component } from 'react';
import cx from 'classnames';
import Img from 'components/Img';
import Child from './Child';
// import Fb from './Fb.png';
class IndIcon extends React.Component{
render() {
return (
<div>
<div>
<Child wait={1000} />
<Child wait={5000} />
<Child wait={4000} />
</div>
</div>
)
}
};
export default IndIcon;
.hidden,
.loading{
display: none;
}
Normally my styles would inject themselves by className={styles.exampleClassName} but here I'm running into the issue of the class not being injected because the class changes based of state (as I said above, just trying different wording to be clear).
I want to assign more than just the display:none element so I do need classes on these components.
Help would be appreciated. Thanks!
You were not too far off... you just weren't passing your const variables into your JSX. Try modifying your render function as follows (amendments highlighted with bold):
render() {
const hidden = `styles.${this.state.hidden}`;
const loading = `styles.${this.state.loading}`;
return (
<div>
<link rel="stylesheet" type="text/css" href="./app/components/socialgrid/styles.css" />
<div className={hidden}>
<p>Loading...</p>
</div>
<div className={loading}>
<p>Child - {this.props.wait}ms
</div>
</div>
)
}
N.B.
Including your styles within the component in this way pollutes the global CSS namespace which can cause problems if you have styles if the same name defined elsewhere in the application ( by yourself or by another developer) which can give rise to unpredictable style behaviour
Even though I really wanted to update those classes on state change, I went this route instead and its way better:
import React, { Component } from 'react';
import Spinner from './Spinner';
import Icon from './Icon';
class Child extends React.Component {
constructor(props){
super(props);
this.state = {
loading: true
}
}
componentDidMount(){
const wait = this.props.wait;
setTimeout(() => {
this.setState({
loading: false
})
}, wait)
}
render() {
let content = this.state.loading ?
<div><Spinner /></div> :
<div><Icon /></div>;
return (
<div>{content}</div>
)
}
};
This way it loads components based off state change and a settimeout.

Categories