styled-components: style based on non-styled class parent - reactjs

Let's say I have this:
const Child = styled.button`
background-color: grey;
`;
<div className="some-non-styled-class">
<Child>Hello World</Child>
</div>;
<div className="some-other-non-styled-class">
<Child>Hello World</Child>
</div>;
How can I style Child so it's "red" when under some-non-styled-class and "blue" when it's under some-other-non-styled-class?
I'm aware of the ability to refer to other styled-components, but I have a use case where I need to change styling based on an existing library with static class names.

You could try something like this then:
export const Child = () => {
const [parentClass, setParentClass] = useState("")
useEffect(() => {
const currentClass = document.querySelector(".childComp").parentElement.className
setParentClass(currentClass)
},[])
return (<MyStyledComponent className="childComp" parentClassName={parentClass}>
{children}
</MyStyledComponent>)
}

Related

React styled component not passing props from attribute

I'm trying (unsuccessfully) to pass a single color value from a parent to a child using styled-components.
If the color is passed correctly, the background should be the color that is passed. Else green.
No matter what I do, the background-color is green
What am I missing here?
The parent:
function App() {
return (
<div>
<Article color="red"/>
</div>
);
}
The child:
const MainContetnt = styled.div`
background-color: ${props => props.color ?? "green"};
flex: 1;
`;
const Article = (props) => {
return (
<MainContetnt>
Main content
</MainContetnt>
)
};
Props will not be passed automatically to a styled component, you still have to do it the usual way:
const Article = (props) => {
return (
<MainContetnt color="props.color">
Main content
</MainContetnt>
)
};

How to set state in useEffect hook using React and Typescript?

I want to change right property from 16px to 40px when user is in page "/items/:itemId" using React and Typescript.
Below is my component snippet,
const root = () => {
<PopupContextProvider>
<App/>
</PopupContextProvider>
}
export const PopupContextProvider = ({ children }: any) => {
return (
<popupContext.Provider value={context}>
{children}
{(condition1 || condition2) && (
<Popup onHide={dismiss} />
)}
</popupContext.Provider>
);
}
export function Popup({ onHide }: Props) {
return (
<Dialog>
<DialogBody>
<span>Title</span>
<Description/>
</DialogBody>
<Actions>
<span>Hide</span>
</Actions>
</Dialog>
);
}
const Dialog = styled.div`
position: fixed;
right: 16px;//want to change this to 40px if user is in page
"/items/:itemId"
display: flex;
flex-direction: column;
`;
What I have tried?
export function Popup({ onHide }: Props) {
const location = useLocation();
const [isView, setIsView] = React.useState(false);
if (location.pathname === '/items/:itemId') {
setIsView(true);
//Here, it doesn't change to true.
//How can I do the same in useEffect or something that updates
}
return (
<Dialog isView={isView}>
<DialogBody>
<span>Title</span>
<Description/>
</DialogBody>
<Actions>
<span>Hide</span>
</Actions>
</Dialog>
);
}
const Dialog = styled.div<isView?:boolean>`
position: fixed;
${({ isView }) => isView && 'right: 40px;'}
display: flex;
flex-direction: column;
`;
My above code doesn't update the position of the popup with right 40px even though user is in page "/items/:itemId".
I am not sure what is going wrong. Can someone help me with this? Thanks.
EDIT:
what i have tried based on one of the answer provided.
export function Popup({ onHide }: Props) {
const location = useLocation();
const [isView, setIsView] = React.useState(false);
React.useEffect(() => {
const match = matchPath(
location.pathname,
'/items/:itemId'
);
if (match) { //it doesnt get it into this condition since match is
//null
setIsScheduleView(true);
}
}, []);
return (
<Dialog isView={isView}>
<DialogBody>
<span>Title</span>
<Description/>
</DialogBody>
<Actions>
<span>Hide</span>
</Actions>
</Dialog>
);
}
You can use useEffect hook to implement the side effect that you want.
First, you can put "location" variable as one of the dependencies inside useEffect hook as below.
useEffect(() => {
// do your if condition
// set isView state as side effect
},[location]);
Secondly, I believe your condition is not correct.
location.pathname will not equal to '/items/:itemId' since :itemId is a dynamic parameter. So, you have an option to use includes method to check if it is your url or you can add a wrapper which will resolve to "/items" first , then to "/items/:itemId". When the route comes into the wrapper, you can do the styling.
EDIT: or better yet you can use matchPath api of react-router as #ludwiguer has mentioned also to match your path.
As explained by #Kevin Moe Myint Myat in his answer. Your condition is not correct as it contains a dynamic id in it. So you can check for /items using includes method and if this.props.match is available, then you can use it to check if param itemId is present or not. Like this.props.match.params.itemId, you can use this with previous condition with AND & operator in useEffect.
I imagine you are using react router, if so you can use matchPath https://reactrouter.com/web/api/matchPath
import { matchPath } from 'react-router';
setIsView(
!!matchPath(
this.props.location.pathname,
{path: 'items/:itemId'}
));

Passing custom props to each styled component through Provider

I would like to pass a custom prop (exactly: theme name as string) to each passed styled component through Provider, so it was available throughout the css definition.
ThemeProvider almost does it, but it expects object, not the string. I do not want to pass whole object with theme settings, just the name of my theme.
I do not want to use special theme prop or similar, because then I would have to it manually every single time I create new styled component. Provider seems like the best option if only it cooperated with string.
Is there any possibility to pass a string through Provider to Consumer builded in styled components?
EDIT:
[PARTIAL SOLUTION]
I found what I was looking for when I realized styled-components exports their inner context. That was it. Having access to pure react context gives you original Provider, without any 'only objects' restriction ('only objects' is a styled-components custom provider restriction).
Now I can push to each styled component exactly what I want and if I want.
import styled, { ThemeContext } from 'styled-components';
const StyledComponent = styled.div`
color: ${props => props.theme == 'dark' ? 'white' : 'black'};
`;
const Component = props => {
const theme = 'dark';
return (
<ThemeContext.Provider value={theme}>
<NextLevelComponent>
<StyledComponent />
</NextLevelComponent>
</ThemeContext.Provider>
);
};
Hope I have this correct, from what I've been able to glean. I haven't tried this out but it seems it might work for you. This is lifted directly from the reactjs.org docs regarding context. It passed the string name of the theme down.
const ThemeContext = React.createContext('green');
class App extends React.Component {
render() {
return (
<ThemeContext.Provider value="blue">
<SomeComponent />
</ThemeContext.Provider>
);
}
}
function SomeComponent(props) {
return (
<div>
<OtherComponent />
</div>
);
}
class OtherComponent extends React.Component {
static contextType = ThemeContext;
render() {
return <ThirdComponent theme={this.context} />
}
}
I hope this helps you understand the idea behind ThemeContext from styled-components. I've passed string "blue" to ThemeContext just to show, that it should not be object and you can use just string.
import React, { useContext } from "react";
import ReactDOM from "react-dom";
import styled, { ThemeContext } from "styled-components";
// Define styled button
const Button = styled.button`
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border-radius: 3px;
color: ${props => props.theme};
border: 2px solid ${props => props.theme};
`;
// Define the name of the theme / color (string)
const themeName = "blue";
const ThemedButton = () => {
// Get the name from context
const themeName = useContext(ThemeContext);
return <Button theme={themeName}>Themed color: {themeName}</Button>;
};
function App() {
return (
<div className="App">
<ThemeContext.Provider value={themeName}>
<ThemedButton />
</ThemeContext.Provider>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Demo: https://codesandbox.io/s/styled-components-example-with-themecontext-cso55

styled-components: styled custom component, with some props

I have a <Header /> component, which takes a size prop. I want to take a Header with a size prop, and additionally style it with styled-components.
Something like the following, but this obviously doesn't work.
const MyHeader = styled(<Header size="huge />)`
margin-top: 2rem;
`
Any ideas how to achieve that?
You can do:
const MyHeader = styled(Header)({ ... });
For example:
const MyHeader = styled(Header)`
color: red;
`
Or if you want:
const Temp = () => <Header size="huge" />;
const MyHeader = styled(Header)({ ... });

how to pass multiple classNames to inner children with emotion js

I want to split the components into baseUI one and styled one:
eg.
MyComponent.jsx
export default class MyComponent extends React.Component {
...
...
render() {
const { wrapperClassName, className, childClassName } = this.props;
return (
<div className={wrapperClassName>
<div className={className />
<div className={childClassName} />
</div>
)
}
}
StyledMyComponent.jsx
import styled from 'react-emotion'
const StyledMyComponent = styled(MyComponent)(
...
...
)
export default StyledMyComponent
however anything I put to the styled function's argument they will go to the className only, is there a way I specify which props goes to which className?
also can I do something like sass/less with children selector?
hypothetically something like this:
const classes = css`
color: red;
span { // this works
color: black;
}
.childClassName { // this doesn't work
color: green;
}
`
<MyComponent className={classes} />
No you can't.
What you can do, is create specific components for the underlying div. This is how I make my components:
const MyComponentStyle = styled('div')....;
const MySecondComponentStyle = styled('div')...;
const MyThirdStyle = styled('div')...;
const MyComponent = ({ wrapperClassName, childClassName, className }) =>
<MyComponentStyle className={wrapperClassName}>
<MySecondComponentStyle className={className} />
<MyThirdStyle className={childClassName} />
</MyComponentStyle>
)
}
}
Conditionally styling the element and its children based on class names
You can conditionally change the styling of stuff below the main component based on its classes.
Taking your example:
const Something = () => (
<MyComponent className={classes}>
<div className="childClassName">child</div>
<div className="otherChildClassName">child</div>
</MyComponent>
You can style the children like so:
const classes = css`
color: red;
span {
color: black;
}
& .childClassName {
color: green;
}
`
note the & character. It essentially means "this class". So & .childClassName means "childrens of this element with class childClassName.
You could also use &.someClassName (note the lack of space), which would mean: "this element when it also has a class named someClassName.

Resources