Live example available here
I'm trying to make a basic layout where, on mobiles, only the latest posts appear. On desktop, the left column should be the posts and the right column the top categories and most popular posts.
Here is the layout:
const IndexLayout: React.FC<IndexLayoutProps> = ({}) => {
const cols = useScreenType()
return cols === '2-cols' ? (
<div className="w-full flex justify-between items-start">
<ListPosts data-comp="ListPosts" className="w-4/6" />
<div className="sticky ml-12 w-2/6 flex flex-col">
<TopCategories data-comp="TopCategories" className="w-full" />
<PopularPosts data-comp="PopularPosts" className="mt-4" />
</div>
</div>
) : (
<ListPosts data-comp="ListPosts" className="w-full" />
)
}
Here's the useScreenType hook:
import { useMediaQuery } from 'react-responsive'
export const useScreenType = () => {
const is2Cols = useMediaQuery({ minWidth: 1300 })
const is1Cols = useMediaQuery({ minWidth: 800 })
if (is2Cols) {
return '2-cols'
}
if (is1Cols) {
return '1-cols'
}
return 'fullscreen'
}
And I keep getting this error:
Warning: Expected server HTML to contain a matching <div> in <div>.
div
ListPosts#webpack-internal:///./components/posts/ListPosts.tsx:31:19
div
IndexLayout#webpack-internal:///./components/layout/IndexLayout.tsx:28:149
div
Index#webpack-internal:///./pages/index.tsx:24:149
ApolloProvider#webpack-internal:///./node_modules/#apollo/client/react/context/ApolloProvider.js:13:18
s#webpack-internal:///./node_modules/next-apollo/dist/index.es.js:26:1911
div
div
MyApp#webpack-internal:///./pages/_app.tsx:37:19
ErrorBoundary#webpack-internal:///./node_modules/#next/react-dev-overlay/lib/internal/ErrorBoundary.js:23:47
ReactDevOverlay#webpack-internal:///./node_modules/#next/react-dev-overlay/lib/internal/ReactDevOverlay.js:73:20
Container#webpack-internal:///./node_modules/next/dist/client/index.js:155:20
AppContainer#webpack-internal:///./node_modules/next/dist/client/index.js:643:18
Root#webpack-internal:///./node_modules/next/dist/client/index.js:779:19
Now I think the issue is due to the useScreenType hook not being able to get a width because window isn't defined on the server. But how can I fix this issue? And not only do I get an error, but my HTML renders weirdly.
The final render ends up being something like this (when it renders as '2-cols'):
<div class="flex flex-col justify-start items-start w-full">
<div class="mt-6 w-full"></div>
<div class="mt-4 flex items-center cursor-pointer transform transition hover:scale-105 text-sm">
<div class="w-full p-6 rounded-lg flex flex-col dark:bg-gray-800 shadow-md"></div>
<div class="mt-4 p-6 rounded-lg flex flex-col dark:bg-gray-800 shadow-md"></div>
</div>
</div>
Note: I am using Next.js v10.2.0
Code can be found on GitHub
As you notice, you cant access window object on server, so if you want to server-render something based on window object - you must hardcode these values.
The only thing you can rely on is user-agent in request headers, which gives you some understanding of user device.
For example this way you can detect user device in _app.js:
const device = deviceDetector.detect(isServer() ? ctx.req.headers['user-agent'] : window.navigator.userAgent)
deviceDetector is any kind of device detection implementation based on user agent
For anyone wondering how I fixed this, I ditched the responsive design with logic and switched to CSS. Here is my layout post fix (changed some classes with the lg prefix [documentation]):
const IndexLayout: React.FC<IndexLayoutProps> = ({}) => {
return (
<div className="mt-12 lg:mt-24 w-5/6 mx-auto flex items-start">
<div className="w-full flex justify-between items-start">
<ListPosts className="lg:w-4/6 w-full" />
<div className="hidden sticky ml-12 w-2/6 lg:flex flex-col">
<TopCategories className="w-full" />
<PopularPosts className="mt-4" />
</div>
</div>
</div>
)
}
Related
I'm fetching an array of reviews I want to display in a carousel element showing one slide at a time. I am trying to loop through and use the index of the array as the current slide.
My problem is that all the reviews are showing at the same time on the page even though in Vue Dev Tools the current slide and slide count values are correct.
In addition, In the elements panel in dev tools, there are extra elements for all of the slides.
I've been able to do this in Vue 3 but I can't in Nuxt 3.
I've added a screenshot of the Chrome Dev Tool Elements panel showing the additional HTML which is weird since it's not like that in the Vue Dev Tools but I can see the extra elements being painted because of the dashes on either side of the value in the h5 with the reviewerName. I can't figure out what part of my code is causing it.
Elements Panel in Chrome Dev Tools
In Vue Dev Tools I can see the currentSlide value changing correctly according to the delay and the getSlideCount value is correct on the BaseCarousel component.
Vue Dev Tools BaseCarousel
Here are the components in the order of reviews list to the carousel to the slide and finally the individual review.
// ReviewsCarousel.vue
<script lang="ts" setup>
defineProps(["reviews"]);
</script>
<template>
<div class="relative">
<BaseCarousel>
<template #slide="{ currentSlide }">
<BaseSlide v-for="(review, index) in reviews" :key="index">
<template #review v-show="currentSlide === index + 1">
<ReviewItem :review="review" />
</template>
</BaseSlide>
</template>
</BaseCarousel>
</div>
</template>
Here
// BaseCarousel.vue
<script setup>
const currentSlide = ref(1);
const getSlideCount = ref(null);
const autoPlayEnabled = ref(true);
const changeDelay = ref(30000);
// slide navigation
// next slide
const nextSlide = () => {
if (currentSlide.value === getSlideCount.value) {
currentSlide.value = 1;
return;
}
currentSlide.value += 1;
};
// prev slide
const prevSlide = () => {
if (currentSlide.value === 1) {
currentSlide.value = getSlideCount.value;
return;
}
currentSlide.value -= 1;
};
// autoplay
const autoPlay = () => {
setInterval(() => {
nextSlide();
}, changeDelay.value);
};
if (autoPlayEnabled.value) {
autoPlay();
}
onMounted(() => {
getSlideCount.value = document.querySelectorAll(".slide").length;
});
</script>
<template>
<div id="carousel">
<slot name="slide" :currentSlide="currentSlide" />
<!-- navigation -->
<div class="z-50 w-full h-full flex">
<div #click="prevSlide" class="flex grow cursor-pointer z-50 -mt-20">
<font-awesome-icon
class="h-14 w-14 text-3xl text-primary cursor-pointer"
:icon="['fas', 'angle-left']"
/>
</div>
<div #click="nextSlide" class="flex grow cursor-pointer z-50 -mt-20 justify-end">
<font-awesome-icon
class="h-14 w-14 text-3xl text-primary cursor-pointer"
:icon="['fas', 'angle-right']"
/>
</div>
</div>
</div>
</template>
// BaseSlide.vue
<template>
<div class="slide">
<transition name="fade" mode="out-in">
<slot name="review" />
</transition>
</div>
</template>
<style>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.8s ease-in-out;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
.fade-enter-to,
.fade-leave-from {
opacity: 1;
}
</style>
// ReviewItem.vue
<script setup>
defineProps(["review"]);
</script>
<template>
<div
class="text-light flex text-center justify-center backdrop-blur-sm backdrop-grayscale rounded-lg p-8 md:px-2"
>
<ul v-for="fields in review">
<p class="text-2xl tracking-wider max-w-prose">
{{ fields.reviewerComment }}
</p>
<div>
<font-awesome-icon
v-for="i in fields.rating"
class="text-accent text-xl p-1"
:icon="['fas', 'star']"
/>
</div>
<h5 class="text-xl tracking-wider">- {{ fields.reviewerName }} -</h5>
</ul>
</div>
</template>
I am using tailwindcss and flexboxes to create the following elements which are perfectly aligned horizontally:
<div className="flex flex-row justify-between">
<div className="text-body-light text-lg">Top 500</div>
{/** Percentage */}
{this.state.top_500_progress >= 0 ?
<div className="flex flex-row items-center bg-report-green-progress-background gap-x-1 px-1">
<ProgressUpIcon />
<div className="text-report-green-progress text-lg">{this.state.top_500_progress.toFixed()}%</div>
</div>
: <div className="flex flex-row items-center bg-report-red-progress-background gap-x-1 px-1">
<ProgressDownIcon />
<div className="text-report-red-progress text-lg">{this.state.top_500_progress.toFixed()}%</div>
</div>
}
</div>
Then I am exporting my page as pdf using jspdf this way:
generatePDF = () => {
var chartEl = document.getElementById("page2")
let input: any = window.document.getElementsByClassName("page2")[0]
html2canvas(input).then(canvas => {
const img = canvas.toDataURL("image/jpeg", 1.0);
var doc = new jsPDF('landscape', 'pt', [842, 455]);
doc.addImage(img, 'JPEG', 0, 0, 850, 455 );
doc.save("chart.pdf");
});
}
However my text is not aligned anymore:
I have tried adding a div with my-auto for each text element but still same result.
How to make it aligned in jspdf?
As you can this is a string js and I want to use onClick on the input whose view is being toggled by selecting the boolean value edit. Please suggest how to use onchnage here.
Already tried normal HTML onchange (not working)
onchange="${onchnage}"
Pls, suggest if you happen to know the answer.
export const DefaultNode = (d, selectedNodeIds, edit, fomatOptions, inputOnclick) => {
const mainData = d.data.data
return `<div style='background:${selectedNodeIds.length!==0 ? (selectedNodeIds.includes(d.data.id) ? `rgba(${ fomatOptions.nodeBg.r }, ${ fomatOptions.nodeBg.g }, ${ fomatOptions.nodeBg.b }, ${ fomatOptions.nodeBg.a })`: "#fff"): `rgba(${ fomatOptions.nodeBg.r }, ${ fomatOptions.nodeBg.g }, ${ fomatOptions.nodeBg.b }, ${ fomatOptions.nodeBg.a })`};
color:${selectedNodeIds.length!==0 ?(selectedNodeIds.includes(d.data.id) ?`rgba(${ fomatOptions.textColor.r }, ${ fomatOptions.textColor.g }, ${ fomatOptions.textColor.b }, ${ fomatOptions.textColor.a })`:'#000'): `rgba(${ fomatOptions.textColor.r }, ${ fomatOptions.textColor.g }, ${ fomatOptions.textColor.b }, ${ fomatOptions.textColor.a })`}'
class=${`"w-[250px] p-3 rounded-[15px] relative border-[3px] h-[140px] ${selectedNodeIds.includes(d.data.id)? 'drop-shadow-md' :"shadow"} ${ selectedNodeIds.includes(d.data.id) && fomatOptions.fontFamily.value}"`}>
<div class='flex justify-between w-full '>
<div class="">
${edit? `<input onclick='${inputOnclick}' class="fullName text-[13px] font-semibold" value="${mainData.name}"/>` : `<div class=" text-[13px] font-semibold">${mainData.name} </div>`}
<div class=" text-[11px] opacity-70 mt-0.5 font-medium">${mainData.position } </div>
<div class='mt-2'>
<div class=" text-[11px] opacity-70 mt-0.5 font-medium">${mainData.email } </div>
<div class=" text-[11px] opacity-70 mt-0.5 font-medium">${mainData.phone } </div>
</div>
</div>
<img class='w-10 h-10 mr-2 rounded-[10px]' src=${mainData.imgUrl} />
</div>
<div class='flex pt-4 justify-between items-center'>
<p class='text-[10px] font-medium uppercase bg-theme-gray px-2 text-black rounded-full py-0.5'>${mainData.department}</p>
<p class='text-[10px] font-medium uppercase mr-2'>${mainData.location}</p>
</div>
${((selectedNodeIds.includes(d.data.id))) ? `<div class="absolute left-4 -top-5 font-semibold text-[10px] p-1 bg-gray-400 text-white rounded-t-md">
Selected
</div>`: `<p></p>`}
</div>`
}
I believe what you're missing here is the different naming conventions for default HTML event listeners in React, not all of your code is here so I'm assuming you do not have a custom function called onchange, but in React its called onChange (or onClick, etc) so you're looking something like this for your code snippet.
onChange="${onchnage}"
Also double-check to make sure you have all your syntax and spelling correct. Also for writing better JSX for returning HTML elements you can write code like the following
return (
<div>
<p>Text here</p>
</div>
);
Hi i am making a photography portfolio with strapi as my CMS and graphql to fetch data in my React frontend
the problem is whenever i add a new pic using strapi the entire site crashes and returns
Uncaught TypeError: Cannot read properties of undefined (reading 'gears')
but when i edit Gearfetch variable by removing it and rewriting it all again it starts working untill i add another pic via strapi and then it gives same error again
is there a permanent solution to this ?
my gearfetch.js :
import React from "react";
import { gql, useQuery } from "#apollo/client";
import MyGear from "../pages/MyGear";
function Gearfetch() {
const Geardata = gql`
query Getgears {
gears {
data {
id
attributes {
Name
description
image {
data {
attributes {
formats
}
}
}
}
}
}
}
`;
const data = useQuery(Geardata);
const GearFetch = data.data.gears.data //<-- to fix i have to remove and rewrite this again
console.log(GearFetch)
return (
<div className="">
<div >
<div className="flex justify-center font-mono font-extrabold text-lg mt-6 ">
MyGear
</div>
<div className="flex justify-center">
<div className=" p-4 xl:mt-4 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 md:gap-2 md:p-4 ">
{GearFetch.map((Gears) => (
<div className="p-4">
<MyGear
key={Gears.attributes.Name}
name={Gears.attributes.Name}
description={Gears.attributes.description}
image = {Gears.attributes.image.data[0].attributes?.formats?.medium.url}
/>
</div>
))}
</div>
</div>
</div>
</div>
);
}
export default Gearfetch;
I have a product that I want to programatically set a Checkout Session URL and then redirect a user when they click on an tag.
const PlanComponent = () => {
const [tiers, setTiers] = useState([]);
async function CheckoutSessionUrl(tierId) {
var result = null
await CreateCheckoutSession(
"PRODUCT_ID",
tierId,
"UID"
).then(function(response) {
result = response
})
return result;
};
async function LoadProducts() {
var result = []
await GetProducts("PRODUCT_ID").then(function(response) {
for (var i = 0; i < response.length; i++) {
var tier_id = response[i]["tier_id"]
CheckoutSessionUrl(tier_id).then(function(tier_response) {
response[i]["url"] = tier_response;
})
}
console.log("printing tiers");
console.log(response);
return response;
}).then(function(response){
result = response;
setTiers(result)
return result;
});
return result;
}
useEffect(() => {
// Some initialization logic here
LoadProducts().then(function(response) {
console.log("setting tiers")
console.log(response)
//setTiers(response)
})
}, []);
return (
<div className="bg-white">
<div className="max-w-7xl mx-auto py-24 px-4 sm:px-6 lg:px-8">
<div className="sm:flex sm:flex-col sm:align-center">
<h1 className="text-5xl font-extrabold text-gray-900 sm:text-center">Pricing Plans</h1>
<p className="mt-5 text-xl text-gray-500 sm:text-center">
Start building for free, then add a site plan to go live. Account plans unlock additional features.
</p>
</div>
<div className="mt-12 space-y-4 sm:mt-16 sm:space-y-0 sm:grid sm:grid-cols-2 sm:gap-6 lg:max-w-4xl lg:mx-auto xl:max-w-none xl:mx-0 xl:grid-cols-4">
{tiers.map((tier) => (
<div key={tier.name} className="border border-gray-200 rounded-lg shadow-sm divide-y divide-gray-200">
<div className="p-6">
<h2 className="text-lg leading-6 font-medium text-gray-900">{tier.name}</h2>
<p className="mt-4 text-sm text-gray-500">{tier.description}</p>
<p className="mt-8">
<span className="text-4xl font-extrabold text-gray-900">${tier.price}</span>{' '}
<span className="text-base font-medium text-gray-500">/mo</span>
</p>
<a
href={tier.url}
className="mt-8 block w-full bg-gray-800 border border-gray-800 rounded-md py-2 text-sm font-semibold text-white text-center hover:bg-gray-900"
>
Buy {tier.name}
</a>
</div>
<div className="pt-6 pb-8 px-6">
<h3 className="text-xs font-medium text-gray-900 tracking-wide uppercase">What's included</h3>
<ul role="list" className="mt-6 space-y-4">
{tier.users.map((feature) => (
<li key={feature} className="flex space-x-3">
{/*<CheckIcon className="flex-shrink-0 h-5 w-5 text-green-500" aria-hidden="true" />*/}
<span className="text-sm text-gray-500">{feature}</span>
</li>
))}
</ul>
</div>
</div>
))}
</div>
</div>
</div>
)
}
so if you look at the href={tier.url} it's not being set correctly. I feel like I'm not doing this correctly. Would love some feedback on how to actually get this working properly. IT looks like the tier.url new field isn't being set correctly (doesn't exist in the initial request but all the other attributes work).
The other option I wanted to do was when a user clicked a Button, it would generate a URL and redirect a user to that new external url but the navigation kept breaking.
Oh I found out what was happening. I needed to replace
await GetProducts("PRODUCT_ID").then(function(response) {
with
await GetProducts("PRODUCT_ID").then(async function(response) {