I have a React component where I'm trying to style a button for my web app. I've done this countless times with other components the exact same way. For some reason, the class names are not being used in the rendered page. I use parceljs to bundle my scripts and have checked the source in the browser, the css classes exist. I have tried for hours changing things around to get it to work with no luck, so I'm reaching out to the community for ideas.
Button.tsx
import * as React from 'react'
import styles from './Button.module.css'
import cx from 'classnames'
type ButtonProps = {
color?: 'default' | 'primary' | 'alert' | 'success'
value: string
onClick?: () => void
}
export const Button: React.FC<ButtonProps> = (props) => {
const { color = 'default' } = props;
return (
<div className={styles.buttonContainer}> {/* part of my testing, tried wrapping everything in a div to see if the style would work */}
<button className={cx(styles.buttonContainer, styles[color])} onClick={props.onClick}>
<div className={styles.value}>{props.value}</div>
</button>
</div>
)
}
Button.module.css
.buttonContainer {
color: black;
}
.default {
background: white;
color: var(--colorBlack100);
}
.primary {
background: var(--colorBlue80);
color: white;
}
.alert {
background: var(--colorAlert);
color: white;
}
.success {
background: var(--colorSuccess);
color: white;
}
.buttonContainer .value {
color: inherit;
}
index.ts - used to group all components together
...
export { Button } from './Button/Button'
...
Index.tsx - page to display it
import * as React from 'react'
import { Button } from '../Components'
const Index: React.FC = () => {
return (
<div>
<Button value="Default" onClick={() => console.log("test")} color="primary" />
</div>
)
}
export default Index
Here is the Button div that gets rendered
<div><button class=""><div>Default</div></button></div>
Here is the css from parcel that I pull from source using Chrome dev tools, the classes exist so I know parcel is picking it up
._9p9acG_buttonContainer {
color: #000;
}
._9p9acG_default {
color: var(--colorBlack100);
background: #fff;
}
._9p9acG_primary {
background: var(--colorBlue80);
color: #fff;
}
._9p9acG_alert {
background: var(--colorAlert);
color: #fff;
}
._9p9acG_success {
background: var(--colorSuccess);
color: #fff;
}
._9p9acG_buttonContainer ._9p9acG_value {
color: inherit;
}
As mentioned, I've done this successfully with many other components, this is the only one acting funny and I'm at a loss.
Thank you in advance for your help.
I don't know why, but after deleting the files and adding each line 1 at a time and testing until it no longer worked, I found that it was the CSS class name .default that made it not work. Is this some kind of reserved word? My only other guess is that it's a bug in either parceljs or react-css-modules.
Related
strong textI'm trying to make a Pokedex App with an animation when we click on a card.
I have a Component PokemonList who render PokemonCard.
When i click on PokemonCard, I navigate to /{pokemonName} and an detail component shows up with an animation.
The Detail component is rendered with of React router
My PokemonCard and my PokemonView (Detail pannel) are in the same Framer Motion LayoutGroup. My cards have the layoutId card-${pokemonInfo.name} and my PokemonView have too. On the opening, i fetch current pokemon with the location.pathname and fill detail pannel with infos.
PokemonView.ts
import { LayoutGroup, motion } from "framer-motion"
import { useLocation, useNavigate } from "react-router-dom"
import styled from "styled-components"
import { colours } from "../../data/typeColors"
import { hexToRgb } from "../../helpers/hexToRgb"
import { useDisableScroll } from "../../hooks/useDisableScroll"
import {
useGetPokemonInfoQuery,
useGetPokemonSpeciesInfoQuery,
} from "../../redux/api/api"
import PokemonInfoPannel from "./PokemonInfoPannel"
import ViewTab from "./Tabs/ViewTab"
const PokemonViewContainer = styled.div`
height: 100%;
width: 100vw;
background-color: ${(props: { background: string }) =>
hexToRgb(props.background, 0.5)};
position: fixed;
top: 0;
display: flex;
justify-content: center;
align-items: center;
`
const PokemonViewStyle = styled(motion.div)`
background-color: ${(props: { background: string }) => props.background};
height: 100%;
width: 100%;
min-height: 680px;
max-width: 600px;
max-height: 800px;
overflow: hidden;
opacity: 1;
#media (min-width: 600px) {
border-radius: 2rem;
box-shadow: 0 0 10px 0 rgba(255, 255, 255, 0.5);
}
#media (max-width: 600px) {
max-height: 100vh;
}
`
const PokemonView = () => {
console.log("PokemonView")
const navigate = useNavigate()
useDisableScroll()
const location = useLocation()
const { data: pokemonInfo } = useGetPokemonInfoQuery(
location.pathname.split("/")[1]
)
const { data: pokemonSpeciesInfo } = useGetPokemonSpeciesInfoQuery(
location.pathname.split("/")[1]
)
return (
<>
<PokemonViewContainer
id="pokemon-view-container"
background={colours[pokemonInfo!.type[0]].background}
onClick={(e) => {
navigate("/")
}}
>
<LayoutGroup id="card-swap-animation">
<PokemonViewStyle
layoutId={`card-${pokemonInfo.name}`}
id="pokemon-view"
background={colours[pokemonInfo!.type[0]].background}
onClick={(e) => {
e.stopPropagation()
}}
>
{/* <PokemonInfoPannel
pokemonDetailed={{
info: pokemonInfo!,
speciesInfo: pokemonSpeciesInfo!,
}}
></PokemonInfoPannel>
<ViewTab
pokemonDetailed={{
info: pokemonInfo!,
speciesInfo: pokemonSpeciesInfo!,
}}
></ViewTab> */}
</PokemonViewStyle>
</LayoutGroup>
</PokemonViewContainer>
</>
)
}
export default PokemonView
With this I have the correct behavior. But, I have some typescript errors because my pokemonInfo and pokemonSpeciesInfo can be undefined (i fill them via RTK-Query).
So, I decided to add this before the return
if (!pokemonInfo || !pokemonSpeciesInfo) {
return null
}
But with this line, the first time i click on a card the animation doesn't work. I close detail pannel, open it again and the animation works.
Another think that I don't understand is that when I do that
const { data: pokemonInfo } = useGetPokemonInfoQuery(
location.pathname.split("/")[1]
)
console.log(pokemonInfo)
const { data: pokemonSpeciesInfo } = useGetPokemonSpeciesInfoQuery(
location.pathname.split("/")[1]
)
console.log(pokemonSpeciesInfo)
I see that the first request is never undefined but the second is always, then became fullfilled. I don't know if this is the problem.
Someone can help me ?
Thanks in advance
The source code is here : https://github.com/RomainHoffmann/Pokedex
UPDATE :
I understood why the first console was never null. It's because I do this request on my PokemonCard component, so the response is cached by RTK Query.
To fix my problem, I add the other query in my Card component, it works but I call it "in the void".
I have understand now thats returning <></> in the first render makes the problem but how could I do to avoid Typescript errors and render the component only when datas are fullfiled ?
I want to creare a template theme in React for my software suite and allow developers to customize the theme for each software they develop.
The theme will be shipped in a library using styled components
Here is an example of the code:
import styled from 'styled-components'
const ButtonStyled = styled.button`
font-size: 1.5em;
text-align: center;
color: green;
`;
const TomatoButton = styled(ButtonStyled)`
color: tomato;
`;
//This is the default template
const DefaultTemplate = () => {
return <div>
<ButtonStyled className='button'>Button</ButtonStyled>
<TomatoButton className='tomato-button'>Button II</TomatoButton>
</div>
}
//This is the template styled by developers
const DefaultTemplateStyled = styled(DefaultTemplate)`
color: white;
&.button{
color: violet
}
&.tomato-button{
color: black;
}
`;
function App() {
return (<DefaultTemplateStyled />);
}
export default App;
In this app I cannot see the override of the styles, am I missing something?
In styled-components what you should do is pass the className propm like this:
//This is the default template
const DefaultTemplate = ({ className }) => {
return (
<div>
<ButtonStyled className={`button ${className}`}>Button</ButtonStyled>
<TomatoButton className={`tomato-button ${className}`}>
Button II
</TomatoButton>
</div>
);
};
All the other code is fine
If i use a prop condition on the main component it will work per instance.
So for example if i have:
const Div = styled.div<Props>`
${(props) => {
return css`
${props.test && "border: 5px solid red;"};
`;
}}
`;
only components which have the test prop will have this ugly border :)
But if i use this same condition on a nested css rule like that:
const Div = styled.div<Props>`
${(props) => {
return css`
.tabletScreen & {
${props.test && "border: 5px solid red;"};
}
`;
}}
`;
All instances of this component will have this ugly border if one of the components has this test prop.
When inspect it i see that all instances of the component gets the same class generated, so the this:
.tabletScreen .sc-jcFjpl {
border: 5px solid red;
}
Is implemented to all instances.
But on the first case (when the condition is not nested) the component with the test prop will get another class so it won't override the others.
How can i fix this?
Use && instead of & and it'll be scoped to that stylistic instance of the component. Single ampersand refers to the "static component class".
More info here: https://styled-components.com/docs/basics#pseudoelements-pseudoselectors-and-nesting
import React from "react";
import { render } from "react-dom";
import styled, { css } from "styled-components";
import Wrapper from "./Wrapper";
import Title from "./Title";
// Render these styled components like normal react components.
// They will pass on all props and work
// like normal react components – except they're styled!
const Div = styled.div`
${(props) => {
return css`
.tabletScreen {
border: ${props.test && '5px solid red'};
}
`;
}}
`;
const App = () => (
<Wrapper>
<Div test>
Without Test
<div className="tabletScreen">TabletScreen</div>
</Div>
</Wrapper>
);
render(<App />, document.getElementById("root"));
At the moment I'm using SCSS, as it is easy to use with NextJS. I really like how this system works, using the SCSS modules, and so I would also like to use it when using Material-UI. Material-UI uses JSS, which is a lot of boilerplate to write every time. Additionally, I prefer not to work with two ways of styling (SCSS modules and JSS). I already changed the order of CSS injection, so that I can use my SCSS modules on Material-UI components. However, I'm wondering if there is a way to overwrite styles of Material-UI components using SCSS modules? I have tried the following and a few similar things, but nothing seemed to work:
import styles from "./Login.module.scss";
import Button from "#material-ui/core/Button";
function Login() {
return (
<section>
<Button className={styles.button} variant="contained" color="primary">
Verify
</Button>
</section>
);
}
export default Login;
.button {
padding: 10px;
margin-bottom: 10px;
.MuiButton-containedPrimary {
border: 2px solid red;
background-color: red;
}
}
Below is the correct syntax:
.button {
padding: 10px;
margin-bottom: 10px;
&:global(.MuiButton-containedPrimary) {
border: 2px solid red;
background-color: red;
}
}
The example above has two key changes:
Using :global(.MuiButton-containedPrimary). Without using :global, CSS modules will transform the MuiButton-containedPrimary into a class name unique to this module and then it won't match what Material-UI puts on the button.
Adding the & in front effectively creates a selector like .button.MuiButton-containedPrimary matching an element with both classes on it. Your original syntax would treat .MuiButton-containedPrimary as a descendant selector and only match elements with that class that are a descendant of the button rather than the button itself.
You can use makeStyles of #material-ui. And pass to classes to override CSS default of material-ui.
import { makeStyles } from "#material-ui/styles";
const useStyle = makeStyles(() => ({
root: {
padding: "10px",
marginBottom: "10px",
},
containedPrimary: {
border: "2px solid red",
backgroundColor: "red"
}
}))
function Login() {
const classes = useStyle();
return (
<section>
<Button classes={classes} variant="contained" color="primary">
Verify
</Button>
</section>
);
}
I'm writing tests for my components, the components work fine in the application. Importing and consuming them works great but getting them to work with react-test-renderer has not been as great. For example, I have a social component that reads config and renders out links.
./components/Social/social.js
import React from 'react'
import PropTypes from 'prop-types'
import config from '../../../content/meta/config'
import GithubIcon from '!svg-react-loader!../../images/svg-icons/github.svg?name=GithubIcon'
import LinkedInIcon from '!svg-react-loader!../../images/svg-icons/linkedin.svg?name=LinkedInIcon'
import TwitterIcon from '!svg-react-loader!../../images/svg-icons/twitter.svg?name=TwitterIcon'
import InstagramIcon from '!svg-react-loader!../../images/svg-icons/instagram.svg?name=InstagramIcon'
import DevIcon from '!svg-react-loader!../../images/svg-icons/dev.svg?name=DevIcon'
const Social = props => {
const { theme } = props
const items = config.authorSocialLinks
const icons = {
twitter: TwitterIcon,
github: GithubIcon,
linkedin: LinkedInIcon,
instagram: InstagramIcon,
dev: DevIcon,
}
return (
<React.Fragment>
<div className="social">
{items.map(item => {
const Icon = icons[item.name]
return (
<a
href={item.url}
key={item.name}
className="socialLink"
target="_blank"
rel="noopener noreferrer"
title={item.name}
>
<Icon className="svg" />
</a>
)
})}
</div>
{/* --- STYLES --- */}
<style jsx global>
{`
#media screen and (max-width: 450px) {
.social {
width: 95%;
}
}
#media screen and (min-width: 450px) {
.social {
width: 40%;
}
}
.social {
display: flex;
justify-content: center;
align-items: center;
margin: 0 auto;
padding: 20px;
:global(svg) {
width: 30px;
height: 30px;
transition: all 0.5s;
}
&:hover :global(svg) {
fill: ${theme.color.special.attention};
}
}
.svg path {
fill: ${theme.color.brand.primary};
}
.socialLink {
margin: 0 auto;
display: inline-block;
padding: 1px;
text-align: center;
}
`}
</style>
</React.Fragment>
)
}
Social.propTypes = {
theme: PropTypes.object.isRequired,
}
export default Social
./components/Social/index.js
export { default } from './Social'
In my test I'm importing the component and rendering to do snapshot testing. Test:
./components/tests/social.spec.js
import React from 'react'
import { create } from 'react-test-renderer'
import Social from '../Social'
const theme = require('../../theme/theme.json')
describe('Social Component', () => {
it('renders correctly', () => {
const tree = create(<Social theme={theme} />).toJSON()
expect(tree).toMatchSnapshot()
})
})
I am getting the error only in my test and not in the app.
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
Check the render method of Social.
I've tried many little tweaks to no avail.
When running jest your entire build system doesn't exist - in this case webpack. So imports like this '!svg-react-loader!../../images/svg-icons/github.svg?name=GithubIcon' will throw an error (or in your case maybe return undefined?).
You need to run jest in a webpack context if you want those - https://jestjs.io/docs/en/webpack
I imagine the test runner is getting confused over the export default in the barrel index.js file.
Two things to try:
Change index.js to
import Social from './Social'
export {
social
};
Or,
If you are going to ultimately use named exports, why not just do so from the start? Change Social to export export const Social = props => {...} and in your index file export * from './Social
export { default as Social } from './Social'
Can you try it out like in the code snippet above?
I also see that you try to import your Social component wrong in social.spec.js
Shouldn't it be import Social from '../components/Social'?
You try to import it like import Social from '../Social'