I am writing a UI Framework with Emotion and I wanted to have a default theme without forcing the users to provide a theme with ThemeProvider.
i.e.:
// ui-framework/Button.js
import styled from 'react-emotion'
const ButtonRoot = styled.button((props) => ({
// This won't work, because theme would be an empty object without a default theme in ThemeProvider
backgroundColor: props.theme.palette.primary.main,
borderColor: props.theme.palette.primary.light,
}))
const Button = (props) => (
<ButtonRoot>
{props.label}
</ButtonRoot>
)
export default Button
So in our code, users can use my Button component as a standalone component, without having to inject the theme with a theme provider.
// UserCode.js
import Button from 'ui-framework/Button'
const MyComponent = () => (
<div>
…
<Button label="This is awesome button, it works without a ThemeProvider!"/>
</div>
);
In create-emotion-styled, we have this line. It is the line that provides a theme to the styled function. Priority goes as follow:
{}So my solution to this is... instead of falling back to an empty object, why not give it the possibility of falling back to a defaultTheme? We could simply change the function signature of createEmotionStyled to:
function createEmotionStyled(emotion: Emotion, view: ReactType, defaultTheme?: Object = {}) {
…
theme = (state !== null && state.theme) || props.theme || defaultTheme // fallback to defaultTheme, last
…
}
Then, to use it you just create your own instance of styled with your defaultTheme.
Great idea @lucasterra! Will get started working on this :+1:
Probably makes sense to add it to emotion@10 instead of 9.
You can already do this today in userland by importing the theme you'd use as default or setting default values manually (which as far as I can tell is the pretty much the same thing you'd have to do if createStyled accepted a default theme arg)
// ui-framework/Button.js
import styled from 'react-emotion';
import defaultTheme from 'our-theme';
const ButtonRoot = styled.button(({ theme = defaultTheme, ...props }) => ({
backgroundColor: theme.palette.primary.main,
borderColor: theme.palette.primary.light,
}))
const Button = (props) => (
<ButtonRoot>
{props.label}
</ButtonRoot>
)
export default Button
In v10 yes, this will work. But in v9 it would return an empty object, instead of null/undefined. :)
ah, I thought you were familiar with the concepts so I just used your example code to illustrate the point. You can do the same thing in v9 because default args are a JS feature. Here's a working example broken out into it's pieces (and a codesandbox)
import React from "react";
import ReactDOM from "react-dom";
import styled from "react-emotion";
import { ThemeProvider } from "emotion-theming";
const myalttheme = { palette: { primary: { main: "red" } } };
const mytheme = { palette: { primary: { main: "blue" } } };
const p = mytheme.palette;
function withBG({ theme: { palette = p }, theme, ...props }) {
return palette.primary.main;
}
const Button = styled("div")`
background-color: ${withBG};
`;
function App() {
return (
<ThemeProvider theme={myalttheme}>
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<Button>stuff</Button>
</div>
</ThemeProvider>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
With the ThemeProvider, the background is red; without the ThemeProvider, the background is blue.
Yea, this works. Thanks for the snippet. :)
Closing the issue, then.
It really sucks. Why they're initialising theme prop with an empty object. Also, there's no easy way to define defaultTheme. Using this approach
const ButtonRoot = styled.button(({ theme = defaultTheme, ...props }) => ({
backgroundColor: theme.palette.primary.main,
borderColor: theme.palette.primary.light,
}))
defeats the possibility of defining style properties as css properties.
@paramsinghvc what you can do is:
const ButtonRoot = styled.button(({ theme }) => ({
backgroundColor: theme.palette.primary.main,
borderColor: theme.palette.primary.light,
}));
ButtonRoot.defaultProps = {
theme: defaultTheme,
}
@FezVrasta Thanks for the input. I tried it, but what I'm looking for is to write the styles in css and not in camelCasedProperties i.e background-color in place of backgroundColor. Is that possible?
const ButtonRoot = styled.button`
background-color: ${props => props.theme.palette.primary.main};
border-color: ${props => props.theme.palette.primary.light};
`;
ButtonRoot.defaultProps = {
theme: defaultTheme,
}
I noticed what I suggested seems to have stopped working, now the defaultProps.theme value is always used and never gets overridden by ThemeProvider...
Here, The solution to use defaultProps is not working in typescript.
import React from 'react';
import { render } from '@testing-library/react';
import { matchers } from 'jest-emotion';
import styled from '@emotion/styled';
expect.extend(matchers);
describe('Theme Component Testing', () => {
const ThemedColorDiv = styled.div(({ theme }) => ({
// Error Here: Property 'colors' does not exist on type 'object'.
color: theme.colors.primary,
}));
ThemedColorDiv.defaultProps = {
theme: { colors: { primary: 'red' } },
};
const text = 'colored Text';
const styledComponent = <ThemedColorDiv>{text}</ThemedColorDiv>;
it('Receives Theme color', () => {
const { getByText } = render(styledComponent);
expect(getByText(text)).toHaveStyleRule('color', 'red');
});
});
here because of the type of theme from the props is the object, so any properties from theme says does not exist.
Also from the viewpoint of creating a component library, having extra prop for default theme helps a lot to create a component that can be used without theme providers. and where we don't need to write a default theme processor every time for every component
Most helpful comment
ah, I thought you were familiar with the concepts so I just used your example code to illustrate the point. You can do the same thing in v9 because default args are a JS feature. Here's a working example broken out into it's pieces (and a codesandbox)
With the
ThemeProvider, the background is red; without theThemeProvider, the background is blue.