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

Feature: theme listener #3

Merged
merged 13 commits into from
Jul 12, 2017
Merged

Feature: theme listener #3

merged 13 commits into from
Jul 12, 2017

Conversation

iamstarkov
Copy link
Member

@iamstarkov iamstarkov commented May 21, 2017

as a way to create custom High-Order Components which are supposed to react to or interact with theme updates.

as an example withTheme has been refactored and became very lightweight and simple:

class withTheme extends React.Component {
  static contextTypes = themeListener.contextTypes;
  constructor(props) {
    super(props);
    this.state = { theme: {} };
    this.setTheme = theme => this.setState({ theme });

    this.themeListenerInit = themeListener.init.bind(this);
    this.themeListenerSubscribe = themeListener.subscribe.bind(this);
    this.themeListenerUnsubscribe = themeListener.unsubscribe.bind(this);
  }
  componentWillMount() {
    this.themeListenerInit(this.setTheme);
  }
  componentDidMount() {
    this.themeListenerSubscribe(this.setTheme);
  }
  componentWillUnmount() {
    this.themeListenerUnsubscribe();
  }
  render() {
    const { theme } = this.state;
    return <Component theme={theme} {...this.props} />;
  }
};

@coveralls
Copy link

coveralls commented May 21, 2017

Coverage Status

Coverage decreased (-0.3%) to 94.068% when pulling aa1cb576b7bfe084fc5e8964f1ca38a4b05dc94f on feat/theme-listener into af0faaa on dev-review.

@iamstarkov
Copy link
Member Author

If themeListener offers good enough level of abstraction, then i'll write tests and docs for it. then lets merge it

@iamstarkov
Copy link
Member Author

If themeListener offers good enough level of abstraction

if it doesnt, lets iterate and think of smth

@kentcdodds
Copy link
Collaborator

Honestly, I'm not the right person to ask about the theme functionality in glamorous. I neither developed it nor use it. @vesparny was the one who implemented it, so he probably has some thoughts :)

@mxstbr
Copy link
Collaborator

mxstbr commented May 22, 2017

I like this, seems like a good level of abstraction!

@iamstarkov
Copy link
Member Author

@mxstbr nice

@iamstarkov
Copy link
Member Author

@kof your turn

const PropTypes = require('prop-types');
const channel = require('./channel');

function createThemeListener(CHANNEL = channel) {
Copy link
Member

Choose a reason for hiding this comment

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

I would keep the channel lower case, because this mapping from lower to upper doesn't makes sense or look good. Knowing that its a constant doesn't give you any benefits when using in this way.

Copy link
Member Author

Choose a reason for hiding this comment

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

its minor implementation detail, and lets discuss to change it later in separate issue

static contextTypes = themeListener.contextTypes;
constructor(props) {
super(props);
this.themeSubscribe = themeListener.subscribe.bind(this);
Copy link
Member

Choose a reason for hiding this comment

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

Why do you need the binding? Seems like you are using arrow functions anyways.

Copy link
Member Author

Choose a reason for hiding this comment

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

take a look again, subscribe and unsubscribe are using react component's this

Copy link
Member

Choose a reason for hiding this comment

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

yeah, this is very implicit

Copy link
Member

@kof kof May 22, 2017

Choose a reason for hiding this comment

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

createThemeListener() returns basically a mixin, it would be more explicit to merge that object with the prototype of withTheme class.

Object.assign(WithTheme.prototype, themeListener)

Copy link
Member Author

@iamstarkov iamstarkov May 22, 2017

Choose a reason for hiding this comment

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

i dont like mixins, it allows one mixin to do too much and what is worse, allows it to do it in very implicit way

Copy link
Member Author

Choose a reason for hiding this comment

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

theme listener is low level api for cssinjs libs authors, shall it that implicit and "easy"?

Copy link
Member

@kof kof May 22, 2017

Choose a reason for hiding this comment

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

I don't like them either. Though implementation detail over prototype here seems to me nicer/more clear than the bindings for each function.

Copy link
Member

@kof kof May 22, 2017

Choose a reason for hiding this comment

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

theme listener is low level api for cssinjs libs authors, shall it that implicit and "easy"?

Interface is ok for now like that as we don't know it better. I am just talking about how you use it, Object.assign on prototype is more explicit to me than .bind on each function.

Copy link
Member Author

Choose a reason for hiding this comment

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

can you sketch the api surface?

Copy link
Member Author

Choose a reason for hiding this comment

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

we discussed it in gitter and decided it current api design is good enough


function createWithTheme(CHANNEL = channel) {
const themeListener = createThemeListener(CHANNEL);
return Component =>
class withTheme extends React.Component {
Copy link
Member

Choose a reason for hiding this comment

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

class name should start with upper case

Copy link
Member Author

Choose a reason for hiding this comment

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

why? it works

Copy link
Member

Choose a reason for hiding this comment

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

sure, its just a convention everyone agreed on in javascript world. Class names upper case, instances lower.

Copy link
Member

Choose a reason for hiding this comment

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

withTheme lower case is the function you return on ln 7, which accepts a component, don't mix it up with the class name.

Copy link
Member Author

Choose a reason for hiding this comment

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

then, it will be withTheme(Comp) because its function, and WithTheme(Comp) in react dev tools, and its confusing. lets stick to withTheme because its even more against common practice to have a normal function (not a class) with capital letter

Copy link
Collaborator

Choose a reason for hiding this comment

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

All of recomposes HoC are lowercased in both the function and the DevTools, so we're definitely fine going lowercase.

Copy link
Member

Choose a reason for hiding this comment

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

In Redux it is Connect(Something)
react-intl (very popular as well) - InjectIntl(Something)

Copy link
Member

Choose a reason for hiding this comment

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

After looking up other popular libs, UpperCase(Component) is def. a common way.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't care either way, let's not bikeshed on this for ages. This shouldn't block.

Copy link
Member Author

Choose a reason for hiding this comment

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

lets go with Capital letter then, but I will change it after pull-request will be merged

@iamstarkov
Copy link
Member Author

@vesparny what do you think?

@iamstarkov
Copy link
Member Author

@vesparny can you take a look?

@kentcdodds
Copy link
Collaborator

Maybe @kwelch would have an opinion here...

@kwelch
Copy link

kwelch commented May 23, 2017

Personally, I prefer the lowercase withTheme. One thing that was added in glamorous that I was a fan of was the merging of local theme with global theme. As it is written now if a theme prop is passed it will take precedence over the theme in context.

Here is how the merging is being done in glamorous. https://github.com/paypal/glamorous/blob/afbd1e1cf106ae46dd53b89015b46649bc833155/src/theme-provider.js#L17-L20

@iamstarkov
Copy link
Member Author

@kwelch no, right now we merge outer theme and current one as well, i have tests for that

@kwelch
Copy link

kwelch commented May 23, 2017

Ah, thanks. I was only looking at within the scope of this PR. 😝

@iamstarkov
Copy link
Member Author

accidentally closed this pull-request. will reopen it soon with updated implementation, tests and docs

@iamstarkov iamstarkov reopened this Jun 1, 2017
@coveralls
Copy link

coveralls commented Jun 1, 2017

Coverage Status

Coverage increased (+0.8%) to 93.277% when pulling abd34df on feat/theme-listener into 1a3e027 on dev-review.

@kof
Copy link
Member

kof commented Jun 1, 2017

lgtm

@coveralls
Copy link

coveralls commented Jun 13, 2017

Coverage Status

Coverage increased (+1.2%) to 93.636% when pulling 2a9e33f on feat/theme-listener into df0a20e on master.

README.md Outdated
* `createTheming` allows you to integrate `theming` into your CSSinJS library with custom `channel` (if you need custom one).
=======
Copy link

Choose a reason for hiding this comment

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

Merge conflict

Copy link
Member Author

Choose a reason for hiding this comment

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

thanks, fixed

};
constructor(props) {
super(props);
this.state = { theme: {} };
Copy link

Choose a reason for hiding this comment

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

Should init theme be allowed to be passed via props?

Copy link
Member Author

@iamstarkov iamstarkov Jun 13, 2017

Choose a reason for hiding this comment

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

i dunno, the same can easily be achieved by defaultProps of Component being enhanced

Copy link

Choose a reason for hiding this comment

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

Good point. That works for me.

import defaultChannel from './channel';

export const channel = defaultChannel;
export const withTheme = createWithTheme();
export const ThemeProvider = createThemeProvider();
export const themeListener = createThemeListener();
Copy link

Choose a reason for hiding this comment

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

Question: How can these exports be used since they all require a channel but they are invoked without one?

Copy link
Member Author

Choose a reason for hiding this comment

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

these exports are defaulted to default channel __theming__ internally

Copy link
Member Author

@iamstarkov iamstarkov Jun 13, 2017

Choose a reason for hiding this comment

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

withTheme:

import channel from './channel';

export default function createWithTheme(CHANNEL = channel) {
   // …
}

ThemeProvider:

import channel from './channel';

export default function createThemeProvider(CHANNEL = channel) {
  // …
}

themeListener:

import channel from './channel';

export default function createThemeListener(CHANNEL = channel) {
  // …
}

Copy link

Choose a reason for hiding this comment

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

I see that now. Thanks, I knew it made sense, but I didn't see the connection.

@coveralls
Copy link

coveralls commented Jun 13, 2017

Coverage Status

Coverage increased (+1.2%) to 93.636% when pulling 69836a0 on feat/theme-listener into 3cd9d5b on master.

@iamstarkov
Copy link
Member Author

this branch has been published under @iamstarkov/theming-w-listener@1.0.1 if one want to try out themeListener integration

@iamstarkov
Copy link
Member Author

i started to integrate it into jss, and it seems to be working. But themeListener needs to be adjusted a bit, but its a good news, because API surface will be smaller and simpler

@iamstarkov
Copy link
Member Author

status update:

  • themeListener's code is updated
  • themeListener's docs are updated
  • withTheme implmentation is updated
  • themeListener's tests yet need to be updated, but hey they will become much more simpler

@iamstarkov
Copy link
Member Author

no more bindings:

import { themeListener } from 'theming';

function CustomWithTheme(Component) {
  return class CustomWithTheme extends React.Component {
    static contextTypes = themeListener.contextTypes;
    constructor(props) {
      super(props);
      this.state = { theme: {} };
      this.setTheme = theme => this.setState({ theme });
    }
    componentWillMount() {
      this.setTheme(themeListener.initial(this.context))
    }
    componentDidMount() {
      this.unsubscribe = themeListener.subscribe(this.context, this.setTheme);
    }
    componentWillUnmount() {
      this.unsubscribe();
    }
    render() {
      const { theme } = this.state;
      return <Component theme={theme} {...this.props} />;
    }
  }
}

@iamstarkov
Copy link
Member Author

published as @iamstarkov/theming-w-listener@1.0.2 if one want to try it out

@jcheroske
Copy link

What is the preferred method to map from the theme to props? For instance, if I wanted to pull stuff out of the theme and hand it to a MUI component, what would that look like? I'd like to see mapping from theme to props be a first-class operation. Something like:

import {mapTheme} from 'theming'

const mapThemeToProps = theme => ({
  underlineStyle: {
    color: theme.brandColors.color3
  }
})

const enhancer = mapTheme(mapThemeToProps)

export default enhancer(MyComponent)

Knowing that this lib will be embedded in react-jss, what I'd really like to see is the theme->props mapping functionality be elevated to a peer of the theme->jss-styles functionality. Not sure if injectSheet already takes in a second argument, but I've done something like this and it worked well:

import injectSheet from 'react-jss'

const mapThemeToStyles = theme => ({
  button: {
    color: theme.buttons.color
  }
}

const mapThemeToProps = theme => ({
  zDepth: theme.buttons.zDepth
})

const enhancer = injectSheet(mapThemeToStyles, mapThemeToProps)

const MyButton = ...

export default enhancer(MyButton)

Maybe this already exists in this PR?

@iamstarkov
Copy link
Member Author

i tried this minimalistic abstraction in react-jss and it fits quite nicely

@coveralls
Copy link

coveralls commented Jul 12, 2017

Coverage Status

Coverage increased (+0.9%) to 93.578% when pulling d0ccc43 on feat/theme-listener into 8ad0df2 on master.

```js
import { themeListener } from 'theming';

function CustomWithTheme(Component) {
Copy link
Member

Choose a reason for hiding this comment

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

I would call it createThemedComponent

Copy link
Member

@kof kof left a comment

Choose a reason for hiding this comment

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

LGTM

@iamstarkov iamstarkov merged commit def27be into master Jul 12, 2017
@iamstarkov iamstarkov deleted the feat/theme-listener branch July 12, 2017 15:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants