Certain object properties undefined in React component - javascript

I have a container component which fetches weather data from https://openweathermap.org/. The container component then feeds that state data into a presentational component.
While some of the state properties exist, in the presentational component, others however are undefined. If I wrap them in a timeout however they appear.
I've been searching for why this might be but I've been unable to find any reasons why they'd be undefined after being fetched in the container component and passed into the presentational component.
CodePen: https://codepen.io/ZCKVNS/pen/wpGaMe?editors=0010
Article about presentational and container components: https://medium.com/#dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0
const App = data => {
setTimeout( () => {
console.log('clouds', data.data.clouds.all); //defined
console.log('lat',data.data.coord.lat); //defined
console.log('lon', data.data.coord.lon); //defined
}, 100);
return (
<div>
{ data.data.clouds.all } <!-- Not defined -->
</div>
);
}
class AppContainer extends React.Component {
constructor() {
super();
this.state = { data: {} };
}
componentDidMount() {
fetch( 'https://api.openweathermap.org/data/2.5/weather?zip=43055,us&appid=4e08bb16c8936bd92b4780f9e2cdf00f' )
.then( res => res.json() )
.then( data => this.setState( { data } ) );
}
render() {
return React.createElement( App, { data: this.state.data } );
}
}
ReactDOM.render(
<AppContainer />,
document.getElementById( 'container' )
);

You could just return null from App until you receive the data, or, as a convention, return some kind of UI (such as a loading icon) while the fetch is completing:
In AppContainer:
this.state = { data: null }; // so ternary operator is falsey when data isn't done fetching
Then in App:
return (
<div>
{
data.data ?
data.data.clouds.all
:
<img src={LOADING_ICON_HERE} />
}
</div>
);
What this does is check if the prop data exists. If it does then it renders data.data.clouds.all. If not, it shows a loading icon while the request finishes. If you didn't want to show anything you could shorten it to:
{
data.data &&
data.data.clouds.all
}

Related

Component sending undefined via props, because it is mounted before a database query ends

I have a problem related to the asynchronous world with react native.
I need to perform a database query, this query returns me a city vector, this vector should be sent to a picker via props.
The problem is: I perform the query within the ComponentWillUpdate function (by default it's the first function to call before mounting the screen). But even using componentWillMount the component (picker) is being assembled and sending undefined via props.
The data that should be sent is not being processed on time.
The flow of my program is being:
Starts the query (componentWillMount) -> rendering components (render) -> End of Query.
Is it possible to pause the screen mount until the query in ComponentWillUpdate ends?
I tried using async but dont work.
async componentWilldMount() {
try {
await axios.get(`${server}/getdata`)
.then(
//code
})
.catch(function (error) {
//handle error
})
} catch (err) {
// handle error
}
}
It's impossible to pause or stop the rendering the component. What you can do though is to set some property in your state like let's say data. So in your constructor you will have omething like that:
constructor(props) {
this.state = {
data: null
}
}
Then in your componentWillMount or even better componentDidMount you do:
componentDidMount() {
axios.get(`${server}/getdata`)
.then(response => {
this.setState({
data: response
})
)
.catch(function (error) {
//handle error
})
Last step is to render depending on your data so in your render method:
render() {
if(!state.data) {
return null;
}
<SomeComponent data={this.state.data} />
}
Solution : Use isReady flag in the parent component.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isReady: false,
}
}
componentDidMount() {
setTimeout(() => {
this.setState({value: "bbb", isReady: true});
}, 5000)
}
render() {
return (
<div>
{this.state.isReady && <Child value={this.state.value} />}
</div>
);
}
}
class Child extends React.Component {
constructor(props) {
super(props);
console.log(props.value);
}
render() {
return (
<div>{this.props.value}</div>
);
}
}
ReactDOM.render( < App / > , document.getElementById('root'));
<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>
<div id="root"></div>

TypeError: this.props.propName.map is not a function react js

I want to fetch JSON data, store it in state and then pass it to component through props. In the component I want to use the map function but it shows me this error :
TypeError: this.props.dataQueries.map is not a function.
This is my code:
class App extends Component {
constructor(props) {
super(props);
this.state = {
dataQueries: ''
}
}
fetchData() {
fetch('https://jsonplaceholder.typicode.com/posts', {method: "GET"}).
then(res => res.json()).
then(result => this.setState({ dataQueries: result }));
}
componentWillMount() {
this.fetchData();
}
render() {
return (
<div>
<ShowPosts dataQueries={ this.state.dataQueries } />
</div>
);
}
}
And this is my component :
class ShowPosts extends Component {
render() {
return (
<div>
{
this.props.dataQueries.map((query, index) => {
return index;
})
}
</div>
);
}
}
Initially, you're setting dataQueries to ''. Which has no map, as it's a string. It's only an array later, when your fetchData async call has completed. Nothing prevents render from being called before the async call completes.
Initialize it to [] instead, or modify render to avoid trying to use it as an array when it's not an array.
You should call this.fetchData() inside componentDidMount() lifecycle method. So when component is mounted only then you update the state with response from API. Also, you should render ShowPosts component when there are posts to render using conditional rendering.
render() {
return (
<div>
{this.state.dataQueries.length && <ShowPosts dataQueries={ this.state.dataQueries } />}
</div>
);
}
And your initial dataQueries should be an empty array. dataQueries = []

React seems like doesn't pass props to another component

I follow a tutorial on how to build a React application, and I am stacked with a kind of strange issue.
When I try to pass some information to another component, the other component getting the props but it is empty.
In my case, the index.js is like that:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import YTSearch from 'youtube-api-search';
import SearchBar from './components/search_bar';
import VideoList from './components/video_list';
const API_KEY = 'AIzaSyDdvc_zComCpdqqfmwgOsZvLOwwPEabcde';
class App extends Component {
constructor ( props ) {
super ( props );
this.state = {
videos : []
};
}
componentDidMount() {
YTSearch (
{
key : API_KEY,
term : 'surfboards'
},
videos => {
this.setState ( { videos } );
}
);
}
render () {
return (
<div>
<SearchBar />
<VideoList videos={this.state.videos} />
</div>
);
}
};
ReactDOM.render (
<App />,
document.querySelector ( '.container' )
);
and my code in the ./components/video_list.js is like this:
import React, { Component } from 'react';
import VideoListItem from './video_list_item';
class VideoList extends Component {
constructor( props ) {
super( props );
this.state = {
videoItems : []
};
}
componentDidMount() {
this.setState(
{
videoItems: this.props.videos.map(
video => {
console.log( video );
return <VideoListItem video={video} />;
}
)
}
)
}
render() {
return (
<ul className="col-md-4 list-group">{ this.state.videoItems }</ul>
);
}
}
export default VideoList;
The code seems to be very strateforward, but in reality it has issues.
If I try this statement console.log( this.state.videos ); before the return statement in the index.js I get the following output:
// Initially I get this because the API is not yet completed:
Array[0]
length: 0
__proto__: Array[0]
// And then, once the API Request it is completed I get this:
Array[5]
0: Object
etag : ""5C5HHOaBSHC5ZXfkrT4ZlRCi01A/2H00YaVLWV4Xof09xk9Q8k6vlxw""
id: Object
kind: "youtube#searchResult"
snippet: Object
__proto__: Object
1: Object
2: Object
3: Object
4: Object
length: 5
__proto__: Array[0]
At the same time if I try a console.log( props ) inside the constructor method of the VideoList component I get the following output:
Object {videos: Array[0]}
videos: Array[0]
length: 0
__proto__: Array[0]
__proto__: Object
Do you have any idea of what can be wrong ? Do you see something I don't see ?
Regarding this -
At the same time if I try a console.log( props ) inside the constructor method of the VideoList component I get the following output:
Object {videos: Array[0]}
videos: Array[0]
length: 0
__proto__: Array[0]
__proto__: Object
This is absolutely correct behaviour.
This is happening because during react component life cycle your child component gets rendered first and at that time props which you are passing to your child component will be having default or empty values ( e.g []).
Now your child gets rendered the parent rendering happens.
When parent gets rendered completely componentDidMount method of parent gets called in which you have made some ajax request to download dynamic data which in your case is video lists.
Which you are doing like this and this is also perfectly valid -
componentDidMount() {
YTSearch (
{
key : API_KEY,
term : 'surfboards'
},
videos => {
this.setState ( { videos } );
}
);
}
After the data comes in via ajax you set the state again in your parent component which causes rendering cycle to happen again.
this.setState ( { videos } );
Now your child will receive updated new video list array.
And rendering of child happens again which will be having video lists.
But since you have parent props changed and to receive new props you need to add a new life cycle method.
componentWillReceiveProps
Here you can compare old props with new props and set the states to the updated props. Which will render the child component with latest updated data.
componentWillReceiveProps(nextProps) {
this.setState(
{
videoItems: nextProps.videos.map(
video => {
console.log( video );
return <VideoListItem video={video} />;
}
)
}
)
}
In "./components/video_list.js"
Instead of setting state on componentDidMount use componentWillReceiveProps()
OR
in index.js
const API_KEY = 'AIzaSyDdvc_zComCpdqqfmwgOsZvLOwwPEabcde';
class App extends Component {
constructor ( props ) {
super ( props );
this.state = {
videos : []
};
}
componentDidMount() {
YTSearch (
{
key : API_KEY,
term : 'surfboards'
},
videos => {
this.setState ( { videos } );
}
);
}
render () {
const { videos } = this.state
return (
<div>
<SearchBar />
{videos.length ?
<VideoList videos={this.state.videos} />
: null
}
</div>
);
}
};
ReactDOM.render (
<App />,
document.querySelector ( '.container' )
);

React: Pass Firebase Data Down Via Props

I'm trying to pass some Firebase data down from one component via props to another component, but it doesn't seem to be letting me iterate over the Firebase data in the child component.
App.js
class App extends Component {
constructor(props) {
super(props);
this.state = {
games: []
};
}
componentDidMount() {
const gamesRef = firebase.database().ref('games').orderByKey();
gamesRef.once('value', snap => {
snap.forEach((childSnapshot) => {
this.state.games.push(childSnapshot.val());
})
})
}
render() {
return (
<div className="App">
<Games data={ this.state.games } />
</div>
);
}
}
Games.js
class Games extends Component {
componentDidMount() {
console.log(this.props.data); // this logs successfully
}
render() {
return (
<div className="container">
<div className="Games flex flex-end flex-wrap">
{ this.props.data.map(function (game, i) {
return (
<h1>{ game.title }</h1>
)
}) }
</div>
</div>
);
}
}
For some reason I'm having a problem when trying to map() over my props.data. It's definitely being passed down to my Games component, because it's printing the console.log(this.props.data) to the console with the data it gets back from Firebase.
Do I have to wait for my Firebase data to resolve before mapping over it, and if so how do I do this?
Any help is appreciated. Thanks in advance!
I think the problem lies with your componentDidMount in your App class. You're updating state with
this.state.games.push(childSnapshot.val());
You shouldn't do that. State should only be updated with this.setState (or at the very least you should use this.forceUpdate()), as otherwise it will not re-render. I would instead advise doing
componentDidMount() {
const gamesRef = firebase.database().ref('games').orderByKey();
let newGames;
gamesRef.once('value', snap => {
snap.forEach((childSnapshot) => {
newGames.push(childSnapshot.val());
})
})
this.setState({games: newGames});
}
This will cause a re-render of the App component, causing the new data to be passed as a prop to the Games component.

Avoid recalculating variable on every render in React

I have a component ParentToDataDisplayingComponent that is creating a few lookups to help format data for a child component based on data in a redux store accessed by the parent of ParentToDataDisplayingComponent.
I am getting some lagging on the components rerendering, where the changing state has not affected this.props.dataOne or this.props.dataTwo - the data in these lookups is guaranteed the same as last render, but the data in props is not guaranteed to be the available (loaded from the backend) when the component mounts. mapPropsToDisplayFormat() is only called after all of the data passed in through the props is available.
I would like to declare the lookup variables once, and avoid re-keyBy()ing on every re-render.
Is there a way to do this inside the ParentToDataDisplayingComponent component?
export default class ParentToDataDisplayingComponent extends Component {
...
mapPropsToDisplayFormat() {
const lookupOne = _(this.props.dataOne).keyBy('someAttr').value();
const lookupTwo = _(this.props.dataTwo).keyBy('someAttr').value();
toReturn = this.props.dataThree.map(data =>
... // use those lookups to build returnObject
);
return toReturn;
}
hasAllDataLoaded() {
const allThere = ... // checks if all data in props is available
return allThere //true or false
}
render() {
return (
<div>
<DataDisplayingComponent
data={this.hasAllDataLoaded() ? this.mapPropsToDisplayFormat() : "data loading"}
/>
</div>
);
}
}
Save the result of all data loading to the component's state.
export default class ParentToDataDisplayingComponent extends Component {
constructor(props) {
super(props)
this.state = { data: "data loading" }
}
componentWillReceiveProps(nextProps) {
// you can check if incoming props contains the data you need.
if (!this.state.data.length && nextProps.dataLoaded) {
this.setState({ data: mapPropsToDisplayFormat() })
}
}
...
render() {
return (
<div>
<DataDisplayingComponent
data={this.state.data}
/>
</div>
);
}
}
I think depending on what exactly you're checking for in props to see if your data has finished loading, you may be able to use shouldComponentUpdate to achieve a similar result without saving local state.
export default class ParentToDataDisplayingComponent extends Component {
shouldComponentUpdate(nextProps) {
return nextProps.hasData !== this.props.hasData
}
mapPropsToDisplayFormat() {
...
toReturn = data.props.dataThree
? "data loading"
: this.props.dataThree.map(data => ... )
return toReturn;
}
render() {
return (
<div>
<DataDisplayingComponent
data={this.mapPropsToDisplayFormat()}
/>
</div>
);
}
}

Categories