source issue with my image element and React - javascript

Alright hey guys. Trying to get this clock functioning with the pictures. I have more than just the one but if I can get the one figured out I can handle the others. After much searching I found out you can't use string interpolation within an attribute but I kept it there to show I tried. The second one I think "should work" and it shows the variable i'm trying or pic0, pic1 and so on but it's a string not the actual module i'm importing at the top (in the actual app I have them all imported but cut them down for brevity on the question. Any assistance?? I'm bashing my head what is causing this issue
import React, { Component } from 'react';
import './App.css';
import './bootstrap-3.3.7-dist/css/bootstrap.min.css';
import pic0 from './0.png';
export default class Clock extends Component {
constructor(props) {
super(props);
this.incrementTime = this.incrementTime.bind(this);
this.dayInWords = this.dayInWords.bind(this);
this.state = {
clock: new Date(),
day: (new Date()).getDay(),
hours0: 0,
minutes0: 0,
seconds0: 0,
hours1: 0,
minutes1: 0,
seconds1: 0
}
}
componentWillMount() {
let intervalTimer = setInterval(this.incrementTime, 1000);
}
componentWillUnmount() {
clearInterval(this.state.intervalTimer);
}
incrementTime() {
this.setState(currentState => {
return {
clock: new Date(),
seconds1: ((new Date()).getSeconds())%10
};
});
}
render() {
return(
<div className='panel-body imgContainer'>
<img src={pic0} alt='the digit 0' />
<p>Know this one doesnt work but kept here to show I tried it</p>
<img src={`pic${this.state.seconds1}`} alt='seconds1' />
<p>I think this should work and the src is what I want as the variable that im importing at the top but its just a string not the variable.</p>
<img src={'pic' + this.state.seconds1} alt='seconds1' />
<p>This is the end goal im trying to accomplish but im trying to see if it can be done using this.state.seconds so its kind of "dynamic" and the source changes every second thus changing the image</p>
<img src={pic0} alt='seconds1' />
</div>
);
}
}
this is what i'm seeing it's the image not showing the src is the "variable" im importing at the top but it's not actually calling that variable/module sorry if my terminology is wrong i'm clearly pretty new at this.
Thanks for all the assistance!!!!

Although this was clarified in the comments, for future visitors of this post - you need to reference the imported image directly:
import pic from './myImage';
// works
<img src={ pic } />
It can be placed in the state, or some other variable, but can't be accessed via a string with the same name:
// Doesn't work
<img pic={ 'pic' } />

Related

How to preload image in a React JS component?

I'm currently rendering a child component when a signInError occurs. The signInError is stored in the parent component and if it's not null, it renders the <SignInError/> component, as per the code below:
ParentComponent.js
// Some code...
render() {
return(
<div>
<SignInForm
doSignIn={this.doSignIn}
resetSignInError={this.resetSignInError}
signInError={this.state.signInError}
/>
{this.state.signInError && <SignInError/>}
</div>
);
}
So far, so good, here's the child component SignInError.js
import React from 'react';
import RoundImage from '../../../UI/Common/RoundImage';
import Newman from '../../../../assets/newman-min.png';
class SignInError extends React.Component {
constructor(props){
super(props);
}
componentDidMount(){
const img = new Image();
img.src = Newman;
}
render(){
return(
<div>
<div>
<RoundImage src={img.src}/> // <--- img is undefined here!!!
</div>
<div>
Hello... Newman!
</div>
</div>
);
}
}
export default SignInError;
RoundImage.js
import React from 'react';
const RoundImage = (props) => {
return (
<div>
<img src={props.src}/>
</div>
);
}
export default RoundImage;
How to preload images in React.js?
This question's answer (link above) here on Stack Over flow tells me to create the img object inside the componentDidMount() method to force the browser to load it. And so I did, as you can see from the code above. But now, when I try to pass it as a prop to my grand-child component inside my render method, I can't access the img, because it was defined inside of another method.
What's the best way around this? I just need the image to be loaded and to be displayed together with the error message. Otherwise the error message will show before the image, if your browser hasn't cached it yet. Thanks for the help.
Image download happens in the browser. Rendering to the DOM also happens in the browser.
By preloading, do you mean that you want that the component renders only when the image is ready?
If so, you could do something like this:
componentDidMount() {
const img = new Image();
img.onload = () => {
// when it finishes loading, update the component state
this.setState({ imageIsReady: true });
}
img.src = Newman; // by setting an src, you trigger browser download
}
render() {
const { imageIsReady } = this.state;
if (!imageIsReady) {
return <div>Loading image...</div>; // or just return null if you want nothing to be rendered.
} else {
return <img src={Newman} /> // along with your error message here
}
}
A bit of a different approach, but if you have the image source in advance, you can add the image source to the main html file as a link ref with preload option. The browser will preload the image (with relatively low priority) and at the time your app will load the image, it should be cached in the browser memory.
<head>
..
..
<link rel="preload" href="<your_image_source_here>" as="image">
...
</head>
In this approach, we separate the preloading process from the code. It is more relevant when you have the image source in advance (and not dynamically) and when you don't need to cache a large amount of images (will be a bit messy to add a large list of links in the html head, although possible)
you can learn more about link preloading here: Preloading content with rel="preload"
In my case I start with an initial src attribute for my images and wanted to delay changing them after the image has been loaded by the browser, so I wrote the following hook in Typescript:
import { useEffect, useState } from 'react';
export const useImageLoader = (initialSrc: string, currentSrc: string) => {
const [imageSrc, _setImageSrc] = useState(initialSrc);
useEffect(() => {
const img = new Image();
img.onload = () => {
_setImageSrc(currentSrc);
};
img.src = currentSrc;
}, [currentSrc]);
return [imageSrc];
};

React - Dynamic image rendering stutters when bundled/live

I have an issue with a component that takes a name property for use as an avatar (replaces spaces with dashes and adds file format). This then gets used as the img src dynamically.
Works absolutely fine locally, no stuttering changing the image. When I bundle and deploy to surge.sh and change routes the image stutters. I expected this as each image needs to be downloaded initially but even after the image is cached, swapping the image is jerky. Any ideas? Or is there a better way to do this?
class Athlete extends Component {
constructor (props) {
super(props);
}
render () {
let name = this.props.name;
let imgName = name.replace(/\s+/g, '-').toLowerCase();
let imgSrc = '/img/athletes/' + imgName + '.jpg';
return (
<div className="winner__inner">
<img src={imgSrc} alt={name} className="winner__avatar"/>
<h3 className="winner__name">{name}</h3>
</div>
);
}
}
export default Athlete;
Link: http://boulder.surge.sh/ (desktop only - click one of the events)
GIF: https://i.gyazo.com/4dea5302d9671b4de0fadc2334f872da.gif
Cached images: https://i.imgur.com/xQLgIWq.png
Any help would be great, thanks!

React updating DOM in an unpredictable manner with setState and class names

I'm attempting to do an animation with React and CSS classes. I have created a live demo, if you visit it and click the Start button you will see the text fade in and up one by one. This is the desired animation that I am after.
However, there seems to be issues of consistency when you hit Start multiple times and I cannot pinpoint why.
The Issue: Below is a recording of the issue, you can see the number 1 is not behaving as expected.
live demo
The process: Clicking Start will cancel any previous requestAnimationFrame' and will reset the state to it's initial form. It then calls the showSegments() function with a clean state that has no classNames attached to it.
This function then maps through the state adding a isActive to each segment in the state. We then render out the dom with a map and apply the new state.
This should create a smooth segmented animation as each class gets dropped one by one. However when i test this in Chrome (Version 56.0.2924.87 (64-bit)) and also on iOS, it is very inconsistent, sometimes it works perfectly, other times the first DOM element won't animate, it will just stay in up and visible it's completed transitioned state with "isActive".
I tried to replicate this issue in safari but it worked perfectly fine, I'm quite new to react so i am not sure if this is the best way to go about things, hopefully someone can offer some insight as to why this is behaving quite erratic!
/* MotionText.js */
import React, { Component } from 'react';
import shortid from 'shortid';
class MotionText extends Component {
constructor(props) {
super(props);
this.showSegments = this.showSegments.bind(this);
this.handleClickStart = this.handleClickStart.bind(this);
this.handleClickStop = this.handleClickStop.bind(this);
this.initialState = () => { return {
curIndex: 0,
textSegments: [
...'123456789123456789123456789123456789'
].map(segment => ({
segment,
id: shortid.generate(),
className: null
}))
}};
this.state = this.initialState();
}
handleClickStop() {
cancelAnimationFrame(this.rafId);
}
handleClickStart(){
cancelAnimationFrame(this.rafId);
this.setState(this.initialState(), () => {
this.rafId = requestAnimationFrame(this.showSegments);
});
}
showSegments() {
this.rafId = requestAnimationFrame(this.showSegments);
const newState = Object.assign({}, this.state);
newState.textSegments[this.state.curIndex].className = 'isActive';
this.setState(
{
...newState,
curIndex: this.state.curIndex + 1
},
() => {
if (this.state.curIndex >= this.state.textSegments.length) {
cancelAnimationFrame(this.rafId);
}
}
);
}
render(){
const innerTree = this.state.textSegments.map((obj, key) => (
<span key={obj.id} className={obj.className}>{obj.segment}</span>
));
return (
<div>
<button onClick={this.handleClickStart}>Start</button>
<button onClick={this.handleClickStop}>Stop</button>
<hr />
<div className="MotionText">{innerTree}..</div>
</div>
)
}
}
export default MotionText;
Thank you for your time, If there any questions please ask
WebpackBin Demo
Changing the method to something like this works
render(){
let d = new Date();
const innerTree = this.state.textSegments.map((obj, key) => (
<span key={d.getMilliseconds() + obj.id} className={obj.className}>{obj.segment}</span>
));
return (
<div>
<button onClick={this.handleClickStart}>Start</button>
<button onClick={this.handleClickStop}>Stop</button>
<hr />
<div className="MotionText">{innerTree}..</div>
</div>
)
}
How this helps is that, the key becomes different than previously assigned key to first span being rendered. Any way by which you can make the key different than previous will help you have this animation. Otherwise React will not render it again and hence you will never see this in animation.

Add fallback src to image on React during server fail (S3)

I have a React + Rails app and the current S3 server issues made me realize that I don't have a proper fallback mechanism when data can't be pulled from s3. I'm trying to make it work with my locally stored images for the moment and I plan on adding it the same way to my other image tags.
My img is as follows:
errorLink() {
this.onError = null;
this.src = '/img/icons/static/credentials.svg';
}
<img alt="Icon for credentials and experience" src="/img/icons/static/credentials123.svg" onError={this.errorLink.bind(this)()}/>
the src inside the image pointing to credentials123.svg is a dummy and I've added it specifically to raise the error. But it's not updating the src of my image. How can I achieve this in react? I'd rather not show broken image links to my users next time during an Amazon outage
One of the many many solutions (since it is really based on your inner React project structure). You just keep your image url in a state somewhere as a default image. Once you get your proper image url from S3 then you will replace default one in state with a new one you got.
const DEFAULT_IMAGE = '/img/icons/static/default.svg';
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
url: DEFAULT_IMAGE
};
}
render() {
return (
{/* ... some UI here ... */}
<img
alt="Icon for credentials and experience"
src={this.state.url}
/>
);
}
// ....
_someAsync = () => {
// some async logic here
// In here in 5 seconds state will be updated by replacing default url
// with new url image link
setTimeout(() => this.setState({
loaded: true,
url: '/img/icons/static/credentials.svg'
}), 5000);
};
}
One simple way to do it is
Make sure you put/set the value this.state = {image: 'image-to-load.jpeg'} in render.
<img src={this.state.image} ref={image => this.image = image} onError={() => this.image.src = '/alternate-failover.jpg'
It worked for me. Small, precise, and reliable code
Ref: https://stackoverflow.com/a/42848501/10231115

How to combine JSX component with dangerouslySetInnerHTML

I'm displaying text that was stored in the database. The data is coming from firebase as a string (with newline breaks included). To make it display as HTML, I originally did the following:
<p className="term-definition"
dangerouslySetInnerHTML={{__html: (definition.definition) ? definition.definition.replace(/(?:\r\n|\r|\n)/g, '<br />') : ''}}></p>
This worked great. However there's one additional feature. Users can type [word] and that word will become linked. In order to accomplish this, I created the following function:
parseDefinitionText(text){
text = text.replace(/(?:\r\n|\r|\n)/g, '<br />');
text = text.replace(/\[([A-Za-z0-9'\-\s]+)\]/, function(match, word){
// Convert it to a permalink
return (<Link to={'/terms/' + this.permalink(word) + '/1'}>{word}</Link>);
}.bind(this));
return text;
},
I left out the this.permalink method as it's not relevant. As you can see, I'm attempting to return a <Link> component that was imported from react-router.However since it's raw HTML, dangerouslySetInnerHTML no longer works properly.
So I'm kind of stuck at this point. What can I do to both format the inner text and also create a link?
You could split the text into an array of Links + strings like so:
import {Link} from 'react-router';
const paragraphWithLinks = ({markdown}) => {
const linkRegex = /\[([\w\s-']+)\]/g;
const children = _.chain(
markdown.split(linkRegex) // get the text between links
).zip(
markdown.match(linkRegex).map( // get the links
word => <Link to={`/terms/${permalink(word)}/1`}>{word}</Link> // and convert them
)
).flatten().thru( // merge them
v => v.slice(0, -1) // remove the last element (undefined b/c arrays are different sizes)
).value();
return <p className='term-definition'>{children}</p>;
};
The best thing about this approach is removing the need to use dangerouslySetInnerHTML. Using it is generally an extremely bad idea as you're potentially creating an XSS vulnerability. That may enable hackers to, for example, steal login credentials from your users.
In most cases you do not need to use dangerouslySetHTML. The obvious exception is for integration w/ a 3rd party library, which should still be considered carefully.
I ran into a similar situation, however the accepted solution wasn't a viable option for me.
I got this working with react-dom in a fairly crude way. I set the component up to listen for click events and if the click had the class of react-router-link. When this happened, if the item has a data-url property set it uses browserHistory.push. I'm currently using an isomorphic app, and these click events don't make sense for the server generation, so I only set these events conditionally.
Here's the code I used:
import React from 'react';
import _ from 'lodash';
import { browserHistory } from 'react-router'
export default class PostBody extends React.Component {
componentDidMount() {
if(! global.__SERVER__) {
this.listener = this.handleClick.bind(this);
window.addEventListener('click', this.listener);
}
}
componentDidUnmount() {
if(! global.__SERVER__) {
window.removeEventListener("scroll", this.listener);
}
}
handleClick(e) {
if(_.includes(e.target.classList, "react-router-link")) {
window.removeEventListener("click", this.listener);
browserHistory.push(e.target.getAttribute("data-url"));
}
}
render() {
function createMarkup(html) { return {__html: html}; };
return (
<div className="col-xs-10 col-xs-offset-1 col-md-6 col-md-offset-3 col-lg-8 col-lg-offset-2 post-body">
<div dangerouslySetInnerHTML={createMarkup(this.props.postBody)} />
</div>
);
}
}
Hope this helps out!

Categories