How to dynamically update styled-components in React? - javascript

So on a project I'm working on I want to be able to update the theme of my website when a user changes the currentTheme variable which is set up using useState, and references some objects I have in another file with predefined styles. I am having trouble updating and setting these styles up using styled-components.
I currently have it so whenever the user changes the theme(which is done in another component) it will update the current chosen theme. As you can see there's a console.log there, and the background colour does update accordingly, it just doesn't work with the styled-components. I would like to state that I have tried moving the styled.div into my CodeContainer component and it worked there, but the performance of the app was HORRIBLE when I made that change.
I basically need to know how to update a variable in the styled.div. Should I pass through props instead? Should I move it in another way? Not too sure what to do here. Thanks in advance for any help!
import React, { useState } from "react";
import Editor from "./Editor";
import { monokai, solarizedDark, terminal, textmate } from "./ui/themes";
import styled from "styled-components";
const CodeContainer = ({ setHtml, setCss, setJs, setRenderDoc, theme }) => {
let currentTheme = monokai;
if (theme === "textmate") {
currentTheme = textmate;
} else if (theme === "solarized_dark") {
currentTheme = solarizedDark;
} else if (theme === "terminal") {
currentTheme = terminal;
}
return (
<Container background={currentTheme}>
<Editor
setHtml={setHtml}
setRenderDoc={setRenderDoc}
languageName="html"
theme={theme}
/>
<Editor
setCss={setCss}
setRenderDoc={setRenderDoc}
languageName="css"
theme={theme}
/>
<Editor
setJs={setJs}
setRenderDoc={setRenderDoc}
languageName="javascript"
theme={theme}
/>
</Container>
);
};
export default CodeContainer;
const Container = styled.div`
background: ${(currentTheme) => currentTheme.background};
height: 40vh;
border-bottom: 4px #211e1c solid;
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 15px;
`;

Use the following syntax. Docs.
const Container = styled.div((props) => ({
background: props.background,
height: "40vh",
borderBottom: "4px #211e1c solid",
//..
}));
Note: Make sure to use the camelCase syntax with CSS properties.

Related

passing default theme in material UI5

can somebody explain how pass in the defauklt theme in Material UI5
in Material UI6 i use to do it like this
const useStyles = makeStyles((theme) => ({
home: {
display: "flex",
paddingTop: "8rem",
width: "100vw",
height: "100vh",
backgroundColor: theme.palette.primary.dark,
color: "white",
},
}));
but as i got throught M-UI5 docs (as far as i found) there is no explanation on how it can be done , the only part they mention about makeStyle it contains this code in this page docs
+import { makeStyles } from '#mui/styles';
+import { createTheme, ThemeProvider } from '#mui/material/styles';
+const theme = createTheme();
const useStyles = makeStyles((theme) => ({
background: theme.palette.primary.main,
}));
function Component() {
const classes = useStyles();
return <div className={classes.root} />
}
// In the root of your app
function App(props) {
- return <Component />;
+ return <ThemeProvider theme={theme}><Component {...props} /></ThemeProvider>;
}
so am i suppose to run createTheme() on every component to get the theme? , apology if i missed out an obvious thing in the docs , probably coz my poor english
The part you are missing is from this part of the migration guide: https://mui.com/material-ui/guides/migration-v4/#style-library.
if you are using JSS style overrides for your components (for example overrides created by makeStyles), you will need to take care of the CSS injection order. To do so, you need to have the StyledEngineProvider with the injectFirst option at the top of your component tree.
Without this, the default styles for the MUI Card in your About component win over the styles specified via classes.about where the styles overlap (e.g. background).
Changing your AllProviders component to the following (just adding <StyledEngineProvider injectFirst>) fixes it:
import React from "react";
import CountriesProvider from "./countries-context";
import QuestionsProvider from "./questions-context";
import {
ThemeProvider,
createTheme,
StyledEngineProvider
} from "#mui/material/styles";
const theme = createTheme();
const AllProviders = (props) => {
return (
<StyledEngineProvider injectFirst>
<QuestionsProvider>
<ThemeProvider theme={theme}>
<CountriesProvider>{props.children}</CountriesProvider>
</ThemeProvider>
</QuestionsProvider>
</StyledEngineProvider>
);
};
export default AllProviders;
https://codesandbox.io/s/funny-flower-w9dzen?file=/src/store/AllProviders.js:303-342
The theme was being passed in fine without this change (otherwise you would have had errors when it tried to access parts of the theme), but the CSS injection order was not correct.

How to switch between global CSS onclick in Gatsby?

I'm building a portfolio site in Gatsby, and as I work more on the illustration and design side, would like to serve up multiple design options to showcase that work, based on a user selection via click. I've found some similar questions, but none that are specific to Gatsby.
I'm using styled components to create the layout, so It seems to me that just swapping between multiple Global stylesheets (with fonts, colors, etc) via a button would be the way to go, so the layout remains intact, but I'm honestly not sure how to go about this as I'm pretty beginner in Gatsby and Javascript in general.
Thanks in advance!
Given a simply <Layout> structure like:
import React from "react"
import { createGlobalStyle } from "styled-components"
const GlobalStyle = createGlobalStyle`
body {
color: ${props => (props.theme === "purple" ? "purple" : "white")};
}
`
export default function Layout({ children }) {
return (
<React.Fragment>
<GlobalStyle theme="purple" />
{children}
</React.Fragment>
)
}
You can easily attach an onClick event to the children by adding a boolean operator to switch your <GlobalStyle>, for example:
export default function somePage(){
const [defaultStyles, setDefaultStyles]=useState(false);
const handleChange=()=>setDefaultStyles(!defaultStyles)
return <Layout defaultStyles={defaultStyles}>
<h1 onClick={handleChange}> Click me to toggle the global styles</h1>
</Layout>
}
Basically, you are creating a boolean state (initially set as false) to change between default styles. The result of that state (toggled by clicking the <h1>) will be sent upwards to your <Layout> so, there:
import React from "react"
import { createGlobalStyle } from "styled-components"
const GlobalStyle = createGlobalStyle`
body {
color: ${props => (props.theme === "purple" ? "purple" : "white")};
}
`
export default function Layout({ children, defaultStyles }) {
return (
<React.Fragment>
{defaultStyles && <GlobalStyle theme="purple" />}
{children}
</React.Fragment>
)
}
The && operator will only render <GlobalStyle> if defaultStyles is true. Adapt it to your needs or add a ternary condition if needed.

Image not displaying inside List, React

I am creating a simple React component that displays images in a list/array.
The source is shown below:
import React from "react";
import styled from "styled-components";
import burgerTop from "../../assets/images/burgerTop.png";
import burgerBottom from "../../assets/images/burgerBottom.png";
const BurgerComponent = props => {
console.log(JSON.stringify(props));
const BurgerDiv = styled.div`
width: 500px;
margin: 0 auto;
text-align: center;
display: flex;
flex-direction: column;
`;
return (
<BurgerDiv>
{props.burger.map(burgerItem => {
console.log(burgerItem);
const imgSrc = burgerItem.img;
console.log(imgSrc);
return <img key={burgerItem.id} src={imgSrc} />;
})}
</BurgerDiv>
);
};
export default BurgerComponent;
Props are shown as below
{
"burger":[
{
"type":"top",
"img":"burgerTop",
"value":1,
"id":"sss2",
"addable":false
},
{
"type":"bottom",
"img":"burgerBottom",
"value":1,
"id":"aaa7",
"addable":false
}
]
}
But the image path is not taken from import.
Not sure what I am doing wrong.
Please help.
img's source property should be a URL, or base64 string. Your burger prop doesn't have a source property like that.
I noticed that you are importing burgerTop and burgerBottom from assets folder. Maybe you can use those images like this:
{props.burger.map(burgerItem => {
const imgSrc = burgerItem.img === "burgerTop" ? burgerTop : burgerBottom ;
return <img key={burgerItem.id} src={imgSrc} />;
})}

TypeScript + React + Emotion: typesafe ref forwarding when using `as`

I am using emotion and typescript. I have a component which (roughly) looks as follows
import styled from '#emotion/styled';
export default Box = styled.div`
display: flex;
align-items: center;
justify-content: center;
`;
In another component, I am using that div but using the as prop to make it into a button.
However, when I forward the ref, I want to appropriately type it to be the type of whatever that element is. i.e.,
import React, { forwardRef } from 'react';
import Box from '../Box';
const Button = forwardRef((props, ref) => {
return <Box {...props} as="button" ref={ref} />;
});
If I took this one step further, ideally as could be anything that is accepted by styled.div() and know what the type of ref would be.
However, I can't find the appropriate way to type ref in a way that matches as expected.
How would this be done?
You can try something like:
interface ButtonProps = {
props: {}
}
const Button = forwardRef(({ ...props }: ButtonProps, ref?: React.Ref<HTMLButtonElement & HTMLAnchorElement>) => {
return <Box {...props} as="button" ref={ref} />;
});

Combine multiple styled elements with styled-components

I know that we can extend or add styling to existing components with styled-components like the Link component of react-router-dom. The said feature is indicated here. But, my problem is, how can I combine two or more existing components then add some more styles?
In my case, I have a reusable component for text elements like span, p and a with standard font-size, font-weight, etc. At the same time, I want to use the react-router-dom's Link component. Currently, I have something like this:
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import { TextElement } from '../common';
/*
Can I do something like this?
const MyLink = styled(Link, TextElement)`
margin: 10px 0;
`;
or this?
const MyLink = styled([Link, TextElement])`
margin: 10px 0;
`;
*/
const MyPage = props => (
<>
<MyLink to="/next-page" />
</>
);
Any suggestion would be appreciated.
EDIT
My TextElement component is just something like this:
const Element = styled.span`
font-size: 13px;
font-weight: 500;
`;
// These styles are sample only. Actual styles depend on a "variant" prop.
// I did not show it here since it doesn't have to do with the question I'm asking.
export default ({ tag }) => (<Element as={tag} />);
You can use mixin for this using css helper.
Let's say you put the code for TextElement in a spaced mixin like:
import { css } from 'styled-components';
const spaced = css`
margin: 10px 0;
`;
And another mixin for Link
const styledLink = css`
color: blue;
`;
Then you can define your MyLink as:
import styled from 'styled-components'
const MyLink = styled(Link)`
${spaced}
${styledLink}
`;
The style component could be the wrapper of your custom component. For example:
Your style component:
export const CustomSpanWrapper = styled.div`
span {
...your-styles
}
`;
Your other component:
<CustomSpanWrapper>
<CustomSpan>
</CustomSpanWrapper>

Categories