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' )
);
Related
I have two components, one parent one child. I am using the fetch method in componentDidMount() callback. Once I do this, I set the state with key items to that data that is pulled from the api. Once I do this it should be able to be console logged in the child component as a prop. However this is not working. What am I doing wrong here?
Parent Component:
import React, {Component} from 'react';
import Map from './maps/Map';
class Main extends Component {
constructor(props){
super(props);
this.state = {
name: "John",
items: []
}
}
componentDidMount() {
fetch('https://hn.algolia.com/api/v1/search?query=')
.then(dat => dat.json())
.then(dat => {
this.setState({
items: dat.hits
})
})
}
render() {
return (
<div>
<Map list={this.state.name} items={this.state.items}></Map>
</div>
)
}
}
export default Main;
Child Component:
import React, {Component} from 'react';
class Map extends Component {
constructor(props) {
super(props);
console.log(props.items)
}
render () {
return (
<h1>{this.props.name}</h1>
)
}
}
export default Map;
First, fetch is asynchronous. So, the fetch statement might be pending by the time you try to console.log the result inside the child constructor.
Putting the console.log inside the render method would work, because the component will be rerendered, if the state items changes.
The constructor for a component only runs one time during a lifecycle. When it does, props.items is undefined because your ajax request is in-flight, so console.log(props.items) doesn't show anything.
If you change your constructor to console.log("constructed");, you'll see one-time output (stack snippets may not show this--look in your browser console). Henceforth, componentDidUpdate() can be used to see the new props that were set when your ajax request finishes.
You could also log the props inside the render method, which will run once before the ajax request resolves and again afterwards when props.items changes.
As a side point, you have <Map list=... but the component tries to render this.props.name, which is undefined.
Also, if you aren't doing anything in the constructor (initializing state or binding functions) as here, you don't need it.
class Map_ /* _ added to avoid name clash */ extends React.Component {
constructor(props) {
super(props);
console.log("constructed");
}
componentDidUpdate(prevProps, prevState) {
const props = JSON.stringify(this.props, null, 2);
console.log("I got new props", props);
}
render() {
return (
<div>
<h1>{this.props.name}</h1>
<pre>
<ul>
{this.props.items.map((e, i) =>
<li key={i}>{JSON.stringify(e, null, 2)}</li>)}
</ul>
</pre>
</div>
);
}
}
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {name: "John", items: []};
}
componentDidMount() {
fetch('https://hn.algolia.com/api/v1/search?query=')
.then(dat => dat.json())
.then(dat => {
this.setState({items: dat.hits})
});
}
render() {
return (
<div>
<Map_
name={this.state.name}
items={this.state.items}
/>
</div>
);
}
}
ReactDOM.createRoot(document.querySelector("#app"))
.render(<Main />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="app"></div>
The only problem you have is that you are trying to use this.props.name and your Map component props are called list and items, so it will return undefined.
If you log your props in the constructor you will get the initial state of Main because the fetch hasn't returned anything yet. Remember that the constructor only runs once. So you are probably getting an empty array when you log props.items in the constructor because that's what you have in your initial state.
{
name: "John",
items: []
}
If you log the props in your render method you will see your array filled with the data you fetched, as you can see here:
https://codesandbox.io/s/stoic-cache-m7d43
If you don't want to show the component until the data is fetched you can include a boolean property in your state that you set to true once you the fetch returns a response and pass it as a prop to your component. Your component can you use that variable to show, for example, a spinner while you are fetching the data. Here's an example:
https://codesandbox.io/s/reverent-edison-in9w4
import CircularProgress from "#material-ui/core/CircularProgress"
class Main extends Component {
constructor(props) {
super(props);
this.state = {
name: "John",
items: [],
fecthed: false
};
}
componentDidMount() {
fetch("https://hn.algolia.com/api/v1/search?query=")
.then(dat => dat.json())
.then(dat => {
this.setState({
items: dat.hits,
fecthed: true
});
});
}
render() {
return (
<Map
fetched={this.state.fecthed}
list={this.state.name}
items={this.state.items}
/>
);
}
}
class Map extends Component {
render() {
return (
<div>
{this.props.fetched ? (
<div>
<h1>{this.props.list}</h1>
{this.props.items.map((item, indx) => (
<div key={indx}>Author: {item.author}</div>
))}
</div>
) : (
<CircularProgress />
)}
</div>
);
}
}
Hope this helps. Cheers!
I am running a client and server setup (react and axios api calls) And I would like to understand how to access the returned data from my child components within the React Framework. I have the connection working to the http server, however i lack the foundational knowledge of working with this.state or props.
here is effectively my app.js
import React, { Component } from 'react';
import axios from 'axios';
import ChildComponent from "./components/childComponent"
class App extends Component {
state = {
data: [],
intervalIsSet : false
};
componentDidMount() {
this.getDataFromDb();
if (!this.state.intervalIsSet) {
let interval = setInterval(this.getDataFromDb, 10000);
this.setState({ intervalIsSet: interval });
}
}
getDataFromDb = () => {
fetch("http://localhost:3001/api/getData")
.then(data => data.json())
.then(res => this.setState({ data: res.data }));
};
render() {
const { data } = this.state;
return (
<div>
<childComponent />
</div>
);
};
}
export default App;
and here is the child component. --> my intention is to simply access (or print out) my returned data from the server from within the child component.
import React, { Component } from 'react';
class ChildComponent extends React.Component {
render() {
return (
{this.props.data}
);
}
}
export default ChildComponent;
First make sure you uppercase the first letter of ChildComponent. If you want to pass data you should add that object as an attribute to the component, and then access it throught this.props. Also you need to render a single top element, and if you don't need div or any other html element, you can wrap it with React.Fragment.
Regarding to data, if its an array you can simply map through it and return data you want to see, if you want the entire object to show as a string, you can use JSON.stringify(). And if that's an object you can show only data you want.
class App extends React.Component {
//...
render() {
const { data } = this.state;
return (
<div>
<ChildComponent data={data} />
</div>
);
}
}
//for array, ex: data = ["first","name"];
class ChildComponent extends React.Component {
render() {
return (
<React.Fragment>
{this.props.data.map(item =>
<p>{item}</p>
)}
</React.Fragment>
);
}
}
//for object, ex: data = {id: 1, name: 'John'};
class ChildComponent extends React.Component {
render() {
const {data} = this.props;
return (
<React.Fragment>
<p>{data.id}</p>
<p>{data.name}</p>
</React.Fragment>
);
}
}
//for single value (string, int, etc), ex: data = "my name";
class ChildComponent extends React.Component {
render() {
return (
<React.Fragment>
<p>{this.props.data}</p>
</React.Fragment>
);
}
}
//to show object as a string (could be any object mentioned before)
class ChildComponent extends React.Component {
render() {
return (
<React.Fragment>
{JSON.stringify(this.props.data)}
</React.Fragment>
);
}
}
You can pass down the data array as the data prop to the child component. Make sure you also uppercase the first character in the component name, or it won't work properly.
A component needs to render a single top level element, so you could e.g. render a div with the JSON stringified data prop inside of it in the child component.
class App extends React.Component {
// ...
render() {
const { data } = this.state;
return (
<div>
<ChildComponent data={data} />
</div>
);
}
}
class ChildComponent extends React.Component {
render() {
return <div>{JSON.stringify(this.props.data)}</div>;
}
}
I'm trying to create an application in ReactJs based on GitHub Api.
I get this error this.props.users.items is undefined. This is my Api link https://api.github.com/search/users?q=A and I see it so that there is an object and in it an array of items. How to refer to this api to get a list of users?
This is UserList component
import React, { Component } from 'react';
import User from "./ItemUser"
class UsersList extends Component {
get users() {
return this.props.users.items.map(user => <User key={user.id} user={user}/>);
}
render() {
return (
<div>
{ this.users }
</div>
);
}
}
export default UsersList;
Since you are updating state asynchronously the initial state ie: {} an empty Object is passed as props.
Set initial state as null
and in render check whether it is updated or not and return the element
....
this.state = {
users: null
};
....
render() {
....
{this.state.users ? (
<UsersList users={this.state.users}/>
) : (
null
// or a loading placeholder
)}
....
}
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
}
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>
);
}
}