Display the content of the clicked item in a modal popUp - javascript

i'm using React to create an App where i can see the flags and various infos about every country. I'm using an API to fetch the data and i've already mapped them all with a grid. That's my code so far:
class App extends React.Component{
constructor (props){
super (props);
this.state={
countries : [],
info: ""
}
}
componentDidMount(){
axios.get(`https://restcountries.eu/rest/v2/all`)
.then(res => {
const data = res.data;
console.log(data);
this.setState({
countries : data
})
this.showInfo = this.showInfo.bind(this)
})
}
showInfo (e) {
console.log(e.target.key);
}
render() {
return (
<div className="container">
{this.state.countries.map(country=>
<Country name={country.name}
key={country.name}
population ={country.population}
region={country.region}
capital={country.capital}
flag={country.flag}
showInfo={this.showInfo}
/>
)}
</div>
)
}
}
export default App;
And this is my Country-item component:
const Country = ({name, population, region, capital, flag, showInfo})=>{
return (
<div onClick={showInfo} className="country-item">
<div className="img">
<img src={flag}/>
</div>
<p>{name}</p>
<p>population: {population}</p>
<p>Region: {region}</p>
<p>Capital: {capital}</p>
</div>
)
}
export default Country
So far for now i have something like this:
enter image description here
Now i would like to click on each country box item and display that clicked data inside a modal popUp. If i create a modal and i will map it, of course on click i will have all of them displayed in once. how can i pass the props of that box i clicked on the modal component? i created a function trying to capture for example the key props, but i didn't suceed. What's the best strategy? thank you very much for the help

Attach an onClick handler to each country. When it's clicked, save the country name to the state of the containing component. Render the modal with content only from the country clicked:
class App extends React.Component{
constructor (props){
super (props);
this.state={
countries : [],
info: "",
clicked: ''
}
this.countryClickHandler = e => {
this.setState({clicked: country.name}, () => {
window.addEventListener('click', this.closeCountryPopup)
})
}
this.closeCountryPopup = e => {
this.setState({clicked: ''}, () => {
window.removeEventListener('click', this.closeCountryPopup)
})
}
}
componentDidMount(){
axios.get(`https://restcountries.eu/rest/v2/all`)
.then(res => {
this.setState({
countries : res.data
})
})
}
renderPopup() {
// if the clicked flag is falsy, null, or an empty string, don't render anything
if(!this.state.clicked || this.state.clicked === null || !this.state.clicked.length) return null
// otherwise, render the only clicked country by filtering it by matching it with the string in the state
const clickedCountry = this.state.countries.find(country => country.name === this.state.clicked)
return (
<div className="popup_container">
<Country
name={clickedCountry.name}
key={clickedCountry.name}
population ={clickedCountry.population}
region={clickedCountry.region}
capital={clickedCountry.capital}
flag={clickedCountry.flag}
/>
</div>
)
}
render() {
return (
<div className="container">
{this.state.countries.map(country =>
<div onClick={this.countryClickHandler}>
<Country
name={country.name}
key={country.name}
population ={country.population}
region={country.region}
capital={country.capital}
flag={country.flag}
/>
</div>
)}
{ this.renderPopup() }
</div>
)
}
}
export default App;

Your App component should maintain the state of which country should be displayed in a modal. More specifically App component will save in it's state if a modal should be displayed and which country to be displayed in the modal.
The showInfo prop that you pass to the Country component, should notify App component when a country is clicked.
I have created a representative example on CodePen.
class App extends React.Component {
constructor () {
super();
this.state = {
showModal: false,
selectedCountry: {},
countries: [
{name: "Germany", continent: "Europe"},
{name: "South Korea", continent: "Asia"},
{name: "New Zealnd", continent: "Australia"}
]
};
}
handleCloseModal = () => {
this.setState({
showModal: false
});
}
showInfo = (name) => {
this.setState({
selectedCountry: this.state.countries.find(it => it.name===name),
showModal: true
});
}
render () {
return (
<div>
{
this.state.countries.map((country) => <Country
name={country.name}
continent={country.continent}
showInfo={this.showInfo}
/>
)
}
<ReactModal
isOpen={this.state.showModal}
contentLabel={this.state.selectedCountry.name}
>
<div className="modal">
<div>{this.state.selectedCountry.name}</div>
<div>{this.state.selectedCountry.continent}</div>
</div>
<button onClick={this.handleCloseModal}>Close Modal</button>
</ReactModal>
</div>
);
}
}
const Country = (props) => {
return (
<div className="country" onClick={() => props.showInfo(props.name)}>
<div>{props.name}</div>
<span>-</span>
<div>{props.continent}</div>
</div>
)
};

Related

React js conditionally rendering a class to a specific mapped item

I have been attempting to toggle a class on click so that when I click on one of the mapped items in my Tasks component, I add the 'complete' class and put a line through that item (crossing items off of a todo list). However with my current code set up, when I click on one element to add the class, all the other elements get crossed out as well and vice versa.
Here is my current setup. The class 'complete' is what will add a line through one of the mapped items in the Tasks component.
import { Container, Row} from 'react-bootstrap';
import {Link} from 'react-router-dom';
import axios from 'axios';
const List = (props) =>{
return(
<div>
<Link style={{textDecoration:'none'}} to={`/lists/${props.listId}`} > <p className="list-item">{props.item}</p></Link>
</div>
)
}
const Tasks = (props) =>{
return(
<div onClick={props.onClick} className={props.className} >
<div className='task-item' >
<p >{props.item}</p>
</div>
</div>
)
}
export default class Display extends Component {
constructor(props){
super(props)
this.onCompletedTask = this.onCompletedTask.bind(this);
this.state = {
list: [],
tasks:[],
complete:false
}
}
componentWillUpdate(nextProps){
axios.get(`http://localhost:8080/lists/${this.props.match.params.listId}`)
.then(response =>{
this.setState({
tasks:response.data
})
})
}
componentDidMount(){
axios.get('http://localhost:8080/lists')
.then(response=>{
this.setState({
list:response.data
})
})
.catch(error =>{
console.log(error)
});
}
onCompletedTask(item){
this.setState({ complete: !this.state.complete});
}
listCollection(){
return(
this.state.list.map(item=>{
return(<List item = {item.title} listId={item._id} key = {item._id} />)
})
)
}
taskCollection(){
return(
this.state.tasks.map((item, index) =>{
return(<Tasks onClick = {()=>this.onCompletedTask(item)} className={this.state.complete ? 'complete': ''} item={item.task} key={index}/>)
})
)
}
render() {
return (
<div id='main' >
<Container>
<Row>
<div className="sidebar">
<h1 style={{fontSize:"25pt"}}>Lists</h1>
<div className="list-menu">
{this.listCollection()}
</div>
<form action='/new-list' method='GET'>
<div style={{textAlign:'center'}}>
<button className='list-button' style={{fontSize:'12pt', borderRadius:'5px'}}>
+ New List
</button>
</div>
</form>
</div>
<div className='tasks'>
<h1 style={{fontSize:'25pt'}}>Tasks</h1>
{this.taskCollection()}
<form action={`/lists/${this.props.match.params.listId}/new-task`} method='GET'>
<button className='task-button'>
+
</button>
</form>
</div>
</Row>
</Container>
</div>
)
}
}
Your state holds only a single completed value, which OFC toggle all tasks. You could instead store a map of completed tasks.
this.state = {
list: [],
tasks: [],
complete: {}, // <--- use empty object as simple map object
}
Update onCompletedTask to store some uniquely identifying property of a task, like an id field
onCompletedTask(item){
this.setState(prevState => ({
completed: {
...prevState.completed, // <--- spread existing completed state
[item.id]: !prevState.completed[item.id] // <--- toggle value
},
}));
}
Update. taskCollection to check the completed map by id
taskCollection = () => {
const { completed, tasks } = this.state;
return tasks.map((item, index) => (
<Tasks
onClick={() => this.onCompletedTask(item)}
className={completed[item.id] ? "complete" : ""} // <--- check completed[item.id]
item={item.task}
key={index}
/>
))
};

How Do I Dynamically Set State Of Variable For Render Function

For My Class We Are Making A Website With React And Neither Me Or my Group Can Figure Out How To Just Render A Function In A Variable State And Make It Dynamic
My Code Is As Follows:
class App extends React.Component {
constructor(props)
{
super(props)
this.state = {
screen: this.home(),
movies: []
}
}
home = () =>{
this.state.movies.map((movie)=>{
return(
<div>
<Popular
title={movie.title}
rating={movie.vote_average}
poster={movie.poster_path}
desc={movie.overview}
/>
</div>
)
})
}
render(){
return(
<div>{this.state.screen}</div>
)
}
}
When I Run This The Error Reads
TypeError: Cannot read property 'movies' of undefined
You Can Assume That The Variable in State Movies Is Filled With An Array Of Movies Set By An API
Edit: The End Result I'm Attempting To Achieve Is To Return A Variable Or State Which Can Hold A Function Which Would Be The Different Screens/Pages To Be Rendered
If your movies array filled with data from any API call, then you can directly use that array to render the data,
class App extends React.Component {
constructor(props)
{
super(props)
this.state = {
movies: []
}
}
render(){
return(
<div>
{
this.state.movies.map((movie)=>{
return(
<div>
<Popular
title={movie.title}
rating={movie.vote_average}
poster={movie.poster_path}
desc={movie.overview}
/>
</div>
)
})
}
</div>
)
}
}
The root cause here is that this.state is not initialized when you're using it the home() invocation in the constructor.
Either way, you're not supposed to store rendered content within state.
Based on the comment, here's a refactoring, but I would recommend looking into an actual router like react-router instead.
const HomeView = ({ movies }) =>
movies.map(movie => (
<div>
<Popular
title={movie.title}
rating={movie.vote_average}
poster={movie.poster_path}
desc={movie.overview}
/>
</div>
));
const FavoritesView = ({ movies }) => <>something else...</>;
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
movies: [],
view: "home",
};
}
render() {
let view = null;
switch (this.state.view) {
case "home":
view = <HomeView movies={this.state.movies} />;
break;
case "favorites":
view = <FavoritesView movies={this.state.movies} />;
break;
}
return (
<div>
<a href="#" onClick={() => this.setState({ view: "home" })}>
Home
</a>
<a href="#" onClick={() => this.setState({ view: "favorites" })}>
Favorites
</a>
{view}
</div>
);
}
}

Easy communication of image between siblings

I'm new to ReactJS and I would like to communicate between my components.
When I click an image in my "ChildA" I want to update the correct item image in my "ChildB" (type attribute in ChildA can only be "itemone", "itemtwo", "itemthree"
Here is what it looks like
Parent.js
export default class Parent extends Component {
render() {
return (
<div className="mainapp" id="app">
<ChildA/>
<ChildB/>
</div>
);
}
}
if (document.getElementById('page')) {
ReactDOM.render(<Builder />, document.getElementById('page'));
}
ChildA.js
render() {
return _.map(this.state.eq, ecu => {
return (
<img src="../images/misc/ec.png" type={ecu.type_eq} onClick={() => this.changeImage(ecu.img)}/>
);
});
}
ChildB.js
export default class CharacterForm extends Component {
constructor(props) {
super(props);
this.state = {
items: [
{ name: "itemone" image: "defaultone.png"},
{ name: "itemtwo" image: "defaulttwo.png"},
{ name: "itemthree" image: "defaultthree.png"},
]
};
}
render() {
return (
<div className="items-column">
{this.state.items.map(item => (<FrameCharacter key={item.name} item={item} />))}
</div>
);
}
}
I can retrieve the image on my onClick handler in my ChildA but I don't know how to give it to my ChildB. Any hints are welcomed, thanks you!
What you need is for Parent to pass an event handler down to ChildA which ChildA will call when one of the images is clicked. The event handler will call setState in Parent to update its state with the given value, and then Parent will pass the value down to ChildB in its render method.
You can see this working in the below example. Since I don't have any actual images to work with—and to keep it simple—I've used <button>s instead, but the principle is the same.
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
clickedItem: 'none',
};
}
render() {
return (
<div>
<ChildA onClick={this.handleChildClick}/>
<ChildB clickedItem={this.state.clickedItem}/>
</div>
);
}
handleChildClick = clickedItem => {
this.setState({ clickedItem });
}
}
const items = ['item1', 'item2', 'item3'];
const ChildA = ({ onClick }) => (
<div>
{items.map(name => (
<button key={name} type="button" onClick={() => onClick(name)}>
{name}
</button>
))}
</div>
);
const ChildB = ({clickedItem}) => (
<p>Clicked item: {clickedItem}</p>
);
ReactDOM.render(<Parent/>, document.querySelector('div'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.development.js"></script>
<div></div>

React click on item to show details

Iam new to React and I'm trying to interact with the swapi API.
I want to get the list of films (movie titles list) and when I click on a title to show the opening_crawl from the json object.
I managed to get the film titles in an array. I don't know how to proceed from here.
Here is my code:
class StarWarsApp extends React.Component {
render() {
const title = "Star Wars";
const subtitle = "Movies";
return (
<div>
<Header title={title} />
<Movies />
</div>
);
}
}
class Header extends React.Component {
render() {
return (
<div>
<h1>{this.props.title}</h1>
</div>
);
}
}
class Movies extends React.Component {
constructor(props) {
super(props);
this.handleMovies = this.handleMovies.bind(this);
this.state = {
movies: []
};
this.handleMovies();
}
handleMovies() {
fetch("https://swapi.co/api/films")
.then(results => {
return results.json();
})
.then(data => {
console.log(data);
let movies = data.results.map(movie => {
return <div key={movie.episode_id}>{movie.title}</div>;
});
this.setState(() => {
return {
movies: movies
};
});
});
}
render() {
return (
<div>
<h1>Episodes</h1>
<div>{this.state.movies}</div>
</div>
);
}
}
ReactDOM.render(<StarWarsApp />, document.getElementById("app"));
To iterate over movies add this in render method:
render(){
return (
<div>
<h1>Episodes</h1>
{
this.state.movies.map((movie, i) => {
return (
<div className="movie" onClick={this.handleClick} key={i}>{movie.title}
<div className="opening">{movie.opening_crawl}</div>
</div>
);
})
}
</div>
);
}
Add this method to your Movies component to add active class on click to DIV with "movie" className:
handleClick = event => {
event.currentTarget.classList.toggle('active');
}
Include this css to your project:
.movie .opening {
display: none;
}
.active .opening {
display: block
}
After fetching the data, just keep it in your state then use the pieces in your components or JSX. Don't return some JSX from your handleMovies method, just do the setState part there. Also, I suggest using a life-cycle method (or hooks API maybe if you use a functional component) to trigger the fetching. By the way, don't use class components unless you need a state or life-cycle methods.
After that, you can render your titles in your render method by mapping the movies state. Also, you can have a place for your opening_crawls part and render it with a conditional operator. This condition changes with a click. To do that you have an extra state property and keep the movie ids there. With the click, you can set the id value to true and show the crawls.
Here is a simple working example.
const StarWarsApp = () => {
const title = "Star Wars";
const subtitle = "Movies";
return (
<div>
<Header title={title} />
<Movies />
</div>
);
}
const Header = ({ title }) => (
<div>
<h1>{title}</h1>
</div>
);
class Movies extends React.Component {
state = {
movies: [],
showCrawl: {}
};
componentDidMount() {
this.handleMovies();
}
handleMovies = () =>
fetch("https://swapi.co/api/films")
.then(results => results.json())
.then(data => this.setState({ movies: data.results }));
handleCrawl = e => {
const { id } = e.target;
this.setState(current => ({
showCrawl: { ...current.showCrawl, [id]: !current.showCrawl[id] }
}));
};
render() {
return (
<div>
<h1>Episodes</h1>
<div>
{this.state.movies.map(movie => (
<div
key={movie.episode_id}
id={movie.episode_id}
onClick={this.handleCrawl}
>
{movie.title}
{this.state.showCrawl[movie.episode_id] && (
<div style={{ border: "1px black solid" }}>
{movie.opening_crawl}
</div>
)}
</div>
))}
</div>
</div>
);
}
}
ReactDOM.render(<StarWarsApp />, document.getElementById("root"));
<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>
<div id="root"></div>
I am using id on the target div to get it back from the event object. I don't like this method too much but for the sake of clarity, I used this. You can refactor it and create another component may be, then you can pass the epoisde_id there and handle the setState part. Or you can use a data attribute instead of id.

Pass item data to a react modal

I have a map that render few items and one of its line is below
<a onClick={()=> this.setState({"openDeleteModal":true)}>Delete</a>
Obviously I want to open a modal when user click the delete, but I have to pass a few things like the name of the item, id of the item to perform the deletion. How can I pass says the name to the modal?
I can bind the obj name to a like this
Delete
Am I on the right track?
When working on React applications, try not to think in terms of passing values to other components, but rather updating state that your components are exposed to.
In your example, assuming your modal component is a child of the same component your list of a tags belongs to, you could set the values you are interested in exposing to the modal on the state, as well as updating the property that signals whether the modal is open or not. For example:
class Container extends React.Component {
constructor(props) {
super(props)
this.state = {
openDeleteModal: false,
activeItemName: '', //state property to hold item name
activeItemId: null, //state property to hold item id
}
}
openModalWithItem(item) {
this.setState({
openDeleteModal: true,
activeItemName: item.name,
activeItemId: item.id
})
}
render() {
let buttonList = this.props.item.map( item => {
return (<button onClick={() => this.openModalWithItem(item)}>{item.name}</button>
});
return (
<div>
{/* Example Modal Component */}
<Modal isOpen={this.state.openDeleteModal}
itemId={this.state.activeItemId}
itemName={this.state.activeItemName}/>
{ buttonList }
</div>
)
}
}
Copying over my answer from How to pass props to a modal
Similar scenario
constructor(props) {
super(props)
this.state = {
isModalOpen: false,
modalProduct: undefined,
}
}
//****************************************************************************/
render() {
return (
<h4> Bag </h4>
{this.state.isModalOpen & (
<Modal
modalProduct={this.state.modalProduct}
closeModal={() => this.setState({ isModalOpen: false, modalProduct: undefined})
deleteProduct={ ... }
/>
)
{bag.products.map((product, index) => (
<div key={index}>
<div>{product.name}</div>
<div>£{product.price}</div>
<div>
<span> Quantity:{product.quantity} </span>
<button onClick={() => this.props.incrementQuantity(product, product.quantity += 1)}> + </button>
<button onClick={() => this.decrementQuantity(product)}> - </button> // <----
</div>
</div>
))}
)
}
//****************************************************************************/
decrementQuantity(product) {
if(product.quantity === 1) {
this.setState({ isModalOpen: true, modalProduct: product })
} else {
this.props.decrementQuantity(product)
}
}
Try this: this is the form which has the button, and is a child component of some other component that passes the handleButtonAction method as props, and the button takes the input data and invokes this parent component method
handleSubmit = (e) => {
e.preventDefault();
const data = e.target.elements.option.value.trim();
if (!data) {
this.setState(() => ({ error: 'Please type data' }));
} else {
this.props.handleButtonAction(data, date);
}
}
{this.state.error && <p>{this.state.error}</p>}
<form onSubmit={this.handleSubmit}>
<input type="text" name="option"/>
<div>
<button>Get data</button>
</div>
</form>
The parent component:
handleButtonAction = (data) => {
axios.get(`http://localhost:3000/someGetMethod/${data}`).then(response => {
const resData = response.data;
this.setState({
openModal: true,
status: response.status,
data: resData
});
}).catch((error) => {
if (error.message.toLowerCase() === 'network error') {
this.setStateWithError(-1, {});
}
else { // not found aka 404
this.setStateWithError(error.response.status, '', {currency, date: ddat});
}
});
}

Categories