i try to make a loading screen while waiting for all images are fully loaded.
React Lifecycle is Render -> componentDidMount -> render, my images are not fully loaded, just got called but my componentDidMount always finishes and executes render even my image isn't fully loaded.
componentDidMount() {
const ie = [document.querySelectorAll('img')];
ie.map(imgElm => {
for (const img of imgElm) {
if (!img.complete) {
this.setState({ imageIsReady : true});
}
}
return this.setState({ imageIsReady : false});
})
}
on the componentDidMount for loop function try to check every img is complete or not, give me a hundred true (my image is a lot, just try to make gallery). and loading screen shows but only a few ms, then I can scroll over my image but more than half of my image is still loading.
render() {
<div>
{
this.state.imageIsReady ?
<div className='inset-0 fixed flex justify-center z-20 w-full h-full bg-black bg-opacity-25 blur'>
<img src={loading} className='w-3/12' alt="load"/>
</div> :
<div className='hidden '>
<img src={loading} alt="load"/>
</div>
}
<div>page......</div>
</div>
}
my code: https://alfianahar.github.io/MobileLegendHeroList/ in this site I use setTimeout on my componentDidMount, this does not solve my problem when using slow 3g nor fast 3g/
Maybe this example can help you. But remember that will works only for image which aren't nested inside components.
class Component extends Component {
constructor(props) {
super(props)
this.state = {
ready: false
}
}
componentDidMount() {
Promise.all(
Array.from(document.images)
.filter(img => !img.complete)
.map(img => new Promise(
resolve => { img.onload = img.onerror = resolve; }
))).then(() => {
this.setState({ ready: true })
});
}
render() {
if ( ! this.state.ready ) return <div>Loader</div>
return <div>Content</div>
}
}
<Container>
<img/> <!-- work -->
<Component>
<img/> <!-- doesn't work -->
</Component>
</Container>
React 16.8.x
import React from "react";
function App() {
const [imagesRequested, setImagesRequested] = React.useState({});
const [images, setImages] = React.useState([
{ name: "first image", src: "https://picsum.photos/200/300" },
{ name: "second image", src: "https://picsum.photos/300/300" }
]);
return (
<React.Fragment>
{images.map((currentImage) => (
<React.Fragment>
{!imagesRequested[currentImage.name] && <div>loading...</div>}
<img
style={{ opacity: imagesRequested[currentImage.name] ? 1 : 0 }}
src={currentImage.src}
onLoad={() => {
setTimeout(() => { // Fake server latency (2 seconds for per image)
setImagesRequested((previousState) => ({
...previousState,
[currentImage.name]: true
}));
}, 2000);
}}
/>
</React.Fragment>
))}
</React.Fragment>
);
}
export default App;
Your best bet is to possibly create a LoadableImage component which will then handle onLoad event for an object. This onLoad event can then call a parent callback function to set its loaded status.
LoadableImage.js
import { useState } from "react";
const LoadableImage = (props) => {
const { src, alt, width, height, onLoad, onError, id } = props;
//you can use this to render a custom broken image of some sort
const [hasError, setHasError] = useState(false);
const onLoadHandler = () => {
if (typeof onLoad === "function") {
onLoad(id);
}
};
const onErrorHandler = () => {
setHasError(true);
if (typeof onError === "function") {
onError(id);
}
};
return (
<img
src={src}
alt={alt}
width={width}
height={height}
onLoad={onLoadHandler}
onError={onErrorHandler}
/>
);
};
export default LoadableImage;
now you can handle the callbacks in your implementation and act appropriately. You can keep state of all your images, and their loading status.
App.js
export default function App() {
const [images, setImages] = useState(imageList);
const imagesLoading = images.some((img) => img.hasLoaded === false);
const handleImageLoaded = (id) => {
setImages((prevState) => {
const index = prevState.findIndex((img) => img.id === id);
const newState = [...prevState];
const newImage = { ...newState[index] };
newImage.hasLoaded = true;
newState[index] = newImage;
return newState;
});
};
return (
<div className="App">
{imagesLoading && <h2>Images are loading!</h2>}
{images.map((img) => (
<LoadableImage
key={img.id}
id={img.id}
src={img.src}
onLoad={handleImageLoaded}
/>
))}
</div>
);
}
Here handleImageLoaded will update the hasLoaded property of an image in the images state array when a image is loaded. You can then conditionally render your loading screen while (in this case) imagesLoading is true, as I have conditionally rended the "Imags are loading" text.
Codesandbox
imageList looks like this
const imageList = [
{
id: 1,
src:
"https://images.unsplash.com/photo-1516912481808-3406841bd33c?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=683&q=80",
hasLoaded: false
},
{
id: 2,
src: "https://via.placeholder.com/150",
hasLoaded: false
},
{
id: 3,
src: "https://via.placeholder.com/151",
hasLoaded: false
}
];
Related
This solution (below) was given and fixes the problem of checking PlacesAutocomplete is loaded first before trying to load it and causing an error(within a class component), but I'm struggling to convert it to use in a functional component with react hooks as I can't access window.initMap.
state = {
gmapsLoaded: false,
}
initMap = () => {
this.setState({
gmapsLoaded: true,
})
}
componentDidMount () {
window.initMap = this.initMap
const gmapScriptEl = document.createElement(`script`)
gmapScriptEl.src = `https://maps.googleapis.com/maps/api/js?key=SECRET_EATING&libraries=places&callback=initMap`
document.querySelector(`body`).insertAdjacentElement(`beforeend`, gmapScriptEl)
}
render () {
return (
<div>
{this.state.gmapsLoaded && (
<PlacesAutocomplete />
)}
</div>
)
}
was trying:
const [gmapsLoaded,setgmapsLoaded ] = useState(false)
initMap = () => {
setgmapsLoaded(true)
}
useEffect(() => {
window.initMap = this.initMap
const gmapScriptEl = document.createElement(`script`)
gmapScriptEl.src = "https://maps.googleapis.com/maps/api/js?key=AIzaSyAmlRtE1Ggrzz-iSUAWGIcm0mmi7GXbKtI&callback=initMap"
document.querySelector(`body`).insertAdjacentElement(`beforeend`, gmapScriptEl)
});
but to no avail
I also tried this though it doesn't work, it solves it from crashing though it won't load the suggestions on the screen found inside PlacesAutoComplete and just keeps saying "Loading..."
import React, { useContext, useState , useEffect} from "react";
const initMap = () => {
setgmapsLoaded(true)
}
useEffect(() => {
if (gmapsLoaded === false) {
window.initMap = initMap
const gmapScriptEl = document.createElement(`script`)
gmapScriptEl.src = "https://maps.googleapis.com/maps/api/js?key=AIzaSyAmlRtE1Ggrzz-iSUAWGIcm0mmi7GXbKtI&callback=initMap"
document.querySelector(`body`).insertAdjacentElement(`beforeend`, gmapScriptEl)
}
});
{gmapsLoaded && (
<PlacesAutocomplete
value={address}
onChange={setAddress}
onSelect={handleSelect}
>
{({ getInputProps, suggestions, getSuggestionItemProps, loading }) => (
<div>
<p> latitude: {coordinates.lat}</p>
<p> longitude: {coordinates.lng}</p>
<p> Address: {address}</p>
<input
{...getInputProps({
placeholder: 'Search Places ...',
className: 'location-search-input',
})}
/>
<div className="autocomplete-dropdown-container">
{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,
style,
})}
>
<span>{suggestion.description}</span>
</div>
);
})}
</div>
</div>
)}
</PlacesAutocomplete>
)
}
I'm currently building my first app with React and today I encountered a problem I think I cannot resolve on my own yet.
So what's the matter:
I'm rendering a Result.js container, which consists of smaller components displaying data from the API. Initially, it is hidden (not rendered) and it gets rendered after passing query into Search component and receiving a response. I'm trying to implement a transition so it fades in (opacity 0 -> 1) after response. This is working fine, but also I want it to fade out when the user sends another request and fades in again. This is what's not working, or working parts. Right now the fade-out animation plays out, but right near the end, there is a flash of an earlier state of the component with previous data. Like there was an additional render in there. I tried different approaches like with inline styling (display: none) but most of them ended with fade-out animation not playing at all.
I'm using Redux to store API response and components' display property.
The code I've been working on can be found below. I'll be very thankful for any suggestions or insights, also related to my coding style/code 'cleanness' :) Thank you!
Result.js container:
const Result = props => {
return (
<Transition
in={props.displayResult}
timeout={1000}
mountOnEnter
unmountOnExit
>
{state => (
<div
className={`${classes.Box} ${
state === 'entering'
? classes.ResultOpen
: state === 'entered'
? classes.ResultVisible
: state === 'exiting'
? classes.ResultClosed
: state === 'exited'
? classes.ResultVisible
: null
}`}
>
<div className={classes.BoxRow}>
<Sprites />
<NameId />
</div>
<div className={classes.BoxRow}>
<div className={classes.BoxColumn}>
<Abilities />
<Metrics />
</div>
<Types />
</div>
<div className={classes.BoxRow}>
<Stats />
</div>
</div>
)}
</Transition>
);
};
const mapStateToProps = state => {
return {
displayResult: state.result.displayResult
};
};
export default connect(mapStateToProps)(React.memo(Result));
reducer.js
const initialState = {
id: null,
name: '',
spriteFront: '',
spriteBack: '',
types: [],
height: null,
weight: null,
stats: [],
baseExperience: null,
abilities: [],
moves: [],
displayResult: false,
error: false,
loading: false
};
const setResult = (state, action) => {
return updateObject(state, {
id: action.result.id,
name: action.result.name,
spriteFront: action.result.sprites.front_default,
spriteBack: action.result.sprites.back_default,
types: action.result.types,
height: action.result.height,
weight: action.result.weight,
stats: action.result.stats,
baseExperience: action.result.base_experience,
abilities: action.result.abilities,
moves: action.result.moves,
displayResult: true
});
};
const resetBox = (state, action) => {
return updateObject(state, {
displayResult: false
});
};
const fetchResultFailed = (state, action) => {
return updateObject(state, { error: true });
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.SET_RESULT:
return setResult(state, action);
case actionTypes.FETCH_RESULT_FAILED:
return fetchResultFailed(state, action);
case actionTypes.RESET_BOX:
return resetBox(state, action);
default:
return state;
}
};
export default reducer;
actions.js
export const setResult = result => {
return {
type: actionTypes.SET_RESULT,
result: result
};
};
export const resetBox = () => {
return {
type: actionTypes.RESET_BOX
};
};
export const fetchResultFailed = () => {
return {
type: actionTypes.FETCH_RESULT_FAILED
};
};
export const nextResult = query => {
return dispatch => {
dispatch(resetBox());
setTimeout(() => {
dispatch(initResult(query));
}, 100);
};
};
export const initResult = query => {
return dispatch => {
axios
.get(`https://pokeapi.co/api/v2/pokemon/${query}`)
.then(response => {
dispatch(setResult(response.data));
console.log(response.data);
})
.catch(error => {
dispatch(fetchResultFailed());
});
};
};
I have a React component which gets from an API data with fetch of 10 images. I would like to use infinite scroll to load more sets of 10 images.
What I made to do is to listen the event of reaching the bottom of the website and posting new url of nest 10 images in console only :)
Should I focus on getting all data in my url, or focus on render and usage of related function?
Or maybe the problem is because I get data in componentDidMount and I don't know how to update whole state?
import React from 'react';
import ReactDOM from 'react-dom';
class ViewSection extends React.Component {
constructor(props) {
super(props);
this.state = {
image: [],
like: [],
location: [],
first_name: [],
last_name: [],
pictureId: [0,1,2,3,4,5,6,7,8,9],
page: 1
};
this.handleScroll = this.handleScroll.bind(this) // I'M MOVING DATA TO HANDLE SCROLL
};
handleScroll(e) {
e.preventDefault();
let documentHeight = document.documentElement.offsetHeight;
let windowHeight = window.innerHeight;
let windowScroll = window.scrollY;
let scrollTotal = windowScroll + windowHeight;
if (scrollTotal == documentHeight) {
this.setState({ page: this.state.page + 1 })
// console.log(this.state.page);
}
};
componentDidMount() {
let urlImage = ('https://api.website.com/categories/' + this.props.params.sectionId + '/photos/?client_id=MYID&page=' + this.state.page); // MAP ALL PAGES?????
window.addEventListener("scroll", this.handleScroll,false);
fetch(urlImage)
.then(resp => resp.json())
.then(response => {
// console.log(response);
// console.log(this.state.page);
let arrayOfImages = response.map((item) => item.urls.small );
let arrayOfLikes = response.map((item) => item.likes );
let arrayOfLoc = response.map((item) => item.user.location );
let arrayOfFirst_Names = response.map((item) => item.user.first_name );
let arrayOfLast_Names = response.map((item) => item.user.last_name );
this.setState ({
image : arrayOfImages,
like : arrayOfLikes,
location : arrayOfLoc,
first_name : arrayOfFirst_Names,
last_name : arrayOfLast_Names
})
});
};
componentWillUnmount() {
window.removeEventListener('scroll', this.handleScroll,false);
};
render() {
// console.log(this.state.image);
console.log(this.state.page); // LISTENS AND RENDERS ALL CHANGES... MAYBE PROMISE.ALL ON urlImage...?
let section = this.state.image.map((elem, i, page) => {
return (
<Link key={i} onScroll={this.handleScroll} className="section-picture" to= {`/section/${this.props.params.sectionId}/picture/${this.state.pictureId[i]}`}>
<img className="image" src={elem} alt="" />
<div className="section-picture-stats">
<div className="section-picture-stat"> author: {this.state.first_name[i]} {this.state.last_name[i]}</div>
<div className="section-picture-stat">{this.state.like[i]} like(s)</div>
<div className="section-picture-stat">{this.state.location[i]}
</div>
</div>
</Link>
)
});
return (
<div className="gallery">
<h1>Section</h1>
<div className="buttons">
<div className="sort-clicks click">sort by: <a className="click" href="">new</a> or <a className="click" href="#">trending</a></div> <Link className="click" to='/'>back</Link>
</div>
<div className="section-picture-list">{section}</div>
</div>
)
};
};
export { ViewSection }
It looks to me like the value of section will be the result of mapping an empty array, since this.state.image will be empty until the fetch in componentDidMount finishes. Try adding a check in your render function like so:
let section;
if (this.state.images.length === 0) section = <p>Loading...</p>;
else section = this.state.image.map((elem, i, page) => {
...your code goes here...
});
This way it should update properly at least on the initial render
I can't seem to pass this handler correctly. TabItem ends up with undefined for onClick.
SearchTabs
export default class SearchTabs extends Component {
constructor(props) {
super(props)
const breakpoints = {
[SITE_PLATFORM_WEB]: {
displayGrid: true,
autoFocus: true,
},
[SITE_PLATFORM_MOBILE]: {
displayGrid: false,
autoFocus: false,
},
};
this.state = {
breakpoints,
filters: null,
filter: null,
isDropdownOpen: false,
selectedFilter: null,
tabs: null,
};
this.tabChanged = this.tabChanged.bind(this);
this.closeDropdown = this.closeDropdown.bind(this);
}
... more code
createTabs(panels) {
if(!panels) return;
const tabs = panels.member.map((panel, idx) => {
const { selectedTab } = this.props;
const { id: panelId, headline } = panel;
const url = getHeaderLogo(panel, 50);
const item = url ? <img src={url} alt={headline} /> : headline;
const classname = classNames([
searchResultsTheme.tabItem,
(idx === selectedTab) ? searchResultsTheme.active : null,
]);
this.renderFilters(panel, idx, selectedTab);
return (
<TabItem
key={panelId}
classname={classname}
idx={idx}
content={item}
onClick={this.tabChanged(idx, headline)}
/>
);
});
return tabs;
}
tabChanged(idx, headline) {
const { selectedTab } = this.props;
const { selectedFilter } = this.state;
const selectedFilterIdx = _.get(selectedFilter, 'idx', null);
if (selectedTab !== idx) {
this.props.resetNextPage();
this.props.setTab(idx, selectedFilterIdx, headline);
this.closeDropdown();
}
}
render() {
// const { panels, selectedTab } = this.props;
// if (!panels || panels.length === 0) return null;
//
//
// const { tabs, selectedTab } = this.props;
return (
<div>
<ul>{this.state.tabs}</ul>
</div>
);
}
}
export const TabItem = ({ classname, content, onClick, key }) => (
<li key={key} className={`${classname} tab-item`} onClick={onClick} >{content}</li>
);
so in TabItem onClick={onClick} ends up with undefined for onClick.
More info
here's how this used to work, when this was a function in the parent Container:
// renderDefaultTabs() {
// const { panels, selectedTab } = this.props;
//
// if (!panels || panels.length === 0) return;
//
// let filter = null;
//
// const tabs = panels.member.map((panel, idx) => {
// const { id: panelId, headline } = panel;
// const url = getHeaderLogo(panel, 50);
// const item = url ?
// <img src={url} alt={headline} /> : headline;
// const classname = classNames([
// searchResultsTheme.tabItem,
// (idx === selectedTab) ? searchResultsTheme.active : null,
// ]);
//
// filter = (idx === selectedTab) ? this.renderFilters(panel) : filter;
//
// return (
// <li
// key={panelId}
// className={classname}
// onClick={() => {
// this.tabChanged(idx, headline);
// }}
// >
// {item}
// </li>
// );
// });
So I extracted that out to that SearchTabs including moving the tabChange d method to my new SearchTabs component. And now in the container the above now does this:
renderDefaultTabs() {
const {
onFilterClick,
panels,
resetNextPage,
selectedTab,
selectedFilter,
isDropdownOpen,
} = this.props;
return (<SearchTabs
panels={panels}
...
/>);
}
Note: renderDefaultTabs() is sent as a prop to in the render() of the container and the Search calls it back thus rendering it in the Search's render():
Container
render() {
return (
<Search
request={{
headers: searchHeaders,
route: searchRoute,
}}
renderTabs={this.renderDefaultTabs}
renderSearchResults={this.renderSearchResults}
handleInputChange={({ input }) => {
this.setState({ searchInput: input });
}}
renderAltResults={true}
/>
);
}
Search is a shared component our apps use.
Update
So I mentioned that the Container's render() passes the renderDefaultTabs function as a prop to <Search />. Inside <Search /> it ultimately does this: render() { <div>{renderTabs({searchResults})}</div>} which calls the container's renderDefaultTabs function which as you can see above, ultimately renders
So it is passing it as a function. It's just strange when I click a TabItem, it doesn't hit my tabChanged function whatsoever
Update
Christ, it's hitting my tabChanged. Errr..I think I'm good. Thanks all!
onClick={this.tabChanged(idx, headline)}
This is not a proper way to pass a function to child component's props. Do it like (though it is not recommended)
onClick={() => this.tabChanged(idx, headline)}
UPDATE
I want to add more explanation. By onClick={this.tabChanged(idx, headline)}, you are executing tabChanged and pass its returned value to onClick.
With your previous implementation: onClick={() => { this.tabChanged(idx, headline); }}, now onClick will be a function similar to:
onClick = {(function() {
this.tabChanged(idx, headline);
})}
So it works with your previous implementation.
With your new implementation, onClick={() => this.tabChanged(idx, headline)} should work
I am using draftjs editor. I could render the content but I could not show images. How can i show image when using draftjs? Right now the url is only shown instead of images.The server sends the data as following
img src="http://image_url" style="argin:30px auto; max-width: 350px;"
Sorry i could not use img tag html way so excluded the tag syntax.
function findImageEntities(contentBlock, callback, contentState) {
contentBlock.findEntityRanges(character => {
const entityKey = character.getEntity();
return (
entityKey !== null &&
contentState.getEntity(entityKey).getType() === "IMAGE"
);
}, callback);
}
const Image = props => {
const { height, src, width } = props.contentState
.getEntity(props.entityKey)
.getData();
return <img src={src} height={height} width={width} />;
};
class AdminEditor extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
editorState: EditorState.createEmpty(),
editorContent: undefined,
contentState: "",
touched: false
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.htmlMarkup !== this.props.htmlMarkup) {
const content = nextProps.htmlMarkup;
const blocksFromHTML = convertFromHTML(content);
const plainState = ContentState.createFromBlockArray(
blocksFromHTML.contentBlocks,
blocksFromHTML.entityMap
);
this.setState(state => ({
editorState: EditorState.createWithContent(plainState, decorator)
}));
}
}
onEditorStateChange = editorState => {
this.setState({
editorState
});
};
onEditorChange = editorContent => {
this.setState({
editorContent
});
};
handleChange = event => {
this.props.setEditorState(
this.state.editorState.getCurrentContent().hasText() && this.state.touched
);
};
render() {
const { editorState } = this.state;
const { stateOfEditor } = this.props;
return (
<div>
<Editor
tabIndex={0}
editorState={editorState}
initialContentState={this.props.htmlMarkup}
toolbarClassName="home-toolbar"
onEditorStateChange={this.onEditorStateChange}
toolbar={{
history: { inDropdown: true },
inline: { inDropdown: false },
link: { showOpenOptionOnHover: true },
image: {
uploadCallback: this.imageUploadCallBack,
defaultSize: { height: "auto", width: "50%" }
}
}}
onContentStateChange={this.onEditorChange}
onChange={this.handleChange}
/>
</div>
);
}
}
export default AdminEditor;
exact copy of decorator is in top of the findImageEntities which i haven't pasted just to reduce the number of lines of code
I saw the props onEditorStateChange={this.onEditorStateChange} . I doubt you're using the draft-js-wysiwyg not draft-js.
In draft-js-wysiwyg , u can visit here :
https://github.com/jpuri/react-draft-wysiwyg/issues/589