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>
Related
Hey I am trying to use hooks inside styled-components - createGlobalStyle.
I know that this is not possible, because createGlobalStyle is a function and neither a component nor a custom hook.
Maybe someone knows a workaround or a solution :).
export const GlobalStyle = createGlobalStyle`
p {
padding: ${p => useMediaQuery('min-width(768px)') && 12px};
}
`
One workaround can be to add a media query hook anywhere in your code, catch the screen size, send value in props (prop drilling, context Api, redux) to make your styled components adapt to your screen.
You may go through this link
You are not need to use hooks outside of functional components. Poof
Two ways, which i see, how you can do your task.
add media query directly in createGlobalStyle.
export const GlobalStyle = createGlobalStyle`
#media only screen and (min-width: 768px) {
p {
padding: 12px;
}
}
`
make separate function with some logic
const minWidthMedia = (minWidth: string) => {
const size = getScreenSize()
const isValid = minWidth > size.width
if (!isValid) return
return css`
p {
padding: 12px;
}
`
// or
// without validation logic
// return css`
// #media only screen and (min-width: ${minWidth}px) {
// p {
// padding: 12px;
// }
// }
}
export const GlobalStyle = createGlobalStyle`
${minWidthMedia('768px')}
`
I have created a Dropdown Component in React using Styled Components. Here is a simplified outline of the component:
const Dropdown = (
<DropdownBase>
<Trigger>
{title}
</Trigger>
<Submenu>
{children}
</Submenu>
</DropdownBase>
)
const DropdownBase = styled.div`
/* Default Styles */
`
const Trigger = styled(Link)`
/* Default Styles */
`
const Submenu = styled.div`
/* Default Styles */
`
Now, when I import and use the component I want to be able to override the default styles of the nested components (i.e., DropdownBase, Trigger and Submenu). And I want to be able to override those default styles using Styled Components. The problem is, that I do not import those nested components -- I only import the Dropdown component -- like this:
import { Dropdown } from '../path/to/dropdown'
<Dropdown />
So I am wondering, how can I override those nested components when I import the parent component using Styled Components?
The best way to do this would be to export DropdownBase, Trigger, and Submenu from your Dropdown component, then import them along with Dropdown and override this like this:
import { Dropdown, DropdownBase, Trigger, Submenu } from '../path/to/dropdown'
import styled from 'styled-components'
const MyComponent = () => {
return <StyledDropdown />
}
const StyledDropdown = styled(Dropdown)`
${DropdownBase} {
// custom styles
}
${Trigger} {
// custom styles
}
${Submenu} {
// custom styles
}
`
This works well because it targets the specific child styled components.
Alternatively, you could target them based on their tag or child order, but this may fail if you make updates to the Dropdown component.
How about this:
const Dropdown = (
<DropdownBase className={dropdownBaseClassName}>
<Trigger className={triggerClassName}>
{title}
</Trigger>
<Submenu className={submenuClassName}>
{children}
</Submenu>
</DropdownBase>
)
import { Dropdown } from '../path/to/dropdown'
<StyledDropdown />
const StyledDropdown = styled(Dropdown).attrs({ dropdownBaseClassName:..., triggerClassName:..., submenuClassName:... })`
.${dropdownBaseClassName} {
// styles
}
.${triggerClassName} {
// styles
}
.${submenuClassName} {
// styles
}
Extending the answer by #Appel21, I would do something like this using dot notation:
import styled from 'styled-components'
export const Dropdown = () => (
<DropdownBase>
<Trigger>
{title}
</Trigger>
<Submenu>
{children}
</Submenu>
</DropdownBase>
)
const DropdownBase = styled.div`
/* Default Styles */
`
const Trigger = styled(Link)`
/* Default Styles */
`
const Submenu = styled.div`
/* Default Styles */
`
Dropdown.base = DropdownBase;
Dropdown.trigger = Trigger;
Dropdown.subMenu = Submenu;
And then to use it:
import { Dropdown } from '../path/to/dropdown'
import styled from 'styled-components'
const MyComponent = () => {
return <StyledDropdown />
}
const StyledDropdown = styled(Dropdown)`
${Dropdown.base} {
// custom styles
}
${Dropdown.trigger} {
// custom styles
}
${Dropdown.submenu} {
// custom styles
}
`
This way you have to export only one component and you get autocompletion of the subcomponents, knowing perfectly what subcomponents you can style :)
It looks like themes are what you want.
import { render } from "react-dom"
import { ThemeProvider } from "styled-components"
const Dropdown = (
<DropdownBase>
<Trigger>
{title}
</Trigger>
<Submenu>
{children}
</Submenu>
</DropdownBase>
)
const defaultTheme = {color:'black'}
const specificTheme = {color:'red'}
const DropdownBase = styled.div`
/* Default Styles */
color:${props=>props.theme.color};
`
const Trigger = styled(Link)`
/* Default Styles */
color:${props=>props.theme.color};
`
const Submenu = styled.div`
/* Default Styles */
color:${props=>props.theme.color};
`
render(<ThemeProvider theme={defaultTheme}>
<div>
<Dropdown>Your default dropdown</Dropdown>
<div>
Your hierarchy
<ThemeProvider theme={specificTheme}>
<Dropdown>Your custom dropdown</Dropdown>
</ThemeProvider>
</div>
</div>
</ThemeProvider>)
What I would like to do is to compose multiple styled components into one.
With plain css this is very easy:
<button class="btn large red"></button>
This is my attempt with emotion in React:
import styled from "#emotion/styled/macro";
const A = styled.button({
color: "red"
});
const B = styled.button({
fontSize: 32
});
// I know how to add single styled component. But how to also add B here?
const C = styled(A)({
// some additional styles
});
function App() {
return (
<div className="App">
<A>A</A>
<B>B</B>
<C>C</C>
</div>
);
}
Please check the demo:
Demo
It seems like styled is not capable of combining multiple styled components by default.
You might want to look at the css functionality of emotion here. This allows the composition of multiple defined css styles. Even though this required more lines of code for the extra definition of the css objects.
Using css your example could look like this:
import styled from "#emotion/styled/macro";
import { css } from "#emotion/core";
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const red = css({
color: "red"
});
const A = styled.button(red);
const bolt = css({
fontSize: 32
});
const B = styled.button(bolt);
const C = styled.button(red, bolt);
function App() {
return (
<div className="App">
<A>A</A>
<B>B</B>
<C>C</C>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Demo
a bit new to react.
I used the create react app https://github.com/facebook/create-react-app
to start a new react project.
the full code is here. https://github.com/bryandellinger/reactswitch/tree/master/src
I am trying to get the background color of a selected element to change and the text to become bold but it appears the class is never added not sure what I am doing wrong.
Switch.js
import React, { PropTypes } from 'react';
import styles from './Switch.css';
const CREDITCARD = 'Creditcard';
const BTC = 'Bitcoin';
const Choice = function (props) {
const cssClasses = [];
if (props.active) {
// <-- check props, not state
cssClasses.push(styles.active);
}
return (
<div
onClick={props.onClick}
className={cssClasses}
>
{props.label} {/* <-- allow any label */}
</div>
);
};
class Switch extends React.Component {
state = {
payMethod: BTC,
};
select = (choice) => {
return (evt) => {
this.setState({
payMethod: choice,
});
};
};
render() {
return (
<div className='switch'>
<Choice
onClick={this.select(CREDITCARD)}
active={this.state.payMethod === CREDITCARD}
label='Pay with Creditcard'
/>
<Choice
onClick={this.select(BTC)}
active={this.state.payMethod === BTC}
label='Pay with Bitcoin'
/>
Paying with: {this.state.payMethod}
</div>
);
}
}
export default Switch;
and Switch.css
.active {
background-color: #4619eb;
font-weight: bold;
}
it appears the active class from switch.css never gets added on the onclick event. not sure what I am missing.
Because of the way webpack is configured in CRA, you need to write your css like this:
:local(.active) {
background-color: #4619eb;
font-weight: bold;
}
CRA only supports importing the whole CSS file directly out of the box. So instead of importing the CSS file as a component, you would do:
import './Switch.css';
CRA docs for adding a stylesheet: https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#adding-a-stylesheet
Also, the className property should be a string with class names separated with a while space. If you want to set the class name dynamically, check out classnames: https://github.com/JedWatson/classnames.
I have a ref on a component I am converting over to a styled component in my app. The ref is used to access the offsetHeight and scrollHeight properties on the raw html element of the component. Once I switched this component to a styled component, the ref now points to the styled component instead of the raw html element, and I'm unsure how to reference the base element. Can this be done?
example:
const TextArea = styled.textarea`
display: block;
margin: 0 0 0 18%;
padding: 4px 6px;
width: 64%;
font-size: 1rem;
color: #111;`;
export default class Input extends Component {
componentDidMount() {
const height = this.textInput.scrollHeight;
// do something....
}
render() {
return (
<div>
<TextArea
ref={(input) => this.textInput = input}
></TextArea>
</div>
);
}
}
Passing ref to a styled component will give you a ref to the styled-components wrapper, not the DOM node. To get a ref to actual DOM node pass the innerRef prop. (see the docs)
This is what you need to do:
const TextArea = styled.textarea``;
export default class Input extends Component {
componentDidMount() {
const height = this.textInput.scrollHeight;
// do something....
}
render() {
return (
<div>
<TextArea
innerRef={(input) => this.textInput = input}
></TextArea>
</div>
);
}
}
Yes it can be done. You can access raw html using ReactDOM.findDOMNode(). However, bear in mind that the use of this method is discouraged. You can read more about this in the referenced page.