How to fetch data using link in Nextjs - reactjs

My data is stored in Mongo Atlas. I can fetch the data (posts) but when I click on the post, it doesn't provide me with the data of that ID. I have created a pages/posts page. I am only able to view the ID of the post due to the code below which is in postlist.tsx and is only passing the id to the next page via its url. I want to be able to view the title and body of the post associated with that particular ID as well:
This is my blogs.tsx
interface PostState{
posts: Post[];
}
export default class PostList extends React.Component <{},PostState> {
state = {
posts: []
};
componentDidMount = () => {
this.getBlogPost();
};
getBlogPost = () => {
axios
.get('/api')
.then(({data}) => {
const reverseData = new Array;
for (let datetime = data.length - 1; datetime >= 0; datetime--) {
reverseData.push(data[datetime]);
}
this.setState({posts: reverseData})
})
.catch((error) => {
alert('Error: there was an error processing your request')
})
}
displayBlogPost = (posts : Post[]) => {
const currentDateTime = new Date();
if (!posts.length)
return null;
return posts.map((post, index) => (
<div key={index}>
<Card>
<Title>
<Link href={`/post?id=${post._id}`} as= .
{`/post/${post._id}`}></Title>
{/* <p>{post.date}</p>
<p>{post.name}</p> */}
<FullName>{`${post.name} |
${dateToString(currentDateTime)}`}</FullName>
<Line />
<Question >{post.body}</Question>
</Card>
</div>
));
}
render() {
return (
<div>
<Container>
<Headers/>
<div className="blog">
{this.displayBlogPost(this.state.posts)}
</div>
</Container>
</div>
);
}
}

First you should create the Post route
pages/post/index.js
import React from 'react'
class Post extends React.Component {
static async getInitialProps({ query }) {
return { query };
}
render() {
return <div>id: {this.props.query.id}</div>
}
}
export default Post
Then create pages/post/[id].js
import Post from './index'
export default Post
Now when you open localhost:port/post/22 or localhost:port/post?id=22 it should show you the number 22.
To link to a post from another page you can make the following link:
<Link href={`/post?id=${id}`} as={`/post/${id}`}>
<a>{title}</a>
</Link>
If that still doesn't work then please show us the code (on github).
If it works then now you can pass an id to post page so you only need to fetch the post. You can do that in getInitialProps
class Post extends React.Component {
static async getInitialProps({query}) {
const post = await axios.get(url, { // make sure the url is full url, no relative urls
params: {
id: query.id
}
})
return { post: post.data }
}
render() {
return (
<pre>
post:{' '}
{JSON.stringify(this.props.post, undefined, 2)}
</pre>
)
}
}

Related

React - error result map is not a function?

Framework: React
Error type: result.map is not a function
I am following the book Road to learn React, I tried the below code with hacker news API it worked well but it is not working with this API. I don't know why I'm getting this error, please help.
Link to my sandbox --> https://codesandbox.io/s/react-setup-forked-q0hti?file=/src/App.js
import React, { Component } from "react";
// API chunks
const PATH_BASE = "https://jsonplaceholder.typicode.com/todos";
const PATH_SEARCH = "/search";
const PARAM_SEARCH = "query=";
const DEFAULT_QUERY = "redux";
//const url = `${PATH_BASE}${PATH_SEARCH}?${PARAM_SEARCH}${DEFAULT_QUERY}`;
//console.log(url);
class App extends Component {
constructor() {
super();
this.state = {
result: null
};
this.hitStories = this.hitStories.bind(this);
}
// handling the local state value
hitStories(result) {
this.setState({
result
});
}
// lifecycle method
// Note: componenetDidMount runs after the render method
componentDidMount() {
fetch(`${PATH_BASE}${PATH_SEARCH}?${PARAM_SEARCH}${DEFAULT_QUERY}`)
.then((response) => response.json())
.then((json_result) => this.hitStories(json_result))
.catch((error) => error);
}
render() {
console.log(this.state)
const { result } = this.state;
if (!result) {
return null;
}
return (
<div>
<h2>Fetch API in React</h2>
{result.map((
item
) => (
<div>
{item.title}
</div>
))}
</div>
);
}
}
export default App;
.map is used on an array, your result is not an array but an object. Try this:
render() {
const { result } = this.state;
return result ? (
<div>
<h2>Fetch API in React</h2>
{ Object.values(result).map((item) => (
<div>{item.title}</div>
))
}
</div>
): null;
}

React render array of images in array

end app for woocommerce store, but i have problem rendering the first image of array in
when i console.log(images.src) i see the list of urls of the images, but in img src= it return : TypeError: Cannot read property 'src' of undefined
I will be very thankful to help me correctly map the images.
here is my code:
class App extends React.Component {
constructor(props) {
super(props);
this.getPosts = this.getPosts.bind(this);
this.state = {
posts : [],
images: []
};
}
getPosts = async () => {
let res = await api.get("products", {
per_page: 20,
})
let { data } = await res;
this.setState({ posts: data });
}
componentDidMount = async () => {
await this.getPosts();
};
render() {
const { posts } = this.state;
const { images } = this.state
return(
<div>
<Head>
<title>Онлайн магазин KIKI.BG</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<React.Fragment >
{posts.map((posts, index ) => {
{
posts.images.map((images, subindex) =>
console.log(images.src),
<img src={images[0].src} />
)}
return (
<div>
<h1>{posts.name}</h1>
<h2>{posts.price}</h2>
</div>
)})}
</React.Fragment>
</div>
)
}
}
export default App;
{posts.map((posts, index ) => {
{
posts.images.src.map((image, subindex) =>
<img src={image.src} />
)}
return (
<div>
<h1>{posts.name}</h1>
<h2>{posts.price}</h2>
</div>
)
})}
well, console.log(images.src) i see the list of urls of the images doesn't make any sense.. images is array. So images[0] should be image with data with property src on it?. Btw a lot of stuff in this code is just wrong.
Don't rebind getPosts already bound getPosts function in constructor (via class property) (getPosts). BTW you dont need to bind is here at all, its not called as a callback.
Its weird, that you call await res after api.get() ... shouldn't be it just await api.get()? Another await is usually used on fetch, when you do something like await response.json().
There is no need for async/await in componentDidMount
If getPosts will throw it will mess up your component, its better to handle error in catch and call props.onError(error) for example
You don't have any key attributes on element in map, thats wrong. You should put some unique id there (url fe? if not same, or id) for proper component re-render.
You have some weird brackets issue in your maps...
You shouldn't use more than one h1 one the page :-)
images.src should be string, not array...
Why is there subindex and index when u are not using it?
Why you store images when they are not filled anywhere? Are they in the response of get? Thats maybe why u get an TypeError !
I would add loading and no data message...
That would be my code:
import { Component, Fragment } from 'react';
class App extends Component {
static defaultProps = {
onError: console.error;
};
state = {
posts: [],
images: [],
loading: false,
};
// This could be done with hooks much better tho...
async componentDidMount () {
this.setState({ loading: true });
try {
await this._fetchData();
}
catch (error) {
this.props.onError(error); // Or something rendered in state.error?
}
finally {
this.setState({ loading: false });
}
}
render () {
const { images, posts, loading } = this.state;
if (!images.length) {
return <div>No data.</div>;
}
if (loading) {
return <div>Loading</div>;
}
const postBoxes = posts.map((post, index) => {
const image = images[index];
// Because you don't know, if that specific image is there... if this are your data..
const imageElement = image ?
<img src={image.src} alt="dont know" /> :
null;
const { name, price } = post;
// If name is unique, otherwise some id.
return (
<Fragment key={name} >
{imageElement}
<h2>{name}</h2>
<h3>{price}</h3>
</Fragment>
);
});
return (
<div>
<Head>
<title>Онлайн магазин KIKI.BG</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Fragment>
{postBoxes}
</Fragment>
</div>
);
}
async _fetchData () {
const { data } = await api.get('products', { per_page: 20 });
const { posts, images } = data;
this.setState({ posts, images });
}
}
export default App;
if console.log(images.src) -> gives list of images.
Then,
<img src={images.src[0]}/> -> should do the trick.
may be, Add a null check to be certain.
images.src[0] && <img src={images.src[0]}/>

React this.props.id is undefined in part of class

In my React application I need the userId in the Timeline class to get the posts from a user, but React says that it's undefined.
If I say in the rendered part
{ this.props.id }
Than it will show the right id..
I already tried every solution that I could possibly find on the internet.
import React, { Component } from 'react'
import axios from 'axios'
import Timeline from './Timeline'
class Profile extends Component{
state = {
user: {}
}
componentDidMount() {
axios.get(`http://localhost:8090/user/${this.props.match.params.id}`)
.then(res =>{
const user = res.data
this.setState({ user: user })
})
}
render(){
return(
<div>
<h1>This is the profile page of { this.state.user.username }.</h1>
<img src={this.state.user.profilePicture} ></img>
<h3> E-mailaddress: { this.state.user.mail }</h3>
<Timeline id={this.state.user.id}/>
</div>
)}
}
export default Profile
import Cookies from 'universal-cookie'
import React, { Component } from 'react'
import axios from 'axios'
const cookies = new Cookies()
class Timeline extends Component {
state = {
user: cookies.get('user'),
posts: []
}
componentDidMount() {
const id = this.props.id
console.log("ID IS " + id)
if (this.state.user === undefined)
return
axios.get(`http://localhost:8090/user/${id}/postEntities`)
.then(response => {
this.setState({
posts: response.data._embedded.post
})
})
.catch(error => {
console.log(error)
})
}
render() {
if (this.state.user !== undefined) {
if (this.state.posts.length <= 0) {
return (
<main>
<h2>Personal timeline</h2>
<h2>This id works: { this.props.id }</h2>
<h6>There does not seem to be anything here..<br />Create a post and come back later!</h6>
</main>
)
} else {
return (
<main>
<h2>Personal timeline</h2>
{
this.state.posts.map(post => {
return (
<div>
<h5>{ post.title }</h5>
<img src={post.pictureUrl} width="200" height="200"></img>
<p><i>You took this picture at { post.longitude }, { post.latitude }</i></p>
</div>
)
})
}
</main>
)
}
}
else {
return (
<h5>You need to be logged in to use this feature</h5>
)
}
}
}
export default Timeline
The expected output in the url needs to be 2 but is undefined, the expected value in the rendered part is 2 and it outputs 2.
With react, the componentDidMount of children is called BEFORE the one from the parent.
So, when the componentDidMount of Timeline is called the first time, the componentDidMount of Profile has not been called, so there is no userId yet.
To avoid this problem, you should render the Timeline only when the Profile component has been mounted and when you have your user id.
So something like that in the render of Profile
render(){
return(
<div>
<h1>This is the profile page of { this.state.user.username }.</h1>
<img src={this.state.user.profilePicture} ></img>
<h3> E-mailaddress: { this.state.user.mail }</h3>
{this.state.user.id && (
<Timeline id={this.state.user.id}/>
)}
</div>
)}
Because
this.state.user.id
only has value when function axios.get in componentDidMount has done. while function render() is called before.
So, To avoid undefined, you must set state with format:
state = {
user: {id : 0} //or null
}
Initially you won't have user.id, it is coming from axios service call. In this case wait till you get response and then show timeline based on condition in render.
import React, { Component } from 'react'
import axios from 'axios'
import Timeline from './Timeline'
class Profile extends Component{
state = {
user: {}
}
componentDidMount() {
axios.get(`http://localhost:8090/user/${this.props.match.params.id}`)
.then(res =>{
const user = res.data
this.setState({ user: user })
})
}
render(){
return(
<div>
<h1>This is the profile page of { this.state.user.username }.</h1>
<img src={this.state.user.profilePicture} ></img>
<h3> E-mailaddress: { this.state.user.mail }</h3>
{typeof(this.state.user.id) !== 'undefined' ? <Timeline id={this.state.user.id}/> : ''}
</div>
)}
}
export default Profile
What variable is undefined? this.state.user.id?
If so, that probably means that you start with user: {}, then you make a promise and then set the state. The problem is that a promise will take time to fulfill, so meanwhile you are still with user: {} and this.state.user.id gives undefined.
When you call <Timeline id={this.state.user.id}/> make sure you have a id and email in your state. Or define your state with user: {is: '', email:''} or do a conditional render. Hope I understood your problem correctly!

Conditional within a render in the content display component in React.js

I have a component that needs to display the details of a movie according to the id that is passed in the URL (parameter). I'm having difficulty doing the conditional on the RENDER method. It's probably quite simple, but I'm still not very familiar with the React flow. Can you give me an idea?
Ex: Codesandbox
import React, { Component } from "react";
import api from "../../services/api";
export default class Movie extends Component {
state = {
movies: [],
movieId: {}
};
async componentDidMount() {
const { id } = this.props.match.params;
const response = await api.get("");
const currentParams = this.props.match.params;
this.setState({
movies: response.data,
movieId: `${id}`
});
console.log(this.state.movies);
console.log(this.state.movieId);
}
render() {
const movies = this.state.movies,
currentParams = this.state.movieId;
return (
<div className="movie-info">
{this.state.movies.map(movie => (
if( movie.event.id === currentParams ) {
<h1 key={movie.event.id}>{movie.event.title}</h1>
}
))}
</div>
);
}
}
You might not want to use map in this case since you only want to render one movie. You could instead use the find method and render that single movie if it's found.
class Movie extends Component {
// ...
render() {
const { movies, movieId } = this.state;
const movie = movies.find(movie => movie.event.id === movieId);
return (
<div className="movie-info">
{movie ? <h1 key={movie.event.id}>{movie.event.title}</h1> : null}
</div>
);
}
}

React setState fetch API

I am starting to learn React and creating my second project at the moment. I am trying to usi MovieDb API to create a movie search app. Everything is fine when I get the initial list of movies. But onClick on each of the list items I want to show the details of each movie. I have created a few apps like this using vanilla JS and traditional XHR call. This time I am using fetch API which seems straightforward ans simply to use, however when I map through response data to get id of each movie in order to retrieve details separately for each of them I get the full list of details for all the items, which is not the desired effect. I put the list of objects into an array, because after setState in map I was only getting the details for the last element. I know that I am probably doing something wrong within the API call but it might as well be my whole REACT code. I would appreciate any help.
My code
App.js
import React, { Component } from 'react';
import SearchInput from './Components/SearchInput'
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state =
{
value: '',
showComponent: false,
results: [],
images: {},
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleOnChange = this.handleOnChange.bind(this);
this.getImages = this.getImages.bind(this);
this.getData = this.getData.bind(this);
}
ComponentWillMount() {
this.getImages();
this.getData();
}
getImages(d) {
let request = 'https://api.themoviedb.org/3/configuration?api_key=70790634913a5fad270423eb23e97259'
fetch(request)
.then((response) => {
return response.json();
}).then((data) => {
this.setState({
images: data.images
});
});
}
getData() {
let request = new Request('https://api.themoviedb.org/3/search/movie?api_key=70790634913a5fad270423eb23e97259&query='+this.state.value+'');
fetch(request)
.then((response) => {
return response.json();
}).then((data) => {
this.setState({
results: data.results
});
});
}
handleOnChange(e) {
this.setState({value: e.target.value})
}
handleSubmit(e) {
e.preventDefault();
this.getImages();
this.setState({showComponent: true});
this.getData();
}
render() {
return (
<SearchInput handleSubmit={this.handleSubmit} handleOnChange={this.handleOnChange} results={this.state.results} images={this.state.images} value={this.state.value} showComponent={this.state.showComponent}/>
);
}
}
export default App;
SearchInput.js
import React, {Component} from 'react';
import MoviesList from './MoviesList';
class SearchInput extends Component {
render() {
return(
<div className='container'>
<form id='search-form' onSubmit={this.props.handleSubmit}>
<input value={this.props.value} onChange={this.props.handleOnChange} type='text' placeholder='Search movies, tv shows...' name='search-field' id='search-field' />
<button type='submit'>Search</button>
</form>
<ul>
{this.props.showComponent ?
<MoviesList value={this.props.value} results={this.props.results} images={this.props.images}/> : null
}
</ul>
</div>
)
}
}
export default SearchInput;
This is the component where I try to fetch details data
MovieList.js
import React, { Component } from 'react';
import MovieDetails from './MovieDetails';
let details = [];
class MoviesList extends Component {
constructor(props) {
super(props);
this.state = {
showComponent: false,
details: []
}
this.showDetails = this.showDetails.bind(this);
this.getDetails = this.getDetails.bind(this);
}
componentDidMount() {
this.getDetails();
}
getDetails() {
let request = new Request('https://api.themoviedb.org/3/search/movie?api_key=70790634913a5fad270423eb23e97259&query='+this.props.value+'');
fetch(request)
.then((response) => {
return response.json();
}).then((data) => {
data.results.forEach((result, i) => {
let url = 'https://api.themoviedb.org/3/movie/'+ result.id +'?api_key=70790634913a5fad270423eb23e97259&append_to_response=videos,images';
return fetch(url)
.then((response) => {
return response.json();
}).then((data) => {
details.push(data)
this.setState({details: details});
});
});
console.log(details);
});
}
showDetails(id) {
this.setState({showComponent: true}, () => {
console.log(this.state.details)
});
console.log(this.props.results)
}
render() {
let results;
let images = this.props.images;
results = this.props.results.map((result, index) => {
return(
<li ref={result.id} id={result.id} key={result.id} onClick={this.showDetails}>
{result.title}{result.id}
<img src={images.base_url +`${images.poster_sizes?images.poster_sizes[0]: 'err'}` + result.backdrop_path} alt=''/>
</li>
)
});
return (
<div>
{results}
<div>
{this.state.showComponent ? <MovieDetails details={this.state.details} results={this.props.results}/> : null}
</div>
</div>
)
}
}
export default MoviesList;
MovieDetails.js
import React, { Component } from 'react';
class MovieDetails extends Component {
render() {
let details;
details = this.props.details.map((detail,index) => {
if (this.props.results[index].id === detail.id) {
return(
<div key={detail.id}>
{this.props.results[index].id} {detail.id}
</div>
)} else {
console.log('err')
}
});
return(
<ul>
{details}
</ul>
)
}
}
export default MovieDetails;
Theres a lot going on here...
//Here you would attach an onclick listener and would fire your "get details about this specific movie function" sending through either, the id, or full result if you wish.
//Then you getDetails, would need to take an argument, (the id) which you could use to fetch one movie.
getDetails(id){
fetch(id)
displayresults, profit
}
results = this.props.results.map((result, index) => {
return(
<li onClick={() => this.getDetails(result.id) ref={result.id} id={result.id} key={result.id} onClick={this.showDetails}>
{result.title}{result.id}
<img src={images.base_url +`${images.poster_sizes?images.poster_sizes[0]: 'err'}` + result.backdrop_path} alt=''/>
</li>
)
});
Thanks for all the answers but I have actually maanged to sort it out with a bit of help from a friend. In my MovieList I returned a new Component called Movie for each component and there I make a call to API fro movie details using each of the movie details from my map function in MovieList component
Movielist
import React, { Component } from 'react';
import Movie from './Movie';
class MoviesList extends Component {
render() {
let results;
if(this.props.results) {
results = this.props.results.map((result, index) => {
return(
<Movie key={result.id} result={result} images={this.props.images}/>
)
});
}
return (
<div>
{results}
</div>
)
}
}
export default MoviesList;
Movie.js
import React, { Component } from 'react';
import MovieDetails from './MovieDetails';
class Movie extends Component {
constructor(props) {
super(props);
this.state = {
showComponent: false,
details: []
}
this.showDetails = this.showDetails.bind(this);
this.getDetails = this.getDetails.bind(this);
}
componentDidMount() {
this.getDetails();
}
getDetails() {
let request = new Request('https://api.themoviedb.org/3/search/movie?api_key=70790634913a5fad270423eb23e97259&query='+this.props.value+'');
fetch(request)
.then((response) => {
return response.json();
}).then((data) => {
let url = 'https://api.themoviedb.org/3/movie/'+ this.props.result.id +'?api_key=70790634913a5fad270423eb23e97259&append_to_response=videos,images';
return fetch(url)
}).then((response) => {
return response.json();
}).then((data) => {
this.setState({details: data});
});
}
showDetails(id) {
this.setState({showComponent: true}, () => {
console.log(this.state.details)
});
}
render() {
return(
<li ref={this.props.result.id} id={this.props.result.id} key={this.props.result.id} onClick={this.showDetails}>
{this.props.result.title}
<img src={this.props.images.base_url +`${this.props.images.poster_sizes?this.props.images.poster_sizes[0]: 'err'}` + this.props.result.backdrop_path} alt=''/>
{this.state.showComponent ? <MovieDetails details={this.state.details}/> : null}
</li>
)
}
}
export default Movie;

Resources