setting url query for modal - javascript

I'm using image search and display app. Users can click on a photo and a modal would pop up. Those modal would have id in the url. However when I refresh the page, the modal isn't there and an error is shown. I get the url from unsplash api so with page refresh reload the url is gone. How do I Keep the url in url query so that the url persists even on page refresh?
Lisitem
import React, { useState } from "react";
import { Link, BrowserRouter as Router, Route } from "react-router-dom";
import ModalWrapper from "./ModalWrapper";
const ListItem = ({ photo }) => {
return (
<>
<Router>
<div key={photo.id} className="grid__item card">
<div className="card__body">
<Link to={{ pathname: `/${photo.id}`, state: photo }}>
<img src={photo.urls.small} alt="" />
</Link>
<Route path="/:photoId" component={ModalWrapper} />
</div>
</div>
</Router>
</>
);
};
export default ListItem;
Modal wrapper
import React from "react";
import Modal from "react-modal";
import { useHistory, useLocation } from "react-router-dom";
const customStyles = {
content: {
top: "50%",
left: "50%",
right: "auto",
bottom: "auto",
marginRight: "-50%",
transform: "translate(-50%, -50%)"
}
};
Modal.setAppElement("#root");
function ModalWrapper() {
const history = useHistory();
const location = useLocation();
const photo = location.state;
function downloadImage() {}
function close() {
history.push("/");
}
return (
<Modal isOpen={true} onRequestClose={close} style={customStyles}>
<img src={photo.urls.small} alt="" />
<div>
<button onClick={close} className="button">
Close
</button>
<button onClick={downloadImage()}>Download</button>
</div>
</Modal>
);
}
export default ModalWrapper;

The reason why it doesn't work when you refresh the page is because the photo that you passed as a param while navigating is no longer available. But, pathname is something that's still available (because it's part of the URL itself)
So, on the ModalWrapper page, you can check if photo is absent, then make a new API call to get the photo based on the id that is available in the pathname. I've never used unsplash API but I think it would be this API.
Your ModalWrapper would look like this
function ModalWrapper() {
const history = useHistory();
const location = useLocation();
const [photo, setPhoto] = useState(location.state);
useEffect(() => {
if (location.pathname && !location.state) {
// call the new API here using pathname (photo ID) and setPhoto
console.log(location.pathname);
}
}, [location]);
function downloadImage() {}
function close() {
history.push("/");
}
return (
!!photo && (
<Modal isOpen={true} onRequestClose={close} style={customStyles}>
<img src={photo.urls.small} alt="" />
<div>
<button onClick={close} className="button">
Close
</button>
<button onClick={downloadImage()}>Download</button>
</div>
</Modal>
)
);
}
You haven't asked this but, I would also move the Router and Route outside the ListItem and keep it in App.js (wrapping everything in there with Router). Keeping it in ListItem is like having a router and route for each list-item, which is not something you would ideally want. You would want to keep one router and route across the application, and it usually belongs to App.js or a wrapper or sorts. Here's the codesandbox after such changes

Related

REACT JS - how to access data from another component

I have a NavBar component which holds login information on the user. When the user is logged in it says "Welcome" along with the user details. I want to implement the same idea in another component so that when a user posts a blog, it says "Posted By: " along with the users log in details. How would I pass the details form NavBar.js to Products.js ?
import React, { useState, useEffect } from 'react';
import { NavLink } from 'react-router-dom';
const NavBar = (props) => {
const providers = ['twitter', 'github', 'aad'];
const redirect = window.location.pathname;
const [userInfo, setUserInfo] = useState();
useEffect(() => {
(async () => {
setUserInfo(await getUserInfo());
})();
}, []);
async function getUserInfo() {
try {
const response = await fetch('/.auth/me');
const payload = await response.json();
const { clientPrincipal } = payload;
return clientPrincipal;
} catch (error) {
console.error('No profile could be found');
return undefined;
}
}
return (
<div className="column is-2">
<nav className="menu">
<p className="menu-label">Menu</p>
<ul className="menu-list">
<NavLink to="/products" activeClassName="active-link">
Recipes
</NavLink>
<NavLink to="/about" activeClassName="active-link">
Help
</NavLink>
</ul>
{props.children}
</nav>
<nav className="menu auth">
<p className="menu-label">LOGIN</p>
<div className="menu-list auth">
{!userInfo &&
providers.map((provider) => (
<a key={provider} href={`/.auth/login/${provider}?post_login_redirect_uri=${redirect}`}>
{provider}
</a>
))}
{userInfo && <a href={`/.auth/logout?post_logout_redirect_uri=${redirect}`}>Logout</a>}
</div>
</nav>
{userInfo && (
<div>
<div className="user">
<p>Welcome</p>
<p>{userInfo && userInfo.userDetails}</p>
<p>{userInfo && userInfo.identityProvider}</p>
</div>
</div>
)}
</div>
);
};
export default NavBar;
This is a snippet from Products.js, where I want the user details data to be passed to:
<footer className="card-footer ">
<ButtonFooter
className="cancel-button"
iconClasses="fas fa-undo"
onClick={handleCancelProduct}
label="Cancel"
/>
<ButtonFooter
className="save-button"
iconClasses="fas fa-save"
onClick={handleSave}
label="Save"
/> Posted By: {}
</footer>
One way is to use state variable in parent component of both footer and navbar, then passing into navbar as prop function to set the state variable to the userInfo, and in footer you can now use the userInfo
//beginning of parent component
const [userInfo, setUserInfo] = useState(null);
...
//navbar component
<NavBar setUserInfoParent={setUserInfo}/>
...
//footer component
<footer>
Posted By: {userInfo && userInfo.userDetails}
</footer>
There will likely be many opinions on this as there are many ways to accomplish storing some Global state.
Assuming your project will be a decent size and you don't want to keep all of this data in a component and pass it down through/to each component, I would look at these few options:
Context API: https://reactjs.org/docs/context.html
RTK: https://redux-toolkit.js.org/tutorials/quick-start (my preference)
And many others these days including Flux, Zustand, Mobx, Recoil...and on and on..

Next JS error with linking between dynamic pages of a dynamic route

I'm having some trouble with a dynamic route in Next JS, and navigating from one dynamic page to another.
I have a custom type called 'projects'. In that custom type I have set up a Content Relationship field from Prismic called 'relatedProject', so I can select another project in the CMS as a linked document at the end of each project page.
Displaying this related project works fine, and is displaying the correct information when I navigate to a project page, but when I click the link to take me to the related project document - which is the same dynamic route, just with different content, the content is not changing to the content that should be getting pulled in from the new project. A proper load doesn't occur as the page load animations are not playing when clicking the link. When I perform a hard refresh the page is rendering correctly with the content of the project page I clicked through to.
I'm unsure whether this is something wrong with Prismic or something wrong with Next JS? Apologies if I have not explained this brilliantly. I am sure getStaticPaths is setup correctly, as I can navigate to any other project page from the home or work pages, just not from one project page to another. Really has got me stumped!
This is my code for the dynamic page template [uid].js, any help is appreciated:
import { useEffect, useRef } from 'react';
import { createClient } from '../../prismic';
import resolver from '../../sm-resolver.js';
import * as prismicH from '#prismicio/helpers';
import { linkResolver } from '../../utils/linkResolver';
import { SliceZone } from '#prismicio/react';
import { gsap } from 'gsap';
import { Layout } from '../../components/Layout';
import ProjectHero from '../../components/Hero/Project';
import RelatedProjectCta from '../../components/RelatedProjectCta';
const ProjectDetail = ({ data, url, lang, layout }) => {
const seo = {
metaTitle: data.metaTitle || layout.metaTitle,
metaDescription: data.metaDescription || layout.metaDescription,
metaImage: data.metaImage?.url || layout.metaImage?.url,
url: url,
article: true,
lang: lang,
};
const pageData = { data };
const relatedProject = { data };
const revealOverlay = useRef();
// Hero reveal
useEffect(() => {
gsap.to(revealOverlay.current, {
opacity: 0,
duration: 2.3,
ease: "power2.out"
});
}, []);
return (
<Layout seo={seo} {...layout}>
<ProjectHero {...pageData} />
<SliceZone slices={data.slices} resolver={resolver} />
{
prismicH.isFilled.link(data.relatedProject) ? (
<RelatedProjectCta {...relatedProject}/>
)
: null
}
</Layout>
);
};
// Fetch content from prismic - previews but doesn't hot reload
export const getStaticProps = async ({ params, previewData }) => {
const client = createClient({ previewData });
// Default Layout components reused across the site
// If a singleton document is missing, `getStaticProps` will throw a PrismicError.
const seo = await client.getSingle("defaultSeo");
const header = await client.getSingle("header");
const footer = await client.getSingle("footer");
const socials = await client.getSingle("socials");
const projectDetail = await client.getByUID("project", params.uid, {'fetchLinks': 'project.theme, project.client, project.projectTitle, project.projectIntroduction, project.featuredImage'} );
return {
props: {
layout: {
seo,
header,
footer,
socials,
},
...projectDetail
}
};
};
export const getStaticPaths = async () => {
const client = createClient();
const projectDetail = await client.getAllByType("project");
return {
paths: projectDetail.map((page) => prismicH.asLink(page, linkResolver)),
fallback: false,
};
};
export default ProjectDetail;
This is the code of the component that is the link for the related project:
import React from 'react';
import { PrismicText } from '#prismicio/react';
import { PrismicNextImage } from '#prismicio/next';
import { Link } from "../Link";
const RelatedProjectCta = ({ data }) => {
const relatedProject = {
uid: data.relatedProject.uid,
url: data.relatedProject.url,
theme: data.relatedProject.theme,
client: data.relatedProject.data.client,
title: data.relatedProject.data.projectTitle,
introduction: data.relatedProject.data.projectIntroduction,
image: data.relatedProject.data.featuredImage,
}
return (
<section className={`component cta-slice ${relatedProject.theme}`} data-header={relatedProject.theme === "light" && (`is-dark`) || relatedProject.theme === "dark" && ('is-light')}>
<div className="container">
<div className="cta-slice_text-wrapper">
<div className="eyebrow-heading">
Related project
</div>
<h2 className="primary-heading">
<PrismicText field={relatedProject.client}/>
</h2>
<div className="description lead-body">
<PrismicText field={relatedProject.title}/>
</div>
<Link
href={`/work/${relatedProject.uid}`}
className="btn animated-button">
View project
</Link>
</div>
</div>
<div className="cta-slice_background">
<div className="cta-slice_background_image">
<PrismicNextImage
className="block width-100% object-cover"
field={relatedProject.image}
imgixParams={{ q: 80 }}
layout="fill"
/>
</div>
</div>
</section>
)
};
export default RelatedProjectCta
Link component:
import NextLink from "next/link";
import { asLink } from "#prismicio/helpers";
import { linkResolver } from "../utils/linkResolver";
export const Link = ({
href: link,
target,
disabled,
children,
className,
...rest
}) => {
if (disabled) {
return <span {...rest}>{children}</span>;
}
//Standard link
if (typeof link === "string") {
if (link[0] === "/") {
return (
<NextLink href={link}>
<a className={className} {...rest}>
{children}
</a>
</NextLink>
);
}
return (
<a
href={link}
target={target ?? "_blank"}
className={className}
{...rest}
rel="noopener noreferrer"
>
{children}
</a>
);
}
//Unknown link
if (link.link_type === "Any") return null;
//Prismic Link
if (link.link_type === "Web") {
if (!link.url) return null;
//Same page anchor links
if (link.url.includes("://#")) {
const anchor = link.url.split("://")[1];
return (
<a href={anchor} className={className} {...rest}>
{children}
</a>
);
}
return (
<a
href={asLink(link, linkResolver)}
target={target ?? "_blank"}
className={className}
{...rest}
rel="noopener noreferrer"
>
{children}
</a>
);
}
if (link.link_type === "Document") {
return (
<NextLink href={asLink(link, linkResolver)}>
<a className={className} {...rest}>
{children}
</a>
</NextLink>
);
}
if (link.link_type === "Image") {
return (
<a
href={asLink(link, linkResolver)}
className={className}
{...rest}
rel="noopener noreferrer"
>
{children}
</a>
);
}
return null;
};

JSX, can't show the correct link in a new tab

I'm trying to open in a new tab a URL retrieved from an API. It's basically a shortened URL. But when I try to add it to the href attribute of my , it returns me something like:
localhost:3000/[the correct shortened url]
or if I'm deploying the website:
[my website url]/[the correct shortened url]
Here's my component, where the link is the one with the "shortened-url" className:
import { React, useState } from "react";
import "../styles/ModalUrl.scss";
import CloseIcon from "#mui/icons-material/Close";
const ModalUrl = ({ modalClass, setModalClass, shortenedUrl }) => {
const closeModal = () => {
setModalClass("hidden");
};
return (
<div className="modal-and-background">
<div className={`dark-background ${modalClass}`}></div>
<div className={`modal-url ${modalClass}`}>
<h1>Your URL is ready!</h1>
<a href={shortenedUrl} className="shortened-url">
{shortenedUrl}
</a>
<div className="button-container">
<button className="btn-close" onClick={closeModal}>
CLOSE
<CloseIcon className="close-icon" />
</button>
</div>
</div>
</div>
);
};
export default ModalUrl;
Thank you!

how can i make a component re render in react

so i'm creating my first fullstack website and once a user signs in it gets stored in the localStorage and i want to display the name of the user in my header once he is logged in but my header is not re rendering so nothing happens : this is the header before logging in
header
and this is how i want it to Be after signing in :
header after logging in this is my Layout code:
import "../assets/sass/categoriesbar.scss";
import Header from "./Header/Header";
const Layout = (props) => {
return (
<>
<Header/>
<main>
{ props.children}
</main>
</>
);
}
export default Layout;
and this is the toolBar in my Header :
const ToolBar = () => {
const history = useHistory();
let currentUser= JSON.parse(localStorage.getItem("user-info"));
const logoutHandler = () => {
localStorage.clear("user-info");
history.push("/login");
};
return (
<>
<div className={classes.NavigationBar}>
<h1>
<Link to="/">Pharmashop</Link>
</h1>
<NavLinks logout={logoutHandler}/>
{localStorage.getItem("user-info")?
<h5>Welcome {currentUser.name} !</h5>
:
<RegisterButton />
}
</div>
</>
);
};
export default ToolBar;
please help me it's frustrating
PS: this is my first stackoverflow question sorry if it's unorganized and unclear and sorry for my bad english.
Hazem, welcome to Stack Overflow.
In react, if you want the component to re-render when some data changes, that info must be in the component state. In your code the current user is a const, not bind to the component's state. This is how it could auto re-render when the user logs in:
const ToolBar = () => {
const [currentUser, setCurrentUser] = useState(JSON.parse(localStorage.getItem("user-info")));
const logoutHandler = () => {
localStorage.clear("user-info");
history.push("/login");
};
return (
<>
<div className={classes.NavigationBar}>
<h1>
<Link to="/">Pharmashop</Link>
</h1>
<NavLinks logout={logoutHandler}/>
{currentUser?
<h5>Welcome {currentUser.name} !</h5>
:
<RegisterButton />
}
</div>
</>
);
};
export default ToolBar;
See more about state in the official documentation.

Problem with time when I navigate in my nextjs project

I have a doubt in my nextjs project.
I added a new route inside my header calls /files and I don't know why takes a long time to load the data when I want to return to the home.
I console.log the request I and notice calls to my API and my INDEX twice, but I'm not sure if it's a problem.
The endpoint with the data is a little slow, but I believe that if I call it inside my pages/index getInitialProps, the data it's loaded in server at the beginning, I am right? and if I am right why is it taking so long to show me the data again?
Here is my code!
Header
import React, { Component } from "react";
class Header extends Component {
render() {
return (
<>
<Navbar collapseOnSelect expand="lg" bg="light" variant="light">
<Navbar.Toggle
aria-controls="responsive-navbar-nav"
style={{ outline: "0", display: 'none' }}
/>
<Navbar.Collapse id="responsive-navbar-nav">
<Nav className="mr-auto"></Nav>
<Nav>
<Link href="/" passHref>
<Nav.Link>
Home
</Nav.Link>
</Link>
<Link href="/files" passHref>
<Nav.Link>
Files
</Nav.Link>
</Link>
</Nav>
</Navbar.Collapse>
</Navbar>
</>
);
}
}
export default Header;
pages/index
import React, { useState, useEffect } from "react";
/* Others */
import Error from "./_error";
import { getDataLatestEvents } from "../helper/api";
/* Components */
import MyComponent from "../components/MyComponent";
/* Bootstrap Components */
import Row from "react-bootstrap/Row";
const Index = props => {
console.log('index*************')
const [contentData, setData] = useState([]);
const res = props.data.data.data;
useEffect(() => {
setData(res);
}, [props]);
if (props.statusCode !== 200) {
return <Error statusCode={props.statusCode} />;
}
return (
<>
<Row>
<StyledContainer>
<MyComponent
data={contentData}
/>
</StyledContainer>
</Row>
</>
);
};
Index.getInitialProps = async ({ res }) => {
try {
let req = await getDataLatestEvents();
return { data: req, statusCode: 200 };
} catch (e) {
res.statusCode = 503;
console.log(`error /pages/index: ${e}`);
return { data: null, statusCode: 503 };
}
};
export default Index;
helper/api
import fetch from "isomorphic-unfetch";
const BASE_URL = "https://myendpoint/api";
export async function getDataLatestEvents() {
const res = await fetch(`${BASE_URL}/eventos?latest`);
let data = await res.json();
console.log('API*************')
return { data: data};
}
This sort of delay is often encountered when using next dev (via yarn dev or npm dev). This is because when using yarn dev, page is rebuild every time it is requested. So when you navigate back to the index page, Next.js first rebuild that page for you and then it is served. That's why there is a delay.
You should not find similar delay in production (when using next build and then next start)
Edit
getInitialProps enables server-side rendering in a page. In case you don't need to fetch any data every time the request is sent or page is reloaded, use getStaticProps instead.

Categories