Which is a better to write a react component with styled-components? - reactjs

Given some buttons to be shared across a website which one of these two methods is best to use:
const ButtonA = styled.button`
color: 'red'
`
const ButtonB = styled.button`
color: 'blue'
`
to be used like this
const Home = () => {
return <ButtonA>Button A</ButtonA><ButtonB>Button B</ButtonB>;
}
or
const Button = styled.button`
${({variant }) => variant === 'A' && `
color: red;
`
${({variant }) => variant === 'B' && `
color: blue;
`
`
to be used like this:
const Home = () => {
return <Button variant="A">Button A</Button><Button variant="B">Button B</Button>;
}
Please give reasons, advantages and disadvantages for using one method over the other.

As always depends on the case.
It's usually better to pass variant prop as in your second example.
The problem with the first solution is that you have to repeat the same code. Better to do it like this:
const Button = styled.button`
// basic styles here
`
const ButtonWithDifferentStyles = styled(Button)`
// additional styles here
`

Related

How to avoid long conditional rendering with styled components?

I'm working on a project using a lot of different variables. Depending on props, they will render a certain color (for example). The problem is that with the amount of variables my code ends up really long. Is there a solution to clean up this code?
This is an example:
Variables:
const Variables = {
Colors: {
Primary50: "rgb(244, 246, 247)",
Primary100: "rgb(209, 218, 225)",
...rest of colors
},
...rest of variables
}
And the styled component:
const Component = styled.div<{ $color: number }>`
background-color: ${({ $color }) =>
$color === 50
? Variables.Colors.Primary50
: $color === 100
? Variables.Colors.Primary100
: ...rest};
`
I tried something like this, but it is not working:
const Component = styled.div<{ $number: number }>`
background-color: ${Variables.Colors.Primary[$number]};
`
Thanks for your answers!
Better use switch instead of ternary operator for your instance. The second aproach Primary[$number] is wrong, it look like Variables.Colors.Primary.100 you need to enclose Primary50 entirely in square brackets.
const Variables = {
Colors: {
Primary50: 'rgb(244, 246, 247)',
Primary100: 'rgb(209, 218, 225)',
},
};
const Component = styled.div<{ $color: number }>`
background-color: ${props => {
switch (props.$color) {
case 50:
return Variables.Colors.Primary50;
case 100:
return Variables.Colors.Primary100;
default:
return 'white';
}
}};
`;
const Component2 = styled.div<{ $number: 50 | 100 }>`
background-color: ${props => Variables.Colors[`Primary${props.$number}`]};
`;
function App() {
return (
<div className="App">
<Component $color={100}>First example</Component>
<Component2 $number={50}>Second example</Component2>
</div>
);
}
export default App;

The component styled.p with the id of "sc-iseJRi" has been created dynamically

Good evening everyone, I need some help.
I can't solve a warning:
Keyframes.js:20 The component styled.p with the id of "sc-iseJRi" has
been created dynamically. You may see this warning because you've
called styled inside another component. To resolve this only create
new StyledComponents outside of any render method and function
component.
In this link ( https://pastebin.com/a0kMztfD ) is an example of how I use the styled-component.
In a checkboxes file I have all the functions I use for the styled-component rules, which I then call in the App.js file to assign them to a const variable to use in the return()
How could I solve this problem? It doesn't create any errors for me but of course it creates a thousand warnings.
I also put the code in addition to the link put previously:
In cards.js:
export function getCard(Card) {
let fillMode = (Card.style === null) ? 'both' : Card.style.fillMode
let duration = (Card.style === null) ? '1s' : Card.style.duration
const tmp = keyframes`
from,to {
width: ${Card.width};
height: ${Card.height};
background-color: ${Card.colorCard};
background: linear-gradient(${Card.colorCard2}, ${Card.colorCard});
box-shadow: 0 16px 16px -8px rgba(0,0,0,0.4);
border-radius: 6px;
overflow: hidden;
position: relative;
margin: ${Card.marginCard};
}
`;
const CardFinal = styled.div`
animation: ${duration} ${tmp} ${fillMode};
`;
return CardFinal
}
In App.js:
Const CardContainer = getCard(card1)
return (
<CardContainer></CardContainer>
);
The problem is that you're creating a styled.div inside your getCard function.
The way you get rid of this warning is to move the creation of CardFinal outside of getCard and use getCard function to return whatever css you want to generate and pass them as props later on. Here's how you can pass props with styled-components.
This is how it would look like for your code
const CardFinal = styled.div`
${getAnimation}
`;
export function getCardProps(Card) {
const fillMode = Card.style === null ? "both" : Card.style.fillMode;
const duration = Card.style === null ? "1s" : Card.style.duration;
const tmp = keyframes`
from,to {
width: ${Card.width};
height: ${Card.height};
background-color: ${Card.colorCard};
background: linear-gradient(${Card.colorCard2}, ${Card.colorCard});
box-shadow: 0 16px 16px -8px rgba(0,0,0,0.4);
border-radius: 6px;
overflow: hidden;
position: relative;
margin: ${Card.marginCard};
}
`;
return { fillMode, duration, tmp };
}
const getAnimation = ({ duration, tmp, fillMode }) => {
return css`
animation: ${duration} ${tmp} ${fillMode};
`;
};
Now you'll just use the getCardProps function to the props that CardFinal expects from the getCardProps.
export default function App() {
const cardProps = getCardProps({
style: null,
weight: "100px",
height: "100px",
colorCard: "grey",
marginCard: "10px"
});
return (
<CardFinal {...cardProps}>
YO
</CardFinal>
);
}
Here's a codesandbox link of where you can try & play around to see how it works.
You can also try to un-comment a // const WarningDiv, that basically replicates the warnings you've been encountering with just a basic function that returns an empty styled.div

My React component renders before the props data is available using hooks

So I'm creating a simple tile matching app.
I have a tile component that is fed props from a game board component that houses the tiles and that itself is within the app obviously that has the main logic and there state data.
I need each Tile to have its own logic to know if it should be flipped or not.
The problem I have encountered is that when I create a state data for which tiles are flipped and/or should stay flipped the tiles are running their component level logic with previous render data.
This means I match two tiles, these are added to relevant state - selectedTiles and matchedTiles which is passed to the all the tiles. The tiles check if there are two values in selectedTiles and if so to then check matchedTiles to see if that value matches a component level prop.
however the logic to check matchedTiles, which is triggered by selectedTiles changing, runs before the matchedTiles prop reaches the component.
I understand that this is because of the snapshot of the state but I'm unsure how to actually solve the problem.
apologies for any misunderstanding or explaining as this is my first fully self written react app.
I have linked the repository on git below.
https://github.com/Samuel-Programmer/React-Tiles
I pulled you repo and changed the code a little bit.
In my opinion, it is better to hold 2 separate variables with useState, one for the opened tile and one for the 2nd 'guess'.
I also added a setTimeout to hide both open tiles after 1 second, in case they do not match.
I added an 'awaitingFlip' variable to prevent the user from flipping more tiles while the 2 last tiles have not been flipped back on a wrong guess.
Passing all the information in each tile is bad coding. The app gets slower, since react has to calculate more stuff. I added a prop 'isFlipped' to each tile, which is true in case two tiles have been matched or a tile has been checked as a first or second guess.
I also added a 'tileId' property to each tile, because I do not like working with indexes as ids.
I changed the unsplash search from 'south africa' to 'patagonia', 'cause I was getting a duplicate image which was frustrating. I advise you to hide your unsplash api key from the repo and generate a new one, as people will be able to use it.
You should be able to do the rest of the work (like 'play again', 'reshuffle', etc...).
I hope I helped. If you need any more help, leave a comment.
Here's the code (the files I do not include are the same):
App.js:
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import unsplash from "../api/unsplash";
import Header from "./Header";
import GameBoard from "./GameBoard";
function App() {
const [searchTerm, setSearchTerm] = useState("patagonia");
const [images, setImages] = useState([]);
const [randomisedImages, setRandomisedImages] = useState([]);
useEffect(() => {
randomiseImages(images);
}, [images]);
useEffect(() => {
getImages();
}, [searchTerm]);
const generateTileId = () => {
return 'tile_id_' + Math.random().toString().substr(2, 8);
}
const getImages = async () => {
const response = await unsplash.get("/search/photos", {
params: { query: searchTerm },
});
setImages(response.data.results);
};
const randomiseImages = (images) => {
let randomizedImages = [...images, ...images];
var m = images.length,
t,
i;
while (m) {
i = Math.floor(Math.random() * m--);
t = randomizedImages[m];
randomizedImages[m] = randomizedImages[i];
randomizedImages[i] = t;
}
let finalArray = [];
for (let x of randomizedImages) {
finalArray.push({
...x,
tileId: generateTileId()
})
}
setRandomisedImages([...finalArray]);
};
return (
<div>
<Container>
<Header />
<Main>
<GameBoard images={randomisedImages} />
</Main>
</Container>
</div>
);
}
export default App;
const Container = styled.div`
width: 100%;
height: 100vh;
display: grid;
grid-template-rows: 7rem;
`;
const Main = styled.div`
display: grid;
grid-template-columns: auto;
`;
GameBoard.js:
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import Tile from "./Tile";
function GameBoard({ images }) {
const [selectedTile, setSelectedTile] = useState(null);
const [secondTile, setSecondTile] = useState(null);
const [matchedTiles, setMatchedTiles] = useState([]);
const [awaitingFlip, setAwaitingFlip] = useState(false);
useEffect(() => {
if (matchedTiles.length > 0 && matchedTiles.length === images.length / 2) {
alert('YOU WON!!! :)');
}
}, [matchedTiles])
const onTileClick = (tileId, id) => {
if (!matchedTiles.includes(id) && tileId !== selectedTile && !awaitingFlip) {
let subjectId = images.find(x => x.tileId === selectedTile)?.id;
if (!selectedTile) {
setSelectedTile(tileId);
} else {
if (id === subjectId) {
setMatchedTiles([...matchedTiles, id]);
setSelectedTile(null);
} else {
setSecondTile(tileId);
setAwaitingFlip(true);
setTimeout(() => {
setSecondTile(null);
setSelectedTile(null);
setAwaitingFlip(false);
}, 1000);
}
}
}
}
return (
<TileContainer>
{images.length > 1 &&
images.map(({ description, urls, id, tileId }, index) => (
<Tile
key={index}
id={id}
tileId={tileId}
alt={description}
src={urls.regular}
onTileClick={onTileClick}
isFlipped={selectedTile === tileId || secondTile === tileId || matchedTiles.includes(id)}
/>
))}
</TileContainer>
);
}
export default GameBoard;
const TileContainer = styled.div`
background: black;
padding: 7rem;
display: flex;
justify-content: space-evenly;
align-items: center;
flex-wrap: wrap;
`;
Tile.js:
import React, { useState, useEffect, useLayoutEffect } from "react";
import tile from "./tile.jpeg";
import styled from "styled-components";
function Tile({ id, alt, src, onTileClick, tileId, isFlipped }) {
return (
<Imgcontainer onClick={() => onTileClick(tileId, id)}>
<img alt={alt} src={isFlipped ? src : tile} id={id} />
</Imgcontainer>
);
}
export default Tile;
const Imgcontainer = styled.div`
height: 10rem;
width: 10rem;
margin: 1rem;
border: 2px solid white;
border-radius: 10%;
cursor: pointer;
img {
width: 100%;
height: 100%;
border-radius: 10%;
}
img:hover {
transform: scale(1.1);
box-shadow: 0 8px 15px #dcfffe;
border: 2px solid white;
border-radius: 10%;
}
`;

Styled Components - content passed through props (content)

I am trying to toggle text inside a div (styled component) depending on the props value. The idea is simple: state changes, its value goes to props of the styled component, and then depending what it is, the content adjusts. What is not working is the content in before / after. Any idea why?
STYLED COMPONENT:
export const Info = styled.div`
padding: 5px;
margin-top: 15px;
&::after {
content: ${props => props.content === "intro" && "hello"};
}
`
JS
const CheckNumber = () => {
const [msg, setMsg] = useState("intro")
return (
<Info content={msg}/>
)
}
export default CheckNumber
Your current code generates style
content: hello
that is invalid CSS. You need extra quotes(one pair for JS level and nested quotes for CSS):
&::after {
content: ${props => props.content === "intro" && "'hello'"};
}
PS found that really accidentally by duplicating content like:
&::after {
content: "aaaa";
content: ${props => props.content === "intro" && "hello"};
}
because browser just did not create "::after" prseudoelement while content is invalid - so we were unable to check what styles were actually generated.

How to properly extend a styled component using TypeScript

As per styled-components v4, .extend is deprecated, the correct way to extend, or compose components is:
const ButtonA = styled('button')`color: ${props => props.color};`
const ButtonB = styled(ButtonA)`background: 'white';`
However I can't find the correct way to do this with TS, as I get some errors, for example:
import styled from "styled-components";
// Let's create ButtonA
type ButtonAProps = { a: string };
const ButtonA = styled<ButtonAProps, "button">("button")`
color: ${props => props.a};
`;
// So, here is what I've tried
// Fail #1
// =======
type ButtonBProps = { b: string };
const ButtonB = styled<ButtonBProps, ButtonAProps>(ButtonA)`
background: ${props => props.b};
`; // Here I get autocompletion only for B props :(
const Test = () => <ButtonB a="something" />; // And here I get autocompletion only for A props :(
// Fail #2
// =======
type ButtonBProps = { b: string } & ButtonAProps;
const ButtonB = styled<ButtonBProps, ButtonAProps>(ButtonA)`
background: ${props => props.b};
`; // Here I get autocompletion for A & B props, good!
const Test = () => <ButtonB a="something" />; // Here I still get autocompletion only for A props :(
// Fail #3
// =======
type ButtonBProps = { b: string } & ButtonAProps;
const ButtonB = styled<ButtonBProps, ButtonBProps>(ButtonA)` // Property 'b' is missing in type 'ButtonAProps', of course
background: ${props => props.b};
`; // Here I get "props has implicitly any type"
const Test = () => <ButtonB />; // Here I don't get any type checking at all
Seems to be almost there, but can't figure it out.
Any advices? Thank you!
As of September 2019, the following code also works:
// First extend ButtonAProps with an additional prop
interface ButtonBProps extends ButtonAProps {
b:string;
}
// ButtonB Component
const ButtonB = styled(ButtonA)<ButtonBProps>`
background: ${props => props.b};
`;
This seems to work for me:
type ButtonBProps = { b: string };
const ButtonB = styled<ButtonAProps>(ButtonA)<ButtonBProps>`
background: ${props => props.b};
`;
const Test = () => <ButtonB a="something" b="somethingelse" />;
The #types/styled-components declarations are hard to understand (with unhelpful type parameter names P, T, O, U) and apparently undocumented, so I can't be sure this is the intended approach. I found a related issue, but it doesn't seem to confirm this approach.
As of October 2021, #Zalaboza's answer in the comments helped me most and is the simplest implementation.
Styled-components is smart enough to infer parent type:
styled(ButtonA)<{b: string}>

Resources