I am new to React and being held back by a seemingly simple task.
I've got a Header component nested within which is a HamburgerButton component. Clicking the latter should make a sidenav appear but for now I would like the icon to change from the 'hamburger' to the big 'X'.
Here is my parent component:
import { MyMoviesLogo } from 'components/Icons';
import HamburgerButton from 'components/HamburgerButton/HamburgerButton';
import styles from './Header.module.css';
const Header = (): JSX.Element => {
const [isActive, setIsActive] = useState(false);
return (
<header className={styles.header}>
<MyMoviesLogo className={styles.headerIcon} />
<HamburgerButton
isActive={false}
/>
</header>
);
};
export default Header;
And here is the HamburgerButton
import styles from './HamburgerButton.module.css';
type HamburgerButtonProps = {
isActive: boolean;
onClick?: () => void;
};
const addMultipleClassNames = (classNames: string[]): string => classNames.join(' ');
const HamburgerButton = ({ isActive, onClick }: HamburgerButtonProps): JSX.Element => {
return (
<div className={isActive ? addMultipleClassNames([styles.hamburger, styles.active]) : styles.hamburger} onClick={onClick}>
<div className={styles.bar}></div>
<div className={styles.bar}></div>
<div className={styles.bar}></div>
</div>
);
}
export default HamburgerButton;
Here's my HamburgerButton.module.css file:
.hamburger {
cursor: pointer;
display: block;
width: 25px;
}
.bar {
background-color: var(--hamburger-button-global);
display: block;
height: 3px;
margin: 5px auto;
transition: all 0.3s ease-in-out;
width: 25px;
}
.hamburger.active .bar:nth-child(2) {
opacity: 0;
}
.hamburger.active .bar:nth-child(1) {
transform: translateY(8px) rotate(45deg);
}
.hamburger.active .bar:nth-child(3) {
transform: translateY(-8px) rotate(-45deg);
}
Manually changing the isActive prop to false verifies that the styling is applied as required.
My question is, how could I make it so when I click the icon its state gets toggled? I am familiar with React hooks like useState but can't quite put something together.
Any help would be greatly appreciated.
Thank you.
P.S.: It's probably obvious but I am using TypeScript.
You should use your onClick prop from your <HamburgerButton /> to change the parent state.
<HamburgerButton isActive={isActive} onClick={() => { setIsActive(oldState => !oldState) } />
I have some css declarations shared with multiple style-components.
For example:
margin-top:${props.marginTop};
margin-bottom:${props.marginBottom};
So i know can add file with:
export const baseStyles = css`
margin-top: 10px;
margin-bottom: 10px;
`;
and then import it and use:
const MyComponent = styled.div`
${(props: Props) => {
return css`
${baseStyles};
`;
}}
`;
But is there a way to this and still have the current component props used inside ${baseStyled} ?
props are automatically provided inside of mixins.
export const baseStyles = css`
margin-top: ${(props) => props.marginTop};
margin-bottom:${(props) => props.marginBottom};
`;
const MyComponent = styled.div`
${baseStyles}
`;
const App = () => {
return (
<MyComponent marginTop="12px" marginBottom="16px" />
)
};
I want to load an external styled component and use extending styles, but it doesn't work. How can I solve this problem?
common/Button.js
import styled from 'styled-components';
const StyledButton = styled.button`
padding: 1rem 2rem;
border: none;
border-radius: 8px;
`;
const Button = ({ children, onClick }) => {
return <StyledButton onClick={onClick}>{children}</StyledButton>;
};
export default Button;
pages/Login.js
import Button from '../Components/common/Button';
import styled from 'styled-components';
const StyledButton = styled(Button)`
// not working
color: red;
`;
const Login = () => {
return (
<div>
<StyledButton>Submit</StyledButton>
</div>
);
}
export default Login;
enter image description here
Component can be styled only if it passes className property to it's child. So you should make your Button component like this:
const Button = ({ children, onClick, className }) => {
return <StyledButton onClick={onClick} className={className}>{children}</StyledButton>;
};
This is my first attempt at writing unit tests for a Svelte app. The below example is based on the standard Svelte template with an additional element added. In this element I conditionally render some text with await block logic.
<script land="ts">
import { asyncthing } from "./asyncthing";
import { onMount } from "svelte";
export let name: string;
let isReady: Promise<boolean> = asyncthing().then((resolveTo) => {
console.log("asyncthing resolved");
return resolveTo;
});
</script>
<main>
<h1 data-testid="greeting">Hello {name}!</h1>
<p>
Visit the Svelte tutorial to learn
how to build Svelte apps.
</p>
<p data-testid="async">
{#await isReady}Awaiting...{:then value}Resolved to: {value}{/await}
</p>
</main>
<style>
main {
text-align: center;
padding: 1em;
max-width: 240px;
margin: 0 auto;
}
h1 {
color: #ff3e00;
text-transform: uppercase;
font-size: 4em;
font-weight: 100;
}
#media (min-width: 640px) {
main {
max-width: none;
}
}
</style>
The function asyncthing returns a Promise<boolean> that resolves after 5 seconds with true.
export const asyncthing = async () => {
console.log("asyncthing() called");
return new Promise<boolean>((resolve) => {
setTimeout(() => {
console.log("timeout expired");
resolve(true);
}, 5000);
});
};
Opening the app in a browser works as expected, but the test below fails.
import App from "./App.svelte";
import { render } from "#testing-library/svelte";
import { tick } from "svelte";
import * as asyncthings from "./asyncthing";
test("messing with async", async () => {
jest.setTimeout(20000);
jest.useFakeTimers();
jest.spyOn(global, "setTimeout");
const asyncthingMock = jest.spyOn(asyncthings, "asyncthing");
const app = render(App, { name: "Everybody" });
expect(app.getByTestId("greeting")).toBeInTheDocument();
expect(app.getByTestId("greeting")).toHaveTextContent("Hello Everybody!");
expect(app.getByTestId("async")).toBeInTheDocument();
expect(app.getByTestId("async")).toHaveTextContent("Awaiting...");
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 5000);
jest.runAllTimers();
await tick();
expect(app.getByTestId("async")).toHaveTextContent("Resolved to: true");
});
The log statements are printed, so the promise does seem to resolve. However, according to Jest the text is not updated. I'm sure I must be doing something wrong, so please, somebody set me straight.
What is the best way to create variants using styled components? Heres what i am currently doing.
const ButtonStyle = styled.button`
padding:8px 20px;
border:none;
outline:none;
font-weight:${props => props.theme.font.headerFontWeight};
font-size:${props => props.theme.font.headerFontSize};
display:block;
&:hover{
cursor:pointer;
}
${({ variant }) =>
variant == 'header' && css`
background-color:${props => props.theme.colors.lightblue};
color:${({ theme }) => theme.colors.white};
&:active{
background-color:${props => props.theme.colors.blue}
}
`
}
${({ variant }) =>
variant == 'white' && css`
background-color:white;
color:${({ theme }) => theme.colors.lightblue};
&:active{
color:${props => props.theme.colors.blue}
}
`
}
`;
I cannot tell if this is the standard way of doing things.
I have also been using other components as bases to create other components from while changing a few things
eg
const InnerDiv = styled(otherComponent)`
position: unset;
background-color: red;
overflow-x: hidden;
display: flex;
`;
Which is the better approach? Are there any better alternatives?
Inspired by previous solutions, I want to share what I came up with:
import styled, { css, DefaultTheme } from 'styled-components';
const variantStyles = (theme: DefaultTheme, variant = 'primary') =>
({
primary: css`
color: ${theme.colors.light};
background: ${theme.colors.primary};
border: 1px solid ${theme.colors.primary};
`,
}[variant]);
const Button = styled.button<{ variant: string }>`
padding: 1rem;
font-size: 0.875rem;
transition: all 0.3s;
cursor: pointer;
${({ theme, variant }) => variantStyles(theme, variant)}
&:active {
transform: translateY(1.5px);
}
`;
export default Button;
For now it contains only primary and its the default one, by you can add more variants by adding new object to variantStyles object
Then you can use it by passing the variant as a prop or keep the default by not passing any variant.
import { Button } from './HeroSection.styles';
<Button variant="primary">Start Learning</Button>
This is just my opinion:
I don't think we can do anything very different from what you did.
A different way that I thought, would be to create an options object to map the possibilities of the variant, like this:
const variantOptions = {
header: {
backgroundColor: theme.colors.lightblue,
color: theme.colors.white,
active: theme.colors.blue,
},
white: {
backgroundColor: "white",
color: theme.colors.lightblue,
active: theme.colors.blue,
},
};
And use it in your style component like this:
const ButtonStyle = styled.button`
padding: 8px 20px;
border: none;
outline: none;
font-weight: ${(props) => props.theme.font.headerFontWeight};
font-size: ${(props) => props.theme.font.headerFontSize};
display: block;
&:hover {
cursor: pointer;
}
${({ variant }) =>
variant &&
variantOptions[variant] &&
css`
background-color: ${variantOptions[variant].backgroundColor};
color: ${variantOptions[variant].color};
&:active {
color: ${variantOptions[variant].active};
}
`}
`;
And all of this buttons will work:
<ButtonStyle variant="*wrong*">Button</ButtonStyle>
<ButtonStyle variant="header">Button</ButtonStyle>
<ButtonStyle variant="white">Button</ButtonStyle>
<ButtonStyle>Button</ButtonStyle>
When dealing with Styled Component variants here is what I like to do to keep things organised and scalable.
If the variants are stored within the same file I am using the inheritance properties:
const DefaultButton = styled.button`
color: ${(props) => props.theme.primary};
`;
const ButtonFlashy = styled(DefaultButton)`
color: fuchsia;
`;
const ButtonDisabled = styled(DefaultButton)`
color: ${(props) => props.theme.grey};
`;
If if we are talking about a reusable components I would use this technique:
import styled from 'styled-components';
// Note that having a default class is important
const StyledCTA = ({ className = 'default', children }) => {
return <Wrapper className={className}>{children}</Wrapper>;
};
/*
* Default Button styles
*/
const Wrapper = styled.button`
color: #000;
`;
/*
* Custom Button Variant 1
*/
export const StyledCTAFushia = styled(StyledCTA)`
&& {
color: fuchsia;
}
`;
/*
* Custom Button Variant 2
*/
export const StyledCTADisabled = styled(StyledCTA)`
&& {
color: ${(props) => props.theme.colors.grey.light};
}
`;
export default StyledCTA;
Usage:
import StyledCTA, { StyledCTADisabled, StyledCTAFushia } from 'components/StyledCTA';
const Page = () => {
return (
<>
<StyledCTA>Default CTA</StyledCTA>
<StyledCTADisabled>Disable CTA</StyledCTADisabled>
<StyledCTAFushia>Fuchsia CTA</StyledCTAFushia>
</>
)
};
Read more about this in the blog posts I created on the subject here and there.
There are many ways to do this. one simple way is to use the package called Styled-components-modifiers. documentation is simple and straightforward.
https://www.npmjs.com/package/styled-components-modifiers
Simple usage example:
import { applyStyleModifiers } from 'styled-components-modifiers';
export const TEXT_MODIFIERS = {
success: () => `
color: #118D4E;
`,
warning: () => `
color: #DBC72A;
`,
error: () => `
color: #DB2A30;
`,
};
export const Heading = styled.h2`
color: #28293d;
font-weight: 600;
${applyStyleModifiers(TEXT_MODIFIERS)};
`;
In the Component - import Heading and use modifier prop to select the variants.
<Heading modifiers='success'>
Hello Buddy!!
</Heading>
Styled components are usually used with Styled system that supports variants and other nice features that enhance Styled components. In the example below Button prop variant automatically is mapped to keys of variants object:
const buttonVariant = ({ theme }) =>
variant({
variants: {
header: {
backgroundColor: theme.colors.lightblue,
color: theme.colors.white,
active: theme.colors.blue,
},
white: {
backgroundColor: 'white',
color: theme.colors.lightblue,
active: theme.colors.blue,
},
},
})
const Button = styled.button`
${(props) => buttonVariant(props)}
`
Styled System Variants: https://styled-system.com/variants
Use the variant API to apply styles to a component based on a single prop. This can be a handy way to support slight stylistic variations in button or typography components.
Import the variant function and pass variant style objects in your component definition. When defining variants inline, you can use Styled System like syntax to pick up values from your theme.
// example Button with variants
import styled from 'styled-components'
import { variant } from 'styled-system'
const Button = styled('button')(
{
appearance: 'none',
fontFamily: 'inherit',
},
variant({
variants: {
primary: {
color: 'white',
bg: 'primary',
},
secondary: {
color: 'white',
bg: 'secondary',
},
}
})
)