Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OEP 0028: Content theming in React (Draft) #79

Conversation

taranjeet
Copy link

@taranjeet taranjeet commented Aug 23, 2018

It aims to add guidelines for how to create and use React components, so that content modification becomes easy.

This OEP is based on this discussion and this gist

Note: As OpenCraft, our role is to define a proper solution and ensure that the future developments move in the right direction, but not committing on developing any of this at the moment.

@openedx-webhooks
Copy link

Thanks for the pull request, @taranjeet! I've created OSPR-2565 to keep track of it in JIRA. JIRA is a place for product owners to prioritize feature reviews by the engineering development teams.

Feel free to add as much of the following information to the ticket:

  • supporting documentation
  • edx-code email threads
  • timeline information ("this must be merged by XX date", and why that is)
  • partner information ("this is a course on edx.org")
  • any other information that can help Product understand the context for the PR

All technical communication about the code itself will still be done via the GitHub pull request interface. As a reminder, our process documentation is here.

@openedx-webhooks openedx-webhooks added needs triage open-source-contribution PR author is not from Axim or 2U labels Aug 23, 2018
@mduboseedx
Copy link

@taranjeet Thank you for the proposal. Feel free to ping me when you feel it is ready for an edX review

oeps/oep-0028-content-theming-in-react.rst Outdated Show resolved Hide resolved
Decision
--------

* We will build small modular components which will be non-customizable, stateless and contain HTML, and certain logic. We can see an example of a smaller component SiteLogo, which will later be used in a customizable component _Header.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not really explained here that there are meant to be two types of components: internal/non-customizable and customizable ones.

At first read, this sentence just sounds like "we will build everything out of small components that cannot be customized."

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added a point before this, where it mentions that there will be two types of component.

oeps/oep-0028-content-theming-in-react.rst Outdated Show resolved Hide resolved
oeps/oep-0028-content-theming-in-react.rst Outdated Show resolved Hide resolved
oeps/oep-0028-content-theming-in-react.rst Outdated Show resolved Hide resolved

* We will provide support for a global redux store which will be Open edX global redux store. This will act as a central place to store data and will have access to all data available to the system.

* We will consider the layout of the data in the Open edX global redux store as a stable API. We will provide support to pre-fill the store with some common data like current user, current course, list of courses enrolled, etc. We will provide the choice for themes to fetch data that's not part of the Open edX global redux store from REST API's using built-in redux actions and store it in their own separate redux store. We will announce breaking changes if the layout of the data changes in global store.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The redux store would be specific to each frontend (LMS, Studio, ecommerce, etc.) and not global to all of "Open edX".

using built-in redux actions and store it in their own separate redux store

Actions tend to be fairly coupled to the reducers and overall store state, so I'm not sure how useful it would be to provide standard action [creator]s but expect them to work with custom stores/reducers. It might be better to just provide a standard API client library for the REST APIs, without redux actions, and let each custom theme that needs to use them include its own redux actions/reducers/state. But maybe this would help reduce boilerplate etc., and most any reducer could be adapter to ingest standard actions. Curious to know what others think.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we can expect that there will be 100% coverage for each API in the frontend, so people will have to create their own actions. I don't think it's reasonable to have the actions and reducers etc for data not used by the default theme. Where a built-in action exists, chances are that data is already loaded, and if not, the standard actions can perhaps be used.

While an API client could be automatically generated using Swagger or such, I think there should probably be multiple API clients for multiple sets of APIs because the API is huge so why should all that code be loaded if most of it isn't used. If a custom theme uses some API that the default theme doesn't, it can also include the relevant API client. This probably should cover the whole API.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xitij2000 @bradenmacdonald I have updated this point but I am not sure if it covers your concern. Please let me know your thoughts on this. Thanks.


* We will use containers [2] to access data from the redux store and provide it to components via props. A container is a react component that has a direct connection to the state managed by redux and access data from the state via mapStateToProps. We will use Container as a mechanism to separate data access functionality from the Component. This way we can keep both non redux connected version as well as redux connected version of the same component.

* We will have support to convert any component into a container if it needs to access any data from the redux store, which it currently does not have access to. We can see this by an example where NavbarHeader component initially displays site title. This component now needs to display authenticated username, which is there in the redux store.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like the example below should be showing a NavbarHeader component that doesn't use the username, and an inherited version that does. The example here doesn't work without the container, so it isn't an example of "converting any component into a container".

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have updated this to include NavbarHeaderWithUserName which extends from NavbarHeader but completely overrides its render function. The extension will help in propagating other functionalities from NavbarHeader to NavbarHeaderWithUserName.
NavbarHeader contains support to display username which is overridden by a container. This also brings us to a point where sometimes both the component(to display additional properties) and container needs to be overridden in case we need to display some additional data.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also don't like the idea of "converting" we are not really converting anything, we are just adding another component that passes some props by default by fetching that data from the store. So it's standard component composition, you're wrapping an existing component with another.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xitij2000 : I have updated the "converting" part to "composition" now. Thanks:)

oeps/oep-0028-content-theming-in-react.rst Outdated Show resolved Hide resolved
@taranjeet
Copy link
Author

@mduboseedx : Thanks. Sure I will ping you when the proposal is ready for an edx review. :)

);
}

* We will build the complete UI out of the above small modular components. We will generally use composition to build a customizable component. However, inheritance can be used at times. These customizable components will neither contain HTML nor logic. We can see an example of a customizable component called the _Header component, which is formed by the composition of SiteLogo, MainNav and UserAvatar component.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think here the language should be "we will aim/prefer to use composition to build components wherever possible" and leave out the bits about inheritance. Instead, explain the difference in approach via an example.

I think we should also explain why this decision was made. Why prefer composition over inheritance?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. What I have done is added about this in the other point and given an example of both cases. Thanks :)

oeps/oep-0028-content-theming-in-react.rst Show resolved Hide resolved
oeps/oep-0028-content-theming-in-react.rst Outdated Show resolved Hide resolved
.. code-block:: js

// SiteLogo being updated in Header
class MyThemedHeader extends _Header {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As @bradenmacdonald mentioned elsewhere, this is using inheritance not composition.

Copy link
Author

@taranjeet taranjeet Aug 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps I think you are talking about this comment. That was for another point wherein the point I talked about using "composition" but gave an example of "composition" "inheritance". I updated it then only.

Also here it makes sense to use "inheritance" only since if we use composition we would end up creating components like BaseHeader, Header, SiteLogoHeader and MyCustomAnimatedLogoWidgetHeader.

Let me know if this is not right.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point is that you mention in the paragraph: "We will prefer composition whenever adding any new component or content and inheritance when replacing or modifying the component." Immediately after that, you are showing an example of using inheritance when that is not the preferred approach. Show both ways of doing this, so that it's clearer which is the preferred approach in what circumstance.

I would also say this is not a good example of inheritance at all. You are extending an existing component, but there is nothing from the original component that you are actually using. Why couldn't this just be a new component? The only thing it gets from _Header is the getNavLinks getter method and that isn't even being used, the render method is entirely replaced.

Also, I think if a component has a good chance of needing modification in some area, there should probably be a hook for it. So, in this case, replacing the Logo in the _Header is a pretty obvious modification, so perhaps there should already be a prop for it, and should just default to the standard logo.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xitij2000 : I am not sure here but I think we should skip this difference part between composition and inheritance since that is something more specific to React and not this proposal in general. The line where we can say that here inheritance or composition which one should be used is very blur.

Keeping this in mind, I think its better to just write via composition/inheritance and leave it to the one who is implementing it because he/she will be a better judge of the situation.

I have also commented on the ticket about how we can use composition for the Header component but that turned out to be a discussion on inheritance vs composition.

class Header extends React.PureComponent {
    public render() {
        return (
            <header>
                {props.children}
            </header>
        );
    }
}
 
const BaseHeader = (props) => {
    <Header>
        {props.children}
        <MainNav/>
        <UserAvatar/>
    </Header>
}
const SiteLogoHeader = (props) => {
    <BaseHeader>
        <SiteLogo />
    </BaseHeader>
}
 
const MyCustomAnimatedLogoHeader = (props) => {
    <BaseHeader>
        <MyCustomAnimatedLogo />
    </BaseHeader>
}

I think Braden can help us here.
@bradenmacdonald : Your inputs please :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's important to have some conventions in mind because otherwise, things can get messy. It will decide what kinds of hooks a component will provide for instance. If you're expecting composition you'd want to provide more props to control parts of the component. In the case of inheritance, you'll want methods that can be overridden etc.

I think the React ecosystem is generally more aligned with composition as you suggested, so we should favour that, and this is precisely why it becomes important to explore under what conditions would inheritance be necessary. If it never is, we should just say that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that inheritance will be necessary any time you want to remove or replace an existing component child such as the SiteLogo in the example above, unless there is some other hook for doing so via props (like logoOverride=<MyCustomAnimatedLogo />) or modifying a global (like replacing a component globally via const SiteLogo = MyCustomSiteLogo; // not StandardSiteLogo;)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bradenmacdonald How will inheritance help in that case though? I might be missing something but here is how I see it:
Case: You have a component where the logo should be removable/replaceable.

  • Provide a prop that allows replacing the logo. e.g. <div> {this.props.logo === undefined?<DefaultLogo />: this.props.logo}</div>. This allows setting the logo as null or providing a custom logo without inheritance.
  • You inherit from the original component. But now you're overriding the original component's render method. So what exactly do you gain by inheriting vs creating a new component? The use for inheritance would be with components that have a lot of logic that will be inherited, but customisable components won't have that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows setting the logo as null or providing a custom logo without inheritance.

Yes, but that assumes that the person who wrote the original component knew that people might want to override the logo. I'm sure there will be lots of other cases where the original component author didn't know what parts people might want to hide/replace/insert. So sometimes I think inheritance will be necessary.

But now you're overriding the original component's render method. So what exactly do you gain by inheriting vs creating a new component?

My original idea was that the render() methods of customizable components should be so straightforward that they are easy to override completely, in order to give the theme complete control over removing, re-ordering, replacing, or inserting children. The logic wouldn't be directly inherited, but because the child components encapsulate the relevant logic, I think it would be pretty powerful.

Maybe we should come up with more concrete examples to figure this out better? It's still fairly theoretical at this point.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should come up with more concrete examples to figure this out better? It's still fairly theoretical at this point.

Yup, I'm just having trouble seeing a component that has a such a straightforward render function, but it still makes sense to inherit, vs create a new component. I can see the case if there is some lifecycle code or other kind of complex code in the component, but I assumed that customisable components wouldn't have that.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @xitij2000 -- inheriting from the default _Header component in this example is confusing since it doesn't accomplish anything and the result is equivalent to class MyThemedHeader extends React.PureComponent { ... }.

If we want to show a case where inheritance is useful we need a better example.

oeps/oep-0028-content-theming-in-react.rst Outdated Show resolved Hide resolved
oeps/oep-0028-content-theming-in-react.rst Outdated Show resolved Hide resolved

* We will provide support for a global redux store which will be Open edX global redux store. This will act as a central place to store data and will have access to all data available to the system.

* We will consider the layout of the data in the Open edX global redux store as a stable API. We will provide support to pre-fill the store with some common data like current user, current course, list of courses enrolled, etc. We will provide the choice for themes to fetch data that's not part of the Open edX global redux store from REST API's using built-in redux actions and store it in their own separate redux store. We will announce breaking changes if the layout of the data changes in global store.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we can expect that there will be 100% coverage for each API in the frontend, so people will have to create their own actions. I don't think it's reasonable to have the actions and reducers etc for data not used by the default theme. Where a built-in action exists, chances are that data is already loaded, and if not, the standard actions can perhaps be used.

While an API client could be automatically generated using Swagger or such, I think there should probably be multiple API clients for multiple sets of APIs because the API is huge so why should all that code be loaded if most of it isn't used. If a custom theme uses some API that the default theme doesn't, it can also include the relevant API client. This probably should cover the whole API.

oeps/oep-0028-content-theming-in-react.rst Outdated Show resolved Hide resolved

* We will use containers [2] to access data from the redux store and provide it to components via props. A container is a react component that has a direct connection to the state managed by redux and access data from the state via mapStateToProps. We will use Container as a mechanism to separate data access functionality from the Component. This way we can keep both non redux connected version as well as redux connected version of the same component.

* We will have support to convert any component into a container if it needs to access any data from the redux store, which it currently does not have access to. We can see this by an example where NavbarHeader component initially displays site title. This component now needs to display authenticated username, which is there in the redux store.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also don't like the idea of "converting" we are not really converting anything, we are just adding another component that passes some props by default by fetching that data from the store. So it's standard component composition, you're wrapping an existing component with another.

@taranjeet
Copy link
Author

@xitij2000 @bradenmacdonald This is ready for your review. Thanks :)

@mtyaka
Copy link

mtyaka commented Nov 7, 2018

Sounds good @nasthagiri, thank you for the information. Nice to hear the Architecture team is including theming in their Roadmap soon :)

@natabene
Copy link

@nasthagiri @ormsbee Is this a good time to return to this PR or not yet?

@nasthagiri
Copy link
Contributor

@natabene We are aware of these 2 theming-related proposals. We plan to address them as part of our replatforming runway. As indicated on our Roadmap Progress, we are currently focusing on our "Design System" (library of reusable components). We plan to address theming only after that - but still in the 1st half of this year as previously indicated.

@nasthagiri
Copy link
Contributor

Also, for any of you following this PR, please contact me (via slack/email) if you'd like to join an Open edX "Theming Advisory Group". We plan to consult this group on requirements and thoughts on "theming" on the Open edX platform. Thanks.

@natabene
Copy link

@nasthagiri Thank you for the update, super helpful to me.

@natabene natabene added the blocked by other work PR cannot be finished until other work is complete label Feb 22, 2019
@openedx-webhooks openedx-webhooks added needs triage waiting on author PR author needs to resolve review requests, answer questions, fix tests, etc. community manager review blocked by other work PR cannot be finished until other work is complete and removed blocked by other work PR cannot be finished until other work is complete needs triage waiting on author PR author needs to resolve review requests, answer questions, fix tests, etc. community manager review labels Mar 5, 2019
@openedx-webhooks openedx-webhooks added needs triage and removed blocked by other work PR cannot be finished until other work is complete labels May 21, 2019
@openedx openedx deleted a comment from openedx-webhooks May 29, 2019
@mtyaka
Copy link

mtyaka commented Nov 6, 2019

@nasthagiri I don't have access to the replatforming runway or roadmap progress docs.

Has there been any progress on made on theming?

@mtyaka
Copy link

mtyaka commented Jan 13, 2020

@nasthagiri There has been some exciting development around micro-frontends in the past year. Is there any documentation or guidelines on how how to theme micro-frontends?

@nasthagiri
Copy link
Contributor

@bradenmacdonald Are there elements of this proposed OEP that you believe are still valid given the latest developments in Open edX MFEs? If not, should we close this old Draft and reopen with the latest considerations?

@bradenmacdonald
Copy link
Contributor

@nasthagiri My understanding is that newer guidance is here in extension_points.rst#Theming Microfrontends, and specifically that the proposals in this OEP would instead be replaced by "Frontend Plugins" or entire custom MFEs (as well as Overriding Brand Specific Elements).

AFAIK frontend plugins still needs to be defined, but there's a proposal at openedx/frontend-app-discussions#5

I think it makes sense to close this and re-open when there's a solid, tested frontend plugins approach that we're ready to recommend more widely across MFEs.

@nasthagiri
Copy link
Contributor

Thanks, @bradenmacdonald. I agree that the various efforts you enumerated will collectively form a basis for the extensibility and configurability of our frontends. Once those efforts are matured enough past their proof-of-concept phases (in ADR forms), we can create/update a frontend OEP that provides a cohesive narrative.

Given that, I will close this PR. I'm looking forward to seeing these other efforts move forward.

Cc: @abutterworth @davidjoy

@nasthagiri nasthagiri closed this Sep 4, 2020
@openedx-webhooks openedx-webhooks added blocked by other work PR cannot be finished until other work is complete rejected and removed needs triage blocked by other work PR cannot be finished until other work is complete labels Sep 4, 2020
@openedx-webhooks
Copy link

@taranjeet Even though your pull request wasn’t merged, please take a moment to answer a two question survey so we can improve your experience in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
open-source-contribution PR author is not from Axim or 2U rejected
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants