Passing data from child component(api call) to parent page in Next JS - reactjs

I have created a project in Next js and rendering the component using getServerSideProps.
It has an index page like this with two component, side and Hero
export default function HeroPage({data, data1, data2, data3}) {
return (
<div className="h-full flex flex-row">
<Side years={data2} chapters={data3} />
<MyMap districts = {data} mis={data1}/>
</div>
);
}
//using Serversideprops and fetching the data from api
// and passing the data to respective components
export async function getServerSideProps() {
const response = await fetch("https://dev.ksrsac.in/testjson/district.json");
const data = await response.json();
const response1 = await fetch("http://localhost:3000/api/mis");
const data1 = await response1.json();
const response2 = await fetch("http://localhost:3000/api/year");
const data2= await response2.json();
const response3 = await fetch("http://localhost:3000/api/sidebar");
const data3 = await response3.json();
return {
props: { data:data, data1:data1, data2:data2, data3:data3 }
};
}
and it is rendering properly.
The side bar component code is as below
export default function Side({ years, chapters }) {
const [yr, setYr] = useState(years);
const [ch, setCh] = useState(chapters);
const [showTab, setShowTab] = useState(false);
function clickHandler(){
setShowTab(true);
}
return (
<div className="w-1/5 bg-blue-200">
{/* year component */}
<div className="pt-2 px-4 m-4 bg-blue-900 rounded-lg">
<label className="text-white"> Year : </label>
<select className="px-10 text-white bg-blue-900">
{yr.map((y) => {
return <option key={y.id}>{y.year}</option>;
})}
</select>
</div>
{/* chapter component */}
<ul className="p-2 px-4">
{ch.map((c) => {
return (
<li key={c.id} onClick={clickHandler} className="bg-blue-900 text-white border border-white-800 rounded-lg p-2 hover:bg-sky-700 hover:text-yellow-300">
{c.id}. {c.cName}
</li>
);
})}
</ul>
{showTab && <Tab handler = {setShowTab}/>}
</div>
);
}
Side bar having a child component called Tab, if user clicks on chapter-> I am showing the Tab
export default function Tab({handler}) {
return (
<div className="absolute z-40 right-4 top-28 w-62 border border-blue-500">
<div className="absolute top-0 right-0">
<button onClick={handleClick=>handler(false)}>X</button>
</div>
<main className="mt-3 underline underline-offset-1">
it contains min/max/avg
</main>
<div className="overflow-y-scroll">
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
</ul>
</div>
</div>
);
}
In the Tab component, I have created a closing button. If user clicks on this, I am closing the tab component by changing the parent setShowTab status to false.
The challenge is
As the user clicks on ay list item - One, Two, Three
I have to call an api, which return array of object like below
One=[{id:1, femalePopCount:200}, {id:2, femalePopCount:300}, {id:3, femalePopCount:400}]
And I have to pass this data to MyMap component, which I am rendering in the index page.
How to do this ?
MyMap component code is as below - in map component I am joining two array i.e.,
district =[{id:1, name:’bidar’},{id:2, name:’dharwad’},{id:3, name:’yadgir’}]
mis =[{id:1, malePopCount:200}, {id:2, malePopCount:300}, {id:3, malePopCount:400}]
which i have received from the getServerSideProps
In MyMap component, as user click different list item from Tab component, I have to change the mis data and render the MyMap component.

Related

How to transfer product items to the payment page after clicking the checkout button in React? [duplicate]

This question already has an answer here:
How do you pass data when using the navigate function in react router v6
(1 answer)
Closed 5 months ago.
Goal: I should transfer all the added product items from the product cart to the payment page after the user will click the checkout button.
Product items inside the product cart:
I should transfer the product items here in the Payment page (after checkout button is clicked):
Problem: I am not sure how to transfer the product items(including its prices, total prices, and increment/decrement buttons) to the payment page, even by using props and replicating mapping functionality of the Basket.jsx(where the cart functionality is found).
I know I shouldn't be replicating the functionality, especially in terms with mapping since there would be one and only one parent component for this.
Source code for Basket.jsx:
import React from "react";
import {
BrowserRouter as Router,
Routes,
Route,
useNavigate
} from "react-router-dom";
export default function Basket(props) {
const navigate = useNavigate();
const navigateToPaymentPage = () => {
navigate("/paymentpage");
};
const { cartItems, onAdd, onRemove } = props;
const itemsPrice = cartItems.reduce((a, c) => a + c.price * c.qty, 0);
const totalPrice = itemsPrice;
// const totalPrice = itemsPrice + discountItemPrice ---- for discount items soon
return (
<aside className="block col-1">
<h2>Cart Items</h2>
{/* Display message when cartItemsLength is 0 */}
<div>{cartItems.length === 0 && <div>Cart is Empty</div>} </div>
{/* Renders the added item to the basket of the shopping cart through mapping cartItems */}
{cartItems.map((item) => (
<div key={item.id} className="row">
<div className="col-2">
{item.name} -- ${item.price.toFixed(2)}
</div>
{/* Increment and Decrement Buttons */}
<div className="col-2">
<button onClick={() => onRemove(item)} className="remove">
-
</button>
<button onClick={() => onAdd(item)} className="add">
+
</button>
Qty: {item.qty}
</div>
<div className="col-2 text-right">
${(item.price * item.qty).toFixed(2)}
</div>
</div>
))}
{cartItems.length !== 0 && (
<>
<hr></hr>
<div className="row">
<div className="col-2">
<strong>Total Price</strong>
</div>
<div className="col-1 text-right">
<strong>${totalPrice.toFixed(2)}</strong>
</div>
</div>
<hr />
<div className="row">
<button onClick={navigateToPaymentPage}>Checkout</button>
</div>
</>
)}
</aside>
);
}
Source code for PaymentPage.jsx:
import React from "react";
import {
BrowserRouter as Router,
Routes,
Route,
useNavigate
} from "react-router-dom";
export default function PaymentPage(props) {
//I replicated the functionality here:
const { cartItems, onAdd, onRemove } = props;
const itemsPrice = cartItems.reduce((a, c) => a + c.price * c.qty, 0);
const totalPrice = itemsPrice;
const navigate = useNavigate();
const navigateToHomeOrderPage = () => {
navigate("/");
};
return (
<aside className="block col-1">
<button
sx={{ width: 10 }}
style={{ maxWidth: "60px" }}
onClick={navigateToHomeOrderPage}
>
Go back
</button>
<h2>PAYMENT PAGE</h2>
{/* Display message when cartItemsLength is 0 */}
<div>{cartItems.length === 0 && <div>Cart is Empty</div>} </div>
{/* Renders the added item to the basket of the shopping cart through mapping cartItems */}
{cartItems.map((item) => (
<div key={item.id} className="row">
<div className="col-2">
{item.name} -- ${item.price.toFixed(2)}
</div>
{/* Increment and Decrement Buttons */}
<div className="col-2">
<button onClick={() => onRemove(item)} className="remove">
-
</button>
<button onClick={() => onAdd(item)} className="add">
+
</button>
Qty: {item.qty}
</div>
<div className="col-2 text-right">
${(item.price * item.qty).toFixed(2)}
</div>
</div>
))}
{cartItems.length !== 0 && (
<>
<hr></hr>
<div className="row">
<div className="col-2">
<strong>Total Price</strong>
</div>
<div className="col-1 text-right">
<strong>${totalPrice.toFixed(2)}</strong>
</div>
</div>
<hr />
</>
)}
</aside>
);
}
Full source codes (functioning App):
https://codesandbox.io/s/productitemsdisplayfixing-cherry-h0br4o-forked-nvu3d0?file=/src/components/PaymentPage.jsx:0-1954
Code Basis: https://www.youtube.com/watch?v=AmIdY1Eb8tY&t=2209s
Your responses would indeed help and guide me a lot since I am very much confused on how to pass these data from one page component to another. Thus, it would be great to hear guides and responses from all of you. Thank you very much!
Just as in the linked answer in the comments, you can pass the data in the state option of the navigate function
const navigate = useNavigate();
const { cartItems, onAdd, onRemove } = props;
const itemsPrice = cartItems.reduce((a, c) => a + c.price * c.qty, 0);
const totalPrice = itemsPrice;
const navigateToPaymentPage = () => {
navigate("/paymentpage", {
state: {
totalPrice,
cartItems
}
});
};
And then get it on the other page from the location object
const navigate = useNavigate();
const location = useLocation();
const navigateToHomeOrderPage = () => {
navigate("/");
};
const data = location.state;
return (
<aside className="block col-1">
<button
sx={{ width: 10 }}
style={{ maxWidth: "60px" }}
onClick={navigateToHomeOrderPage}
>
Go back
</button>
<h2>PAYMENT PAGE</h2>
{JSON.stringify(data)}
</aside>
);
You can check the updated sandbox https://codesandbox.io/s/react-router-dom-pass-state-r1iis7?file=/src/components/Basket.jsx
However, a cart implementation usually uses a backend as a source of truth, but if your project is for learning purposes, I recommend you to also take a look at global state management libraries, the most common is reduxjs in case you need to persist the data through route changes or even between page reloads if you add redux-persist

How to push new value in array useState in react

I want when I click on a card of the movie name, this movie name is pushed in useSate. The problem is when I click on the same card, data is saved in the useState as an array. But when I click on a different card previous value of the useState is deleted.
Here is my code. movie is a single object.
const MovieCard = ({ movie }) => {
const [firstCat, setFirstCat] = React.useState([]);
const movieList = (movie) => {
setFirstCat([...firstCat, movie]);
}
console.log(firstCat);
return (
<div className='mt-3 cursor-pointer' onClick={() => movieList(movie)}>
<div className="max-w-sm bg-white rounded-lg border border-gray-200 shadow-md">
<img className="rounded-t-lg" src="/docs/images/blog/image-1.jpg" alt="" />
<div className="p-5">
<h5 className="mb-2 text-xl font-bold tracking-tight text-gray-900">{movie.Title}</h5>
</div>
</div>
</div>
);
};
export default MovieCard;
when clicking on the same card, useState is like this,
When clicking on the different card
Your problem is you scoped your states only in MovieCard which means each MovieCard will have different states.
You need to add states on the upper/parent component of MovieCard. Let's assume you have MovieList contains all MovieCard
const MovieList = () => {
const [firstCat, setFirstCat] = React.useState([]);
console.log(firstCat);
return <div>
<MovieCard movie={movie} setFirstCat={setFirstCat}/>
<div/>
}
And modify your MovieCard like below
const MovieCard = ({ movie, setFirstCat }) => {
const movieList = (movie) => {
setFirstCat([...firstCat, movie]);
}
return (
<div className='mt-3 cursor-pointer' onClick={() => movieList(movie)}>
<div className="max-w-sm bg-white rounded-lg border border-gray-200 shadow-md">
<img className="rounded-t-lg" src="/docs/images/blog/image-1.jpg" alt="" />
<div className="p-5">
<h5 className="mb-2 text-xl font-bold tracking-tight text-gray-900">{movie.Title}</h5>
</div>
</div>
</div>
);
};
export default MovieCard;
This technique is called state lifting
Looks like each MovieCard component has its own firstCat state. When you click another movie card you are updating that card's state.
You likely need to lift state up and store the firstCat state in the common parent component so all MovieCard components can reference the same single state.
Example:
const Movies = () => {
const [movies, setMovies] = React.useState([.......]);
const [firstCat, setFirstCat] = React.useState([]);
const addMovie = (movie) => {
setFirstCat(firstCat => [...firstCat, movie]);
}
return movies.map(movie => (
<MovieCard
key={movie.id}
movie={movie}
addMovie={() => addMovie(movie)}
/>
));
}
...
const MovieCard = ({ movie, addMovie }) => {
return (
<div className='....' onClick={addMovie}>
<div className="....">
<img
className="...."
src="...."
alt=""
/>
<div className="p-5">
<h5 className="....">
{movie.Title}
</h5>
</div>
</div>
</div>
);
};

Dynamically rendering child components in react

I'm using firestore database to store my data in the collection "listings". So for each document in "listings", I need to render a <BookListing/> element in Home.js with the data from each document. From my research, there are a few other questions similar to this one out there, but they're outdated and use different react syntax. Here's my code:
function BookListing({id, ISBN, title, image, price}) {
return (
<div className="bookListing">
<div className='bookListing_info'>
<p className="bookListing_infoTitle">{title}</p>
<p className="bookListing_infoISBN"><span className="bookListing_infoISBNtag">ISBN: </span>{ISBN}</p>
<p className="bookListing_infoPrice">
<small>$</small>
{price}
</p>
</div>
<img className="bookListing_img" src={image} alt=""></img>
<button className="bookListing_addToCart">Add to Cart</button>
</div>
)
}
export default BookListing
function Home() {
document.title ="Home";
useEffect(() => {
getDocs(collection(db, 'listings'))
.then(queryCollection => {
queryCollection.forEach((doc) => {
console.log(doc.id, " => ", doc.data());
const element = <BookListing id="456" ISBN="0101" title="sample_title" image="https://nnpbeta.wustl.edu/img/bookCovers/genericBookCover.jpg" price="25"/>;
ReactDOM.render(
element,
document.getElementById('home-contents-main')
);
})
});
}, []);
return (
<div className="home">
<div className="home_container">
<div id="home-contents-main" className="home_contents">
</div>
</div>
</div>
)
}
export default Home
It's best (and most common) to separate the task into two: asynchronously fetching data (in your case from firestore), and mapping that data to React components which are to be displayed on the screen.
An example:
function Home() {
// A list of objects, each with `id` and `data` fields.
const [listings, setListings] = useState([]) // [] is the initial data.
// 1. Fetching the data
useEffect(() => {
getDocs(collection(db, 'listings'))
.then(queryCollection => {
const docs = [];
queryCollection.forEach((doc) => {
docs.push({
id: doc.id,
data: doc.data()
});
// Update the listings with the new data; this triggers a re-render
setListings(docs);
});
});
}, []);
// 2. Rendering the data
return (
<div className="home">
<div className="home_container">
<div className="home_contents">
{
listings.map(listing => (
<BookListing
id={listing.id}
ISBN={listing.data.ISBN}
title={listing.data.title}
image={listing.data.image}
price={listing.data.price}
/>
))
}
</div>
</div>
</div>
);
}
Some tips:
Fetching data from other web servers or services can be, and typically is, done in the same manner.
This example could be improved a lot in terms of elegance with modern JS syntax, I was trying to keep it simple.
In most cases, you don't want to use ReactDOM directly (only for the entry point of your app), or mess with the DOM manually; React handles this for you!
If you're not familiar with the useState hook, read Using the State Hook on React's documentation. It's important!
You can create a reusable component, and pass the data to it, and iterate over it using map() . define a state, and use it within the useEffect instead of creating elements and handling the process with the state as a data prop.
function BookListing({ id, ISBN, title, image, price }) {
return (
<div className="bookListing">
<div className="bookListing_info">
<p className="bookListing_infoTitle">{title}</p>
<p className="bookListing_infoISBN">
<span className="bookListing_infoISBNtag">ISBN: </span>
{ISBN}
</p>
<p className="bookListing_infoPrice">
<small>$</small>
{price}
</p>
</div>
<img className="bookListing_img" src={image} alt=""></img>
<button className="bookListing_addToCart">Add to Cart</button>
</div>
);
}
function Home() {
const [data, setData] = useState([]);
useEffect(() => {
document.title = 'College Reseller';
getDocs(collection(db, 'listings')).then((queryCollection) => setData(queryCollection));
}, []);
return (
<div className="home">
<div className="home_container">
<div id="home-contents-main" className="home_contents">
{data.map((doc) => (
<BookListing
id="456"
ISBN="0101"
title="sample_title"
image="https://nnpbeta.wustl.edu/img/bookCovers/genericBookCover.jpg"
price="25"
/>
))}
</div>
</div>
</div>
);
}
export default Home;

How to open dynamic modal with react js

I am trying to convert the HTML/Javascript modal to React js.
In Reactjs, I just want to open the modal whenever the user clicks the View Project button.
I have created a parent component (Portfolio Screen) and a child component (Portfolio Modal). The data I have given to the child component is working fine but the modal opens the first time only and then does not open. Another problem is that the data does not load even when the modal is opened the first time.
Codesandbox link is here.
https://codesandbox.io/s/reverent-leftpad-lh7dl?file=/src/App.js&resolutionWidth=683&resolutionHeight=675
I have also shared the React code below.
For HTML/JavaScript code, here is the question I have asked before.
How to populate data in a modal Popup using react js. Maybe with hooks
Parent Component
import React, { useState } from 'react';
import '../assets/css/portfolio.scss';
import PortfolioModal from '../components/PortfolioModal';
import portfolioItems from '../data/portfolio';
const PortfolioScreen = () => {
const [portfolio, setportfolio] = useState({ data: null, show: false });
const Item = (portfolioItem) => {
setportfolio({
data: portfolioItem,
show: true,
});
};
return (
<>
<section className='portfolio-section sec-padding'>
<div className='container'>
<div className='row'>
<div className='section-title'>
<h2>Recent Work</h2>
</div>
</div>
<div className='row'>
{portfolioItems.map((portfolioItem) => (
<div className='portfolio-item' key={portfolioItem._id}>
<div className='portfolio-item-thumbnail'>
<img src={portfolioItem.image} alt='portfolio item thumb' />
<h3 className='portfolio-item-title'>
{portfolioItem.title}
</h3>
<button
onClick={() => Item(portfolioItem)}
type='button'
className='btn view-project-btn'>
View Project
</button>
</div>
</div>
))}
<PortfolioModal portfolioData={portfolio} show={portfolio.show} />
</div>
</div>
</section>
</>
);
};
export default PortfolioScreen;
Child Component
import React, { useState, useEffect } from 'react';
import { NavLink } from 'react-router-dom';
const PortfolioModal = ({ portfolioData, show }) => {
const portfolioItem = portfolioData;
const [openModal, setopenModal] = useState({ showState: false });
useEffect(() => {
setopenModal({
showState: show,
});
}, [show]);
return (
<>
<div
className={`portfolio-popup ${
openModal.showState === true ? 'open' : ''
}`}>
<div className='pp-inner'>
<div className='pp-content'>
<div className='pp-header'>
<button
className='btn pp-close'
onClick={() =>
setopenModal({
showState: false,
})
}>
<i className='fas fa-times pp-close'></i>
</button>
<div className='pp-thumbnail'>
<img src={portfolioItem.image} alt={`${portfolioItem.title}`} />
</div>
<h3 className='portfolio-item-title'>{portfolioItem.title}</h3>
</div>
<div className='pp-body'>
<div className='portfolio-item-details'>
<div className='description'>
<p>{portfolioItem.description}</p>
</div>
<div className='general-info'>
<ul>
<li>
Created - <span>{portfolioItem.creatDate}</span>
</li>
<li>
Technology Used -
<span>{portfolioItem.technologyUsed}</span>
</li>
<li>
Role - <span>{portfolioItem.Role}</span>
</li>
<li>
View Live -
<span>
<NavLink to='#' target='_blank'>
{portfolioItem.domain}
</NavLink>
</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</>
);
};
export default PortfolioModal;
You don't have to use one useState hook to hold all your states. You can and I think you should break them up. In the PortfolioScreen component
const [data, setData] = useState(null);
const [show, setShow] = useState(false);
I changed the function Item that is used to set the active portfolio item to toggleItem and changed it's implementation
const toggleItem = (portfolioItem) => {
setData(portfolioItem);
setVisible(portfolioItem !== null);
};
You should use conditional rendering on the PortfolioModal, so you won't need to pass a show prop to it, and you'll pass a closeModal prop to close the PortfolioModal when clicked
{visible === true && data !== null && (
<PortfolioModal
data={data}
closeModal={() => toggleItem()} // Pass nothing here so the default value will be null and the modal reset
/>
)}
Then in the PortfolioModal component, you expect two props, data and a closeModal function
const PortfolioModal = ({ data, closeModal }) => {
And the close button can be like
<button className="btn pp-close" onClick={closeModal}>
...

how to re-render same page on a click in react

I have written this code using react and redux
class BooksContainer extends Component {
componentDidMount = () => {
const category = this.props.category;
this.props.searchBook(category);
this.props.fetchBook();
}
handleClickPrevious = () => {
console.log('clicked on previous: ' + this.props.previous)
this.props.urlprev(this.props.previous)
}
handleClickNext = () => {
console.log('clicked on next: ' + this.props.next)
this.props.urlnext(this.props.next)
}
render() {
const books = this.props.books;
let content = books
content = books.length > 0 ?
books.map(({ subjects, formats, title, authors }, index) => {
subjects = subjects.join()
if (subjects.includes('fiction') === true || subjects.includes('Fiction') === true) {
return (<div key={index} className="col-md-3 mb-5" >
<div className="card card-body bg-dark text-center h-100">
<img className="w-100 mb-2" src={formats['image/jpeg'] || close} alt="Book Cover" />
<h5 className="text-light text-left card-title">
{title}
</h5>
<p className="text-light text-left">
{authors.map(({ name }) => name)}
</p>
</div>
</div>)
}
}
)
: null;
return (
<div>
<div className="row">
{content}
</div>
<div>
<div className="d-flex justify-content-between">
<img className="bg-dark" src={left} alt="previous" onClick={this.handleClickPrevious} />
<img className="bg-dark" src={right} alt="next" onClick={this.handleClickNext} />
</div>
</div>
</div>
)
}
}
const mapStateToProps = state => ({
books: state.books.books,
next: state.books.next,
previous: state.books.previous
})
export default connect(mapStateToProps, { searchBook, fetchBook, urlprev, urlnext })(BooksContainer);
in the return method when i click next or prev it should render books from next link provided by the api Api->http://skunkworks.ignitesol.com:8000/books.
I want to re-render the same component with books obtained from the next or previous link.
I tried using shouldComponentUpdate but it isnt working if i use shouldComponentUpdate then the current displaying books also do not render
Your component will automatically rerender on any dependency change that is props or state, you can also you this.forceUpdate() but this is highly unrecommended.
Given that your component is connected to the Redux store, dispatching a action to change you currently selected item will also trigger an component rerender. I would have currentlySelected in redux store as a variable which I would change and fetch via mapStateToProps into my component then you could use actions to trigger a change of the currentlySelected and thereby update your components props effectivly rerendering it

Resources