How to check if map function is finished - javascript

UPDATE: The process of what must happen
User drag and drops multiple pdf files
Each of these pdf files are then visually rendered one by one for the user in a list
As long as the list is still updating, a pop up will come up saying "please wait until everything is loaded"
After all the pdfs are loaded, the user can read the pdf in an embed to quickly check if this is what he wants to upload and can add a title and description for each pdf.
Every pdf has received an item; the user now clicks 'upload'
Brief description
So I have a map function that loads a lot of items with large file size. I want to load each result one by one, but I want to hide or block it using a 'isLoading' state until everything is finished loading.
Question
How do I check if a map function is finished loading everything?
UPDATE: My code
{this.state.pdfSrc!== null ? // IF USER HAS UPLOADED FILES
this.state.pdfSrc.map(item => ( // MAP THROUGH THOSE FILES
// AS LONG AS THE MAP FUNCTION KEEPS LOADING FILES, A POP UP MUST COME UP SAYING "please wait until everything is loaded"
<div key={item.fileResult} className='uploadResultList'>
{item.fileName}
<embed src={item.fileResult} />
<input type='text' placeholder='Add title*' required />
</div>
)) /
:
null
}
But this only gives me following error
Warning: Functions are not valid as a React child. This may happen if
you return a Component instead of from render. Or maybe
you meant to call this function rather than return it.

Instead of doing this in a single function, you can implement the map in your component's render() method:
render() {
return(
<div>
{!!imgSrc ? (
<PlaceholderComponents/>
) : this.state.imgSrc.map(item => (
<div key={item.fileResult} className='uploadResultList'>
<embed src={item.fileResult} />
</div>
}
</div>
)
}
However, you would need to 'load' the file data before the doing so, either using componentDidMount or componentWillMount (which is now being deprecated, so try to avoid using it). For example:
componentDidMount() {
getImageSrcData(); // Either a function
this.setState({ imgSrc: imgFile }) // Or from setState
}
If you want more info on setting state from componentDidMount, see this thread:
Setting state on componentDidMount()
EDIT: You can use the React Suspense API to ensure the file loads before rendering...
// You PDF Component from a separate file where the mapping is done:
render() {
return (
<div>
{this.state.imgSrc.map(item => (
<div key={item.fileResult} className='uploadResultList'>
<embed src={item.fileResult} />
</div>}
</div>
)
}
const PleaseWaitComponent = React.lazy(() => import('./PleaseWaitComponent'));
render() {
return (
// Displays <PleaseWaitComponent> until PDFComponent loads
<React.Suspense fallback={<PleaseWaitComponent />}>
<div>
<PDFComponent />
</div>
</React.Suspense>
);
}

Update: You may need to look at creating a higher order component that can take another component and wait till all of those are loaded and then change the state. Perhaps something similar to react-loadable?
You don't need to pass a callback into the setState. .map isn't asynchronous and once the iteration has finished it will then move to the next line in the block.
The issue you're having here is that you're just calling map and returning JSX in the callback but it isn't going anywhere.
You should call the .map inside the render method. I'm not sure why you're trying to update the isLoading state. I think this should be handled elsewhere.
render() {
cosnt { imgSrc } = this.state;
return (
{
imgSrc.map(item => (
<div key={item.fileResult} className="uploadResultList">
<embed src={item.fileResult} />
</div>
));
}
)
}

Related

Defer state update / rerender in React

I created a React component that sends a GraphQL query to the backend to retrieve an image as a base64-encoded string and display it when it's loaded. Until then, it displays a little loading spinner. The code looks more or less like this and works as expected:
const { data, loading, error } = useQuery(...)
const showImage = !loading && !error && data?.image?.base64
const showError = !loading && (error || !showImage)
return (
<div>
{loading && <img src={`/loading.gif`} />}
{showError && <img src={`/error.png`} />}
{showImage && <img src={`data:image/jpg;base64, ${data.image.base64}`}/>}
</div>
)
But on top of that, the control also allows some basic image manipulation (e.g. a button that tints the image purple), which I implemented with useState. Essentially:
const [ purple, setPurple ] = useState(false)
const { data, loading, error } = useQuery(/* pass `purple` to backend */)
const showImage = !loading && !error && data?.image?.base64
const showError = !loading && (error || !showImage)
return (
<div>
{loading && <img src={`/loading.gif`} />}
{showError && <img src={`/error.png`} />}
{showImage && <img src={`data:image/jpg;base64, ${data.image.base64}`}/>}
<input type={`checkbox`} onChange={_ => setPurple(!purple)} />
</div>
)
All of that works fine, too, except as soon as the checkbox is clicked, the old image disappears and I get the loading.gif until the new image is fetched. That's not unexpected but undesired. I'd much rather keep the old image around and set the new image once it arrives.
I experimented with writing the old image's base64 string to a useState hook and read it from there until it's replaced by the new image. That works, but I got the impression that the performance was not great. (There are many, many of these components on the site and when the user scrolls long enough, hundreds of them may be loaded, which leads to a bloated React state. Can that be a problem or did I imagine it? Can I profile this?)
Which brings me to my question: Is there a way in React to somehow defer the full rerender and keep the old state around for a while until the new state goes into effect?
Well, what you are looking, assuming you are using Apollo Client, is previousData hook result property
https://www.apollographql.com/docs/react/data/queries/#previousdata
As per documentation
An object containing the result from the most recent previous
execution of this query.
This value is undefined if this is the query's first execution.
So something like (by using image variable together with ?. (optional chaining) you can avoid the boolean variable to decide if to show the image)
const { data, loading, error, previousData } = useQuery(/* ... */);
const image = data?.image?.base64 || previousData?.image?.base64;
Note: I suggest you to change the loading logic to display the loading spinner only if it's loading and no image is set, to avoid both loading spinner and previous image displaying together. You can eventually hide the image if case of errors.

React component retrieves props just once, goes undefined when refreshed

I'm creating a simple movie app with moviedb. I have successfully retrieved the most popular 20 movies and put them in the app state:
constructor(props) {
super(props);
this.state = {
movieInfo: [],
}
}
componentDidMount() {
this.getMovies();
}
getMovies = async () => {
await axios.get('https://api.themoviedb.org/3/movie/popular?api_key=94d4ad026c5009bdaf4aecb8989dfa07')
.then(res => this.setState({ movieInfo: res.data.results }))
}
I know the array was retrieved correctly because when I look at the React components in Chrome dev tools I see what I want:
Screen cap of App state
Then in the render part of the App I want to pass the first element in the array to a component called Movie, which will then display some info about the movie:
return (
<div>
<Movie movie={this.state.movieInfo[0]} />
</div>
);
}
I know the movie component is getting this info correctly because I see the object representing the first movie in the Movie component props:
Movie component props
My Movie function looks like this:
return (
<div>
<h1>{props.movie.original_title}</h1>
<p>{props.movie.overview}</p>
</div>
)
}
The first time I compile it this works and I see the info I want:
Rendered App with Movie component
But incredibly, when I refresh the page I see the error message
TypeError: Cannot read properties of undefined (reading 'original_title')
How is it possible that the App will correctly pass on the info to Movie and will display it correctly once, but as soon as I refresh the page somehow it's undefined?
Thanks in advance for the help,
JD
I assume that you don't get an error when you are developing and changing code. When you save your code Hot reloading only changes parts that changed.
This means that if your app loads data and populates this.state.movieInfo with an array from BE, and your save you code, it Hot reloads and you get new data. So, this.state.movieInfo[0] is always filled with data.
When you refresh your app, it just resets to an empty array as you put it there in the constructor.
Solution is to always check if there is that first element in array before rendering the Movie component:
return (
<div>
{this.state.movieInfo[0] ? <Movie movie={this.state.movieInfo[0]} /> : null}
</div>
);
You may need to also use componentDidUpdate() with componentDidMount():
componentDidMount() {
this.getMovies();
}
componentDidUpdate() {
this.getMovies();
}
when the page reloads init state is an empty array. you have to check the array has an item to render or not.
return (
<div>
{this.state.movieInfo && this.state.movieInfo.length>0 && (<Movie
movie={this.state.movieInfo[0]} />)}
</div>
);

React doesn't display without conditional rendering from api

Why does the component not render/display anything when I fetch data from an api?
This code below just gives me a blank page
render(){
return (
<div>
<p>{this.state.character[0]}</p>
</div>
)
}
But when I do some conditional rendering it displays the data.
render(){
return (
<div>
{<p>{this.state.character? this.state.character[0] : null}</p>}
</div>
)
}
How are you fetching the data? in componentDidMount() hopefully. componentDidMount() runs AFTER render() meaning, when the component first loads/mounts, there is no data. The data gets received then added to state AFTER the component is mounted
Data from API takes time to load, so the first code doesn't have any data as the request from the API is not completed. you cannot access the index of something that has nothing in it. So the condition basically is making it safe to display.

React setState late updation with API calls [duplicate]

This question already has answers here:
if-else statement inside jsx: ReactJS
(17 answers)
Closed 2 years ago.
I am new to React and as I was creating a project, I came across a rather peculiar event with my code.
It is with the async nature of the setState which doesn't let me render data on my page like i want it to.
I am trying to display files which i have in my database already, onto my webpage. But since the state is set after a while therefore i am not able to render my files onto the screen using the map function, since when it is called it is undefined.
I implplemented a get method in so as to get the json response of the files that i want to display. But as I mount my component and setstate of the Files, it shows that it doesn't have any value in it.I know it is async but i have no idea how to handle it so that i can use map to display onto the webpage.
My code is as follows:
import React, {Component} from "react";
// import axios from 'axios';
import {Link} from 'react-router-dom';
import {Styles} from '../Styling/Styles';
class FileView extends Component {
state = {
fileViewData : {}
}
// viewFunction(){
componentDidMount(){
fetch('http://127.0.0.1:8000/api/uploads/', {
method: 'GET'
})
.then((response) => {
let data = response.json();
return data;
})
.then((data) => {
this.setState({fileViewData: data}, ()=>{
console.log("hi");
});
}).catch(error => {console.log(error)})
// console.log(fileViewData);
}
render(){
return (
<React.Fragment>
<Styles>
<div className = "appbar">
<Link to='/dashboard'>
<button className="homeBtn" label="Back" >
Back
</button>
</Link>
</div>
{/* <button label="View" onClick={this.viewFunction} >
View
</button> */}
</Styles>
//.....section not working since map is not a function of undef......//
{
this.state.fileViewData.map(item =>{
return (
<h2>{item.fields.file}</h2>
);
})
}
//.......section not working ends.........//
{console.log(this.state.fileViewData)}
</React.Fragment>
);
}
}
export default FileView;
The console output is something like this:
The empty object is returned twice and then the data is returned twice.I am not running any kind of loop either.
How should I set the value so that i am able to display my files onto the screen? TIA!
Looks like your data is an array, so your initial state should also be an array
state = {
fileViewData: [],
}
Then your check for array length will be correct, but regular javascript doens't quite work the same in JSX. You'll need to conditionally render JSX.
Conditional Rendering
{this.state.fileViewData.length
? this.state.fileViewData.map(...)
: null
}
Since it seems you don't really render anything if there is no data, you can simply map the data as array::map correctly handles empty arrays.
{
this.state.fileViewData.map(...)
}
Set state to an empty array
Remove the if condition and anonymous function declaration from your map statement
You declare that function but never invoke it also you don't need it.
if you insist on checking the length
{
this.state.fileViewData.length &&
this.state.fileViewData.map(item =>{
return (
<h2>{item.fields.file}</h2>
);
})
}
You are getting multiple console logs because when you set state in React you cause the component to render again.
As for your implementation, you probably want the initial value of fileViewData to have the same data structure as what your back end hands back. Currently you start with a plain javascript object, and then turn it into an array of objects once the response comes from your back end.
Without any consideration for a loading period, a simple thing to do to make your application not crash until data loads in would be to make fileViewData an empty array when it is initialized, not an object with no keys. Then it would have the correct prototype to have the method map.

Conditional Rendering in JSX not hiding the Element

So i have this react component which contains a conditional rendering in some part. so far so good and this practice has been acting as expected all throughout my app. but surprisingly the element we're talking is not getting hidden as a result of change in condition.let me provide you with a minimal representation of relevant code because the original component is too lengthy and cumbersome.
import React from 'react';
import AnotherComponent from '../components/AnotherComponent';
export class TheComponent extends Component {
/*
1. props.data is coming from mapping component state to redux connect
2. connect file and selectors are alright, because other parts of this component
work as expected, and even same props.data is used elsewhere in the component
3. a method wihtout input as in showAnotherComponent = () => false; will hide the
AnotherComponent element successfully. but even sth like
showAnotherComponent = (data) => false; will not work!!!
4. props.data is properly injected to the render method, console.log(props.data) in reder
method will display updated value.
5. props.data is never null or undefined and so on ..
*/
showAnotherComponent = data => data.flag === 'AAA';
render() {
console.log(this.props.data); // will show the updated props.data
return (
<div className="row">
<div className="col-md-10">
<h1>Some heading</h1>
</div>
<div className="col-md-2">
{/* the element in next line will always show up invariably, whatever the
content of props.data. tried ternary, same result. */}
{this.showAnotherComponent(this.props.data) && <AnotherComponent />}
</div>
</div>
);
}
}
export default TheComponent;
Unfortunately creating a fully working sample is a bit hard, considering all the third party dependencies and the redux wiring. Nevertheless, if you have ever run into similar situation and got to the bottom of it, please share your experience with me.
note: Updated props.data is passed to the component normally. in the react dev tools it shows up and in redux dev tools history of state is quite healthy. the only problem here is that the conditional won't hide the element in a falsy state.
UPDATE. the reason for this weird rendering was a dynamic loop in the same component rendering this AnotherComponent regardless of the flag value. what made it hard to pin down was that it was rendering it in map and passing index of dynamic string content. anyhow, thank you all and sorry for the possible misleading question.
It is working fine here.
You should check the data in your props if there is any flag key or not and if there is a flag key check that if it is AAA or not.
class App extends React.Component {
constructor() {
super();
this.state = {
data: "aa"
};
}
/*
1. props.data is coming from mapping component state to redux connect
2. connect file and selectors are alright, because other parts of this component
work as expected, and even same props.data is used elsewhere in the component
3. a method wihtout input as in showAnotherComponent = () => false; will hide the
AnotherComponent element successfully. but even sth like
showAnotherComponent = (data) => false; will not work!!!
4. props.data is properly injected to the render method, console.log(props.data) in reder
method will display updated value.
5. props.data is never null or undefined and so on ..
*/
showAnotherComponent = data => data.flag === "AAA";
render() {
console.log(this.props.data); // will show the updated props.data
return (
<div className="row">
<div className="col-md-10">
<h1>Some heading</h1>
</div>
<div className="col-md-2">
{/* the element in next line will always show up invariably, whatever the
content of props.data. tried ternary, same result. */}
{this.showAnotherComponent({ flag: this.state.data }) && (
<div>asdsdasd</div>
)}
</div>
<button onClick={() => this.setState({ data: "AAA" })}>Toggle</button>
</div>
);
}
}
ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Click on Toggle and the Data will be displayed.

Categories

Resources