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.
Related
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>
);
If I want to call API after the first rendering of component, I know we have useEffect hook to call the API method. (I am talking about functional components only. No class component).
Is there any way, I can call the API before my component renders the first time.
The reason for this question is, If some UI part is dependent on API, I do not want to show any incomplete information to the user on the first render also, which will be changed once I get the data from API.
This seems to be a bad experience with UI.
Edit: I got a couple of advice to use useLayoutEffect or any consumable flag to check if it is rendered or not. I have checked useLayoutEffect does not work, and by using the consumable flag, we are increasing the complexity only.
Do we have any better way for this?
I think useLayoutEffect can be used for something like this, by passing in an empty array as second argument. useLayoutEffect(() => {...}, []);
Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint.
Although you can always fetch the data in the parent component and pass it as props. Or - if you don't mind it being an experimental feature for now - React Suspense is trying to solve this exact problem.
There are no correct ways to make API call before component rendered from the same component.
You may preferred make API call in parent component and render presentation component when and only when have consumable data.
Another workaround for such case is keep consumable flag inside component, make request inside useEffect, render nothing or some kind loader and render something only when request completed with success.
on calling api it is not responding exact on its first render but giving exact response when it's being hit second time
You can have a spinner or loading component be rendered first conditionally (isLoading for example):
if(isLoading) return <Spinner />
and have the api call set (isLoading) to false on status 200 for example.
Just came across something, which may help someone in future. So we can use some library but the specific one I would mention here is React Query
React query does exactly what we are trying to achieve, the hooks like useQuery fetch data as soon as rendering starts so you don’t have to wait until react loads the entire component as follows
// with react query
const { status, data, error, isFetching } = useQuery(
['data'],
async () => {
const data = await (
await fetch(`${API_BASE_URL}/data`)
).json()
return data
}
)
// without react query
useEffect(() => {
try {
setLoading(true)(async () => {
const data = await (await fetch(`${API_BASE_URL}/data`)).json();
setData(data);
})();
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
}, []);
Here is the article link if you want to read
I have two state items inside redux store, both of which fetches data from the API.
componentDidMount() {
this.props.postMDBConfig(`https://api.themoviedb.org/3/configuration?api_key=${this.props.apiKey}`);
this.props.postMoviePopular(`https://api.themoviedb.org/3/movie/popular?api_key=${this.props.apiKey}&language=en-US&page=1®ion=US`)
}
But as soon as I pass (or even use in current component) the information down to a child and use the data, it does not fetch.
render() {
return (
<ItemCarousel MDBConfig={this.props.config} items={this.props.moviesPopular}/>
);
}
class ItemCarousel extends React.Component {
render() {
const slider = (
<AwesomeSlider cssModule={AwesomeSliderStyles}>
<div data-src={`${this.props.config.images.secure_base_url}original${this.props.items[0].poster_path}`}> </div>
</AwesomeSlider>
);
return (
<div>{slider}</div>
);
}
}
Here's what the API fetch looks like when I use vs. don't use the data:
https://ibb.co/G00jzHW (with data not using them)
https://ibb.co/xzkF89R (no data when using the state variables)
I suspect it has something to do with the rendering lifecycle order, but I have already tried componentWillMount and it still does not work compared to componentDidMount.
I think you can have one state variable such as loading initially true. Once your api calls get resolved then you can update the state variable as false.
Now meanwhile you can add this loading check on your component. It will help you out to render the component using the API data.
render() {
const itemCarousel = (this.state.loading) ? '' : <ItemCarousel MDBConfig=
{this.props.config} items={this.props.moviesPopular}/>
return (
{itemCarousel}
)
}
Hope this helps.
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>
));
}
)
}
I using Meteor 1.3 for this application together with react js and Tracker React.
I have a page to view all available users in the application. This page require user to login to view the data. If user not logged in, it will shows the login form and once logged-in the component will render the user's data.
Main component for the logic.
export default class MainLayout extends TrackerReact(React.Component) {
isLogin() {
return Meteor.userId() ? true : false
}
render() {
if(!this.isLogin()){
return (<Login />)
}else{
return (
<div className="container">
<AllUserdata />
</div>
)
}
}
}
And in the AllUserdata component:
export default class Users extends TrackerReact(React.Component) {
constructor() {
super();
this.state ={
subscription: {
Allusers : Meteor.subscribe("AllUsers")
}
}
}
componentWillUnmount(){
this.state.subscription.Allusers.stop();
}
allusers() {
return Meteor.users.find().fetch();
}
render() {
console.log('User objects ' + this.allusers());
return (
<div className="row">
{
this.allusers().map( (user, index)=> {
return <UserSinlge key={user._id} user={user} index={index + 1}/>
})
}
</div>
)
}
};
The problem is when logged in, it only shows the current user's data. All other user objects are not rendered. If I check on the console, console.log('User objects ' + this.allusers()); show objects being rendered 3 times: the first render only shows the current user's data, the second one renders data for all users (the desired result), and the third one again renders only the current user's data.
If I refresh the page, the user data will be rendered properly.
Any idea why?
React calls the render() method of components many times when it's running. If you're experiencing unexpected calls, it's usually the case that something is triggering changes to your component and initiating a re-render. It seems like something might be overwriting the call to Meteor.users.find().fetch(), which is probably happening because you're calling that function on each render. Try inspecting the value outside of the render method or, better yet, rely on tests to ensure that your component is doing what it should be :)
From https://facebook.github.io/react/docs/component-specs.html#render
The render() function should be pure, meaning that it does not modify component state, it returns the same result each time it's invoked, and it does not read from or write to the DOM or otherwise interact with the browser (e.g., by using setTimeout). If you need to interact with the browser, perform your work in componentDidMount() or the other lifecycle methods instead. Keeping render() pure makes server rendering more practical and makes components easier to think about.
See also:
https://facebook.github.io/react/docs/advanced-performance.html
https://facebook.github.io/react/docs/top-level-api.html#reactdom
https://ifelse.io/2016/04/04/testing-react-components-with-enzyme-and-mocha/