How to extend css modules in next js? - css

I am new to nextjs and I have a question about CSS modules
I have a Product component that looks like this
import styles from "./Product.module.css"
function Product({className=""}) {
return (
<div class={`${styles.container} ${className}`}>
<p className={styles.title}>product</p>
</div>
)
}
Then I want to create another component that is called ProductCard which uses the Product component but needs to extend the p tag inside it
import styles from "./ProductCard.module.css"
function ProductCard() {
return (
<div class={styles.container}>
<Product className={styles.product}/>
</div>
)
}
How can I extend the p tag style in my Product component with a className given ProductCard

You need to add an additional custom prop and pass it to the p element. Using an object will allow you to pass any prop to the p element. See React's docs on props in JSX, MDN spread syntax, and nullish coalescing operator.
function Product({ className, titleProps, ...rest }) {
return (
<div className={`${styles.container} ${className ?? ''}`} {...rest}>
<p {...titleProps}>product</p>
</div>
);
}
function ProductCard() {
return (
<div className={styles.container}>
<Product
className={styles.product}
titleProps={{ className: styles.title }}
/>
</div>
);
}

import styles from "./Product.module.css"
function Product({className=""}) {
const classes = {
'product': `${styles.product}`
}
return (
<div class={`${styles.container} ${classes[className]}`}>
<p className={styles.title}>product</p>
</div>
)
}
import styles from "./ProductCard.module.css"
function ProductCard() {
return (
<div class={styles.container}>
<Product className='product' />
</div>
)
}

Related

My Link with href doesn't scroll even though the link changes. Nextjs

I'm working on a react with nextjs project.
I'm using Link to scroll to a specific section on the same page.
Here is one of the components that use Link:
import styles from './section1.module.scss';
import Image from 'next/image';
import Button from '#material-ui/core/Button';
import tought_process from '../../../public/thought_process.png';
import Link from 'next/link';
const Section1 = () => {
return (
<div className={styles.container}>
<div className={styles.left}>
<div className={styles.leftContainer}>
<Link href='#enews'>
<div className={styles.buttonContainer}>
<Button className={styles.buttonstyle1}>Get started</Button>
</div>
</Link>
</div>
</div>
<div className={styles.right}>
<Image
src={tought_process}
className={styles.imageStyle}
alt='how to think about organizing'
layout='responsive'
priority
/>
</div>
</div>
);
};
export default Section1;
And here i mark the element with the id:
<div {...handlers} className={styles.bigBody}>
<NavBar open={menuOpen} toggle={setMenuOpen} scrollY={scrollY} />
<SideMenu open={menuOpen} toggle={setMenuOpen} scrollY={scrollY} />
<div className={styles.sections}>
<Section1 />
<Section2 />
<Section3 id='enews' />
<Section4 />
</div>
Can't figure out what i'm doing wrong.
Multiple clickable elements are wrapping each other. Remove the button and add the anchor element.
<Link href="#enews">
<a>Get started</a>
</Link>
<Link href="#enews">
<a className={styles.buttonContainer}>
<span className={styles.buttonstyle1}>Get started</span>
</a>
</Link>
I'd recommend updating the styles so you can remove the inner span element.
I use a custom link component that does a few things (not shown); one is smooth scroll to hash routes if the browser supports smooth scrolling (not Safari).
import NextLink, { LinkProps } from "next/link";
import { HTMLProps, MouseEvent, FC } from "react";
export const Link: FC<LinkProps & HTMLProps<HTMLAnchorElement>> = ({ as, children, href, replace, scroll, shallow, passHref, ...rest}) => {
const onClick = (event: MouseEvent<HTMLAnchorElement>) => {
if (href.startsWith("#")) {
event.preventDefault();
const destination = document.getElementById(href.substring(1));
if (destination) destination.scrollIntoView({ behavior: "smooth" });
}
};
return (
<NextLink as={as} href={href} passHref={passHref} replace={replace} scroll={scroll} shallow={shallow}>
<a href={href} {...rest} onClick={onClick}>
{children}
</a>
</NextLink>
);
};
I removed new lines to condense the code block
If you went with the above approach, don't include the anchor tag since it's automatically included.
import { Link } from "./custom/path/link"
<Link href="#enews">Get started</Link>
Two points here:
As per the nextjs, passHref has to be used if a custom element is used as a child of Link tag instead of an anchor tag.
As per the same docs value of href should be '/#enews' not '#enews'

Failed prop type: The prop `href` expects a `string` or `object` in `<Link>`, but got `undefined` instead

import BlogData from "./BlogData";
import Link from 'next/link'
function Card(props) {
return (
<>
<div className="post-content">
<div className="post-image">
<Link href={props.to}><a><img srcSet={props.srcset} src={props.src} alt={props.alt} width={props.width} height={props.height} /></a></Link>
</div>
<div className="post-data">
<Link href={props.to}><a><h2>{props.heading}</h2></a></Link>
<p>{props.para}</p>
<Link href={props.to}><a><button className="read-more">Read More <i className="fas fa-arrow-right"></i>
</button></a></Link>
</div>
</div>
<hr />
</>
)
}
const Blog = () => {
return (
<>
<main>
<div className="blogpage">
<section className="posts">
<h1>Blog Posts</h1>
{BlogData.map((val) => {
return (
<Card key={val.id}
srcset={val.srcset}
src={val.src}
alt={val.alt}
width={val.width}
height={val.height}
href={val.to}
heading={val.heading}
para={val.para} />
);
})
}
</section>
</div>
</main>
</>
)
}
export default Blog;
There is an error that Failed prop type: The prop href expects a string or object in <Link>, but got undefined instead. I am passing href link using props but getting this error. The card component is used to display the data. I am fetching data from BlogData array where data is stored as an object. Can somebody tell me how can I fix this issue?
You pass in the wrong prop. As far as I can see you pass href prop, but expect to
<Card
key={val.id}
srcset={val.srcset}
src={val.src}
alt={val.alt}
width={val.width}
height={val.height}
to={val.to} // need to change here
heading={val.heading}
para={val.para}
/>

react-helmet changing css in the head tag during runtime has lag (no css for 1 sec before it shows updated css)

This is a simplified React component that uses helmet to update the link css on runtime:
function App() {
const [brand, setBrand] = useState('nike')
return (
<div className="App">
<Helmet>
<link rel="stylesheet" href={getBrandStyle(brand)} />
</Helmet>
<div>other contents here</div>
<!-- omitted the button components that change the brand state by calling setBrand -->
</div>
);
}
I have recently just used react-helmet as a declarative way to change the head tag's child and with the code I wrote above, when switching the css there is momentary lag when the page has no css stylings and then 1 second later the updated css shows up.
Even during the initial load of the page, if I use queryParameters (code above doesn't show the query parameter approach) such as
https://localhost:3000?brandQueryParam=nike
there is 1 second wherein there is no css styling before the brand css shows up.
Can you please let me know what I am missing and how to resolve this?
This is the solution that I came up with, not sure if setTimeout is the best solution so if anyone else knows a better way, please share it.
const brands = {
nike: 'nike2022',
adidas: 'adidas2017',
fila: 'fila2020'
};
function App() {
const [brand, setBrand] = useState('nike')
const [isLoading, setIsLoading] = useState(false)
const changeBrandStyleOnClick = (brand) => {
setBrand(brand)
setIsLoading(true)
}
return (
<div className="App">
<Helmet>
<link rel="stylesheet"
onChangeClientState={(newState, addedTags, removedTags) => setTimeout(() => setIsLoading(false), 1500)}
href={getBrandStyle(brand)} />
</Helmet>
{isLoading && (
<Overlay>
<Spinner/>
</Overlay>
)}
{!isLoading && (
<>
{Object.keys(brands).filter(b => b !== brand).map(b =>
(<Button onClick={() => changeBrandStyleOnClick (b)} value={b}>
<Logo
alt="default alt name"
appearance="default"
name={b}
size="small"/>
</Button>))
}
<div>other contents here</div>
</>
)}
</div>
);
}

How to add image background from props in reactjs

I am building a website using reactjs. I have a js file which extract props from another file which is an array called sections with data like title and imageurl. I need to use the prop of imageuURL as a background for each element. I tried to use style but it doesn't work.
Here is the code of extracting :
import React from 'react'
const Menuitem = (props) => {
return(
<div>
<h1 className='title'>{props.title}</h1>
<span className='subtitle'>shop now</span>
</div>
)
}
I pass that code through array in app.js using the following function:
function extract(item) {
return <Menuitem title={item.title}/>
}
Then use map function to return result
function App() {
return (
<div className=''>
{sections.map(extract) }
</div>
);
}
The result I get is like the image. I need to get a background image for each section from the array file
( imageURl prop )
react js problem
If the imageURL is inside the props passed to the menu item you can just put it in inline
const Menuitem = (props) => {
return(
<div style={{ backgroundImage: `url(${props.imageUrl})` }}>
<h1 className='title'>{props.title}</h1>
<span className='subtitle'>shop now</span>
</div>
)
}

Passing props that is an array to 2 different components

This is my 3rd day working with Vue CLI, and I want to pass props that is an array to 2 different components. The components are called Products and Modal. The Modal component is dependent on the Products component. When I pass props to both components inside my App.vue, I don't want both components to render in my App.vue. Like I said, my Modal component is dependent on my Products component.
My overall goal is to display the product title, photo, and description in my modal.
The issue I have is I make a get request in my App.vue, which i fill an empty array and then pass that to Products component. If i pass the array as props to both Products and Modal, I will get an extra modal to render at app level which does make sense, but I don't want that.
Im still getting used to how Vue works and tips or help would be much appreciated.
This could be a very obvious answer that i'm just overlooking, but i'm learning, so please understand that.
App.Vue (I make my get request here and fill an empty array to then have that array passed down as props to the two components):
<template>
<div id="app">
<all-products v-bind:newProductsArray="newProductsArray"></all-products>
</div>
</template>
<script>
//imports
import Vue from 'vue'
import AllProducts from './components/AllProducts.vue'
import Modal from './components/Modal.vue'
//import so I can use vue resource for an http request
import VueResource from 'vue-resource'
Vue.use(VueResource)
//export components to be rendered by main.js then by the DOM
export default {
components: {
'all-products': AllProducts
},
data() {
return {
//empty products array to be filled after get request
products: [],
//declare new array to be set to products array, then passed as props. I do this because without another array,
//all products component will receiev an empty array before it can be filled by the http request.
newProductsArray: []
}
},
//request to retrieve all products from API using Created
created() {
//http request using vue resource from dependencies
this.$http.get('https://tap-on-it-exercise-backend.herokuapp.com/products').then(function(data) {
//fill products array by grabbing the data, slicing ot, then setting it to products array
this.products = data.body.slice(0, data.body.length)
//set products array to the new array to be passed down as props to all products component.
this.newProductsArray = this.products
})
}
}
</script>
Product component (This component receives props from app.vue):
<template>
<div class="products">
<h1 class="all-products">All Products</h1>
<div v-for="product in newProductsArray" :key="product.id" class="single-product">
<div class="product-container">
<div class="row">
<div class="col-md-8 center-block">
<div class="title">{{product.title}}</div>
<img class="images" :src="product.photo_url">
<div class="price">${{product.price}}</div>
<modal/>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
//imports
import Vue from 'vue'
import Modal from './Modal.vue'
//import bootstrap to be used to style buttons and modal.
import BootstrapVue from 'bootstrap-vue'
Vue.use(BootstrapVue)
export default {
//receive new products array as props from app
props: ['newProductsArray'],
components: {
'modal': Modal
},
data() {
return {
//set modal show to false by default
modalShow: false
}
},
methods: {
showModal() {
//toggle modal to show
this.modalShow = true
},
closeModal() {
//toggle modal to close
this.modalShow = false
}
},
}
</script>
Modal component (I want to receive the same props that products received)
<template>
<div>
<b-button #click="modalShow = !modalShow">Show Product Info</b-button>
<b-modal v-model="modalShow" title="title">
<div class="modal-body">
<img class="img-responsive" style="margin:0 auto;" src="http://placehold.it/300x340" alt="">
</div>
<p class="product-description">Description</p>
<div slot="modal-footer" class="w-100">
<b-button size="md" class="float-right" variant="warning" #click="modalShow=false">Close</b-button>
<b-button size="md" class="float-right" variant="primary" #click="modalShow=false">Like</b-button>
</div>
</b-modal>
</div>
</template>
<script>
export default {
props: ['newProductsArray'],
data() {
return {
modalShow: false
}
}
}
</script>

Resources