5

hoping someone here can help me solve this.

Am trying to build a website through NextJs. One of my pages has some paragraphs and buttons which are styled differently based on states and events. I can get the styling to work as intended when using pure React, and also when using a Global Stylesheet with NextJs; but when I use CSS Modules I cant get it to function as intended.

(Note: I can also get it to work by using a simple ternary like <h1 className={submitted ? styles.showresult : styles.hideresult}>Correct? {correct}</h1>; but I have some other scenarios where I need to rely on an multiple ifs and create multiple classes, each with their own styling, so I cant make a simple ternary my final solution.

E.g. this is the file pagex.js

import React from 'react';
import ReactDOM from 'react-dom';

const Pagex = () => {

const [submitted, setSubmitted] = React.useState(false); // whether the submit button was pressed

function calculateScore() {
  let correct = 0
  let incorrect = 0
    //......some scoring logic.....    
  setSubmitted(true)
  }

  // function to create a display class based on whether the submit button has been pressed
 function displayResult(){
   if (submitted === true) {
      return "showtheresult"
    } else {
         return "hidetheresult"
    }
  }

return (
  <section className="results">
      <h1 className={displayResult()}>Correct? {correct}</h1>
      <h1 className={displayResult()}>Incorrect? {incorrect}</h1>
  <button className={displayResult()} onClick={handleMovClick}>An instruction</button>
    </section>
    </div>
  );
};
export default Pagex;

the globals.css file contains

h1.hidetheresult, h3.hidetheresult {
  visibility: hidden;
}

h1.showtheresult, h3.showtheresult {
  visibility: visible;
}

button.hidetheresult {
    border-color: pink;
  }

button.showtheresult {
    border-color: aqua;
  }

Changes when using CSS modules

  1. Add a CSS file in the correct folder with the correct name (../styles/Pagex.module.css) which contains the same styling shown above
  2. Additional import in pagex.js import styles from '../styles/Pagex.module.css'
  3. Change reference in the function within pagex.js
    function displayResult(){
       if (submitted === true) {
          return {styles.showtheresult}
        } else {
             return {styles.hidetheresult}
        }
      }

When i do this the '.' in {styles.showtheresult} and {styles.hidetheresult} gets highlighted as an error by vscode with this detail: ',' expected. ts(1005). Saving the js with a dev server running shows a similar message after trying to compile: Syntax error: Unexpected token, expected "," and the browser shows the same message along with "Failed to compile"

Also tried just passing styles.showtheresult / styles.hidetheresult by removing the curly braces from the displayResult() function. That compiles but nothing happens on the compiled webpage, i.e the class doesnt get updated when the button is pressed and so the styling cant be applied.

Also Tried passing as ${styles.showresult} and ${styles.hideresult} (with `)in the return statement. That also compiles but the page itself gives me an "Unhandled Runtime Error ReferenceError: styles is not defined" message and I cant load the page.

Would highly appreciated if someone could help correct my syntax in the function itself or elsewhere in the code.

2
  • 3
    You can use classnames module which was written for this exact use-case of conditionally adding multiple classNames Commented Jul 28, 2021 at 6:24
  • Hi @PsyGik thanks for feedback and pointer. I will definitely take a look at the module, but could you help in correcting my syntax in the example to work without needing to rely on the classnames module? Or are you indicating that it is a limitation rather than a syntax error and the way around it is to include the classnames module? Commented Jul 28, 2021 at 16:29

1 Answer 1

13

Because you asked nicely ;) (just kiddin')

So Next.js is an opinionated framework and uses CSS Modules to enforce component scoped styling.

Basically you define your stylesheet with a name.module.css filename and add regular CSS in it.

.hidetheresult {
    visibility: hidden;
}

.showtheresult{
    visibility: visible;
}
  
.btn-hidetheresult {
    border-color: pink;
}

.btn-showtheresult {
    border-color: aqua;
}

Now to use this, import it like any JS module,

import styles from './styles.module.css'
console.log(styles);
// styles => {
//  hidetheresult: 'contact_hidetheresult__3LvIF',
//  showtheresult: 'contact_showtheresult__N5XLE',
//  'btn-hidetheresult': 'contact_btn-hidetheresult__3CQHv',
//  'btn-showtheresult': 'contact_btn-showtheresult__1rM1E'
//  }

as you can see, the styles are converted to objects and now you can use them like styles.hidetheresult or styles['btn-hidetheresult'].

Notice the absence of element selector in the stylesheet. That's because CSS Modules rewrite class names, but they don't touch tag names. And in Next.js that is the default behaviour. i.e it does not allow element tag selectors.

File extensions with *.module.(css | scss | sass) are css modules and they can only target elements using classnames or ids and not using tag names. Although this is possible in other frameworks like create-react-app, it is not possible in next-js.

But you can override it in the next.config.js file. (Beyond the scope of this answer)

There is an article which explains how to override it. - disclaimer: I am the author


Now coming to your use-case, you can do contitional styling like so: (assuming the styles are as per the sample given in the answer)

import React from "react";
import styles from "./styles.module.css";

const PageX = () => {
  const [submitted, setSubmitted] = React.useState(false);

  const getStyle = () => {
    if (submitted) return styles.showtheresult;
    else return styles.hidetheresult;
  };

  const getButtonStyle = () => {
    if (submitted) return styles["btn-showtheresult"];
    else return styles["btn-hidetheresult"];
  };

  return (
    <div>
      <section className="results">
        <h1 className={getStyle()}>Correct?</h1>
        <h1 className={getStyle()}>Incorrect?</h1>
        <button className={getButtonStyle()} onClick={handleMovClick}>
          An instruction
        </button>
      </section>
    </div>
  );
};

As you add more conditions, the methods do tend to get more complex. This is where the classnames module comes handy.

import styles from "./styles.module.css";
import clsx from "classnames";

const PageX = () => {
  const [submitted, setSubmitted] = React.useState(false);

  const headerStyle = clsx({
    [styles.showtheresult]: submitted,
    [styles.hidetheresult]: !submitted,
  });
  const btnStyle = clsx({
    [styles["btn-showtheresult"]]: submitted,
    [styles["btn-hidetheresult"]]: !submitted,
  });
  return (
    <div>
      <section className="results">
        <h1 className={headerStyle}>Correct?</h1>
        <h1 className={headerStyle}>Incorrect?</h1>
        <button className={btnStyle} onClick={handleMovClick}>
          An instruction
        </button>
      </section>
    </div>
  );
};

Here's a CodeSandbox for you to play with: Edit 68554803-next-js-cant-apply-dynamic-class-names

Sign up to request clarification or add additional context in comments.

2 Comments

thanks so much! I was able to get the example with the "return styles.showtheresult;" working but could not get the "return styles["btn-showtheresult"];" version working. I am sure it has something to do with how I implemented your feedback so I am very happily accepting your solution as it solves my question (while i continue to fiddle with it and also read about the classnames module).
Thanks so much, this helped me create a dark/light theme in next. It makes sense the way you have laid it out but it's not intuitive. Thank you! Next I need to figure out how to apply the change to body and not only a div.className.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.