I'm building a frontend application for e-commerce project and I'm using React 17.x and Nextjs 10.2.3. My problem is when I change page with next/link getServerSideProps doesn't called but when I refresh the page getServerSideProps called and sending request to backend. I looked to documentation of next/link it's says next/link has a shallow property for calling getServerSideProps for pre-fetching or not and I turned that property to false but still getServerSideProps doesn't called with next/link. Is there any way except not using next/link?
One page of my project
interface HomeProps {
announcements: IAnnouncement[];
campaigns: ICampaignResponse[];
companies: ICompaniesResponse[];
creditSummary: ICreditResponse;
}
export const getServerSideProps = async (
context: GetServerSidePropsContext
) => {
const announcements = await queryEndpoints.getAnnouncements();
const creditSummary = await queryEndpoints.getCredit();
const companies = await queryEndpoints.getCompanies();
const campaigns = await queryEndpoints.getCampaigns();
return {
props: {
announcements,
creditSummary,
companies,
campaigns,
},
};
};
export default function Home({
companies,
campaigns,
creditSummary,
}: HomeProps) {
return (
<>
<Container>
<CampaignsBlock campaigns={campaigns} />
<div className="grid grid-cols-12 gap-4 pt-18 pb-24">
<div className="border border-gray-300 p-8 rounded-md col-start-1 col-end-13 lg:col-end-8 xl:col-end-8 order-2 lg:order-1 xl:order-1">
<div className="w-full">
<SectionHeader sectionHeading="Duyuru" />
<p className="text-body text-xs lg:text-sm leading-normal xl:leading-relaxed">
Monochrome elegance. Made with a relaxed wide-leg, these
trousers are made from a sustainable soft organic cotton with a
mechanical stretch making the garment easily recycled.
</p>
</div>
</div>
<div className="col-start-1 col-end-13 lg:col-start-8 xl:col-start-8 order-1 lg:order-2 xl:order-2">
<CreditsBlock creditSummary={creditSummary} />
</div>
</div>
<CompaniesBlock companies={companies} />
</Container>
</>
);
}
Related
I am having some problems with the useState async behavior that can also be related to Redux which I am new at it.
import React, { useState, useEffect } from "react"
import { useSelector, useDispatch } from "react-redux"
import { Link } from "react-router-dom"
import {
getAlltopics,
joinAtopic,
leaveAtopic,
} from "../../../redux/actions/topicActions"
import Icon from "../Icon"
const TopicCard = ({ topics }) => {
const user = useSelector((state) => state.user)
const [join, setJoin] = useState(false)
const dispatch = useDispatch()
console.log(join)
const leaveTopicHandler = async () => {
setJoin(false)
dispatch(leaveAtopic(topics._id))
}
const JoinTopicHandler = () => {
setJoin(true)
dispatch(joinAtopic(topics._id))
}
useEffect(() => {
const checkJoinedUser = () => {
topics.members.map((member) => {
if (member._id === user?._id) setJoin(true)
})
}
checkJoinedUser()
dispatch(getAlltopics())
}, [join, dispatch])
return (
<div
key={topics._id}
className="flex flex-col justify-between w-48 h-72 bg-white shadow-xl rounded-br-3xl rounded-bl-3xl rounded-tr-3xl"
>
<Link to={`/topics/${topics._id}`}>
<section className="">
<img
src={topics.bannerImage}
alt="topic_Image"
className="object-cover h-48 w-full rounded-tr-3xl"
/>
</section>
<section className="border-b-2 border-grey-light ml-3 mr-3 h-12 flex items-center">
<h1 className="text-lg">{topics.title}</h1>
</section>
</Link>
<section>
<div className="flex justify-between">
<div className="flex p-3">
<Icon iconName="member" iconStyle="fill-inactive text-grey-dark" />
<span>{topics.members?.length}</span>
<Icon iconName="file" iconStyle="fill-inactive text-grey-dark" />
<span>{topics.recources?.length}</span>
</div>
<div className="p-3">
{join ? (
<button type="button" onClick={leaveTopicHandler}>
<Icon
iconName="follow"
iconStyle="fill-active text-grey-dark"
/>
</button>
) : (
<button type="button" onClick={JoinTopicHandler}>
<Icon
iconName="follow"
iconStyle="fill-inactive text-grey-dark"
/>
</button>
)}
</div>
</div>
</section>
</div>
)
}
I have defined a join variable to handle a button that depending on if it is true or false will show or not some aspect, also, if it is false the user can join the topic, if it is true the user can leave the topic as it is noticeable in the functions JoinTopicHandler and leaveTopicHandler. Before joining a topic it looks like this:before joining a topic, as it is possible to see, the join variable it's set to false, because I am not in the topic. When joining the topic, after joining the topic, the joinvariable is set to true, the button changed, although the user count didn't changed for 2 (sometimes it does, sometimes I have to refresh the page for it to render), but the weirdest thing is when leaving the topic, as it's shown in the console,leaving the topic the join variable turns to false but then by it self turns again to true and the button still looks the same and I can not fix this...
Without knowning what the leaveAtopic function does exactly, my guess is that since join is in your useEffect hook dependencies, what happens from clicking the leave button is:
leaveTopicHandler is run
setJoin(false) causes re-render and since join is a dependency of the useEffect, we run it again
dispatching leaveAtopic starts but I'm assuming there is async logic there
topics hasn't changed yet so when the useEffect runs topics.members still contains the user
You probably don't even need useState/useEffect for join, and instead could do something like:
const join = !!topics.members.find((member) => member._id === user?._id))
So I had a few mistakes starting from the backend of my app, first of all, I wasn't returning the updated topic because I didn't set the call correctly.
const addUserToTopicDb = async (topicId, user) => {
try {
return await Topic.findByIdAndUpdate(topicId, {
$push: { members: user },
}, {new: true});
} catch (error) {
throw new Error(error);
}
};
I had to add the {new: true} to get the updated topic. This was a major error. Second, my code on the reducer wasn't working properly as I am new at it and learning by solving this kind of problems.
I changed my reducer code to:
case JOIN_TOPIC:
return {
allTopics: state.allTopics.map((eachTopic) =>
eachTopic._id === action.payload.topic._id
? action.payload.topic
: eachTopic
),
}
case LEAVE_TOPIC:
return {
allTopics: state.allTopics.map((eachTopic) =>
eachTopic._id === action.payload.topic._id
? action.payload.topic
: eachTopic
),
}
This basically means that if I have a topic with the same _id, replace it with the new updated topic that I wasn't returning in the beginning.
After this, everything started to work smoothly. Shame on me to assume that the problem was due to the useState... when it was all along in my database set up and reducer...
I have two states (userCustomers and loans) which are depending on a state coming from a custom hook (customers).
const [customers] = useLoanCustomers(state.contents.date)
const [userCustomers, setUserCustomers] = useState<DwCustomerHeadline[]>([])
const [loans, setLoans] = useState<DwLoanContract[]>([])
So I filter data depending on the output of the useLoanCustomers and have an empty array as initial state.
When I refresh the page, the useEffect works fine and sets the states of "userCustomers" and "loans"
But when I switch between pages/routes, the useEffect does not work and keeps the initial state [].
e.g this component is on 'http://localhost:3000/user/overview'.
if I go to another route like 'http://localhost:3000/clients' and then back to '/user/overview', the states "userCustomers" and "loans" are empty. So the useEffect does not set those states.
"useLoanCustomers" is loading as expected and holds all the data.
when I console log inside the useEffect, I can see that useEffect is running properly depending on the dependency change.
But it is not setting the other two states. As said, only when I refresh the page.
Has anyone a clue what's wrong, because I've already tried so much but can't figure it out.
This is my entire component
export const Overview: React.FC<OverviewProps> = ({ user }) => {
const { keycloak } = useKeycloak()
const { state } = useContext(DashboardContext)
const [customers] = useLoanCustomers(state.contents.date)
const [userCustomers, setUserCustomers] = useState<DwCustomerHeadline[]>([])
const [loans, setLoans] = useState<DwLoanContract[]>([])
useEffect(() => {
const filteredCustomers = customers.filter((customer) => customer.stafferEmail === user?.email)
setUserCustomers(filteredCustomers)
Promise.all(filteredCustomers.map((customer) => getCustomerLoans(keycloak, customer.counterpartyId))).then((res) =>
setLoans(res.flat())
)
}, [customers])
return (
<>
<div className="grid gap-4 p-2 md:grid-cols-3 sm:grid-cols-1">
<div className="col-span-2 shadow-sm">
<KeyMetrics />
</div>
<div className="shadow-sm">
<NextReview customers={userCustomers} />
</div>
</div>
<div className="grid gap-4 p-2 md:grid-cols-3 sm:grid-cols-1 grid-rows-2">
<div className="md:h-80 col-span-2 shadow-sm">
<ErpOverview customers={userCustomers} />
</div>
<div className="row-span-2 shadow-sm" style={{ height: '34rem' }}>
<Outflow loans={loans} endDate={state.contents.date} />
</div>
<div className="h-52 col-span-2 shadow-sm">
<LoanArrears customers={userCustomers} />
</div>
</div>
</>
)
}
I am pretty new to React.
Previously the search functionality and the productsPage code lived in one file and search worked as expected.
Now I want to decouple the search functionality from productsPage.
This is products.js file where I need items to be searched.
function ProductsPage() {
var categories = data.categories;
const [search, setSearch] = useState(null);
return (
<>
<Container fluid id="searchBar">
<SearchForm searchValue={search} />
</Container>
<Container fluid id="productPage">
<h2 className="text-center py-5">Our Products</h2>
<CardColumns className="pt-3 mb-5">
{categories.map((catNames) => (
<Fragment key={uuid()}>
{catNames.sub_categories.map((subCat) => (
<Fragment key={uuid()}>
{subCat.items
.filter((item) => {
if (search == null) return item;
else if (
item.itemName
.toLowerCase()
.includes(search.toLowerCase())
) {
return item;
}
})
.map((item) => {
return (
<Card className="mb-4 p-3" key={uuid()}>
<div className="prodBorder">
<Card.Title className="pt-5 pb-4 text-center">
{item.itemName}
</Card.Title>
<Card.Img
src={`${item.image.url}`}
className="d-block w-50 mx-auto my-3"
/>
<Card.Subtitle className="pb-4 text-center text-muted">
{item.itemDesc}
</Card.Subtitle>
</div>
</Card>
);
})}
</Fragment>
))}
</Fragment>
))}
</CardColumns>
</Container>
</>
);
}
export default ProductsPage;
This one is search.js file where the onChange handler lives
function SearchForm(props) {
const [search, setSearch] = useState(null);
const searchSpace = (event) => {
let search = event.target.value;
setSearch(search);
};
return (
<Form inline className="position-relative mt-4 mx-auto w-75">
<FontAwesomeIcon icon={faSearch} className="productsSearchIcon" />
<FormControl
id="searchfield"
type="text"
placeholder="Search products..."
onChange={(e) => searchSpace(e)}
className="ml-0 ml-md-3 w-100"
/>
</Form>
);
}
export default SearchForm;
There are two ways you could do this. You could use state lifting.
Docs: https://reactjs.org/docs/lifting-state-up.html
I've made this example to show how that works:
https://codesandbox.io/s/stupefied-noether-rb1yi?file=/src/App.js
If your app is not small or is going to grow you are better using some sort of global state management such as redux or react context API
Docs: https://redux.js.org/tutorials/essentials/part-1-overview-concepts
Docs: https://reactjs.org/docs/context.html
I have made this example using the react context api to give you an idea of how global state management works.
https://codesandbox.io/s/confident-satoshi-upo4c?file=/src/App.js
You will see in this example you can pass data to nested components without the need to pass data through props. When your app grows in size this will make your code a lot cleaner, more efficient and less prone to error. Redux is an alternative (earlier and more popular solution) for global state management.
As a beginner you should try to understand the concepts of state lifting before global state management.
However, if your app is big with nested components, a global state management approach would be the preferred final solution.
I stumbled upon this question after searching a lot
How to pass state back to parent in React?
This is the explanation and answer that worked
https://stackoverflow.com/a/67084024/3807542
So there is a indexpage with components, listing ads.
const App = ({ads}) => (
{ (ads.items===undefined) ? <></> : ads.items.map(item => <Ad item={item} key={item.postingId} id={item.postingId}/>) }
)
export async function getServerSideProps(context) {
const res = await fetch(`www.apiadress.com/${queryString.stringify(context.query)}`)
const ads = await res.json()
// console.debug(ads)
console.debug(queryString.stringify(context.query))
return {
props: {ads}, // will be passed to the page component as props
}
}
and component Ad receives those props, and renders cards
const Ad = props => (
<Card className="customCard">
<Card.Body>
<Card.Text className="text">
<div className="mainDetails">
<h1 className="ad_Title"><Link href="[ad]" as={props.item.postingId}>{props.item.title}</Link></h1>
<p>posted on: {props.item.postingDate.day} / {props.item.postingDate.month}
{props.item.postingDate.year} by {props.item.employer}
</p>
<div className="description">
<p><span><Cash /><b> {props.item.rateFrom}- {props.item.rateTo} per hour </b></span> <GeoAlt /> <b>{props.item.city}</b></p>
<p> <Clock /> <b>{props.item.jobType}</b></p>
</div>
<p>{props.item.description.slice(0, 230)}.....</p>
</div>
<div>
<img src={props.item.employerLogoUrl} alt={props.item.employer} className="empImg" />
<Button variant="primary" href=""> <Heart width="12" height="12"/> SHORLIST </Button>
</div>
</Card.Text>
</Card.Body>
</Card>
export default Ad;
with NPM run dev - everything works perfectly but at NPM run build I am getting an error
Error occurred prerendering page "/componenets/ad/ad". Read more:
https://err.sh/next.js/prerender-error TypeError: Cannot read property
'postingId' of undefined
What's causing this? I am assuming that components does not have data before render.
How to solve this problem?
it simply that you have some items in your array null so it caused that error. I suggest to rewrite like this so it can avoid unnecessary error
const App = ({ads}) =>
(ads.items || []).map(item => item ? <Ad item={item} key={item.postingId} id={item.postingId}/> : null)
props.item is empty on cards component.
Add validation check in the start of the Ad component:
if (!props.item) {
return null;
}
I am trying to improve loading speed of a react web app.
I have two component imports - one for mobile and one for desktop (Bad design? I think so):
import Posts from '../components/post/posts';
import PostsMobile from '../components/post/postsMobile';
This was easy for development because I did not have to try hard to make the same component compatible for desktop and mobile.
Then to check screen size and load the appropriate component, I do this:
const largeScreen = useMediaQuery(theme => theme.breakpoints.up('sm'));
...
{largeScreen? (
<Posts />
) :
(
<PostsMobile />
)
}
You can resize the browser here to see the two components load: Link to home page showing the two components
Does <PostsMobile /> get imported only when react sees that its needed OR does it automatically get imported in the beginning no matter what?
Is there a better way to conditionally render mutually exclusive components without compromising load time?
Classic conditional rendering, that's the appropriate way to do it, only one component will be added to the DOM in any case. As a side note it's not the best idea to have two different components for mobile vs desktop view, generally your html structure should be the same and you should use CSS for any layout changes (as per Google's suggestions - https://web.dev/responsive-web-design-basics/)
Check out #artsy/fresnel, they have a very straightforward example showing how to configure Next.js and Gatsby.js to achieve screen-width dependent control in SSR environments
Note: it hardly increases overhead size; I am using it in my portfolio currently in conjunction with tailwindcss and it has proven to be a fantastic tool. Easy to configure and its implementation is straightforward. Here is an example of conditionally rendering the same svg icon four times as a function of screen size to customize styles accordingly (xs (mobile), sm, md, greater than md (desktop))
(1) Create a window-width file in your components directory to configure #artsy/fresnel for global sharing
components/window-width.jsx or components/window-width.tsx
import { createMedia } from '#artsy/fresnel';
const PortfolioMedia= createMedia({
breakpoints: {
xs: 0,
sm: 768,
md: 1000,
lg: 1200,
},
})
// Generate CSS to be injected in the head using a styles tag (pages/_document.jsx or pages/_document.tsx)
export const mediaStyles = PortfolioMedia.createMediaStyle();
export const { Media, MediaContextProvider } = PortfolioMedia;
// https://github.com/artsy/fresnel/tree/master/examples/nextjs
Note, you can customize the breakpoints however you'd like; the following breakpoints are from my portfolio's configuration file. They overlap with Tailwind's breakpoints to keep them playing nicely together
// ...
const PortfolioMedia = createMedia({
breakpoints: {
xs: 0,
sm: 640,
md: 768,
lg: 1024,
xl: 1280
}
});
// ...
(2) Wrap pages/index.jsx or pages/index.tsx with the MediaContextProvider component
// ...
import { MediaContextProvider } from 'components/window-width';
interface IndexProps {
allPosts: Post[];
allAbout: AboutType[];
allBlog: BlogType[];
}
const Index = ({ allPosts, allAbout, allBlog }: IndexProps) => {
const morePosts = allPosts.slice(0);
const moreAbout = allAbout.slice(0);
const moreBlog = allBlog.slice(0);
return (
<Fragment>
<MediaContextProvider>
<Lead />
<Head>
<title>{`${CLIENT_NAME} landing page`}</title>
</Head>
<div className='max-w-cardGridMobile md:max-w-cardGrid my-portfolioH2F grid mx-auto content-center justify-center items-center text-center'>
{morePosts.length > 0 && <Cards posts={morePosts} />}
</div>
<div className='max-w-full my-portfolioH2F block mx-auto content-center justify-center items-center text-left'>
{moreAbout.length > 0 && <AboutCoalesced abouts={allAbout} />}
</div>
<div className='max-w-full my-portfolioH2F block mx-auto content-center justify-center items-center text-left'>
{moreBlog.length > 0 && <BlogCoalesced blogs={allBlog} />}
</div>
<Footer />
</MediaContextProvider>
</Fragment>
);
};
export default Index;
// ...
(3) Finally, inject generated mediaStyles CSS into a style tag of type text/css in Next's Head
pages/_document.jsx or pages/_document.tsx
import Document, {
Html,
Head,
Main,
NextScript,
DocumentContext
} from 'next/document';
import { mediaStyles } from 'components/window-width';
export default class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}
render() {
return (
<Html lang='en-US'>
<Head>
<meta charSet='utf-8' />
<link rel='stylesheet' href='https://use.typekit.net/cub6off.css' />
<style type='text/css' dangerouslySetInnerHTML={{ __html: mediaStyles }} />
</Head>
<body className='root'>
<script src='./noflash.js' />
<Main />
<NextScript />
</body>
</Html>
);
}
}
(4) Profit; Configuration complete
That's all there is to it. For the sake of clarity I threw in an actual example from one of my projects below.
(5) Bonus example - conditionally rendering ArIcon as a function of device-size
components/lead-arIcon.tsx
import { ArIcon } from 'components/svg-icons';
import Link from 'next/link';
import { Media } from 'components/window-width';
import { Fragment } from 'react';
import DarkMode from 'components/lead-dark-mode';
const ArIconConditional = (): JSX.Element => {
const arIconXs: JSX.Element = (
<Media at='xs'>
<Link href='/'>
<a
className='container block pl-portfolio pt-portfolio justify-between mx-auto w-full min-w-full '
id='top'
aria-label='top'
>
<ArIcon width='18vw' height='18vw' />
</a>
</Link>
</Media>
);
const arIconSm: JSX.Element = (
<Media at='sm'>
<Link href='/'>
<a
className='container block pl-portfolio pt-portfolio justify-between mx-auto w-full min-w-full '
id='top'
aria-label='top'
>
<ArIcon width='15vw' height='15vw' />
</a>
</Link>
</Media>
);
const arIconMd: JSX.Element = (
<Media at='md'>
<Link href='/'>
<a
className='container block pl-portfolio pt-portfolio justify-between mx-auto w-full min-w-full '
id='top'
aria-label='top'
>
<ArIcon width='12.5vw' height='12.5vw' />
</a>
</Link>
</Media>
);
const arIconDesktop: JSX.Element = (
<Media greaterThan='md'>
<Link href='/'>
<a
className='container block pl-portfolio pt-portfolio justify-between mx-auto w-full min-w-full'
id='top'
aria-label='top'
>
<ArIcon
width='10vw'
height='10vw'
classNames={[
` antialised w-svgIcon max-w-svgIcon transform transition-all`,
' stroke-current',
` fill-primary`
]}
/>
</a>
</Link>
</Media>
);
const DarkModeToggler = (): JSX.Element => (
<div className='pt-portfolio text-customTitle transition-all transform -translate-y-mdmxSocial col-span-4 text-right -translate-x-portfolioPadding'>
<DarkMode />
</div>
);
const ArIconsCoalesced = (): JSX.Element => (
<Fragment>
<div className='relative block justify-between lg:w-auto lg:static lg:block lg:justify-start transition-all w-full min-w-full col-span-2'>
{arIconXs}
{arIconSm}
{arIconMd}
{arIconDesktop}
</div>
</Fragment>
);
return (
<Fragment>
<div className='select-none relative z-1 justify-between pt-portfolioDivider navbar-expand-lg grid grid-cols-6 min-w-full w-full container overflow-y-hidden overflow-x-hidden transform'>
<ArIconsCoalesced />
<DarkModeToggler />
</div>
</Fragment>
);
};
export default ArIconConditional;
You could try to lazy load the components.
posts;
componentDidMount() {
if (largeScreen) {
import('../components/post/posts').then(({ default: Posts }) => {
// ^^^^ make sure it has a default export
this.posts = Posts;
this.forceUpdate();
});
} else {
// here load the other component for lower screens
}
}
Then, inside render:
const Posts = this.posts;
return largeScreen? (
<Posts />
) : (
<PostsMobile />
);
Note: You will have to also add a resize listener, so if the screen reaches certain width, the another component will load and will get rendered.
Note2: If you don't care about SSR - you could either try React.lazy with Suspense: https://en.reactjs.org/docs/code-splitting.html#reactlazy
In my opinion there are very valid usages of Desktop vs. Mobile components, for example, although CSS versions are definitely almost always preferred, it might not make sense for drag and drop components that don't work on mobiles, mobile-only hamburger menus, etc.
React's lazy loading is the way to go (unless you need SSR):
import React, { Suspense } from 'react';
const Posts = React.lazy(() => import('../components/post/posts');
const PostsMobile = React.lazy(() => import('../components/post/postsMobile');
function MainComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
{ largeScreen? ( <Posts />) : <PostsMobile /> }
</Suspense>
</div>
);
}
This will guarantee that the component is only loaded when it is first rendered. Bonus tip: if you change the loading div to an animated loading placeholder, the UI of your application may be more pleasant.
None of the answers were compatible with SSR using Nextjs so I ended up using Dynamic Import feature. Looks very powerful but simple.
https://nextjs.org/docs/advanced-features/dynamic-import
import dynamic from 'next/dynamic'
const Posts = dynamic(() => import('../components/post/posts'),
{ loading: () => <LinearProgress /> });
const PostsMobile = dynamic(() => import('../components/post/postsMobile'),
{ loading: () => <LinearProgress /> });
This saved me a few milliseconds
I am not sure if there is a better option so I hope people will comment.