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:

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

A guide on loading modules dynamically using React and Typescript from a server

I am working on a React / Typescript / Webpack / React-Router application that contains some large JS modules. Currently, I have two bundles (common.js and app.js) included on every page, with common.js being a CommonsChunkPlugin bundle. However, there is ...

Exploring the dynamic capabilities of Next.js in conjunction with

My client's Next.js website has been enhanced with Sanity for content management, but I've noticed a delay in fetching and displaying the data on screen. Previously, with hardcoded data, the site performed much faster. I am currently utilizing g ...

Trouble with CORS blocking NextJs from fetching data from Prismic

I'm encountering an issue while trying to fetch and display lists of my content on a simple blog. Every time I attempt to run the code, it gives me a CORS error message stating that my request has been blocked. It's frustrating because all I wan ...

Having trouble getting the toggle button JavaScript code to function properly in React JS?

I'm in need of some assistance with the comment section located below the JavaScript Code. I am trying to implement a toggle button / hamburger button, but it seems that addEventListener is not necessary for ReactJS. Can someone provide guidance on wh ...

Tips for avoiding multiple function calls in React JS when a value changes

How can I avoid multiple function calls on user actions in my demo application? I have tabs and an input field, and I want a function to be called when the user changes tabs or types something in the input field. Additionally, I need the input field value ...

What is causing the #wrapper element to not fully contain all of its content within?

I am struggling to figure out why the wrapper on this page is not properly wrapping the content. It's only 332px tall for some reason when it should be the height of the content, which is causing the footer to pull up to the top of the page. Could I b ...

Navigating a collection of objects after a button is clicked

My current library project involves looping through an array of objects to display them on a grid based on input values. Here is the code snippet for my loop: const addBook = (ev) => { ev.preventDefault(); let myLibrary = []; let bookIn ...

The v-select menu in Vuetify conceals the text-field input

How can I prevent the menu from covering the input box in Vuetify version 2.3.18? I came across a potential solution here, but it didn't work for me: https://codepen.io/jrast/pen/NwMaZE?editors=1010 I also found an issue on the Vuetify github page t ...

Incorporating RecoilRoot into Next.js with the Next.js 13's App router approach architecture

In my Next.js project, I've opted to utilize Recoil for state management. Following the structure of src/app/pages.tsx (app router approach) in Next.js 13 for routing instead of the default _app.tsx method, I'm now faced with the challenge of wra ...

Tips for updating information when a button is chosen

Hello everyone, I need some help with a form that has three select buttons. The button labeled "Distribute" is already selected when the page loads, and it contains information about full name, password, and location. How can I use JavaScript to create a c ...

Tips on organizing elements

Here are three elements: <!DOCTYPE html> <html lang="en"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script> <link rel="stylesheet" href="https://maxcdn.boo ...

Prevent Bootstrap 4 from causing content to shift downwards when collapsing

Currently, I am utilizing Bootstrap 4 Collapse and it is functioning flawlessly. However, I am interested in making a slight adjustment. Is there a method to prevent the collapse from pushing down the content below it and instead have it overlap? If this i ...

The provided argument, which is of type 'RefObject<HTMLDivElement>', cannot be assigned to the parameter of type 'IDivPosition'

Currently, I am implementing Typescript with React. To organize my code, I've created a separate file for my custom function called DivPosition.tsx. In this setup, I am utilizing useRef to pass the reference of the div element to my function. However ...

Creating a circular shape that adjusts to different screen sizes using only CSS

Is there a way to create perfect circles using only CSS without them turning into ovals when using percentage values for height? ...

Using Redux Form to Access References in a Child Component

Recently, I started working with redux-form along with react-select. Within my redux-form, there is a child component that contains a element, which in turn renders a react-select component. My goal is to figure out how I can pass a reference to this com ...

A guide to setting a custom icon for the DatePicker component in Material-UI 5

Seeking to incorporate custom Icons from react-feathers, I have implemented a CustomIcon component which returns the desired icon based on the name prop. Below is the code for this component. import React from 'react'; import * as Icon from &apo ...

Modifying the appearance of Bootstrap button colors

I recently came across this code on the Bootstrap website. I am curious about how to change the text color from blue to black in this specific code snippet. Currently, the text is appearing as blue by default. For reference and a live example, you can vis ...

Warning: Shadcn-UI Form Alert - An element is converting an uncontrolled input to controlled mode

Throughout the course of this project, I found myself repeatedly using const [fileNames, setFileNames] = useState<string[]>([]); and logging the state with console.log(fileNames). However, every time I clicked on the parent element, an empty Array e ...

Overlaying a div on top of an iframe

Struggling to make this work for a while now. It seems like a small issue related to CSS. The image isn't overlaying the IFrame at the top of the page as expected, it's going straight to the bottom. Here is the code snippet: .overlay{ width: ...

When beginning a test with Jest, an error has occurred in NextJs stating: "TypeError: replace is not a function"

This is a NavBar component that includes a search input. When used, it redirects to another page displaying the search results. The redirection method being utilized here is using "replace" instead of "push". import React, { useEffect, useState } f ...