React.js How to pass data from render to a Method - reactjs

I want to pass data from Component A to Component B from a Route Link and then make an API request in Component B and I was able to pass it from Component A to B but couldn't figure out how to pass that data from inside render to a method that will make an API Request. hopefully, I was clear please look at the code below. and thanx in advance.
Component A
<ul>
{this.state.movies.map(movie => (
<li key={movie.imdbID}>
<img alt="img" src={movie.Poster} />
<h1>{movie.Title}</h1>
<p>{movie.Year}</p>
<button>
<Link to={{ pathname: "./productdetail", movieid: movie.imdbID }}>View More</Link></button>
</li>))}
</ul>
Component B
class ProductDetailPage extends React.Component {
state = {
movieIdSearch: []
};
movieIdRequest(id) {
axios.get(`http://www.omdbapi.com/?apikey=bcfe7e46&i=${id}`).then(res => {
const movieById = res.data;
this.setState({ movieIdSearch: movieById });
});
}
render() {
const {
Poster,
Title,
Year,
Released,
Runtime,
Genre,
Country,
Language,
Actors,
Plot
} = this.state.movieIdSearch;
return (
<div>
{/*how to pass this.props.location.movieid to a movieIdRequest method*/}
<img alt="img" src={Poster} />
<h3>{Title}</h3>
<div>
<p>{Year}</p>
<p>{Released}</p>
<p>{Runtime}</p>
<p>{Genre}</p>
<p>{Country}</p>
<p>{Language}</p>
</div>
<div>
<h5>{Actors}</h5>
<p>{Plot}</p>
</div>
</div>
);
}
}

Related

How to pass an user-created array to another component?

I am new to React. In fact I am new to any frontend programming lanugage. Therefore I encounter many really weird and sometimes even hilarious problems. I am struggling with sending an array to another compontent. The problem is user creates that array, and it's created dynamically inside render(){return(..)}
class Home extends Component {
constructor(props) {
super(props)
this.chosenItems = [];
}
state = {
items: [],
};
// code to import JSON from backend API, irrelevant, that part works fine
addItem(item){
this.chosenItems.push(item);
console.log(this.chosenItems); //logs created array, everything works like a charm
}
render() {
const {items} = this.state;
return (
//some code
<div key={item.id}>
{item.name} {item.price}<img src = {item.url} className="photo"/><button onClick={() => this.addItem(item)}>ADD</button>
</div>
<Basket dataFromParent = {this.getItems} />
</div>
and Basket class
class Basket extends React.Component {
constructor(props) {
super(props);
this.chosenItems = [];
}
state = {
items: []
};
componentDidUpdate()
{
this.chosenItems = this.props.dataFromParent;
console.log(this.props.dataFromParent);
}
render() {
return (
<div>
<h2>{this.chosenItems}</h2>
<h2>{this.props.dataFromParent}</h2>
</div>
);
}
}
export default Basket;
the problem is console log shows "undefined". Could you tell me what I am doing wrong? Or maybe my entire approach is incorrect and I should look for another solution?
Update
class Home extends Component {
state = {
items: [],
chosenItems []
};
// code to import JSON from backend API, irrelevant, that part works fine
addItem(item){
this.setState(prev => ({
chosenItems: [...prev.chosenItems, item]
}))
}
render() {
const {items, chosenItems} = this.state;
return (
<div>
<div><Basket chosenItems ={this.state.chosenItems} /></div>
<Router>
<div className="container">
<ul>
<Link to="/login">login</Link>
<Link to="/basket">basket</Link>
</ul>
<Route path="/login" component={Login} />
<Route path="/basket" component={Basket} />
</div>
</Router>
<div>
{items.map(item =>
<div key={item.id}>
{item.name} {item.price} {item.quantity} <img src = {item.url} className="photo"/><button onClick={() => this.addItem(item)}>Add!</button>
</div>
)}
</div>
</div>
);
}
}
class Basket extends React.Component {
render() {
return (
<div>
{this.props.chosenItems.map(item =>
<div key={item.id}>
{item.name}{item.price}
</div>
)}
</div>
);
}
}
and that works, but the chosenItems array is printed immediatelty where
<Basket chosenItems ={this.state.chosenItems} />
is located after the button is pressed. And when I click on basket redirection I get
TypeError: Cannot read property 'map' of undefined
Firstly, you must understand that things that you don't set in state don't cause a re-render and hence an updated data isn't reflected on to the UI or passed onto the children.
Secondly, you do not need to store the data passed from parent in child again, you can directly use it from props
class Home extends Component {
state = {
items: [],
chosenItems []
};
// code to import JSON from backend API, irrelevant, that part works fine
addItem(item){
this.setState(prev => ({
chosenItems: [...prev.chosenItems, item]
}))
}
render() {
const {items} = this.state;
return (
<div>
//some code
<div key={item.id}>
{item.name} {item.price}<img src = {item.url} className="photo"/><button onClick={() => this.addItem(item)}>ADD</button>
</div>
<Basket chosenItems ={this.state.chosenItems} />
/div>
)
}
}
class Basket extends React.Component {
render() {
return (
<div>
<h2>{this.props.chosenItems.map(item=> <div>{item.name}</div>)}</h2>
</div>
);
}
}
export default Basket;
I see a couple of problems in the snippet -
You are passing this.getItems to your child component as props. It's never defined in the parent. I think it should have been items array state that you have created.
chosenItems should have been a state and you should dig deeper on how to update a state. There is a setState function, learn abt it.
In child, again the the constructor is written like parent's with chosenItems and items which is not needed. You can use them from props.
Please have a look on https://reactjs.org/docs/react-component.html#setstate how to mutate the state of a component. You will find few more basics over this document.
The reason you are getting undefined in the log is because the this.getItems() in Home component is returning undefined either there is no such method or probably the state variable itself is undefined.
In a nutshell few things:
When you want to pass an array to child component, it is as simple as passing any object or property For eg. (I am hoping you want to pass the choosen items to Basket component)
Always initialise state in constructor.
so chooseItems and items should be a part of state and inside constructor.
Your code should look like:
class Home extends Component {
constructor(props) {
super(props)
this.state = {
chosenItems: [],
items: [],
}
}
addItem(item){
this.setState({
chosenItems: [...this.state.chosenItems, item]
})
console.log(this.chosenItems); //logs created array, everything works like a charm
}
render() {
const {items, chooseItems} = this.state;
return (
items.map(item => {
return (
<div key={item.id}>
{item.name} {item.price}<img src = {item.url} className="photo"/>
<button onClick={() => this.addItem(item)}>ADD</button>
</div>
)
})
<Basket dataFromParent={chooseItems} />
div>
)
}
}
and the Basket component would not need constructor since the required data is coming from parent component:
class Basket extends React.Component {
render() {
return (
<div>
{this.props.dataFromParent.map(item => <h2>{this.props.dataFromParent}</h2>)}
</div>
);
}
}
export default Basket;

Accesing object using props in ReactJs

I'm trying to access object keys using props as an index but it's not working. Error: Objects are not valid as a React child (found: object with keys {id, name, img_url, location}). If you meant to render a collection of children, use an array instead.
I am new to React so I appreciate any help.
My code:
class ExpandCard extends React.Component {
render() {
const props = this.props;
const profiles = props.profiles;
return(
<>
<div className="">
{profiles[props.active]}
</div>
</>
);
}
}
class App extends React.Component {
state = {
profiles: testData,
active: null,
}
getActive = (dataFromCard) => {
console.log('the magic number is', dataFromCard);
this.setState({active: dataFromCard});
}
render() {
return (
<div>
<div className="wrapper">
<header>
<div className="logo">LOGO</div>
</header>
<Form />
<div className="cards">
<div className="card-list">
{this.state.profiles.map(profile => <Card key={profile.id} {...profile} activeCard={this.getActive} />)}
</div>
<div className="expand-card">
<ExpandCard active={this.state.active} profiles={this.state.profiles} />
</div>
</div>
</div>
</div>
);
}
}
It looks like {profiles[props.active]} returns an object that looks like this:
{ id, name, img_url, location }
You can't return an object from a React component, maybe you meant to return {profiles[props.active].name}?

Problem to render a component dynamically with React Router

I'm working on a personal website and I have a problem rendering a component dynamically using React Router. To me, everything seems correct but for some reason, it's not working.
I tried to follow the documentation and watched a couple of tutorials but I have been stuck for a long time so I feel like I need help on this one.
In this component, I want to render the 'Articles' component dynamically using the id
class JobCard extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
};
}
componentWillMount() {
fetch(url)
.then(data => data.json())
.then(result =>
this.setState({
data: result
})
);
}
render() {
const { match } = this.props;
const { data, value } = this.state;
return (
<>
<div>
{results.map((job, id) => (
<div key={id}>
<div key={job._id} className="blog-card">
<div className="meta">
<div className="photo">
<img src={job.img} alt="logo" />
</div>
</div>
<div className="description">
<h5>{job.position_name}</h5>
<p className="read-more">
<p>{job.location}</p>
<p>
<span className="learn-pow">
{" "}
<Link
to={{
pathname: `${match.url}/${job._id}`,
state: job
}}
>
{job.workplace_name}
</Link>{" "}
Enter Location
</span>
</p>
</p>
</div>
</div>
</div>
))}
</div>
}
<Route path={`${match.path}/:id`} component={Articles} />
</>
);
}
}
export default JobCard;
And here is the component that i want to render:
import React from 'react';
const Articles = ({ location }) => (
<div>
<h1>{location.state.name}</h1>
<h2>{location.state.email}</h2>
<h2>{location.state.place}</h2>
</div>
)
export default Articles;
When I click on the Card the URL is right so I get the id but I don't have access to the Article component. I tried to console log but nothing appears.
Instead of this
<Route path={`${match.path}/:id`} component={Articles} />
try doing this
<Route path={`${match.url}/:id`} component={Articles} />

How to fix: How to show state with onClick to div?(React)

I have sidebar with document types on it(docs, table, slider, html ..). I want that, if i click on docs element it will show docs in another div like a header.
I have 3 files: DocumentType.tsx, Sidebar.tsx and Results.tsx
In DocumentType.tsx:
import React from 'react';
const documentType = (props ) =>{
return(
<div>
<p id="fileType">{props.type}</p>
</div>
)
};
export default documentType;
In Sidebar.tsx:
typeState = {
documentTypes: [
{ type: "Dokumendid" },
{ type: "PDF" },
]
}
toDocument = () => {
this.setState({
documentTypes: [
{ type: "Dokumendid" }
console.log("Document was clicked");
]
})
}
toPdf = () => {
this.setState({
documentTypes: [
{ type: "Pdf" }
console.log("PDF was clicked")
]
})
}
render(){
return(
<a className="a" href="/search?filter%3Atype=doc" onClick={this.toDocument}>
<div className="icons dokument">
<img src={dokument} alt="dokument"/>
<a className="title">dokument</a>
</div>
</a>
<a className="a" href="/search?filter%3Atype=pdf" onClick={this.toPdf}>
<div className="icons pdf">
<img src={pdf} alt="pdf"/>
<a className="title">pdf</a>
</div>
</a>
)
}
And in Results.tsx:
...
<DocumentType />
..
You want to show a document type in Results component when a document in Sidebar component is clicked.
You have documentType state in Sidebar component and you want to pass it to Results component. So for that you can make Results component as child component of Sidebar component and pass the selected document type i.e documentType state as props.
Sidebar.js
import React, {Component} from 'react'
import Results from 'path-to-results';
class Sidebar extends Component {
state = {
// instead of using "documentType" as array
// you can make it null for initial value
documentType: null
}
// instead of using "toPDF" or "toDocument" method
// you can use single method to update the state
handleDocType = (docType) => {
this.setState({
documentType: docType
})
}
render() {
return (
<div>
// pass "document" as argument to handleDocType method
<a className="a" href="#" onClick={() => this.handleDocType('document')}>
<div className="icons dokument" >
<img src="" alt="dokument"/>
<a className="title">dokument</a>
</div>
</a>
// pass "pdf" as argument to handleDocType method
<a className="a" href="#" onClick={() => this.handleDocType('pdf')}>
<div className="icons pdf">
<img src="" alt="pdf"/>
<a className="title">pdf</a>
</div>
</a>
// checking if "documentType" is null or not
// if it is null nothing is rendered
// if it is not null then "Results" component is rendered
{ this.state.documentType && <Results type={this.state.documentType} /> }
</div>
)
}
}
Results.js
import React, { Component } from 'react'
import DocType from 'path-to-doctype'
class Results extends Component {
// .... your other codes
render() {
return (
<div>
// ....... your other codes
<DocType type={this.props.type} />
</div>
)
}
}
export default Results
DocType.js
import React from 'react';
const DocumentType = (props ) =>{
return(
<div>
<p id="fileType">{props.type}</p>
</div>
)
};
export default DocumentType;
UPDATE
If Sidebar and DocType components are children components of Results component then add documentType state to Results component and pass documentType state as props to DocType component.
Results.js
class Results extends Component {
// add state "documentType"
state = {
documentType: null
}
// add "handleDocType" method
handleDocType = (docType) => {
this.setState({
documentType: docType
})
}
// .... your other codes
render() {
return (
<div>
// .... your other codes
// pass "handleDocType" as props to Sidebar component
<Sidebar handleDocType={this.handleDocType}/>
// pass "documentType" state as props to DocType component
<DocType type={this.state.documentType} />
</div>
)
}
}
export default Results
Sidebar.js
class Sidebar extends Component {
// use "docTypeHandler" to call parent "handleDocType" method
// that updates "documentType" state in Results component
docTypeHandler = (doctype) => {
this.props.handleDocType(doctype)
}
render() {
return (
<div>
<a className="a" href="#" onClick={() => this.docTypeHandler('document')}>
<div className="icons dokument" >
<img src="" alt="dokument"/>
<a className="title">dokument</a>
</div>
</a>
<a className="a" href="#" onClick={() => this.docTypeHandler('pdf')}>
<div className="icons pdf">
<img src="" alt="pdf"/>
<a className="title">pdf</a>
</div>
</a>
</div>
)
}
}
export default Sidebar
DocType.js
const DocType = (props ) =>{
return(
<div>
<p id="fileType">{props.type}</p>
</div>
)
};
If I understood your question correctly.. you wanted to show data in a div when onClick event triggers..
lets say your state object has
state = {
data: ''
}
//clicked function
clicked =() => {
this.setState({data: 'clickedme'})
}
div element: <div onClick={this.clicked} >{this.state.data}</div>
simple example when an onClick event occurs a div and displaying the state data object..

react-redux: Rendering a component after an API call

I am building an app which uses user input and shows number of recipes and they can click on recipe card to view ingredients as well. Every time they click on recipe card I make an API call to get appropriate recipe ingredient. But I am not able to figure out how to show the component which contains the recipe ingredients. I tried with conditional routing and conditional rendering as well but couldn't find the solution.
Recipe_Template.js
export class RecipeTemplate extends Component {
renderRecipe = recipeData => {
return recipeData.recipes.map(recipeName => {
return (
<div className="container">
<div className="row">
<div className="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<a
href={recipeName.source_url}
target="_blank"
onClick={() => {
this.props.fetchRecipeId(recipeName.recipe_id);
}}
>
<img
src={recipeName.image_url}
className="mx-auto d-block img-fluid img-thumbnail"
alt={recipeName.title}
/>
<span>
<h3>{recipeName.title}</h3>
</span>
</a>
<span}>
<h3>{recipeName.publisher}</h3>
</span>
</div>
</div>
</div>
);
});
};
render() {
return (
<React.Fragment>
{this.props.recipe.map(this.renderRecipe)}
</React.Fragment>
);
}
}
Recipe_Detail.js
class RecipeDetail extends Component {
renderRecipeDetail(recipeData) {
return recipeData.recipe.ingredients.map(recipeIngredient => {
return <li key={recipeIngredient}>recipeIngredient</li>;
});
}
render() {
if (this.props.recipeId === null) {
return <div>Loading...</div>;
}
return <ul>{this.props.recipeId.map(this.renderRecipeDetail)}</ul>;
}
}
function mapStateToProps({ recipeId }) {
return { recipeId };
}
export default connect(mapStateToProps)(RecipeDetail);
Not entirely sure why you would need Redux here (unless it's being shared among other nested components), but I'm fairly certain you can just utilize React state.
One approach would be to configure your routes as such:
<Route path="/recipes" component={Recipes} />
<Route path="/recipe/:id" component={ShowRecipe} />
When the user sends a query, gets some results, and you display all matching recipes to a Recipes component. Each recipe then has a name (and other associated displayable data) and a clickable link:
<Link to={`/recipe/id?recipeId=${recipeId}`}>View {recipeName} Recipe</Link>
which for simplicity sake might look like:
<ul>
<Link to="/recipe/id?recipeId=08861626">View Prosciutto Bruschetta Recipe</Link>
<Link to="/recipe/id?recipeId=04326743">View Pasta Bundt Loaf Recipe</Link>
...etc
</ul>
When the user clicks on the link, react-router sends the user to the ShowRecipe component with a unique recipeId.
ShowRecipe then makes another AJAX request to get the recipe details:
ShowRecipe.js
export default class ShowRecipe extends Component {
state = { recipeDetail: '' }
componentDidMount = () => {
const { recipeId } = this.props.location.query; // <== only natively available in react-router v3
fetch(`http://someAPI/recipe/id?recipeId=${recipeId}`)
.then(response => response.json())
.then(json => this.setState({ recipeDetail: json }));
}
render = () => (
!this.state.recipeDetails
? <div>Loading...</div>
: <ul>
{this.state.recipeDetail.map(ingredient => (
<li key={ingredient}>ingredient</li>
)}
</ul>
)
}
Another approach:
Have the recipeDetails stored and available within the original fetched recipes JSON. Then map over the recipes and create multiple <Card key={recipeId} recipeName={recipeName} recipeDetail={recipeDetail} /> components for each recipe.
which for simplicity sake might look like:
<div>
{this.state.recipes.map(({recipeId, recipeName, recipeDetail}), => (
<Card key={recipeId} recipeName={recipeName} recipeDetail={recipeDetail} />
)}
</div>
Then each individual Card has it's own state:
Card.js
export default class Card extends Component {
state = { showDetails: '' }
toggleShowDetails = () => this.setState(prevState => ({ showDetails: !this.state.showDetails }))
render = () => (
<div>
<h1>{this.props.recipeName} Recipe</h1>
<button onClick={toggleShowDetails}> {`${!this.state.showDetails ? "Show" : "Hide"} Recipe<button>
{ this.state.showDetails &&
<ul>
{this.props.recipeDetail.map(ingredient => (
<li key={ingredient}>ingredient</li>
)}
</ul>
}
)
}
Therefore, by default the recipeDetail is already there, but hidden. However, when a user clicks the Card's button, it will toggle the Card's showDetails state to true/false to display/hide the recipe detail.

Resources