Image of the consoleI'm trying to create a website using the movie DB API. I've created a carousel with some movies and want to open a new page with comprehensive information about a movie by clicking on the movie poster. I'm using componentDidMount to fetch data in one component to create a UI and I pass a movie ID to another component where I use componentWillReceiveProps to fetch another data by using the ID. It worked until I started using state, so now it shows two arrays in the console before I click on a movie poster and when I click on a poster it shows an array and loads a movie data from API then if I click on another poster it loads two different objects with the previous and current movie. I found out that componentWillReceiveProps is dangerous to use but componentDidUpdate works in the same manner.
The main idea is when a user clicks on a poster it gets its id and sends it to another component where the id goes to a link
with complete information about the movie. Are there any patterns to achieve it?
class Data extends Component {
state = {
movies: [],
movieId: null
};
onClick = e => {
this.setState({
movieId: e.target.id
});
console.log(e.target.id);
};
componentDidMount() {
fetch(url)
.then(res => res.json())
.then(data => {
let movies = data.results.map(item => {
return (
<Link to="/movieInfo">
<div className="overlay" onClick={this.onClick}>
<img
src=
{`https://image.tmdb.org/t/p/w500/${item.poster_path}`}
alt={item.title}
id={item.id}
/>
</div>
</Link>
);
});
this.setState({
movies: movies
});
})
.catch(err => console.log(err));
}
render() {
const { movies, movieId } = this.state;
return (
<div className="carousel">
<Slider movie={movies} />
<div className="notShow">
<AdditionalInfo id={movieId} />
</div>
</div>
);
}
}
class AdditionalInfo extends Component {
state = {
movie: []
};
componentDidUpdate(prevProps) {
if (prevProps.id !== null && prevProps.id !== this.props.id) {
fetch(
`https://api.themoviedb.org/3/movie/${
prevProps.id
}?api_key=81f382d33088c6d52099a62eab51d967&language=en-US`
)
.then(res => res.json())
.then(data =>
this.setState({
movie: data
})
);
} else {
return null;
}
}
render() {
const { movie } = this.state;
console.log(movie);
return (
<div className="movieInfo-container">
{/* <section className="title" />
<section className="cast">{movie.id}</section> */}
work
</div>
);
}
}
let movieArr = [];
class Slider extends Component {
state = {
currentIndex: 0,
translateValue: 0
};
createNestedArr = () => {
while (this.props.movie.length) {
movieArr.push(this.props.movie.splice(0, 5));
}
return movieArr.map((item, i) => {
return <Slide key={i} movieGroup={item} />;
});
};
nextPic = () => {
if (this.state.currentIndex === movieArr.length - 1) {
return this.setState({
currentIndex: 0,
translateValue: 0
});
}
this.setState(prevState => ({
currentIndex: prevState.currentIndex + 1,
translateValue: prevState.translateValue - this.slideWidth()
}));
};
prevPic = () => {
if (this.state.currentIndex === movieArr.length + 1) {
return this.setState({
currentIndex: 0,
translateValue: 0
});
} else if (this.state.currentIndex === 0) {
return this.setState({
currentIndex: 0,
translateValue: 0
});
}
this.setState(prevState => ({
currentIndex: prevState.currentIndex - 1,
translateValue: prevState.translateValue + this.slideWidth()
}));
};
slideWidth = () => {
return document.querySelector(".new-releases-slide").clientWidth;
};
render() {
return (
<React.Fragment>
<div
className="movie-carousel"
style={{
transform: `translateX(${this.state.translateValue}px)`,
transition: "transform ease-out 0.45s"
}}
>
{this.createNestedArr()}
</div>
<LeftArrow prevPic={this.prevPic} />
<RightArrow nextPic={this.nextPic} />
</React.Fragment>
);
}
}
const Slide = props => {
const { movieGroup } = props;
return <div className="new-releases-slide">{movieGroup}</div>;
};
Use componentDidMount in your AdditionalIno component. You need to pass the id of the clicked movie to MovieInfo component. This <Link to="/movieInfo"> needs to <Link to={'/movieInfo/${item.id}'}> and in your MovieInfo component access the id using const { id } = this.props.match.params;.
import React, { Component } from 'react';
import Loader from "react-loader-spinner";
class AdditionalInfo extends Component {
state = {
movie: [],
isLoading: true,
};
componentDidMount = () => {
const { id } = this.props;
if (!id) {
return;
}
fetch(
`https://api.themoviedb.org/3/movie/${id}?api_key=81f382d33088c6d52099a62eab51d967&language=en-US`
)
.then(res => res.json())
.then(data =>
this.setState({
movie: data,
isLoading: false,
})
);
}
render() {
const { movie } = this.state;
return (
<div className="movieInfo-container">
{this.state.isLoading
? <Loader type="Puff" color="#00BFFF" height="100" width="100" />
: <div><section className="title" />
<h1>{movie.title}</h1>
<section className="cast">ID: {movie.id}</section>
<h2>Overview</h2>
<p>{movie.overview}</p></div>
}
</div>
);
}
}
export default AdditionalInfo;
then in your Data component change your componentDidMount
componentDidMount = () => {
fetch(url)
.then(res => res.json())
.then(data => {
let movies = data.results.map(item => {
return (
<Link to={`/movieInfo/${item.id}`}>
<div className="overlay" onClick={this.onClick}>
<img
src=
{`https://image.tmdb.org/t/p/w500/${item.poster_path}`}
alt={item.title}
id={item.id}
/>
</div>
</Link>
);
});
this.setState({
movies: movies
});
})
.catch(err => console.log(err));
}
In your MovieInfo do something like
class MovieInfo extends Component {
render() {
const {id} = this.props.match.params;
return (
<div>
<AdditionalInfo id={id} />
</div>
)
}
}
Your router should be like
<Route path="/movieInfo/:id" exact component={MovieInfo} />
Working Demo
Related
I have the stake component that is rendered 4 times in the parent class component. I am trying to pass valueNewStake as prop to its parent component and group all the inputs in one common array (see allStakes). For a reason I am not able to change the state and also the dom does not render the button next to the component. Can anyone explain me why it is happening as I am new in react. Thanks
import React, { Component } from 'react';
import Stake from './stake';
class FetchRandomBet extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
bet: null,
value: this.props.value,
allStakes: ['']
};
}
async componentDidMount() {
const url = "http://localhost:4000/";
const response = await fetch(url);
const data = await response.json();
this.setState({
loading: false,
bet: data.bets,
});
}
render() {
const { valueProp: value } = this.props;
const { bet, loading } = this.state;
if (loading) {
return <div>loading..</div>;
}
if (!bet) {
return <div>did not get data</div>;
}
return (
< div >
{
loading || !bet ? (
<div>loading..</div>
) : value === 0 ? (
<div className="bet-list">
<ol>
<p>NAME</p>
{
bet.map(post => (
<li key={post.id}>
{post.name}
</li>
))
}
</ol>
<ul>
<p>ODDS</p>
{
bet.map(post => (
<li key={post.id}>
{post.odds[4].oddsDecimal}
<div className="stake-margin">
<Stake
allStakes={this.props.valueNewStake}
onChange={() => { this.setState({ allStakes: [...this.props.valueNewStake] }) }}
>
<button>ok</button>
</Stake>
</div>
</li>
))
}
</ul>
</div>
import React, { useState } from 'react';
import CurrencyInput from 'react-currency-input-field';
function Stake() {
const [newStake, setStake] = useState(['']);
const changeStake = (e) => {
setStake(e.target.value)
}
return (
<>
<CurrencyInput
onChange={changeStake}
valueNewStake={newStake}
style={{
marginLeft: "40px",
width: "50px"
}}
placeholder="Stake"
decimalScale={2}
prefix="£"
/>
{newStake}
</>
);
}
export default Stake;
You're not passing your props to your Stake component
function Stake({ allStakes, onChange }) {
// do something with your props here
const [newStake, setStake] = useState(['']);
const changeStake = (e) => {
onChange()
setStake(e.target.value)
}
return (
<>
<CurrencyInput
onChange={changeStake}
valueNewStake={newStake}
style={{
marginLeft: "40px",
width: "50px"
}}
placeholder="Stake"
decimalScale={2}
prefix="£"
/>
{newStake}
</>
);
}
I am using NextJs with Typescript.
Only when I add the "any" keyword, my code renders properly otherwise it gives me errors for my post._id, post.title and post.body.
Problem: What is the specific type for displayblog so that I do not set it to any? Also, how can I define my states and props for the code below?
function dateToString(date: Date): string {
return (
`${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}` +
` ${date.getHours()}-${date.getMinutes()}-${date.getSeconds()}`
);
}
export default class LA extends React.Component {
state = {
title: "",
};
componentDidMount = () => {
this.Post();
};
getBlogPost = () => {
axios
.get("/api")
.then(({ data }) => {
const reverseData = new Array();
.catch(error => {
alert("Error: ERROR");
});
};
return posts.map((post, index) => (
<div key={index}>
<Card>
<Title>
{" "}
</Title>
{/* <p>{blog.date}</p>
<p>{blog.name}</p> */}
<FullName>{`${blog.name} | ${dateToString(
currentDateTime
)}`}</FullName>
<Line />
<Question>{blog.body}</Question>
</Card>
</div>
));
};
render() {
return (
<div>
<Container>
<Headers />
<div className="blog">{this.displayBlogPost(this.state.posts)}</div>
</Container>
</div>
);
}
}
This is the line on which I want to change any to a specific type.
displayBlogPost = (posts : Array < any >) => {
Assuming your posts array based on your code in question, create an interface called "Post".
Create separate interfaces for props and state and define as per your format. In this case posts is an array, so I am defining an interface in prior and use it in the state. Similarly if you have got any props, define a format for them too and use it. Also don't forget to use them along while extending the class.
interface Post {
name: string;
body: string;
title: string;
_id: number | string;
}
interface PostState {
posts: Post[];
}
//Define based on your need
interface PostProps {}
export default class PostList extends React.Component<PostProps, 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}`}>
<a>{post.title}</a>
</Link>
</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>
);
}
}
I have five Users in the array.
The code below displays each users info from the arrays when pop up button is clicked and everything works fine.
Now I have created a form to update each user's age based on their respective person Id on form submission via call to nodejs
backend. Am actually getting the result from nodejs backend..
Here is my issue.
Each time I entered age in the input and click on submission button Eg. for user 1. Instead of the age result to
appear near that very user 's name in the space provided in the button, it will appears on the body of the page as can be seen from
screenshots provided.
If call it as props For instance {this.props.messages.personAge}
as per below
<button
onClick={() => this.open(this.props.data.id, this.props.data.name)}
>
(Age should Appear Here-- ({this.props.messages.personAge})--)
{this.props.data.name}
</button>
It shows error
TypeError: Cannot read property 'personAge' of undefined
at User.render
Here is how am getting the response from nodejs server
componentDidMount(){
this.socket = io('http://localhost:8080');
this.socket.on('response message', function(data){
addAge(data);
});
const addAge = data => {
console.log(data);
//this.setState({messages: [...this.state.messages, data]});
this.setState({messages: [data]});
};
}
below is how am displaying the age result for each unique user
{this.state.messages.map((message, i) => {
//if (message.personId == this.props.data.id) {
//if (message.personId == person.id) {
if (message.personId) {
return (
<div key={i}>
<div>
({message.personAge}--years)
</div>
</div>
)
}
})}
</ul>
Here is the Entire Code
import React, { Component, Fragment } from "react";
import { render } from "react-dom";
import { Link } from 'react-router-dom';
import axios from 'axios';
import io from "socket.io-client";
class User extends React.Component {
open = () => this.props.open(this.props.data.id, this.props.data.name);
render() {
return (
<React.Fragment>
<div key={this.props.data.id}>
<button
onClick={() => this.open(this.props.data.id, this.props.data.name)}
>
(Age should Appear Here-- ({this.props.messages})--)
{this.props.data.name}
</button>
</div>
</React.Fragment>
);
}
}
class OpenedUser extends React.Component {
constructor(props) {
super(props);
this.state = {
hidden: false,
personId: '',
personAge: '',
};
}
componentDidMount(){
this.socket = io('http://localhost:8080');
var userId= this.props.data.id;
}
sendPost = (personId,personAge) => {
alert(personId);
alert(personAge);
this.socket.emit('messageUpdate', {
personId: personId,
personAge: personAge,
});
this.setState({personId: ''});
this.setState({personAge: ''});
}
toggleHidden = () =>
this.setState(prevState => ({ hidden: !prevState.hidden }));
close = () => this.props.close(this.props.data.id);
render() {
return (
<div key={this.props.data.id} style={{ display: "inline-block" }}>
<div className="wrap_head">
<button onClick={this.close}>close</button>
<div>user {this.props.data.id}</div>
<div>name {this.props.data.name}</div>
{this.state.hidden ? null : (
<div className="wrap">
<div className="wrap_body">Update Age Info</div>
<div> </div>
<div>
<label></label>
<input type="text" placeholder="personAge" value={this.state.personAge} onChange={ev => this.setState({personAge: ev.target.value})}/>
<br/>
<span onClick={ () => this.sendPost(this.props.data.id, this.state.personAge)} className="btn btn-primary">Update Age</span>
</div>
</div>
)}
</div>
</div>
);
}
}
class App extends React.Component {
constructor() {
super();
this.state = {
showingAlert_UserTyping: false,
shown: true,
activeIds: [],
messages: [],
data: [
{ id: 1, name: "user 1" },
{ id: 2, name: "user 2" },
{ id: 3, name: "user 3" },
{ id: 4, name: "user 4" },
{ id: 5, name: "user 5" }
]
};
}
componentDidMount(){
this.socket = io('http://localhost:8080');
this.socket.on('response message', function(data){
addAge(data);
console.log(' am add message' +data);
});
const addAge = data => {
console.log(data);
//this.setState({messages: [...this.state.messages, data]});
this.setState({messages: [data]});
};
} // close component didmount
toggle() {
this.setState({
shown: !this.state.shown
});
}
open = (id,name) => {
this.setState(prevState => ({
activeIds: prevState.activeIds.find(user => user === id)
? prevState.activeIds
: [...prevState.activeIds, id]
}));
};
close = id => {
this.setState(prevState => ({
activeIds: prevState.activeIds.filter(user => user !== id)
}));
};
renderUser = id => {
const user = this.state.data.find(user => user.id === id);
if (!user) {
return null;
}
return (
<OpenedUser messages={this.state.messages}
key={user.id}
data={user}
close={this.close}
/>
);
};
renderActiveUser = () => {
return (
<div style={{ position: "fixed", bottom: 0, right: 0 }}>
{this.state.activeIds.map(id => this.renderUser(id))}
</div>
);
};
render() {
return (
<div>
<ul>
{this.state.messages.map((message, i) => {
//if (message.personId == this.props.data.id) {
//if (message.personId == person.id) {
if (message.personId) {
return (
<div key={i}>
<div>
({message.personAge}--years)
</div>
</div>
)
}
})}
</ul>
{this.state.data.map(person => {
return (
<User key={person.id} data={person} open={this.open} />
);
})}
{this.state.activeIds.length !== 0 && this.renderActiveUser()}
</div>
);
}
}
Here is how I solved the issue:
I created a const resultdata and using map() and Filter() function.
Here is how I initialized the the variable resultdata and then pass it within state.data.map() method
const resultdata = this.state.messages.filter(res => res.personId == person.id).map(res => res.personAge));
I'm working on an app that makes a call to Unsplash's API and displays the response. I was able to get/display the response easily with just the /photos endpoint when I had the fetch request in the componentDidMount(), but I want to make the app searchable, so I added performSearch() with a default query.
But adding performSearch caused this error:
TypeError: cannot read property 'map' of undefined
This is the JSON I'm getting back when I test:
Search endpoint + query
I've tried other solutions I've found on the forums, but so far nothing's fixed the problem. I'm definitely getting back an array, so shouldn't map work?
class App extends Component {
constructor() {
super();
this.state = {
results: [],
loading: true
};
}
componentDidMount() {
this.performSearch();
}
performSearch = (query = 'camping') => {
fetch(`https://api.unsplash.com/search/photos?page=3&query=${query}&client_id=${client_id}`)
.then(response => response.json())
.then(responseData => {
this.setState({
results: responseData.data,
loading: false
});
})
.catch(error => {
console.log('Error fetching and parsing data', error);
});
}
render() {
return (
<div className = "App">
<SearchPhotos onSearch = {this.performSearch} />
<div>
{
(this.state.loading) ? <p>Loading</p> :<PhotoList results={this.state.results} />
}
</div>
</div>
);
}
}
export default 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>
import React from 'react';
const PhotoList = props =>
<ul>
{props.results.map((result, index) =>
<li key={index}>
<img src={result.urls.small} key={result.id} />
</li>
)}
</ul>;
export default PhotoList;
import React, { Component } from 'react';
class SearchPhotos extends Component {
state = {
searchText: ''
}
onSearchChange = e => {
this.setState({
searchText: e.target.value
});
}
handleSubmit = e => {
e.preventDefault();
this.props.onSearch(this.query.value);
e.currentTarget.reset();
}
render() {
return(
<form className="search-form" onSubmit={this.handleSubmit}>
<input type="search"
onChange={this.onSearchChange}
name="search"
ref={(input) => this.query = input}
placeholder="Search..." />
<button className="search-button" type="submit" id="submit">Go!</button>
</form>
);
}
}
export default SearchPhotos;
performSearch = (query = 'camping') => {
fetch(`https://api.unsplash.com/search/photos?page=3&query=${query}&client_id=${client_id}`)
.then(response => response.json())
.then(responseData => {
this.setState({
results: responseData.results,
loading: false
});
})
.catch(error => {
console.log('Error fetching and parsing data', error);
});
}
responseData.results is the array that your are looking for.
Help! My child component is not updating in my react app!
I want to bring cartNumber to the page component which then is passed onto header component but the number doesn't even show up!
Parent component
class Shop extends Component {
constructor(props) {
super(props);
this.state = {
merchants: [],
error: null,
loading: true,
order: []
};
}
componentWillMount() {
Meteor.call("merchants.getMerchants", (error, response) => {
if (error) {
this.setState(() => ({ error: error }));
} else {
this.setState(() => ({ merchants: response }));
}
});
}
componentDidMount() {
setTimeout(() => this.setState({ loading: false }), 800); // simulates loading of data
}
goBack = () => this.props.history.push("/");
goCart = () => {
try {
Orders.insert(this.state.order), this.props.history.push("/cart");
} catch (error) {
throw new Meteor.Error("there was an error", error);
}
};
onAddToCart(cartItem) {
let { order } = this.state;
order.push(cartItem);
console.log(order.length);
}
render() {
const { loading } = this.state;
const { merchants, error } = this.state;
const { data } = this.state;
const { order } = this.state;
const getProductsFromMerchant = ({ products, brands }) =>
products.map(({ belongsToBrand, ...product }) => ({
...product,
brand: brands[belongsToBrand]
}));
const products = merchants.reduce(
(acc, merchant) => [...acc, ...getProductsFromMerchant(merchant)],
[]
);
if (loading) {
return (
<Page
pageTitle="Shop"
history
goBack={this.goBack}
goCart={this.goCart}
>
<div className="loading-page">
<i
className="fa fa-spinner fa-spin fa-3x fa-fw"
aria-hidden="true"
/>
<br /> <br />
<span>Loading...</span>
</div>
</Page>
);
}
return (
<Page
pageTitle="Shop"
history
goBack={this.goBack}
goCart={this.goCart}
cartNumber={order.length}
>
<div className="shop-page">
{products.map(({ id, ...product }) =>
<Product
{...product}
key={id}
history
onAddToCart={this.onAddToCart.bind(this)}
/>
)}
</div>
</Page>
);
}
}
export default Shop;
Here is the page component which contains the header component
export const Page = ({
children,
pageTitle,
history,
goBack,
goCart,
cartNumber
}) =>
<div className="page">
<Header goBack={goBack} goCart={goCart} history cartNumber>
{pageTitle}
</Header>
<main>
<MuiThemeProvider>
{children}
</MuiThemeProvider>
</main>
<Footer />
</div>;
export default Page;
And Finally this is the header where I want to bring the cartNumber into.
const Header = ({ children, goBack, goCart, cartNumber, pageTitle }) =>
<header>
<button onClick={goBack} className="back-button">
{/* Image added here to show image inclusion, prefer inline-SVG. */}
<img alt="Back" src={`/icon/header/back-white.svg`} />
</button>
<h1>
{children}
</h1>
<div className="right-content">
( {cartNumber} )
<i
className="fa fa-shopping-cart fa-2x"
aria-hidden="true"
onClick={goCart}
/>
</div>
</header>;
export default withRouter(Header);
You're passing cartNumber as a boolean:
<Header goBack={goBack} goCart={goCart} history cartNumber>
Pass it as a value:
<Header goBack={goBack} goCart={goCart} history={history} cartNumber={cartNumber}>