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

Create a withTheme higher-order component #1222

Closed
epicfaace opened this issue Mar 16, 2019 · 28 comments
Closed

Create a withTheme higher-order component #1222

epicfaace opened this issue Mar 16, 2019 · 28 comments

Comments

@epicfaace
Copy link
Member

epicfaace commented Mar 16, 2019

Ideally, we should have a withTheme HOC in which fields, widgets, and/or templates can be passed -- this can be used to implement custom themes.

This was implemented in #1013, but that included several other breaking changes -- so I'd like to see a PR which just implements the withTheme component so it can be added to the library.

Example usage:

import withTheme from 'react-jsonschema-form/lib/components/withTheme';

import widgets from './my-theme/widgets';
import templates from './my-theme/templates';

const Form = withTheme('MyTheme', { widgets, templates });

I'm listing the popular / currently maintained libraries that fork off of react-jsonschema-form and implement a UI framework other than bootstrap 3, so that the maintainers of those repos are aware of the progress on this issue -- or if anyone wants to jump in and do this, that would be great! @peterkelly @cwhatley @vip-git @SelfKeyFoundation @eddyzhang1986 @TwoAbove @nilportugues

BS4
https://github.com/peterkelly/react-jsonschema-form-bs4
Material
https://github.com/cwhatley/material-ui-react-jsonschema-form
https://github.com/vip-git/react-jsonschema-form-material-ui
https://github.com/SelfKeyFoundation/react-jsonschema-form-material-theme
https://github.com/TwoAbove/jsonschema-form-for-material-ui
Antd
https://github.com/eddyzhang1986/antd-jsonschema-form
Semantic UI
https://github.com/nilportugues/react-jsonschema-form-semanticui

See #899

@TwoAbove
Copy link

Woah, never though I'd be mentioned here!

Not sure that I'll be helpful here, but when this is implemented I'll refactor my fork to use this withTheme.

What is 'MyTheme' for? Just identification?

@danbalarin
Copy link
Contributor

I already have implemented something like withTheme(themeObject), where themeObject is containing widgets, fields and templates. It was not that hard but, since this is my first HOC I might have done something wrong 😄

@epicfaace
Copy link
Member Author

@Kenny11CZ that sounds perfect! It would be great if you could make a PR with that 😄

@epicfaace
Copy link
Member Author

@TwoAbove

What is 'MyTheme' for? Just identification?

That's a good point, I don't think the first argument is needed.

For reference, the original implementation of withTheme doesn't seem to use the first argument in a meaningful way.

@danbalarin danbalarin mentioned this issue Mar 18, 2019
8 tasks
@nilportugues
Copy link

I would happily work to change my repo to use withTheme and submit a PR when withTheme is available in this repo.

so +1 👍 to having a withTheme HOC available.

@epicfaace
Copy link
Member Author

Now that #1226 has been merged, I'm thinking about what would be the best way to add new themes going forward. Of course, we will be able to use themes from external libraries as follows:

import Bootstrap4Theme from 'react-jsonschema-form-theme-bs4'
...
const Form = withTheme(Bootstrap4Theme);
...
<Form ... />

But might it be better if we include certain themes within the library itself? i.e.,

import Bootstrap4Theme from "react-jsonschema-form/themes/bootstrap-4"
...
const Form = withTheme(Bootstrap4Theme);
...
<Form ... />

or even something like

import Form from "react-jsonschema-form/themes/bootstrap-4"
...
<Form ... />

I'm thinking that including these themes within the library will allow for themes for a certain number of UI frameworks to be maintained and kept up to date with the latest changes in rjsf, as well as make it easier for people to use rjsf with something other than bootstrap 3 without having to look for other npm packages to find the right theme.

Would be good to hear people's thoughts on this.

@TwoAbove
Copy link

TwoAbove commented May 26, 2019

@epicfaace I think that a schema like this is a good one:

import { withTheme } from "react-jsonschema-form/core";
import MUITheme from "react-jsonschema-form/themes/material-ui";

const Form = withTheme(MUITheme);

This way, thanks to tree shaking, in prod only the used theme remains, and you can keep all of them in one place + keep them easily updatable.

(also keeps good compatibility with typescript, but that's another thing 😉 )

@mirajp
Copy link
Contributor

mirajp commented Jun 6, 2019

Yes, please keep additional themes as separate packages.

@epicfaace
Copy link
Member Author

@TwoAbove yes, that sounds like a good idea.

@themakerman
Copy link

@TwoAbove How do i use material-UI theme with react-jsonschema-form?

@epicfaace
Copy link
Member Author

Yes, please keep additional themes as separate packages.

@mirajp , just to clarify -- were you agreeing with @TwoAbove 's suggestion to keep additional themes as separate imports from react-jsonschema-form, or were you referring to separate npm packages?

@mirajp
Copy link
Contributor

mirajp commented Jul 9, 2019

@epicfaace oh oops misread the import lines as separate packages, not folders in same package (saw "core" and assumed @TwoAbove also meant separate packages -- you can use "/" in package names, right? ex: https://www.npmjs.com/package/@uifabric/icons, https://www.npmjs.com/package/@uifabric/experiments, https://www.npmjs.com/package/@material-ui/icons, https://www.npmjs.com/package/@material-ui/styles)

Can still be in same monorepo if you want, just published as separate packages: https://github.com/OfficeDev/office-ui-fabric-react/tree/master/packages and https://github.com/mui-org/material-ui/tree/master/packages

@agustin107
Copy link
Member

@themakerman I've created rjsf-material-ui, which allows you to use material-ui v4.2 with react-jsonschema-form.
Check it out here: https://github.com/cybertec-postgresql/rjsf-material-ui
Playground: https://cybertec-postgresql.github.io/rjsf-material-ui/
Feedback is welcome!

@epicfaace
Copy link
Member Author

epicfaace commented Jul 9, 2019

@agustin107 looks great! Thanks for the hard work to get the theme working.

@mirajp I believe @TwoAbove might have meant a single package, since he was specifically referring to tree-shaking. I believe both approaches work though; also, another way to do it would be to use package scopes like Babel (like @babel/babel-core, do @rjsf/material-ui).

So, I'm wondering now whether it would be best to have these different themes (material UI, etc.) as different repos or in subfolders of this repository a single monorepo. Having each theme in a different repo will speed up development as react-jsonschema-form will only need to change a single set of widgets -- but there might be the possibility of the theme repos falling behind and not implementing all the widget fixes (since the tests use the default theme widgets, not the custom theme widgets). On the other hand, a single monorepo will ensure that all changes/bugfixes to widgets apply to all themes, but it might involve more work when creating a widget change as it would have to be duplicated across all officially supported themes.

Would appreciate any feedback / thoughts.

@TwoAbove
Copy link

@agustin107 Really nice!

About the packages, that's for the maintainers to decide.
There are two options:

  1. If the maintainers will want to handle all the UI packages somehow under their wing, then you can do something like @react-json-schema-form/material-ui or react-json-schema-form-material-ui and handle the packages here, with public PRs with updates/whatever.
  2. If it's too much of a commitment, then just support one like now and then everyone else will create packages like rjsf-material-ui.

So the gist of it is whether the maintainers have the time to support different UI packages.

Do they?

@themakerman
Copy link

@agustin107 Thanks for the hardwork. Few questions though. I actually plan to use this sometime near in production. I was worried around maintenance thoughts. So will this come under react-jsonschema-form community or will be treated as a fork and maintained on its own?

@TwoAbove Would be great if we use @react-json-schema-form/material-ui and maintainers handle packages within react-jsonschema-form. If we keep it seperate there will be less community involvement and high change of project becoming stale.

Anything kept under the repo react-jsonschema-form will also give devs more confidence for using it in production.

Let me know your thoughts

@LorenzHenk
Copy link

LorenzHenk commented Jul 12, 2019

@themakerman We are planning to use it in production as well, @agustin107 is working on it full-time. It won't be abandoned anytime soon, don't worry 🙂
If the maintainers said that they want to move it to @rjsf/material-ui, we'd be fine with that.
They have to tell us their view on it. Whether they want to manage the themes as well or not.
We ❤️ react-jsonschema-form and want the best for it and its community!

@epicfaace It's a hard decision to make, all ways have pro's and con's.
Until you have decided, we will keep enhancing https://github.com/cybertec-postgresql/rjsf-material-ui!

EDIT:
@themakerman as we'll most likely move the package to a domain of react-jsonschema-form, you should probably wait until that is done, before using our package.

@epicfaace
Copy link
Member Author

Thanks for the feedback. It seems better to bring in themes into the main repo, due to the advantage of keeping the themes centralized and more maintained by the community. Trying to see if we have 10 different themes, how to keep all of these themes synchronized without having to re-implement (copy-and-paste) changes 10 different times. For example, already fixes such as #1335 have not been added to cybertec-postgresql/rjsf-material-ui yet (and perhaps it's impractical to keep up!).

Let's compare the DefaultNormalArrayFieldTemplate in rjsf-material-ui:

const DefaultNormalArrayFieldTemplate = (props: ArrayFieldTemplateProps) => {
  return (
    <Paper elevation={2}>
      <Box p={2}>
        <ArrayFieldTitle
          key={`array-field-title-${props.idSchema.$id}`}
          TitleField={props.TitleField}
          idSchema={props.idSchema}
          title={props.uiSchema['ui:title'] || props.title}
          required={props.required}
        />

        {(props.uiSchema['ui:description'] || props.schema.description) && (
          <ArrayFieldDescription
            key={`array-field-description-${props.idSchema.$id}`}
            DescriptionField={props.DescriptionField}
            idSchema={props.idSchema}
            description={
              props.uiSchema['ui:description'] || props.schema.description
            }
          />
        )}

        <Grid container={true} key={`array-item-list-${props.idSchema.$id}`}>
          {props.items && props.items.map(p => DefaultArrayItem(p))}

          {props.canAdd && (
            <Grid container justify="flex-end">
              <Grid item={true}>
                <Box mt={2}>
                  <AddButton
                    className="array-item-add"
                    onClick={props.onAddClick}
                    disabled={props.disabled || props.readonly}
                  />
                </Box>
              </Grid>
            </Grid>
          )}
        </Grid>
      </Box>
    </Paper>
  );
};

and that in rjsf:

function DefaultNormalArrayFieldTemplate(props) {
  return (
    <fieldset className={props.className} id={props.idSchema.$id}>
      <ArrayFieldTitle
        key={`array-field-title-${props.idSchema.$id}`}
        TitleField={props.TitleField}
        idSchema={props.idSchema}
        title={props.uiSchema["ui:title"] || props.title}
        required={props.required}
      />

      {(props.uiSchema["ui:description"] || props.schema.description) && (
        <ArrayFieldDescription
          key={`array-field-description-${props.idSchema.$id}`}
          DescriptionField={props.DescriptionField}
          idSchema={props.idSchema}
          description={
            props.uiSchema["ui:description"] || props.schema.description
          }
        />
      )}

      <div
        className="row array-item-list"
        key={`array-item-list-${props.idSchema.$id}`}>
        {props.items && props.items.map(p => DefaultArrayItem(p))}
      </div>

      {props.canAdd && (
        <AddButton
          className="array-item-add"
          onClick={props.onAddClick}
          disabled={props.disabled || props.readonly}
        />
      )}
    </fieldset>
  );
}

It seems to me that the only difference here is the tag names, not the actual logic. So what if we did something like this:

In RJSF:

function DefaultNormalArrayFieldTemplate(props) {
  const {theme} = props.registry.theme.ArrayFieldTemplate;
  return (
    <theme.container className={props.className} id={props.idSchema.$id}>
      <ArrayFieldTitle
        key={`array-field-title-${props.idSchema.$id}`}
        TitleField={props.TitleField}
        idSchema={props.idSchema}
        title={props.uiSchema["ui:title"] || props.title}
        required={props.required}
      />

      {(props.uiSchema["ui:description"] || props.schema.description) && (
        <ArrayFieldDescription
          key={`array-field-description-${props.idSchema.$id}`}
          DescriptionField={props.DescriptionField}
          idSchema={props.idSchema}
          description={
            props.uiSchema["ui:description"] || props.schema.description
          }
        />
      )}

      <theme.arrayItemListContainer
        className="row array-item-list"
        key={`array-item-list-${props.idSchema.$id}`}>
        {props.items && props.items.map(p => DefaultArrayItem(p))}
      </div>

      {props.canAdd && (
        <theme.addButtonContainer>
            <AddButton
            className="array-item-add"
            onClick={props.onAddClick}
            disabled={props.disabled || props.readonly}
            />
         </theme.addButtonContainer>
      )}
    </theme.container>
  );
}

And then rjsf-material-ui will just export a config such as this:

{
    "ArrayFieldTemplate": {
        "container": props => <Paper elevation={2}> <Box p={2}> {props.children} </Box></Paper>,
        "arrayItemListContainer": props => <Grid container={true} {...props} />
        "addButtonContainer": () => <Grid container justify="flex-end"> <Grid item={true}> <Box mt={2}> {props.children} </Box></Grid></Grid>
    }
}

This way, all the theme is declaring is what is different in the theme, rather than reimplementing each field/template/widget from scratch. @LorenzHenk @agustin107 Do you think such a solution covers all the use cases for the changes you had to make when creating rjsf-material-ui?

Would welcome suggestions as well.

@videni
Copy link

videni commented Jul 15, 2019

I am trying to integrate Ant Design, it would be great if Separate templates and allow a completely custom UI components can be merged.

@LorenzHenk
Copy link

@epicfaace You are right, we are reimplementing lots of logic.
What about custom handler functions? Should they just be inlined?
e.g. https://github.com/cybertec-postgresql/rjsf-material-ui/blob/master/src/CheckboxWidget/CheckboxWidget.tsx#L23

@epicfaace
Copy link
Member Author

What about custom handler functions? Should they just be inlined?

Yes, exactly. Perhaps we should allow a theme to completely override a widget/field if there are enough changes, but in many scenarios this would let us reduce the amount of duplicated code.

@themakerman
Copy link

themakerman commented Jul 16, 2019

@epicfaace Hi guys, so will it be possible to have some kind of official doc section addressing how to integrate material ui with react-json-schema forms?

@epicfaace
Copy link
Member Author

@LorenzHenk @agustin107 Let's add the existing code for rjsf-material-ui to the main react-jsonschema-form repository. We can use lerna to manage packages in the monorepo and then can publish it under something like @rjsf/material-ui. It would be nice to eventually make the theming extensible and require less code as I outlined above, but that shouldn't stop us from initially releasing what we have so far, maybe in a "beta"/"pre-release" phase.

Probably eventually (perhaps in v2), the way to use react-jsonschema-form would involve installing @rjsf/core (which comes with a default theme, with plain HTML and no Bootstrap) and then @rjsf/[theme name] in order to allow the form to work with a specified UI framework.

@themakerman Probably would want to add this to the docs only once we publish this under the @rjsf org, so we don't change the names around too much.

Thanks for all your work on this!

agustin107 added a commit to agustin107/react-jsonschema-form that referenced this issue Aug 19, 2019
@agustin107 agustin107 mentioned this issue Aug 19, 2019
2 tasks
epicfaace pushed a commit that referenced this issue Sep 20, 2019
* Add material-ui theme

Related to issue #1222

* Delete LICENSE and update README

This is according to PR conversation

* Move material-ui theme into themes folder and delete example folder
@MadelineRitchie
Copy link

I'd like to assist transitioning to lerna. Is there a branch/commit/fork where lerna is being implemented?

@epicfaace
Copy link
Member Author

@jasonritchie not yet! Will post here once it's created though.

@timkindberg
Copy link

timkindberg commented Feb 18, 2020

This way, all the theme is declaring is what is different in the theme, rather than reimplementing each field/template/widget from scratch. @LorenzHenk @agustin107 Do you think such a solution covers all the use cases for the changes you had to make when creating rjsf-material-ui?

@epicfaace It does seem like customization at deeper levels would be good. There seems to be more exploration needed to strike the right abstraction in this lib. I still think there is too much "private" logic happening that is not exposed in the right way for external customization/themeability. Which is normal; this is what happens as libraries grow organically. To fix it, it would require essentially a large rewrite or refactor.

I think these are the layers of abstraction that might be needed:

  1. Extract the core of the schema-to-ui parsing into a headless package with no coupling to any front end framework or css library. There is already an issue for this here: Interest in extracting ui and schema mapping into shared dependency library? #1318. This is the level that would have 99% of the js logic.
  2. Upon 1, build a very unstyled or mildly styled implementation that is not coupled with any theme library, but is built on React. Have every (or near every) level of component customizable just like react-select allows customization: https://react-select.com/components. This level of customization is important for DRY theming. (Other frameworks can use this as an example of making an implementation for their own framework: Vue, Angular). This level's job is to map the agnostic output of level 1 to interchangeable components.
  3. Upon 2, build various implementations coupled to specific UI/CSS libs (Bootstrap, Material, Ant, Chakra, Tailwind). At this level you should see almost NO js logic. Only instructions on how to style or what ui components to use.

@epicfaace
Copy link
Member Author

@timkindberg thanks for your thoughts. Right now, we're going to be releasing multiple packages in v2. The initial plan was to release a package called @rjsf/core which has the default theme and then @rjsf/material-ui which has other themes.

It's worth aiming for the rewrite you mention in the long-term, and it might be best to align the package structure in v2 so that it gets closer to that structure. Right now, @rjsf/core does #1 and #2, as well as #3 for Bootstrap. It may be possible to make @rjsf/core only do #1 and #2, and then create a @rjsf/bootstrap theme to do #3.

I'm a bit unclear as to what exactly the output of #1 would be -- what could an output of "schema-to-ui parsing" be, other than perhaps something like React components?

@timkindberg
Copy link

timkindberg commented Feb 21, 2020

Ah ok, yeah I see the output of #1 to be an agnostic JSON structure that any framework could utilize. Basically JSX in JSON form. So the html tag, the attributes, the children, plus any necessary meta information.

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

No branches or pull requests