react-redux: Rendering a component after an API call - reactjs

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.

Related

How do I add in an additional property for each individual mapped item after already making a call to a 3rd party API?

I want to add a feature that increments how many "likes" someone gets similar to FB. The profiles are getting passed in through an Axios GET request through a 3rd party API. When a user clicks on the like button, the amount of likes someone gets should increment by 1. The code I previously wrote in handleClicks() increments everyone's likes by 1 rather than just one individual person. The data is passed into cards[] in one chunk.
App.js
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
class App extends React.Component {
constructor(props) {
super(props)
this.state = {cards: [], numVotes: 0};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log("This is working!");
this.setState(numVotes: state.numVotes + 1})
}
componentDidMount() {
axios.get('/')
.then(res => {
this.setState({cards: res.data})
console.log(this.state);
})
}
render() {
return (
<div className="main-container">
<Header />
<CardList
cards={this.state.cards}
handleClick={this.handleClick}
/>
<hr className="ui divider"></hr>
</div>
);
}
export default App;
const CardList = props => {
const cards = props.cards.map(card => {
return <Card image={card.image_url}
name={card.name}
title={card.title}
blurb={card.bio}
handleClick={props.handleClick}
numVotes={props.numVotes}
/>
})
return <div className="ui divided items">
{cards}
</div>
}
Card.js
const Card = (props) => {
return (
<div className="card-component item">
<div class="ui small rounded image">
<img className="portrait"
src = {props.image}
onError={(e)=>{e.target.onerror = null; e.target.src='https://image.shutterstock.com/image-vector/no-image-available-vector-illustration-260nw-744886198.jpg'}}
/>
</div>
<div class="content">
<a className="header">{props.name}</a>
<div class="meta">
<span className="title">{props.title}</span>
</div>
<p className="blurb">{props.blurb}</p>
<p><span className="question"> Want to work with {props.name}?</span>
<span className="like-button" onClick={props.handleClick}>
<img className="icon" src={icon} />Yes!
</span>
</p>
<p className="yes-amt">{props.numVotes} people have said Yes!</p>
</div>
</div>
)
}
You need to first decide how to identify each card as unique so you can update the correct one. If you have an id that would be ideal, but I'm going to assume the name is unique since its a value in your question.
// pass the unique identifier to handler
handleClick(name) {
this.setState((prevState) => ({
// map over the previous cards and return a new array
cards: prevState.cards.map((card) => {
// If the name matches the current card, change it
if (card.name === name) {
return {...card, numVotes: card.numVotes + 1};
} else {
// Otherwise just return the same card unchanged.
return card;
}
})
}))
}
Then in your component use it like this:
// Use inline function so we can pass it a prop as a parameter
<span className="like-button" onClick={() => props.handleClick(props.name)}>

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

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>
);
}
}

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 Sort By Like

I am trying to figure out how to add an onClick feature that will then sort the likes in descending order. AKA each project has a 'like' button. I want to add another button to the page to allow the user to sort the project likes by descending order.
import React from 'react';
import ProjectsListItem from './ProjectsListItem'
const Project = ({ projects }) => {
const renderProjects = projects.projects.map(project =>
<ProjectsListItem project={project} key={project.id}/>
);
return (
<div className="container">
<div className="row">
{renderProjects}
</div>
</div>
);
};
export default Project;
Page 2
class ProjectsListItem extends Component {
handleOnClick = () => {
this.props.likeProject(this.props.project)
}
onClick = () => {
this.props.sortBy(this.props.project.like)
}
render() {
return(
<div>
<div className="col-sm-4">
<div className="container-fluid text-left">
<h4> <Link key={this.props.project.id} to=
{`/projects/${this.props.project.id}`}>{this.props.project.title}
</Link> </h4>
<h5> {this.props.project.studio}</h5>
<CounterButton project={this.props.project} likeProject=
{this.handleOnClick}/>
</div>
</div>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
projects: state.projects
}
}
export default connect(mapStateToProps, {likeProject})
(ProjectsListItem);
You would have to make an event handler such as
https://reactjs.org/docs/handling-events.html
In this case you would probably want to do
onSortClick(e) {
e.preventDefault();
this.props.sorted = true
}
bind that to your click handler like this:
<CounterButton project={this.props.project} likeProject=
{this.onSortClick.bind(this)}/>
Hope this helps.

Passing state back to child component

I'm trying to figure out how can i properly pass state back to the child component.
Currently I have list of items and everytime i click on one of the items it changes state of "selectedVideo" variable in parent component. And then I would like to add class to the item that corresponds to that state in that child component. Basically when I click on that item in that list it become highlighted because it just changed the state of parent component.
So the main parent component is here:
index.js
class App extends Component {
constructor(props) {
super(props)
this.state = {
videos2:[],
selectedVideo:null
}
this.DMSearch()
}
DMSearch(term){
fetch(`https://api.dailymotion.com/videos?fields=description,id,thumbnail_60_url,title,url,&limit=5&search=${term}`)
.then(result => result.json())
.then(videos2 => {
//console.log(videos2.list[0]);
this.setState({
videos2: videos2.list,
selectedVideo: videos2.list[0]
});
//console.log(this.state.selectedVideo);
});
}
render () {
const DMSearch = _.debounce((term) => { this.DMSearch(term)}, 400);
return (
<div>
<SearchBar onSearchTermChange= {DMSearch}/>
<VideoDetail video={this.state.selectedVideo}/>
<VideoList
onVideoSelect={selectedVideo=>this.setState({selectedVideo})}
videos2={this.state.videos2}/>
</div>
)
}
}
Now the child component which changes state onclick
video_list_item.js
const VideoListItem = ({video, onVideoSelect}) => {
const imageUrl = video.thumbnail_60_url;
return (
<li onClick={() => onVideoSelect(video)} className="list-group-item">
<div className="video-list media">
<div className="media-left">
<img className="media-obj" src={imageUrl}/>
</div>
<div className="media-body">
<div className="media-heading">{video.title}</div>
</div>
</div>
</li>
);
};
And what I want is to add class "active" to this specific line
<li onClick={() => onVideoSelect(video)} className="list-group-item">
Based on the state of selectedVideo that changed in index.js after clicking on that component.
Also here is the code for the whole list.
video_list.js
const VideoList = (props) => {
const videoItems = props.videos2.map((video)=>{
return (
<VideoListItem
onVideoSelect={props.onVideoSelect}
key={video.id}
video={video} />
)
})
return (
<ul className="col-md-4 list-group">
{videoItems}
</ul>
)
}
You have to pass the selectedVideo state of your App to the VideoList component,
<VideoList
videos2={this.state.videos2}
onVideoSelect={selectedVideo=>this.setState({selectedVideo})}
selectedVideo={this.state.selectedVideo}
/>
which in turn passes it to each VideoListItem
const videoItems = props.videos2.map((video)=>{
return (
<VideoListItem
onVideoSelect={props.onVideoSelect}
key={video.id}
video={video}
active={video === props.selectedVideo}
/>
)
})
so each item can compare itself to the selectedVideo and display an 'active' class if needed.

Resources