Unlock the power of Component level CSS in NextJS with this simple guide!

I don't have much experience with CSS, as I rarely write it these days due to the popularity of libraries like MUI. However, I recently found myself in a tricky situation where I needed to rewrite this example Emabla carousel in typescript and NextJS.

The issue I'm facing is related to CSS. When I import it globally, everything works fine, but I prefer to import it at the component level. Here is how my code structure looks:

https://i.sstatic.net/roY0v.png

The ProductSlider.tsx corresponds to EmblaCarousel.js in the sample project, and the ProductSlider.module.css file is similar to the embla.css.

This is what my ProductSlider component looks like:

import s from './ProductSlider.module.css'
import React, { useState, useEffect, useCallback, memo } from 'react'
import useEmblaCarousel from 'embla-carousel-react'
import Thumbnail from './Thumbnail'
import Image from 'next/image'
import { Box } from '@mui/material'

// Component definition here...

I have successfully implemented basic CSS styling, but my challenge lies in how to handle classes like

embla__container embla__container--thumb
using named imports.

For instance, how do I deal with elements like this:

<div
className={`embla__slide embla__slide--thumb ${
selected ? 'is-selected' : ''
}`}

In cases where there are two identical classes, I understand that I can use clsx like this:

cn(s.embla__container, s.embla__container)
. But when dealing with modifiers like --thumb, some_name--thumb, some_name-is-selected, or simply is-selected, I'm unsure of the best approach.

Answer №1

Attempt to accomplish this task

import s from './ProductSlider.module.css'
import React, { useState, useEffect, useCallback, memo } from 'react'
import useEmblaCarousel from 'embla-carousel-react'
import Thumbnail from './Thumbnail'
import Image from 'next/image'
import { Box } from '@mui/material'

type ProductSliderProps = {
items: {
    url: string
    alt?: string
}[]
}

const ProductSlider = ({ items }: ProductSliderProps) => {
const [selectedIndex, setSelectedIndex] = useState(0)
const [mainViewportRef, embla] = useEmblaCarousel({ skipSnaps: false })
const [thumbViewportRef, emblaThumbs] = useEmblaCarousel({
    containScroll: 'keepSnaps',
    dragFree: true,
})

const onThumbClick = useCallback(
    (index) => {
    if (!embla || !emblaThumbs) return
    if (emblaThumbs.clickAllowed()) embla.scrollTo(index)
    },
    [embla, emblaThumbs]
)

const onSelect = useCallback(() => {
    if (!embla || !emblaThumbs) return
    setSelectedIndex(embla.selectedScrollSnap())
    emblaThumbs.scrollTo(embla.selectedScrollSnap())
}, [embla, emblaThumbs, setSelectedIndex])

useEffect(() => {
    if (!embla) return
    onSelect()
    embla.on('select', onSelect)
}, [embla, onSelect])

return (
    <>
    <Box className={s.embla}>
        <Box className={s.embla__viewport} ref={mainViewportRef}>
        <Box className={s.embla__container}>
            {items.map(({ url, alt }, idx) => (
            <Box className={s.embla__slide} key={idx}>
                <Box className={s.embla__slide__inner}>
                <Image
                    src={url}
                    alt={alt}
                    layout="fill"
                    objectFit="contain"
                    quality="85"
                />
                </Box>
            </Box>
            ))}
        </Box>
        </Box>
    </Box>

    <Box className="embla embla--thumb">
        <Box className={s.embla__viewport} ref={thumbViewportRef}>
        <Box className="embla__container embla__container--thumb">
            {items.map(({ url, alt }, idx) => (
            <Thumbnail
                onClick={() => onThumbClick(idx)}
                selected={idx === selectedIndex}
                imgSrc={url}
                key={idx}
                alt={alt}
                s={s}
            />
            ))}
        </Box>
        </Box>
    </Box>




    <style>
        {
        `
        // Add your custom CSS styles here     
        
        
        `
        }
    </style>

    </>
)
}

export default memo(ProductSlider)

Answer №2

After reading through this response, I decided to implement the following solution:

import s from './ProductSlider.module.css'
import React, { useState, useEffect, useCallback, memo } from 'react'
import useEmblaCarousel from 'embla-carousel-react'
import Thumbnail from './Thumbnail'
import Image from 'next/image'
import { Box } from '@mui/material'

type ProductSliderProps = {
items: {
    url: string
    alt?: string
}[]
}

const ProductSlider = ({ items }: ProductSliderProps) => {
const [selectedIndex, setSelectedIndex] = useState(0)
const [mainViewportRef, embla] = useEmblaCarousel({ skipSnaps: false })
const [thumbViewportRef, emblaThumbs] = useEmblaCarousel({
    containScroll: 'keepSnaps',
    dragFree: true,
})

const onThumbClick = useCallback(
    (index) => {
    if (!embla || !emblaThumbs) return
    if (emblaThumbs.clickAllowed()) embla.scrollTo(index)
    },
    [embla, emblaThumbs]
)

const onSelect = useCallback(() => {
    if (!embla || !emblaThumbs) return
    setSelectedIndex(embla.selectedScrollSnap())
    emblaThumbs.scrollTo(embla.selectedScrollSnap())
}, [embla, emblaThumbs, setSelectedIndex])

useEffect(() => {
    if (!embla) return
    onSelect()
    embla.on('select', onSelect)
}, [embla, onSelect])

return (
    <>
    <Box className={s.embla}>
        <Box className={s.embla__viewport} ref={mainViewportRef}>
        <Box className={s.embla__container}>
            {items.map(({ url, alt }, idx) => (
            <Box className={s.embla__slide} key={idx}>
                <Box className={s.embla__slide__inner}>
                <Image
                    src={url}
                    alt={alt}
                    layout="fill"
                    objectFit="contain"
                    quality="85"
                />
                </Box>
            </Box>
            ))}
        </Box>
        </Box>
    </Box>

    <Box className={`${s.embla} ${s['embla--thumb']}`}>
        <Box className={s.embla__viewport} ref={thumbViewportRef}>
        <Box
            className={`${s.embla__container} ${s['embla__container--thumb']}`}
        >
            {items.map(({ url, alt }, idx) => (
            <Thumbnail
                onClick={() => onThumbClick(idx)}
                selected={idx === selectedIndex}
                imgSrc={url}
                key={idx}
                alt={alt}
                s={s}
            />
            ))}
        </Box>
        </Box>
    </Box>
    </>
)
}

export default memo(ProductSlider)

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the reason for next.js to execute getStaticPaths each time a page is loaded?

During the pre-rendering of my product detail pages, I encountered an issue with Next.js where the getStaticPaths function was being called on every page refresh or URL change even if it was the same page. This caused a delay and made the page unresponsive ...

What are some creative ways to customize and animate the cursor or caret within an input field?

Just to clarify, I am working with React and Material-UI. I have a task at hand where I need to modify the appearance of the caret within an input element by adding an animation to it. I have managed to change its color using caret-color and set a default ...

The property 'tz' of ABP Property does not exist within the type 'moment'

I acquired the .NET Core & ReactJS template from aspnetboilerplate.com. I successfully initialized the database. After navigating to the reactjs folder, I executed the npm install and npm start commands. An error message stating: Property 'tz&apo ...

Unable to retrieve data from a localhost server to a different one

I have both a Webpack server for React (located at localhost:3000) and an Express server (found at localhost:4000). I followed the instructions to set up a proxy in my package.json file but it's still not working. Additionally, I tried setting headers ...

Switch the color (make it gray) of the selected tab in the navigation bar

<!-- Customizing the Navbar --> <nav class="navbar navbar-expand-sm bg-primary navbar-dark"> <a class="navbar-brand" href="<?php echo base_url('Controller_dashboard'); ?>"><strong>StuFAP MS</strong></a> ...

Using localStorage item as query value in socket.io: A step-by-step guide

Goal To authenticate users through a socket.io connection by passing a token from localStorage. Following the instructions in the documentation, I have initialized io() outside of MyComponent and added the token as a query: const token = localStorage.getI ...

Utilize React JS to parse and display a sophisticated JSON structure in a dropdown menu

Looking at the backend data structure provided, we have information on various courses in different departments. { "courseDetails" : [{"departmentId" : 105654, "courses" : [{"stream" : "science","courseIds" : ["104","105 ...

Show a collection of elements containing attributes in JSX

I have a React component where I am displaying data from an array called pkgData: <div className="mainApp"> <div className="pkgsList"> {pkgData.map((p) => <h3 key={p.name}>{p.name}</h3> ...

Establishing a default value as undefined for a numeric data type in Typescript

I have a question regarding setting initial values and resetting number types in TypeScript. Initially, I had the following code snippet: interface FormPattern { id: string; name: string; email: string; age: number; } const AddUser = () => { ...

Can a full-fledged React application be opened in a popup window?

I'm currently working on an application that features a list of devices. When a user clicks on any device item, I want a popup to open with a React application specific to that device, complete with its own routing configuration. I've searched on ...

Utilizing Axios to retrieve data asynchronously within the componentDidMount lifecycle method

Here is my attempt at fetching data using axios in my React component: state = { profiles: [], data: [] } async componentDidMount() { try { // this commented code works. But I want to use axios api. // const response = await fetch( ...

What is the reason behind the automatic activation of a function when a nested React modal is

I've been experimenting with using react-responsive-modal and then switching to react-modal, but I'm encountering the same issue. Additionally, all my forms are built using react-hook-form. The problem arises when I have one modal triggering ano ...

When trying to retrieve a value from a custom render function in React with TypeScript, an error occurs indicating that the value is not assignable to type 'ReactNode'

Recently, I attempted to develop a versatile swiper component using Next.js 13 (App Router) v13.4.12 along with TypeScript. However, I encountered an issue when trying to access data from the component props, which involves a custom function for rendering ...

Create a dynamic AppBar Toolbar in React Admin that changes based on the current page

Recently delving into the world of React, I've been facing some challenges when it comes to loading IconButtons onto the toolBar of an AppBar (from Material UI). For instance, I'm looking to have two specific IconButtons displayed on pageA and d ...

What is the best way to achieve maximum height?

Currently, I am attempting to obtain the total height of the page in order to apply it to the styles, specifically for the darkMask class. The reason for needing this height is that when a certain action is triggered within the header, a dark overlay is a ...

Why does my jQuery IMG center script not work in Chrome and Safari, but it works perfectly in IE?

I am experiencing an issue with centering the thumbnails of products on my e-commerce website. Despite successful centering in IE, FF, and Opera, Chrome and Safari are not aligning the thumbnails properly – they remain at the top of the div instead of be ...

"Stay current with real-time updates in seconds using React technology

Check out this code snippet: const currentTime = new Date().getTime(); const targetTime = new Date('November 15, 2020').getTime(); const difference = currentTime - targetTime; let seconds = Math.floor(difference / 1000) % 60; setInterval(functio ...

Column flexbox self is not functioning properly with text ellipsis

Is there a way to apply text-overflow: ellipsis to a flexbox element? I know I need to set the width and overflow: hidden, but how can I achieve the ellipsis effect within the grey block when the text overflows? Removing the flexbox property from the < ...

CSS Grid having trouble with wrapping elements

Greetings to all, As a newcomer to the world of web development, I am currently exploring the wonders of the CSS grid tool. However, I have encountered a roadblock: My intention is for the cards to automatically flow one by one into the next row while ma ...

Tips for creating a button-like anchor in Bootstrap 4

After using a button in Bootstrap 4, it appears as follows: https://i.sstatic.net/laGLo.png <button type="button" class="btn btn-primary" target="_blank">Download</button> On the other hand, when I substitute an anchor with the same button s ...