I get the catch error or type error on
{films.map((film, i) => { ...
can not read proper 'map' or undefined from the Rest Api swapi in React.
build id url to show information
Create the component
State to add api data
Binding the function fetchData to this state
Call to api function characters
Call to api function movies
Life circle to call the function
Rendering the component
Bring the data of the character to the data to show them
mapping the list
const api_url_films = 'https://swapi.dev/api/films/'
class Card extends Component {
constructor(props) {
super(props)
this.state = {
films: []
}
this.fetchDataFilms = this.fetchDataFilms.bind(this)
}
fetchDataFilms(){
fetch(api_url_films)
.then(data => (data.json()))
.then(results => {
const api_data = results
this.setState({
films: api_data
})
})
}
componentDidMount() {
this.fetchDataFilms()
}
render() {
const {films} = this.state
return(
<div>
{films.map((film, i) => {
return (
<li key={i}>{film}</li>
)
}
)}
</div>
)
}
}
I try to add in a unordered list also but doesn't work
thanks
I think html is rendered before data is fetched although the films variable is an empty array in initial state.
When films variable is set from fetching function, html will be re-rendered.
try this code. This component will show an empty box until film data is fetched.
hope this works!
render() {
let films = this.state.films
if(films == null || films == []) return <></>
return(
<div>
{films.map((film, i) => {
return (
<li key={i}>{film}</li>
)
}
)}
</div>
)
}
The response has this shape:
{
count: number,
next: any,
previous: any,
results: Object[], // <-- this is what you want
}
Update the handler to access into the response object and save the results array.
fetchDataFilms(){
fetch(api_url_films)
.then(data => (data.json()))
.then(results => {
const api_data = results
this.setState({
films: api_data.results, // <-- access the results array
})
})
}
Additionally, the film objects you are mapping are still object, so they are not valid React children. You will need to access specific properties.
{films.map((film) => {
return (
<li key={film.title}>{film.title}</li> // or director, or producer, etc...
)
}
Related
I have a config file which contains an array that gets data from redux
"datapath": [
"common.registration.rgsnInfoRs[0].value", // street name
"common.registration.rgsnInfoRs[1].value", // city
"common.registration.rgsnInfoRs[2].value", // state
"common.registration.rgsnInfoRs[3].value" // zip code
]
In my react component, when I tried to render the data from this array it only returns back the data for the zip code. How can I make my component render all of index data in my render method?
constructor(props) {
super(props);
this.state = {
// data: '',
data: []
}
}
componentDidMount(){
if(_.get(this.props, 'datapath')){
_.map(this.props.datapath, (item, i) => {
let data=_.get(this.props.state,`i`);
this.setState({data})
})
}
}
// static getDerivedStateFromProps(props, state) {
// if(_.get(props, 'datapath')){
// _.map(props.datapath, (item, i) => {
// let data=_.get(props.state,item);
// state.data = data;
// })
// }
// }
static getDerivedStateFromProps(props, state) {
if(_.get(props, 'datapath')){
_.map(props.datapath, (item, i) => {
let data=_.get(props.state,item);
this.setState((prevState) => {
return { data: [...prevState.data, data] }
});
})
}
}
render() {
const {type, label} = this.props;
return (
type === "grid" ? (
<Grid.Column style={style}>
<strong>{label ? `${label}:` : null}</strong> {this.state.data}
</Grid.Column>
) : (
null
)
)
}
Your initial data type for this.state.data is an array. But in the following line, it gets the value of the item (in this case, it's either street name, city, state or zipcode). These values are string type and override the this.state.data datatype from array to string.
let data=_.get(props.state, `i`); // This is incorrect
You should append the value in the state data instead of assign in componentDidMount
componentDidMount() {
if(_.get(this.props, 'datapath')){
_.map(this.props.datapath, (item, i) => {
let data=_.get(this.props.state,`i`);
this.setState((prevState) => {
return { data: [...prevState.data, data] }
});
})
}
}
Now Let's look at getDerivedStateFromProps function. This is a static function and you can not use this inside a static function. According to the react docs, this function returns the updated state.
static getDerivedStateFromProps(props, state) {
const newData = [];
if(_.get(props, 'datapath')) {
_.map(props.datapath, (item, i) => {
let data=_.get(props.state,item);
newData.push(data);
});
return { data: newData };
}
}
I think that you are not asking the right questions. It is generally a bad idea to store data from props in state because it can lead to stale and outdated data. If you can already access it from props, then you don't need to also have it in state.
Your component receive a prop state which is an object and a prop datapath which is an array of paths used to access properties on that state. This is already weird and you should look into better patterns for accessing redux data through selector functions.
But if we can't change that, we can at least change this component. You can convert an array of paths to an array of values with one line of code:
const data = this.props.datapath.map( path => _.get(this.props.state, path) );
All of the state and lifecycle in your component is unnecessary. It could be reduced to this:
class MyComponent extends React.Component {
render() {
const { type, label = "", state, datapath = [] } = this.props;
const data = datapath.map((path) => _.get(state, path, ''));
return type === "grid" ? (
<Grid.Column style={style}>
<strong>{label}</strong> {data.join(", ")}
</Grid.Column>
) : null;
}
}
I think, when you are looping through the data and setting the state you are overriding the old data present in the state already.
may be you will have to include the previous data as well as set new data in it something like this.
this.setState({data : [...this.state.data, data]})
Can someone help me out and tell me why it is not rendering my array object. The data is being fetched from my Mongoose database which gets printed on the console, but how come it is not rendering on the page.
Thanks!
class produce extends Component {
constructor() {
super();
this.state = {
loading: true,
fruits: []
};
}
componentDidMount() {
fetch('http://localhos:2000/api/fruits')
.then(response => response.json())
.then(produce => {
this.setState({ produce });
console.log(produce);
})
.catch(error => console.log(error));
}
render() {
return (
<div>
<ul>
this text above shows
{this.state.fruit.map(d => (
<li key={d.id}>{d.fruit}</li>
))}
</ul>
</div>
);
}
}
When you call the setState function with the parameter { produced }, you are creating the following state.
{
loading: true,
fruit: [],
produced: { data: [/* fruits data */] }
}
To avoid that, you need to access the property data in the API response and then assign it to the fruit property in your state.
The solution is changing the setStatecall to this.
this.setState({ fruit: produce.data })
I'm just getting started with React. As a simple exercise, I wanted to create some components for viewing data retrieved from the JsonMonk API. The API contains 83 user records and serves them in pages of 10.
I am trying to develop a component for viewing a list of users one page at a time which I called UserList. The code for it is below:
class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {
pageNumber: 1,
users: [],
};
this.onPageNext = this.onPageNext.bind(this);
}
componentDidMount() {
this.fetchUsers(this.state.pageNumber)
.then((users) => this.setState({users: users}));
}
async fetchUsers(pageNumber) {
const response = await fetch(`https://jsonmonk.com/api/v1/users?page=${pageNumber}`);
const jsonResponse = await response.json();
return jsonResponse.data.records;
}
onPageNext() {
// ...
}
render() {
const postElements = this.state.users.map(
(props) => <User key={props._id} {...props} />);
return (
<div>
{postElements}
<div>
<button onClick={this.onPageNext}>Next</button>
</div>
</div>
);
}
}
The problem I am having pertains to the onPageNext method of my component. When the user clicks the "Next" button, I want to make a fetch for the next page of data and update the list.
My first attempt used an asynchronous arrow function passed to setState like so:
onPageNext() {
this.setState(async (state, props) => {
const nextPageNumber = state.pageNumber + 1;
const users = await this.fetchUsers(nextPageNumber);
return {pageNumber: nextPageNumber, users: users}
})
}
However, it does not seem React supports this behavior because the state is never updated.
Next, I tried to use promise .then syntax like so:
onPageNext() {
const nextPageNumber = this.state.pageNumber + 1;
this.fetchUsers(nextPageNumber)
.then((users) => this.setState({pageNumber: nextPageNumber, users: users}));
}
This works but the problem here is that I am accessing the class's state directly and not through setState's argument so I may receive an incorrect value. Say the user clicks the "Next" button three times quickly, they may not advance three pages.
I have essentially run into a chicken-or-the-egg type problem. I need to pass a callback to setState but I need to know the next page ID to fetch the data which requires calling setState. After studying the docs, I feel like the solution is moving the fetch logic out of the UsersList component, but I'm not entirely sure how to attack it.
As always, any help is appreciated.
You need to change onPageNext as below:
onPageNext() {
this.setState( prevState => {
return {pageNumber: prevState.pageNumber + 1}
}, () =>{
this.fetchUsers(this.state.pageNumber).then(users => this.setState({users: users}) )
});
}
Here is the Complete Code:
import React from "react";
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {
pageNumber: 1,
users: [],
};
this.onPageNext = this.onPageNext.bind(this);
}
componentDidMount() {
this.fetchUsers(this.state.pageNumber)
.then((users) => {
console.log(users, 'users');
this.setState({users: users})
}
);
}
async fetchUsers(pageNumber) {
const response = await fetch(`https://jsonmonk.com/api/v1/users?page=${pageNumber}`);
const jsonResponse = await response.json();
return jsonResponse.data.records;
}
onPageNext() {
this.setState( prevState => {
return {pageNumber: prevState.pageNumber + 1}
}, () =>{
this.fetchUsers(this.state.pageNumber).then(users => this.setState({users: users}) )
});
}
render() {
const postElements = this.state.users.map(
(user) => <User key={user._id} {...user} />);
return (
<div>
{postElements}
<div>
<button onClick={this.onPageNext}>Next</button>
</div>
</div>
);
}
}
function User(props) {
return (
<div>
<div style={{padding: 5}}>Name: {props.first_name} {props.last_name}</div>
<div style={{padding: 5}}>Email: {props.email}</div>
<div style={{padding: 5}}>Phone: {props.mobile_no}</div>
<hr/>
</div>
);
}
Here is the Code Sandbox
My React app is supposed to display nearby shops to each user depending on their location.
What I'm trying to do is to get the latitude/longitude by window.geolocation in the componentDidMount() in which I'll call a REST API with the latitude/longitude as params to retrieve nearby shops.
I'm just trying to figure out how to organize and where to put the logic.
The idea is that you should try to use React's state before reaching for redux. It's both simpler and keeps as much of the state local as possible.
The React state stores the status of the API call (not made yet, success or failure) and the corresponding information about success (results) or failure (errorMessage). The important point is that your render function must explicitly handle all 3 cases.
class NearbyShops extends React.Component {
constructor(props) {
super(props);
this.state = {
queryStatus: 'initial',
results: [],
errorMessage: '',
}
}
async componentDidMount() {
try {
const results = await fetch('you.api.url/here');
this.setState(prevState => ({...prevState, results, queryStatus: 'success'}))
} catch (e) {
this.setState(prevState => ({...prevState, queryStatus: 'failure', errorMessage: e}))
}
}
render() {
const {results, queryStatus, errorMessage} = this.state;
if (queryStatus === 'success') {
return (
<div>{/* render results here */}</div>
)
} else if (queryStatus === 'failure') {
return (
<div>{errorMessage}</div>
)
} else {
return (
<div>Loading nearby shops...</div>
)
}
}
}
TLDR - You should not implement/call your reusable component within your data fetching request. The correct way to do so is having a container component that is responsible for fetching the data, storing it in state, and passing it down to a presentational component that is responsible for rendering the data as UI. (example below)
Initially what I was trying to do (I assumed components are converted to HTML and can be created as string - BAD PRACTICE):
fetch('http://localhost:8000/api/Books')
.then(response => {
if (!response.ok) {
throw Error('Network request failed.')
}
return response;
})
.then(data => data.json())
.then(data => {
let output = ''
for (let book of data) {
output += (
<Book id={book._id}
title={book.title}
author={book.author}
genre={book.genre}
read={book.read} />
);
}
console.log('parsed json', data);
document.getElementById('database').innerHTML = output;
}, (ex) => {
this.setState({
requestError : true
});
console.log('parsing failed', ex)
})
My question was:
How do i make this work? how do I Implement my Book component inside the GET request to render a reusable Book for every object in the database?
My solution
having <BooksContainer /> as my container component books data is stored in state and is iterated using .map to render each book object as a <Book /> component - our presentational function.
//BooksContainer.js - responsible for data fetching and storing
class BooksContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
books: [];
};
}
componentDidMount () {
fetch('http://localhost:8000/api/Books')
.then(response => {
if (!response.ok) {
throw Error('Network request failed.')
}
return response;
})
.then(data => data.json())
.then(data => {
this.setState({
books: data
});
console.log('parsed json', data);
}, (ex) => {
this.setState({
requestError : true
});
console.log('parsing failed', ex)
})
}
render () {
return (
<div className="books-container">
<ul className="books-list">
{this.state.books.map(book => <Book {...book} />}
</ul>
</div>
)
}
//Book.js - responsible for presenting each book's data
const Book = (props) => (
<li>
<span className="title">{this.props.title}</span>
<span className="author">Author: {this.props.author</span>
<span className="genre">Genre: {this.props.genre}</span>
<span className="read">Read: {this.props.read ? "Yes" : "No"}</span>
</li>
)
You should store the result of the api inside the state and render the <Book /> inside the render function.
Btw it would be better to separate your components:
The container that do all the logic (data fetching, ...).
The presentational component that renders the ui.
You can even go further by using redux for handling a global state, and redux-saga for handling the side effects (api calls)
EDIT
Here is a small example.
The presentational component:
const BookListing = ({ books }) => (
<ul>
{books.map(book => <li key={book.id}>{book.title}</li>)}
</ul>
);
The container component:
class Books extends Component {
constructor(props) {
super(props);
this.state = {books: []};
}
componentDidMount() {
fetch('http://localhost:8000/api/Books')
.then(data => data.json())
.then((data) => { this.setState({ books: data }) });
}
render() {
return <BookListing books={this.state.books} />;
}
}
1) Create a container component where you can do your AJAX request and then save the result in your local state, say books in the render method.
2) Pass the this.state.books into your <Book /> component where you can iterate over the array.
3) (Optional but recommended). You can create another component like <BookDetail /> to render individual book item