I am making a small application in React that fetches a random image using Axios. I am using React-bootstrap to style the image, however a small white box is displayed for half of a second before the image is done loading. How can I resolve this?
This is my code:
import React, { Component } from 'react';
import axios from 'axios';
import { Link } from 'react-router-dom';
import { Image, Button } from 'react-bootstrap';
const ROOT_URL = 'myurl'
export default class WhatDog extends Component {
constructor(props){
super(props);
this.state = { randomImg: '' };
}
componentDidMount(){
axios.get(ROOT_URL)
.then(res => {
const data = res.data.message
this.setState({ randomImg: data })
})
}
renderImage(){
return(
<div>
<Image src={this.state.randomImg} className="img" thumbnail/>
<Link to="/">
<Button bsStyle="danger" bsSize="large">Go back</Button>
</Link>
</div>
)
}
render() {
return (
<div className="App">
{
(this.state.randomImg === '')
? <div>
<h1>Loading...</h1>
</div>
: <div>
{this.renderImage()}
</div>
}
</div>
);
}
}
The browser will fire the onLoad event, after the image has been, you name it.. loaded so before that, set the visibility to hidden.. NOT display: none ! Because display: none will also prevent the loading.
The solution might look something like this
<Image
src={this.state.randomImg}
style={!this.state.imgVisible ? {visibility: 'hidden'} : {}}
onLoad={() => this.setState({ imgVisible: true })}
/>
Note: This is using inline styles and arrow functions, which is not best, but for simplicity of the demo its enough, you could also you a className instead, its up to you ;)
You got the idea...
Related
let me explain my question.
I would like to create expanding flex cards, here is the exemple on codepen : https://codepen.io/z-/pen/OBPJKK
and here is my code for each button :
basically I have a component which is called HomeButtons that generates every flex cards. Inside this component I have a smaller component called readMore. In this component I have a useState that allows me to toggle individually each button to add or retreive an active class. If the active class is present, that means that the selected button must expand and the other ones must shrink.
What I would like to do is to access the readMore state ouside of the readMore subcomponent. That way I could write a function to remove the active class from a card if the user clicks on another card like so :
function setToUnactive() {
if (readMore(true)) {
readMore(false)}
}
My question is how can I get the state of readMore outside of the readMore subcomponent ? Do I need to use useContext ? Because that seems very simple to do but I tried a lot of things and nothing works. Can I pass the state readMore as a prop of the component ReadMore ? Thank you !
import React, { useState } from 'react';
import '../style/catalogue.scss';
import collectionsItems from '../Components/collectionsItemsData';
import { Link } from "react-router-dom";
const HomeButtons = ({}) => {
function ReadMore() {
const [readMore, setReadMore] = useState(false)
function toggleSetReadMore() {
setReadMore(!readMore)
}
return (
<p className='showmore' onClick={toggleSetReadMore} className={readMore ? "collection-item active" : "collection-item"}>TOGGLE BUTTON</p>
)
}
return <div>
{collectionsItems.map((collectionItem) => {
const { id, category, img } = collectionItem;
return < article key={id} >
<img className="item-img" src={img} alt=''/>
<ReadMore />
<Link to={`/${category}`} className="item-title">{category}</Link>
</article>
})}
</div>
}
export default HomeButtons;
First of all you need extract ReadMore component from function outside!
And for your problem you can lift state up(https://reactjs.org/docs/lifting-state-up.html). And since at the same time only one item can be opened you can do something like this:
function ReadMore({ isOpened, setOpened }) {
return (
<p
onClick={setOpened}
className={isOpened ? "collection-item active" : "collection-item"}
>
TOGGLE BUTTON
</p>
);
}
const HomeButtons = () => {
const [openedItemId, setOpenedItemId] = useState(null);
return (
<div>
{collectionsItems.map((collectionItem) => {
const { id, category, img } = collectionItem;
return (
<article key={id}>
<img className="item-img" src={img} alt="" />
<ReadMore
isOpened={openedItemId === id}
setOpened={() => setOpenedItemId(id)}
/>
<Link to={`/${category}`} className="item-title">
{category}
</Link>
</article>
);
})}
</div>
);
};
import FacebookLogin from 'react-facebook-login/dist/facebook-login-render-props'
class MyLogin extends React.Component {
responseFacebook(response) {
console.log(response);
}
render() {
return(
<FacebookLogin
appId="1088597931155576"
autoLoad
callback={this.responseFacebook}
render={renderProps => (
<button onClick={renderProps.onClick}>This is my custom FB button</button>
)}
/>
);
}
}
Now, as soon as my page gets loaded, the responseFacebook function is executed. How can I fix it?
The given answer did not work for me. This is an alternative approach, using react-facebook now.
import React, { Component} from 'react';
import { FacebookProvider, Login } from 'react-facebook';
export default class Example extends Component {
handleResponse = (data) => {
console.log(data);
}
handleError = (error) => {
this.setState({ error });
}
render() {
return (
<FacebookProvider appId="123456789">
<Login
scope="email"
onCompleted={this.handleResponse}
onError={this.handleError}
>
{({ loading, handleClick, error, data }) => (
<span onClick={handleClick}>
Login via Facebook
{loading && (
<span>Loading...</span>
)}
</span>
)}
</Login>
</FacebookProvider>
);
}
}
Remove "autoLoad" line. You won't get that behavior once removed.
I just had the same problem, I uninstalled the react-instagram-login because I don't know why it causes problems in the react-facebook-login and it worked again.
Pretty weird.
I have 2 classes (both React.Component). Let's say, that one of these is my own component, which also built on another custom component (in my case, it's React Places Autocomplete).
Just look at this picture
Code here:
//App.js
import React, { Component } from 'react';
import './App.css';
import PlaceAutocomplete from "./places_autocomplete";
class App extends Component {
constructor(props) {
super(props);
this.state = { output: '' };
}
render() {
return (
<div className="App">
<PlaceAutocomplete/>
<p>{this.state.output}</p>
</div>
);
}
}
export default App;
//places_autocomplete.js
import React, { Component } from 'react';
import './PlaceAutocomplete.css';
import PlacesAutocomplete from 'react-places-autocomplete';
class PlaceAutocomplete extends Component {
constructor(props) {
super(props);
this.state = { address: '', output: '' };
}
handleChange = address => {
this.setState({ address });
};
handleSelect = async address => {
this.setState({address: address});
this.state.output = address;
document.getElementById('lsi').blur();
};
searchOptions = {
types: ['(cities)']
};
hidden = (suggest) => {
return suggest == null || suggest === ""
? "autocomplete-dropdown-container-hidden"
: "autocomplete-dropdown-container";
};
render() {
return (
<PlacesAutocomplete
value={this.state.address}
onChange={this.handleChange}
onSelect={this.handleSelect}
searchOptions={this.searchOptions}>
{({ getInputProps, suggestions, getSuggestionItemProps, loading }) => (
<div>
<input value={this.state.address}
id={'lsi'}
{...getInputProps({
placeholder: 'Select a city',
className: 'location-search-input',
})}/>
<div className={this.hidden(suggestions[1])}>
{loading && <div>Loading...</div>}
{suggestions.map(suggestion => {
const className = suggestion.active
? "suggestion-item--active"
: "suggestion-item";
// inline style for demonstration purpose
const style = suggestion.active
? { backgroundColor: '#fafafa', cursor: 'pointer' }
: { backgroundColor: '#ffffff', cursor: 'pointer' };
return (
<div
{...getSuggestionItemProps(suggestion, {
className: className,
style,
})}
>
<span>{suggestion.description}</span>
</div>
);
})}
</div>
</div>
)}
</PlacesAutocomplete>
);
}
}
export default PlaceAutocomplete;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
So, you can see how I tried to find a solution for this. This code mostly looks ugly, because I don't know any other way to implement these feautures.
System info:
The latest React for 17.08.2018 (I don't really remember, but I do know that it's the latest (I installed it just 1 week ago).
This application created by CRA (Create React Application) template. So please, if your solution won't work with this template (I think there's different styles, like ES6 etc. But it's not the point) then add at least an explanation to your answer.
Try lifting the state up to the parent component and use callbacks to share the data. As stated by the react docs, there should be a single "source of truth" for data changes in a React application - this reduces potential bugs and duplicated code. Take a look at . https://reactjs.org/docs/lifting-state-up.html
import React from "react";
import styles from "../articles.css";
const TeamInfo = props => (
<div className={styles.articleTeamHeader}>
<div className={styles.left}>
style={{
background: `url('/images/teams/${props.team.logo}')`
}}
</div>
<div className={styles.right}>
<div>
<span>
{props.team.city} {props.team.name}
</span>
</div>
<div>
<strong>
W{props.team.stats[0].wins}-L{props.team.stats[0].defeats}
</strong>
</div>
</div>
</div>
);
export default TeamInfo;
the code that render this
import React from 'react';
import TeamInfo from '../../Elements/TeamInfo';
const header = (props) => {
const teaminfofunc = (team) => {
return team ? (
<TeamInfo team={team}/>
) : null
}
return (
<div>
{teaminfofunc(props.teamdata)}
</div>
)
}
export default header;
and I am getting error TypeError: props is undefined in line 8 why is that ?
Line 8 is
background: url('/images/teams/${props.team.logo}')
Update:
I found that in index.js the componentWillMount bring the data correctly but in the render() those data (article and team) was not passed to render, any idea why ?
import React, {Component} from 'react';
import axios from 'axios';
import {URL} from "../../../../config";
import styles from '../../articles.css';
import Header from './header';
import Body from './body';
class NewsArticles extends Component {
state = {
article:[],
team: []
}
componentWillMount() {
axios.get(`${URL}/articles?id=${this.props.match.params.id}`)
.then(response => {
let article = response.data[0];
axios.get(`${URL}/teams?id=${article.team}`)
.then(response => {
this.props.setState({
article,
team:response.data
})
})
})
}
render() {
const article = this.state.article;
const team = this.state.team;
return (
<div className={styles.articleWrapper}>
<Header teamdata={team[0]} date={article.date} author={article.author} />
<Body />
</div>
)
}
}
export default NewsArticles;
You render your component immediately, long before your AJAX call finishes, and pass it the first element of an empty array:
<Header teamdata={team[0]}
componentWillMount does not block rendering. In your render function, short circuit if there's no team to render.
render() {
const { article, team, } = this.state;
if(!team || !team.length) {
// You can return a loading indicator, or null here to show nothing
return (<div>loading</div>);
}
return (
<div className={styles.articleWrapper}>
<Header teamdata={team[0]} date={article.date} author={article.author} />
<Body />
</div>
)
}
You're also calling this.props.setState, which is probably erroring, and you should never call setState on a different component in React. You probably want this.setState
You should always gate any object traversal in case the component renders without the data.
{props && props.team && props.team.logo ? <div className={styles.left}>
style={{
background: `url('/images/teams/${props.team.logo}')`
}}
</div> : null}
This may not be you exact issue, but without knowing how the prop is rendered that is all we can do from this side of the code.
Update based on your edit. You can't be sure that props.teamdata exists, and therefore your component will be rendered without this data. You'll need to gate this side also, and you don't need to seperate it as a function, also. Here is an example of what it could look like:
import React from 'react';
import TeamInfo from '../../Elements/TeamInfo';
const header = (props) => (
<div>
{props.teamdata ? <TeamInfo team={props.teamdata}/> : null}
</div>
)
export default header;
First -- while this is stylistic -- it's not good practice to pass props directly to your functional component. Do this instead.
const TeamInfo = ({team}) => (
<div className={styles.articleTeamHeader}>
<div className={styles.left}>
style={{
background: `url('/images/teams/${team.logo}')`
}}
</div>
<div className={styles.right}>
<div>
<span>
{team.city} {team.name}
</span>
</div>
<div>
<strong>
W{team.stats[0].wins}-L{team.stats[0].defeats}
</strong>
</div>
</div>
</div>
);
Second, you might just want to do some kind of null check. If team is undefined the first time the component tries to render, you might just want to render null so you're not wasting cycles.
In case this isn't the issue, you'd learn a lot by console.log-ing your props so you know what everything is each time your component tries to render. It's okay if data is undefined if you're in a state that will soon resolve.
I am sure that my question is obvious but I cannot find simple answer anywhere. I am not familiar with redux/flux so I don't know if I need to learn them to achieve my goal.
I get from my server urls to images I need to display on the component. I want to display loader till the image is fetched.
What is the best (and easiest) way to do that? Is necessary to use flux/redux?
May I use just fetch(image_URL).then... promise?
For now on just call url while rendering img html tag:
{this.props.data.images.map(img=>{
return(
<img src={img.url}/>
)})
how to manage async of this task? I already use apollo to fetch local db data. May I use apollo for fetching external data?
The easiest way is to define a loading flag and use it to determine if the loader should be rendered. It seems that your fetch logic somewhere else but the idea is the same.
class YourComponent() extends Component {
constructor() {
this.state = {
isLoading: false,
};
}
componentWillMount() {
this.setState({isLoading:true});
fetch('image_URL')
.then(res => {
this.setState({
images: res.images,
isLoading: false,
})
})
}
render() {
const { isLoading , images} = this.state;
if (isLoading) {
return <YourLoaderComponent />
}
return (
<div>
{images.map(img => <img src={img.url} />)}
</div>
);
}
}
You can make a use of onLoad react callback on the <img/> tag.
Tutorial:
Define React Component <LoadedComponent /> which will be a spinner.
Then you can define another React Component <ImageComponent /> which will have a default imageLoaded state set to false.
If imageLoaded is false, <ImageComponent/> will render img with width and height 0px.
The <img /> tag has onLoad binding to function imageLoaded() which then sets the imageLoaded state to true. When the state changes onLoad(when image finished loading) of <img/>, <ImageComponent/> automatically rerenders and it renders only <img/> with normal width and height.
import React from "react";
import { render } from "react-dom";
const LoaderComponent = () => (
<img
width="300"
height="300"
alt="Loading spinner"
src="http://blog.teamtreehouse.com/wp-content/uploads/2015/05/InternetSlowdown_Day.gif"
/>
);
const hiddenImageStyle = {
width: 0,
height: 0
};
class ImageComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
loaded: false
};
}
imageLoaded() {
this.setState({
loaded: true
});
}
render() {
if (this.state.loaded) {
return(
<div>
<img alt="Cat" src={this.props.url} width="300" height="300" />
</div>
)
}
return (
<div>
<img
alt="Cat"
src={this.props.url}
width="300"
height="300"
onLoad={() => this.imageLoaded()}
style={hiddenImageStyle}
/>
<LoaderComponent />
</div>
);
}
}
const imagesUrls = [
"https://c1.staticflickr.com/5/4200/34055408873_e9bf494e24_k.jpg",
"https://c1.staticflickr.com/5/4536/37705199575_ded3cf76df_c.jpg"
];
class App extends React.Component {
render() {
return (
<div>
{imagesUrls.map((url, index) => (
<ImageComponent key={index} url={url} />
))}
</div>
);
}
}
render(<App />, document.getElementById("root"));
Here you can see a working example: https://codesandbox.io/s/zwz9o84kn3
If you have a good Internet speed you will probably not notice the spinner. To see the spinner, the best is to open preview of the sandbox in a separate tab
and then open chrome dev tools and in the Network tab check disable cache and set a preset to Slow 3G
After refreshing you will notice loadining spinner, until the image will load