React component retrieves props just once, goes undefined when refreshed - javascript

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>
);

Related

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.

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.

Updating sibling component on an event in react js

I have a simple app which consists of a home component , an add component , Listing component. Home component has listing and add component. In add component i am adding some items to local storage and in listing component i am retrieving those items and showing as a list. Listing component doesn't have any props, it directly gets the data from local storage and returns ul list. I wanted to know, is there a way that when I save my data to local storage, at the same time my list should be updated that means there has to be some way by that if i click on save button the listing component should render with updated list. Can i trigger the render method of any component manually without using states.
this.forceUpdate() does work, but is in most cases a cheap fix which you want to avoid. You could have a the listings in two places: Local Storage for the actual saving, the state to trigger re-rendering when needed. Your app might look something like this:
constructor() {
super()
this.state = {
listings: []
}
}
componentWillMount() {
localStorage.getItem('listings')
this.setState({ listings })
}
saveListing(newListing) {
const listings = this.state.listings.concat([])
listings.push(newListing)
localStorage.setItem('listings', listings)
this.setState({ listings })
}
render() {
return(
<div>
<ul>
{
this.state.listings.map(listing => {
return(
<ListingComponent key={listing.id} listing={ ...listing } />
)
})
}
</ul>
<AddListingComponent addListing={(newListing) => this.saveListing(newListing)} />
</div>
)
}
There is a method called this.forceUpdate() to re-render , so just call this method after each element added to local storage
addItem(elm){
localStorage.yourArray.push(elm);
this.forceUpdate();
}

React- FLUX - Pass Object as Param - Undefined

I have a simple react app, I get a list of contacts from a web api and i want to display them. Each contact has a name, last name, phone etc
My app class gets the contacts then renders as
render(){
var contact= this.state.contacts[0];
return( <div><Contact label='First Contact' person={contact}/></div>
)
}
Then in my Contact class
render(){
return(
<div> {this.props.label} : {this.props.person.Name}</div>)
}
When I debug on chrome, I see that person is passed as prop, object has all the parameters, however when I run the code, on the {this.props.person.Name} I get error
Cannot read property Name of undefined
If I remove that, {this.props.label} is displayed without issue. So I can pass text as prop but not the object.
Any idea?
Edit: I can also pass the Name property as
Contact personName= {contact.Name}/>
This works but not passing the contact as object and then reading properties in the render of
my guess is (since you're using flux) that upon loading the page (initial load) the contacts on your state represents an empty array.
Try
var contact= this.state.contacts[0] || {};
and for some good tips => don't use vars, use const :-)
let your component listen to your flux store:
Make sure your flux store holds an addChangeListener and removeChangeListener function that you can call in your component so your component gets updated automatically
componentDidMount(){
myStore.addChangeListener(this._onChange);
}
componentWillUnmount(){
myStore.removeChangeListener(this._onChange);
}
_onChange = () => {
this.setState({contacts: myStore.getContacts()});
}

React JS component renders multiple times in Meteor

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/

Categories

Resources