I am working on a movie list search app and then later might try to add lazy loading. This is just for POC purpose. Might add it to my portfolio later.
So, I have first created a global api.js where I will put the API calls with a callback and then call an API using callbacks from the components.
const PageOneApi = (callback, errorCallback) => {
axios("https://run.mocky.io/v3/36e4f9ce-2e48-4d44-9cf8-57f6900f8e12")
.then((response) => {
console.log("response from page one api", response);
callback(response);
})
.catch((err) => {
console.log("error from page one api", err);
errorCallback(err);
});
};
export const movieListApi = {
PageOneApi,
};
I have a movieList component:
function MovieList() {
const [pageOneData, setPageOneData] = useState({});
useEffect(async () => {
await movieListApi.PageOneApi(
(res) => {
const pageOneData = res.data.page;
setPageOneData(pageOneData);
console.log("page one data: ", pageOneData);
},
(err) => {
console.log(err);
}
);
}, []);
const movieList = pageOneData.contentItems;
console.log(movieList);
return (
<div>
<h4 className="text-white p-5">{pageOneData.title}</h4>
<div className="grid grid-flow-row grid-cols-3 grid-rows-3 gap-7 m-5">
{movieList.map((movie) => {
console.log("movie", movie);
})}
</div>
</div>
);
}
Now, the problem is that I am unable to iterate through movieList, even though it's an array.
Since pageOneData is initially an empty object, pageOneData.contentItems will be undefined in the first render cycle. causing movieList.map to fail as your useEffect call is made after the initial render and that fires an API which again is async and will take time to fetch the result
You can use default value for movieList to solve the error
const movieList = pageOneData.contentItems || [];
or
Optional chaining (?.)
<div className="grid grid-flow-row grid-cols-3 grid-rows-3 gap-7 m-5">
{movieList?.map((movie) => {
console.log("movie", movie);
})}
</div>
Initially pageOneData is blank object so pageOneData.contentItems will return undefined so at first render react will throw error.
To solve this, you can use optional chaining so try something like below:-
{movieList?.map((movie) => {
console.log("movie", movie);
})}
Related
Im trying to figure out how to handle different API responses and map over them. So i can present them in my application.
Handeling the getArrayofObjects function is no problem at all. However, the Nasa API contains object structure with nested objects within.
{
element_count: 25,
links: {
next: https:link1.com,
prev: https:linkprev.com,
self: https:linkself.com }
},
near_earth_objects: {
2015-09-08: [{absolute_magnitude_h: 19.3,
close_approach_data: [{close_approachdate: 2015:01:12}]},
estimated-diameter: {}, id: 132342323, links: {}]
i want to map over this object however react gives me the error mentioned in the title. How can i map over this data and allow it to be presented orderly in the application?
I have the following component:
import styles from '../styles/Home.module.css'
const axios = require('axios')
import { useState } from 'react'
export default function Home() {
const [nasa, setNasa] = useState([])
const [posts, setPosts] = useState([])
const returnNestedObjects = () => {
axios
.get(
'https://api.nasa.gov/neo/rest/v1/feed?start_date=2015-09-07&end_date=2015-09-08&api_key=hqIAqlEjXdGOE4K0H44Oj0Bq20tID1ytS3IdYuT4'
) // goed kijken naar de API of het de juiste data heeft als format
.then((response) => {
setNasa(response.data)
console.log('Objects of nested NASA objects', response.data) // returs object with nested objects // TODO uitvogelen hoe je deze mapped
})
.catch((error) => {
setIsError(true)
console.log(error)
})
}
const results = Object.keys(nasa).map((key) => {
console.log(key) // 👉️ name, element_count, near_earth_objects, links console logt the key
console.log(nasa[key]) // 👉️ {next: somelink.com/} 25, {2015-09-08} console logt the value
return { [key]: nasa[key] } // returns key + value
})
console.log('results', results)
const stringifyObjects = JSON.stringify(results)
const getArrayofObjects = () => {
axios
.get('https://jsonplaceholder.typicode.com/posts') // returns Array
.then((response) => {
setPosts(response.data)
console.log('Array Of objects', response.data) // returs Array with nested objects => .map to map over them
})
.catch((error) => {
setIsError(true)
console.log(error)
})
}
return (
<div className={styles.container}>
<main className={styles.main}>
<h1 className={styles.title}>
Welcome to{' '}
<span className={styles.headerTitle}>
<h2>Testing API API</h2>
</span>
</h1>
<div className={styles.buttoncontainer}>
<button onClick={returnNestedObjects}>Nested Objects</button>
<button onClick={getArrayofObjects}>Array of Objects</button>
</div>
<ul>
{posts.map((item) => (
<li>{item.title}</li>
))}
</ul>
<ul>
{results ? (
<ul>
<li>NASA API RESULTS: {results}</li>
</ul>
) : (
<p>Loading...</p>
)}
</ul>
</main>
</div>
)
}
results is Object , no reactElement
Somehow my fetch only fires every second time, I select an item from list:
export default function App() {
...
useEffect(() => {
fetchBookData(books).then((payload) => setPayload(payload));
}, [books]);
const fetchBooksBySubject = () => {
const options = {
method: `GET`,
};
fetch(`${server}/books?subjects_like=${selectedValue}`, options)
.then((response) => {
if(response.ok){
return response.json().then(setBookList)
}
throw new Error('Api is not available')
})
.catch(error => {
console.error('Error fetching data: ', error)
setError(error)
})
}
const handleChange = e => {
setSelectedValue(e);
fetchBooksBySubject();
};
if (!payload) {
return <div>Please start the server</div>;
}
console.log(bookList)
return (
<div className="bg-blue-200">
<div className="container mx-auto py-36 bg-blue-200">
<div className="mt-12 px-96">
<SubjectSelector
options={payload}
selectedVal={selectedValue}
handleChange={handleChange}/>
<ShowcaseBooks selectedVal={selectedValue} />
<ul>
{bookList?.map((item) => {
return(
<li>{item.title}</li>
)
}
)}
</ul>
</div>
</div>
</div>
)
}
So fetchBooksBySubject delivers the bookList first as emty array, then logs correctly, howeverso I have to select two times, to see <li>{item.title}</li> updated. Why is this and how to fix it?
Your selectedValue isn't really updated yet (it will happen only at the next rerender) at the point when you call fetchBooksBySubject. It happens because setSelectedValue doesn't update the associated value immediately.
As a result, fetch(`${server}/books?subjects_like=${selectedValue}`, options) gets an old value.
You could fix it with i.e. useEffect hook:
useEffect(() => {
fetchBooksBySubject();
}, [selectedValue])
instead of calling fetchBooksBySubject immediately in the callback
So I have an app which uses Movies API. I make an API request, then I pass this data to an array using useState hook, basically my code looks like the following:
const App = () => {
type MovieType = { //declaring type
rate: string,
title: string,
tagline: string,
date: string,
};
interface MovieProps { //extending an interface with MovieType
movies: MovieType[],
}
const [movies, setMovies] = useState<MovieType[]>([]); //useState for setting an array to data from api
useEffect(() =>{
fetchMovies();
}, [])
async function fetchMovies() { //function for fetching movies
try{
let apikey = '{api_key}';
let url: string = 'https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=';
url = url + apikey;
const response = await axios.get<[MovieType]>(url);
setMovies(response.data);
}catch(e){
alert('Error');
}
}
return (
<div className="App">
<Header/>
<Hero movies={movies}/>
</div>
);
}
So basically, when I run the app, I get an alert with an error. I've tried renaming the useState, so it differs from props in the <Hero> component, and then I could pass data to an array. But when I do it with [movies, setMovies] it doesn't work. So I guess the problem is somewhere in passing props or type MovieType, but I can't figure out what exactly could be the problem.
Edit: an error I get in try catch:
TypeError: movies.map is not a function
I have movies.map in Container component, which gets movies from the Hero, which gets it from the App:
const Container = ({movies}: MovieProps) => {
return (
<div className="container">
{movies.map((movie) =>(
<MovieItem movie={movie} key={movie.title}/>
))}
</div>
);
};
I don't know why movies.map is not a function if movies is basically an array.
You have to add "await fetchMovies" instead of just "fetchMovies" but since you can't make useEffect async try something like this...
useEffect(() =>{
async function foo(){
await fetchMovies();
}
foo();
}, []);
Hopefully it works!
The problem is that Container component attempting to iterate over movies before fetchMovies has finished getting the data. Essentially, you're trying to iterate over an empty array. To reveal this, and for future debugging purposes, include a console.log(movies) in Container.
To fix, simply include movies.length as a check before you map over the data:
const Container = ({movies}: MovieProps) => {
return (
<div className="container">
{movies.length && movies.map((movie) =>(
<MovieItem movie={movie} key={movie.title}/>
))}
</div>
);
};
I have a list of urls, I want to fetch all of them and to return the images found in all these APIs so I can render it in react component using react-responsive-masonry. I have made my function in javascript but I am not sure how to write it in typescript and also I don't know how to render it in my component.
Here's my function
var photos_info = [];
async function get_photos(urls) {
var promises = urls.map((url) => fetch(url).then((y) => y.json()));
await Promise.all(promises).then((results) => {
photos_info = results;
return photos_info;
});
return photos_info;
}
I want to render it in src in my component
<ResponsiveMasonry columnsCountBreakPoints={columnsCountBreakPoints}>
<Masonry gutter={4}>
{
<img src={} />
}
</Masonry>
</ResponsiveMasonry>
Edit
Another method using useState and useEffect
const [photosList, setPhotosList] = useState<any>();
useEffect(() => {
const photosPromises = urls.map((url) =>
fetch(url).then((res) => res.json())
);
Promise.all(photosPromises).then((data) => {
setPhotosList(data);
});
}, []);
console.log("hi", photosList);
I tried to render a simple one just to see what is inside
<div>
{photosList.map((photo: any) => {
return <pre>{JSON.stringify(photo)}</pre>;
})}
</div>
but it gives me this error Cannot read property 'map' of undefined
I'm trying to update elements after deletion, without refreshing a page. Currently, if delete a record, need to refresh a page to see the result. As I understand, need to update useState, but I do not understand how to do it. If I loop useEffect it works but slowly, but I think it's not the best idea to loop get response.
Get all records from a database.
const PostsGetUtill = () => {
const [posts, setPosts] = useState([]);
const fetchPosts = () => {
axios.get("api/v1.0/post/get").then(response => {
console.log(response.data);
setPosts(response.data);
}).catch(function (error) {
if (error.response) {
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
console.log(error.request);
} else {
console.log('Error', error.message);
}
console.log(error.config);
});
};
useEffect(() => {
fetchPosts();
}, []); // }, [fetchPosts]); <--- working well with loop
return (
<section className="container-post">
<PostMansonry posts={posts} columns={3} />
</section>
);
};
export default PostsGetUtill;
Sort and map records
export default function PostMansonry({ posts, columns }) {
return (
<section className="masonry" style={{ gridTemplateColumns: `repeat(${columns}, minmax(275px, 1fr))` }}>
{posts.sort((a, b) => a.zonedDateTime < b.zonedDateTime ? 1 : -1).map((posts, index) =>
<MasonryPost {...{ posts, index, key: index }} />)
}
</section>
)
}
Put data to the card
export default function MasonryPost({ posts, index }) {
return (
<div key={index} className="masonry-post">
<div className="card">
<div className="card-body">
<h5 className="card-title">{posts.title}</h5>
<p className="card-text">{posts.description}</p>
<p className="card-text"><small className="text-muted"> {posts.zonedDateTime}</small></p>
<div><button type="button" onClick={(e) => PostsDeleteUtill(posts.post_Id)} className="btn btn-danger">Delete</button></div>
</div>
</div>
</div>
)
}
Deleting
const PostsDeleteUtill = async (post_Id) => {
axios.delete(`api/v1.0/post/delete/${post_Id}`).then(response => {
console.log(response);
}).catch((error) => {
if (error.response) {
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
console.log(error.request);
} else {
console.log('Error', error.message);
}
console.log('error config', error.config);
});
};
export default PostsDeleteUtill;
Basically what you need to do is, in your PostsDeleteUtill function, in the promise return of your axios.delete, you need to update your posts state, which is set in PostsGetUtill.
In order to do that, you have 2 options:
Use a global state (React Context, Redux, etc)
Pass your setPosts handle all the way to your PostsDeleteUtill
I think option 1 is a bit cleaner for your specific case, but if you don't need global state anywhere else in your project, maybe it is fine to have a not so clean solution instead of implementing the whole global state structure for only one thing.
Option 1 pseudo code:
Your PostsGetUtill component would use a global state instead of local state:
const PostsGetUtill = () => {
// Remove this:
// const [posts, setPosts] = useState([]);
const fetchPosts = () => {
axios.get("api/v1.0/post/get").then(response => {
console.log(response.data);
// Instead of a local "setPosts" you would have a global
// "setPosts" (in Redux, this would be a dispatch)
dispatch({type: "PUT_POSTS", action: response.data})
}).catch(function (error) {
// No changes here...
});
};
// This runs only the first time you load this component
useEffect(() => {
fetchPosts();
}, []);
// Use your global state here as well:
return (
<section className="container-post">
<PostMansonry posts={globalState.posts} columns={3} />
</section>
);
};
export default PostsGetUtill;
In your PostsDeleteUtill function:
const PostsDeleteUtill = async (post_Id) => {
axios.delete(`api/v1.0/post/delete/${post_Id}`).then(response => {
// Update global state here. Probably filter the data to remove
// the deleted record
const updatedPosts = globalState.posts.filter(post => post.id !== response.data.id)
}).catch((error) => {
// No changes here
});
};
export default PostsDeleteUtill;
Option 2 pseudo code:
In your PostsGetUtill component, create and pass on a handleRemovePost:
// Your existing code ...
const handleRemovePost = (postID) => {
const filteredPosts = posts.filter(post => post.id !=== postID)
setPosts(filteredPosts)
}
return (
<section className="container-post">
<PostMansonry posts={posts} columns={3} handleRemovePost={handleRemovePost} />
</section>
);
In your PostMansonry, pass on again your handleRemovePost
export default function PostMansonry({ posts, columns, handleRemovePost }) {
return (
// Your existing code ...
<MasonryPost {...{ posts, index, key: index, handleRemovePost }} />)
)
}
Again in your MasonryPost
export default function MasonryPost({ posts, index, handleRemovePost }) {
return (
// Your existing code ...
<button type="button" onClick={(e) => PostsDeleteUtill(posts.post_Id, handleRemovePost)} className="btn btn-danger">Delete</button>
)
}
And finally:
const PostsDeleteUtill = async (post_Id, handleRemovePost) => {
axios.delete(`api/v1.0/post/delete/${post_Id}`).then(response => {
handleRemovePost(response);
})
};
PS: Please note that I only added a pseudo-code as a reference, trying to point out specific parts of the code that needs to be updated. If you need more information about global state, you can check React Context and Redux