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>
);
}
To pass props, add them to the JSX, just like you would with HTML attributes. To read props, use the function Avatar({ person, size }) destructuring syntax. You can specify a default value like size = 100 , which is used for missing and undefined props.
Here are few ways you can pass props to a route component. With the react-router v5, we can create routes by wrapping with a component, so that we can easily pass props to the desired component like this. Similarly, you can use the children prop in v5.
You can't pass props from child to parent in React, it's only one way (from parent to child). You should either: Put the state in the parent component and manipulate it from the child component by passing the setter function in the props.
Passed propsIf the styled target is a simple element (e.g. styled.div), styled-components passes through any known HTML attribute to the DOM. If it is a custom React component (e.g. styled(MyComponent)), styled-components passes through all props.
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.
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>;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With