Detuned
Detuned

Reputation: 3748

Is there a way to pass down props to a component implicitly?

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

Answers (3)

Benjamin U.
Benjamin U.

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

Guichi
Guichi

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

I can see a few ways that you might accomplish this:

Extending child props

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>

React context

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>

Redux store

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

Related Questions