Reputation: 3748
I'd like to be able to set a global theme (set of variables) for all of my components to inherit from and extend their default variables. For example, I have a button
component that has default styles (inline CSS) that refer to a set of variables such as primaryColor
, ... and I'd like to be able to update these variables easily wherever I use these components without needing to explicitly pass them to the components.
For example, I'd like the following behavior where I could either (1) wrap them in a component and have primaryColor
cascade down to each Button
component or (2) export this component in a higher order component and feed update the props, etc... however, I cannot get ay of these methods to work. Perhaps, there is a better way or ...
(1)
render() {
return (
<Theme variables={{ 'primaryColor': 'red' }}>
<Button />
<SecondButton />
<ThirdButton />
</Theme>
);
}
(2)
render() {
return (
<div>
<Button />
<SecondButton />
<ThirdButton />
</div>
);
}
export default Theme(SampleComponent)
This method works, as it's, obviously, passed down explicitly to each component:
render() {
return (
<div>
<Button variables={{ 'primaryColor': 'red' }} />
<SecondButton variables={{ 'primaryColor': 'red' }} />
<ThirdButton variables={{ 'primaryColor': 'red' }} />
</div>
);
}
Upvotes: 5
Views: 8455
Reputation: 638
For anybody coming here post-hooks release, here is an excellent guide on how to implicitly pass props via context and hooks. A quick summary of the article:
ThemeContext.js:
const ThemeContext = React.createContext({});
export const ThemeProvider = ThemeContext.Provider;
export const ThemeConsumer = ThemeContext.Consumer;
export default ThemeContext;
HomePage.js:
import {ThemeProvider} from './ThemeContext.js';
const HomePage = () => {
const handleClick = e => {};
const currentTheme = {backgroundColor: 'black', color: 'white'};
return (
<ThemeProvider value={currentTheme}>
<MyButton text='Click me!' onClick={handleClick} ... />
...
</ThemeProvider>
);
}
MyButton.js:
import React, { useContext } from 'react';
import ThemeContext from './ThemeContext';
const MyButton = ({text, onClick}) => {
const style = useContext(ThemeContext);
return <button style={style} onClick={onClick}>{text}</button>;
}
Upvotes: 2
Reputation: 2343
If you prefer to create a higher order component ,you can refer to this http://gaearon.github.io/react-dnd/ but you still need to configure your component properly to determine something like if the props from higher-order component should apply or not.
Alternatively, why not create a wrapper component such as myButton
export default class MyBytton(){
render(){
return <button { 'primaryColor': 'red' } />
}
}
render(){
return <MyButton otherProperty:"foo"/>
}
Upvotes: 0
Reputation: 9482
I can see a few ways that you might accomplish this:
Limited, but allows passing 'extra' props to direct children of a component:
import React, { Component, Children } from 'react';
class Theme extends Component {
getChildren () {
const { children, variables } = this.props;
// Clone the child components and extend their props
return Children.map(children, (child) => React.cloneElement(child, {
variables
}));
}
render () {
return Children.only(this.getChildren());
}
}
// Example
<Theme variables={{ 'primaryColor': 'red' }}>
<Button />
<SecondButton />
<ThirdButton />
</Theme>
The easiest way to pass variables to any part of the React tree is by using context as described in the React documentation (this exact use case too!):
// Context provider
class ThemeProvider extends Component {
getChildContext() {
return {
theme: this.props.variables
};
}
render() {
return Children.only(this.props.children);
}
}
ThemeProvider.childContextTypes = {
theme: PropTypes.object.isRequired
};
// HOC
function Theme(WrappedComponent) {
class ThemeWrapper extends Component {
render() {
return <WrappedComponent { ...this.props } />;
}
}
ThemeWrapper.contextTypes = {
theme: PropTypes.object.isRequired
};
return ThemeWrapper;
};
// Using the HOC
class Button extends Component {
render () {
return <button style={{ color: this.context.theme.primaryColor }} />;
}
}
const ThemeButton = Theme(Button);
// Example
<ThemeProvider variables={{ 'primaryColor': 'red' }}>
<div>
<ThemeButton />
</div>
</ThemeProvider>
As you are using Redux, you could wrap each component that needs to be themed in the connect
HOC and store your theme information in the store state. This is a simple way to share data and avoids the complexities of context:
class Button extends Component {
render () {
return <button style={{ color: this.props.theme.primaryColor }} />
}
}
const ConnectedButton = connect((state) => ({ theme: state.theme }))(Button);
// Example
// During app setup
store.dispatch(setTheme({ 'primaryColor': 'red' }));
// Later
<div>
<ConnectedButton />
</div>
Hope this helps.
Upvotes: 7