Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save Pakinwet/c4f68a2d36fd3c2d6af1c37638b2d17e to your computer and use it in GitHub Desktop.

Select an option

Save Pakinwet/c4f68a2d36fd3c2d6af1c37638b2d17e to your computer and use it in GitHub Desktop.

React guidelines to support content theming in Open edX (Braden's proposal)

  1. Build the UI out of small, modular React components as much as possible.
  2. Build two types of components: "customizable" ones that only compose others using JSX and contain little-or-no HTML nor logic, as well as "internal" components that contain logic and detailed HTML and CSS, etc.
  3. In customizable components, include placeholders like {this.extraContent} in the render() method so that subclasses don't have to override render().

Bad example:

class Header extends React.PureComponent<Props, State> {
    public render() {
        return (
            <header>
                <div className="logoArea">
                    <img src={this.props.brandedLogo} alt={this.props.siteName} />
                </div>
                <div className="mainNav">
                    <ul>
                        <li>
                            <a href="/">Home</a>
                            {this.props.isLoggedIn ? null : <a href="/login">Login</a>}
                        </li>
                    </ul>
                </div>
                <div class="currentUserAvatar">
                    <!-- User avatar, account menu etc. -->
                </div>
            </header>
        );
    }
}

Good example:

// Customizable Header
class _Header extends React.PureComponent<Props, State> {
    public render() {
        return (
            <header>
                <SiteLogo/>
                <MainNav/>
                <UserAvatar/>
            </header>
        );
    }
}
// Customizable Main Navigation Area
class _MainNav extends React.PureComponent<Props> {
    public render() {
        return (
            <MainNavWrapper>
                <a href="/">Home</a>
                <LoginLink/>
                {this.extraNavLinks}
            </MainNavWrapper>
        );
    }
    get extraNavLinks(): JSX.Element[] { return []; }
}
// Internal MainNavWrapper - not meant to be modified in most cases)
class _MainNavWrapper extends React.PureComponent<Props> {
    public render() {
        return <div className="mainNav">
            <ul>
                {React.Children.map(this.props.children, (child) => (child ? <li>{child}</li> : null))}
            </ul>
        </div>;
    }
}

// Default Theme:
export const Header = _Header;
export const MainNav = _MainNav;
export const MainNavWrapper = _MainNavWrapper;

And here's an example of a custom theme:

class MyThemedHeader extends _Header {
    public render() {
        return (
            <header>
                {/* Replace <SiteLogo/> with a fancy widget */}
                <MyCustomAnimatedLogoWidget/>
                <MainNav/>
                <UserAvatar/>
            </header>
        );
    }
}
// Customizable Main Navigation Area
class MyThemedNav extends _MainNav {
    get extraNavLinks() {
        return [
            <a href="/about">About Us</a>,
        ];
    }
}

// My theme:
export const Header = MyThemedHeader;
export const MainNav = MyThemedNav
export const MainNavWrapper = _MainNavWrapper;

I've left out all the Redux glue code, etc., but you can get the idea.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment