In my react app I have a main App component where I fetched data from api in componentDidMount method and saved it in its state. Then I passed that state to another component in App. However, when I consume that data from prop it is showing undefined.
Another strange this I didn't is when I console.log state in App components render method them first I get an empty array then after a second another array with the data in it. Please help me here
The code goes like this-
class App extends React.Component {
constructor() {
super();
this.state = {
data: []
};
}
componentDidMount() {
this.fetchData();
}
fetchData() {
fetch(
`https://api.themoviedb.org/3/movie/popular?api_key=${
process.env.REACT_APP_API_KEY
}&language=en-US&page=1`
)
.then(res => res.json())
.then(data => {
this.setState({ data });
})
.catch(err => console.log(err));
}
render() {
console.log(this.state.data);
return (
<div>
<Movie title="Popular" results={this.state.data} />
</div>
);
}
}
export default App;
this.state.data is undefined in Movie component which is like this
function Movie(props) {
return (
<p>{props.data.results[0].title}</p>
)
}
You are passing results and title as the props to the <Movie> component yet trying to fetch data prop.
component:
<Movie title="Popular" results={this.state.data} />
So you need to fetch the results prop, not the data one.
fixed:
function Movie(props) {
return (
<p>{props.results[0].title}</p>
)
}
additionaly:
If you're already passing the title prop, why not just use that prop for the title?
function Movie(props) {
return (
<p>{props.title}</p>
)
}
Your prop is results so you need to reference that in your component:
function Movie(props) {
return (
<p>{props.results[0].title}</p>
)
}
In your App component you might also want to add a check in render to show a loading message or spinner if the data load hasn't been resolved when the component initially renders:
if (!this.state.data.length) return <Spinner />;
Related
When I am fetching the data from API, If I get Ok I will render the component to display the data,
If an error has occured, I want to display a custom message for different components, and during fetching the data I want to show a loading message.
export default function App() {
const { data: products, loading, error } = useFetch(
"products?category=shoes"
);
if (error) return <div>Failed to load</div>;
if (loading) return <div>wait is Loading</div>;
return (
<>
<div className="content">
<section id="products">{products.map((product) =>{return <div>product.name</div>})}</section>
</div>
</>
);
}
I am planning to have a fetch call in multiple components. So, I was thinking If I could write the above logic in one place, like an HOC or Rendered Props and reuse it.
What I tried: but failed
HOC wont work because you cant call hooks in normal functions. It has to be a Component.
Rendered Props wont work as it gives this error:
Error: Rendered more hooks than during the previous render.
Below is my failed rendered props code
const ShowData= function ({ data: products }) {
return (
<div>
{products.map(product => {return <div>product.name</div>})}
</div>
);
};
function SearchCount({ count }) {
return <h3>Search Results: {count} items found</h3>;
}
const Wrapper = function () {
return (
<LoadingAndError
render={ShowData}
params={{ url: "products?category=shoes" }}
></LoadingAndError>
);
};
export default Wrapper;
Loading and error logic is moved to the LoadingAndError component
const LoadingAndError = function (props) {
const { url } = props.params;
const { data: products, loading,error} = useFetch(url);
if (loading)
return <h1>Loading</h1>;
if (error) return <h1>Error</h1>;
return props.render({ data: products });
};
return props.render({ data: products });
This line is calling props.render as a function, but you've passed in a component. Calling component directly can cause exactly the error you're seeing. If you want props.render to be used like this, then you should pass in a function which creates an element, like this:
<LoadingAndError
render={(props) => <ShowData {...props} />}
params={{ url: "products?category=shoes" }}
></LoadingAndError>
Alternatively, if you want to keep passing in render={ShowData}, then change loadingAndError to create an element:
const loadingAndError = function (props) {
const { url } = props.params;
const { data: products, loading,error} = useFetch(url);
if (loading)
return <h1>Loading</h1>;
if (error) return <h1>Error</h1>;
const Component = props.render
return <Component data={products}/>
}
I'm very new to JS and ReactJS and I try to fetch an endpoint which gives me a JSON Object like this :
{"IDPRODUCT":4317892,"DESCRIPTION":"Some product of the store"}
I get this JSON Object by this endpoint :
http://localhost:3000/product/4317892
But I dont how to use it in my react application, I want to use those datas to display them on the page
My current code looks like this but it's not working and I'm sure not good too :
import React, {Component} from 'react';
class Products extends Component {
constructor(props){
super(props);
this.state = {
posts: {}
};
};
componentWillMount() {
fetch('http://localhost:3000/product/4317892')
.then(res => res.json())
.then(res => {
this.setState({
res
})
})
.catch((error => {
console.error(error);
}));
}
render() {
console.log(this.state)
const { postItems } = this.state;
return (
<div>
{postItems}
</div>
);
}
}
export default Products;
In the console.log(this.state) there is the data, but I'm so confused right now, dont know what to do
Since I'm here, I have one more question, I want to have an input in my App.js where the user will be able to type the product's id and get one, how can I manage to do that ? Passing the data from App.js to Products.js which is going to get the data and display them
Thank you all in advance
Your state doesn't have a postItems property which is considered undefined and react therefore would not render. In your situation there is no need to define a new const and use the state directly.
Also, when you setState(), you need to tell it which state property it should set the value to.
componentWillMount() {
fetch('http://localhost:3000/product/4317892')
.then(res => res.json())
.then(res => {
this.setState({
...this.state, // Not required but just a heads up on using mutation
posts: res
})
})
.catch((error => {
console.error(error);
}));
}
render() {
console.log(this.state)
return (
<div>
<p><strong>Id: {this.state.posts.IDPRODUCT}</strong></p>
<p>Description: {this.state.posts.DESCRIPTION}</p>
</div>
);
}
I have got 3 names for the same thing in your js: posts, postItems and res.
React can not determine for you that posts = postItems = res.
So make changes like this:
-
this.state = {
postItems: {}
};
-
this.setState({
postItems: res
});
-
return (
<div>
{JSON.stringify(postItems)}
<div>
<span>{postItems.IDPRODUCT}</span>
<span>{postItems.DESCRIPTION}</span>
</div>
</div>
);
{postItems["IDPRODUCT"]}
Will display the first value. You can do the same for the other value. Alternatively, you can put
{JSON.stringify(postItems)}
With respect to taking input in the App to use in this component, you can pass that input down through the props and access it in this component via this.props.myInput. In your app it'll look like this:
<Products myInput={someInput} />
In my componentDidMount(), I am calling an actionCreator in my redux file to do an API call to get a list of items. This list of items is then added into the redux store which I can access from my component via mapStateToProps.
const mapStateToProps = state => {
return {
list: state.list
};
};
So in my render(), I have:
render() {
const { list } = this.props;
}
Now, when the page loads, I need to run a function that needs to map over this list.
Let's say I have this method:
someFunction(list) {
// A function that makes use of list
}
But where do I call it? I must call it when the list is already available to me as my function will give me an error the list is undefined (if it's not yet available).
I also cannot invoke it in render (before the return statement) as it gives me an error that render() must be pure.
Is there another lifecycle method that I can use?
Just do this, and in redux store please make sure that initial state of list should be []
const mapStateToProps = state => {
return {
list: someFunction(state.list)
};
};
These are two ways you can play with received props from Redux
Do it in render
render() {
const { list } = this.props;
const items = list && list.map((item, index) => {
return <li key={item.id}>{item.value}</li>
});
return(
<div>
{items}
</div>
);
}
Or Do it in componentWillReceiveProps method if you are not using react 16.3 or greater
this.state = {
items: []
}
componentWillReceiveProps(nextProps){
if(nextProps.list != this.props.list){
const items = nextProps.list && nextProps.list.map((item, index) => {
return <li key={item.id}>{item.value}</li>
});
this.setState({items: items});
}
}
render() {
const {items} = this.state;
return(
<div>
{items}
</div>
);
}
You can also do it in componentDidMount if your Api call is placed in componentWillMount or receiving props from parent.
I'm unable to get the props values from Redux, I checked in reducers, store, action JS files everything working fine. Even I can see the state printed correctly in console.log("MAPSTOSTATEPROPS", state) inside mapsStateToProps function below.
But when I give this.props.posts to access the values I'm not getting them. Not sure, where I'm doing wrong here. Can someone help me?
posts.js:
class Posts extends Component {
componentWillMount() {
this.props.fetchPosts();
**console.log(this.props.posts); // gives empty array result**
}
render() {
return (
<div>
<h2>Posts</h2>
</div>
)
}
componentDidMount() {
console.log("DID mount", this.props);
}
}
Posts.propTypes = {
fetchPosts: PropTypes.func.isRequired,
posts: PropTypes.array
}
const mapsStateToprops = (state) => {
**console.log("MAPSTOSTATEPROPS", state)** // gives correct result from current state
return {
posts: state.items
}
};
export default connect(mapsStateToprops, {fetchPosts}) (Posts);
class Posts extends Component {
/* use did mount method because all will-methods
are will be deprecated in nearest future */
componentDidMount() {
/* on component mount you initiate async loading data to redux store */
this.props.fetchPosts();
/* right after async method you cannot expect to retrieve your data immediately so this.props.posts will return [] */
console.log(this.props.posts);
}
render() {
/* here you getting posts from props*/
const { posts } = this.props;
return (
<div>
<h2>Posts</h2>
/* here you render posts. After async method will be completed
and dispatch action with retrieved data that will cause rerender
of component with new props */
{posts.map(post => (<div>post.name</div>))}
</div>
)
}
}
Posts.propTypes = {
fetchPosts : PropTypes.func.isRequired,
posts : PropTypes.array
}
const mapsStateToprops = (state) => {
/* first call will log an empty array of items,
but on second render after loading items to store they will
appear in component props */
console.log("MAPSTOSTATEPROPS", state)
return {
posts : state.items || [];
}
This may be because of async nature of fetchPost() method.Try to log it inside render() and handle the async nature using promises.
I'm pretty new in React and need some help.
I wanted display data from a movie database based on the search term. I'm using fetch inside my getMovies methode to get the data. The data is stored in data.Search but I don't know how to access it and store it in a variable.
class DataService
{
getMovies (searchTerm) {
//let dataStorage; //store somehow data.Search inside
fetch("http://www.omdbapi.com/?s=" + searchTerm, {
method: 'get'
})
.then((resp) => resp.json())
.then(function(data) {
return data.Search;
}).catch(function(error) {
console.log(error);// Error :(
});
}
//return dataStorage; //return data.Search
}
The below code is the correct react's way for your case, as simple as this:
import React from 'react';
export default class DataService extends React.Component {
constructor(props) {
super(props);
this.state = {
search_data: [], //for storing videos
};
this.getMovies = this.getMovies.bind(this); //bind this function to the current React Component
}
getMovies (searchTerm) {
fetch("http://www.omdbapi.com/?s=" + searchTerm, {
method: 'get'
})
.then((resp) => resp.json())
.then((data) => { //so that this callback function is bound to this React Component by itself
// Set state to bind search results into "search_data"
// or you can set "dataStorage = data.Search" here
// however, fetch is asynchronous, so using state is the correct way, it will update your view automatically if you render like I do below (in the render method)
this.setState({
search_data: data.Search,
});
});
}
componentDidMount() {
this.getMovies(); //start the fetch function here after elements appeared on page already
}
render() {
return (
{this.state.search_data.map((video, i) =>{
console.log(video);
// please use the correct key for your video below, I just assume it has a title
return(
<div>{video.title}</div>
)
})}
);
}
}
Feel free to post here any errors you may have, thanks
There are several ways of doing asynchronous tasks with React. The most basic one is to use setState and launch a Promise in the event handler. This might be viable for basic tasks but later on, you will encounter race conditions and other nasty stuff.
In order to do so, your service should return a Promise of results. On the React side when the query changes, the service is called to fetch new results. While doing so, there are few state transitions: setting loading flag in order to notify the user that the task is pending and when the promise resolves or rejects the data or an error is stored in the component. All you need is setState method.
More advanced techniques are based on Redux with redux-thunk or redux-saga middlewares. You may also consider RxJS - it is created especially for that kind of stuff providing debouncing, cancellation and other features out of the box.
Please see the following example of simple search view using yours DataService.
class DataService
{
//returns a Promise of search results, let the consumer handle errors
getMovies (searchTerm) {
return fetch("http://www.omdbapi.com/?s=" + searchTerm, {
method: 'get'
})
.then((resp) => resp.json())
.then(function(data) {
return data.Search;
})
}
}
const SearchResults = ({data, loading, error}) =>
<div>
{
data && data.map(({Title, Year, imdbID, Type, Poster}) =>
<div key={imdbID}>
{Title} - {Year} - {Type}
<img src={Poster} />
</div>
)
}
{loading && <div>Loading...</div>}
{error && <div>Error {error.message}</div>}
</div>
class SearchExample extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this)
this.state = {
data: [],
loading: false
};
}
handleChange(event) {
const service = new DataService();
//search query is target's value
const promise = service.getMovies(event.target.value);
//show that search is being performed
this.setState({
loading: true
})
//after the promise is resolved display the data or the error
promise.then(results => {
this.setState({
loading: false,
data: results
})
})
.catch(error => {
this.setState({
loading: false,
error: error
})
})
}
render() {
return (
<div>
<input placeholder="Search..." onChange={this.handleChange} type="search" />
<SearchResults data={this.state.data} loading={this.state.loading} error={this.state.error} />
</div>
)
}
}
ReactDOM.render(<SearchExample />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>