Skip to content
This repository has been archived by the owner on Dec 13, 2018. It is now read-only.

feat(glamorous): withProps factory #149

Closed
wants to merge 2 commits into from
Closed

feat(glamorous): withProps factory #149

wants to merge 2 commits into from

Conversation

atticoos
Copy link
Collaborator

@atticoos atticoos commented May 27, 2017

What: Adds withProps factory as discussed in #133

Why: A handy API for composing more than styles on a component 😃

How: glamorousComponentFactory comes with a withProps property on it. This allows you to recompose the factory with properties

const GlamorousComponent = glamorous(/* Comp */).withProps({ /* props */ })({ /* styles */ })

Note: these properties take a lower priority than properties defined on the GlamorousComponent by the user. Just like the styles.

Feedback Requested

Before finalizing documentation, I'd like to get a sense of the approach is what we'd like!

Checklist:

  • Documentation
  • Tests
  • Code complete
  • Added myself to contributors table
  • Followed the commit message format

@codecov-io
Copy link

codecov-io commented May 27, 2017

Codecov Report

Merging #149 into master will not change coverage.
The diff coverage is 100%.

Impacted file tree graph

@@          Coverage Diff          @@
##           master   #149   +/-   ##
=====================================
  Coverage     100%   100%           
=====================================
  Files          10     10           
  Lines         138    148   +10     
  Branches       35     37    +2     
=====================================
+ Hits          138    148   +10
Impacted Files Coverage Δ
src/create-glamorous.js 100% <100%> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 9b77296...167ad34. Read the comment docs.

@atticoos atticoos force-pushed the with-props branch 2 times, most recently from ac72282 to 8f5e395 Compare May 27, 2017 18:07
@atticoos
Copy link
Collaborator Author

Would love some 👀 from @jfrolich too!

Copy link
Contributor

@kentcdodds kentcdodds left a comment

Choose a reason for hiding this comment

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

Looks really neat! Just a few comments. Would love the opinion of other maintainers. I think this is good though!

@@ -433,3 +433,29 @@ test('should accept user defined contextTypes', () => {
const props = {}
expect(dynamicStyles).toHaveBeenCalledWith(props, theme, context)
})

it('should compose a component withProps', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

I prefer test('composes a component...

* props and the return value is used.
* @return {Function} the glamorousComponentFactory function.
*/
glamorousComponentFactory.withProps = (...outerProps) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

I prefer function declarations for all functions. Then above we can do:

glamorousComponentFactory.withProps = withProps
return glamorousComponentFactory

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Great point!

*/
glamorousComponentFactory.withProps = (...outerProps) => {
// This is a higher order function of `glamorousComponentFactory`
return (...styles) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's use a function declaration here too. That way this function can have a name which will be used in stack traces 😀

@atticoos atticoos force-pushed the with-props branch 4 times, most recently from 686e740 to d1c175c Compare May 27, 2017 18:23
const evaluated = typeof prop === 'function' ?
prop(innerProps) :
prop
return {...props, ...evaluated}
Copy link
Collaborator Author

@atticoos atticoos May 27, 2017

Choose a reason for hiding this comment

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

If we attempt to spread evaluated, and it evaluated as null or undefined (or any non object, really) -- that would raise an exception wouldn't it?

We should probably ensure this is an object, and perhaps test that it's a non-empty object to avoid the unnecessary operation?

Copy link
Contributor

Choose a reason for hiding this comment

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

The values of evaluated and props should always be objects. But we should do a falsy check here.

Copy link
Collaborator Author

@atticoos atticoos May 27, 2017

Choose a reason for hiding this comment

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

Agreed, but just like the dynamic styles, it sounds good to safeguard scenarios where the user may have a conditional return value, which could result in null

glamorous.input(props => {
  if (props.foo) {
    return {color: 'blue'}
  }
  return null
})
glamorous.input.withProps(props => {
  if (props.foo) {
    return {type: 'number'}
  }
  return null
})

That's technically valid, isn't it? Having an escape hatch where there might be an edge for the user where they might say "if the prop isn't x, then we have nothing to do here"

Kinda like

render() {
  const {foo, ...rest} = this.props
  if (foo) {
    return <Input {...rest} type="number" />
  }
  return <Input {..rest} />
}

Copy link
Collaborator Author

@atticoos atticoos May 27, 2017

Choose a reason for hiding this comment

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

Ah, by "should always be an object", I realize you mean more that it shouldn't be an array, string, etc -- not saying that null isn't a valid return value 😃

allows composing components with predefined static or dynamic properties

Reference #133
Copy link
Contributor

@kentcdodds kentcdodds left a comment

Choose a reason for hiding this comment

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

Implementation looks great! Let's get some docs 👍

@atticoos atticoos force-pushed the with-props branch 3 times, most recently from c3151f8 to a978804 Compare May 27, 2017 19:50
@atticoos
Copy link
Collaborator Author

atticoos commented May 27, 2017

I've added the docs as a subsection to glamorousComponentFactory below ...styles. Let me know if there's anything I can improve on those

Copy link
Contributor

@kentcdodds kentcdodds left a comment

Choose a reason for hiding this comment

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

Just a couple of thoughts...

@@ -299,6 +299,39 @@ const MyStyledDiv = glamorous.div(
<MyStyledDiv /> // styles applied: {padding-top: 1, padding-right: 2, padding-bottom: 3, padding-left: 4} and anything coming from `extra-thing`.
```

##### withProps
Copy link
Contributor

Choose a reason for hiding this comment

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

I just had a thought. Do you think that people may get confused and try to do:

const MyStyledInputWithProps = glamorous.input({
  padding: 10
}).withProps({type: 'text'})

// or maybe:

const MyStyledInput = glamorous.input({
  padding: 10
})

const MyStyledInputWithProps = MyStyledInput.withProps({type: 'text'})

In interested of that, perhaps we should support both?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think supporting both would be convenient - would your concern in #145 (comment) apply here though?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I've been thinking about it more and I think that I'm ok with doing this after all...

Copy link
Collaborator Author

@atticoos atticoos May 27, 2017

Choose a reason for hiding this comment

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

It does follow familiar patterns in libraries like ImmutableJS, where you use the core API to create the subject, and you can build changes by working against the subject's API itself, and not necessarily on the core's factory API

var map = Immutable.Map({foo: 'bar'}) // factory API
var map2 = map.set('bar', 'foo') // subject API

So, similarly:

var Component = glamorous.div() // factory API 
var Component2 = Component.withProps({foo: 'bar'}) // subject API

I do like this, as it opens the door for us to work with both in the same manner:

var Component = glamorous.div({backgroundColor: 'blue'})
var Component2 = Component.withProps({foo: 'bar'})
var Component3 = Component2.withStyle({color: 'red'})

Giving a consistent behavior to with* APIs.

Copy link
Collaborator Author

@atticoos atticoos May 27, 2017

Choose a reason for hiding this comment

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

I still want to think about this before committing, maybe bring in a few people to vote.

I like both, but they introduce different convention. There's some pluses and minuses I see for both. I'm going to pause on this and write up my thoughts comparing both approaches to share in the main thread of this PR later tonight or tomorrow.

README.md Outdated
// styles applied: {padding: 10}

<MyStyledValidatedInput creditCard />
// renders: <Input type="credit-card" pattern="/([0-9]{4}){4}/" />
Copy link
Contributor

Choose a reason for hiding this comment

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

This should render <input rather than <Input, also I'm pretty sure that credit-card is not a valid type. Which is why the pattern is good :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ha good catch

@kentcdodds
Copy link
Contributor

I think we're going to opt for an integration example with recompose rather than implementing it as a built-in. Unless I'm missing something, that should be enough right?

@atticoos
Copy link
Collaborator Author

atticoos commented May 30, 2017

recompose looks fantastic. I don't see a huge benefit for incorporating this PR after seeing their library, which provides all the utility you might need to compose a HOC. Slippery slope - right? If we do withProps, where does it end? withState becomes asked for next?

I agree that the right move here is to promote the usage of recompose for situations like this, an example of recompose.withProps would be terrific.

I'm going to close this 👍

@atticoos atticoos closed this May 30, 2017
@kentcdodds kentcdodds reopened this Jun 14, 2017
@kentcdodds
Copy link
Contributor

kentcdodds commented Jul 5, 2017

Ok, with #175 done, I think we're ready to give this another go. Based on discussion in #133, I'd like to support this API:

import glamorous from 'glamorous'

const withPropsObject = {propA: 'a', propB: 'b'}

// withProps functions accept the same arguments that glamorous style functions accept.
const withPropsFunction = (props, theme, context) => ({propG: 'g', propH: 'h'})

// we'll call these withPropsThing, meaning when you see withPropsThing it could be either a function or object

const Div1 = glamorous.div({}).withProps(withPropsThing)
<Div1 /> // renders div with the props, filters out invalid `div` props like we do currently.

What do we need to do to make this happen? How can I help you @ajwhite?

@atticoos
Copy link
Collaborator Author

atticoos commented Jul 5, 2017

The most actionable item here is to expose theme and context to the withProps component wrapper. Happy to accept contributions to the existing work, otherwise I can likely turn something around within the next few days

@kentcdodds
Copy link
Contributor

How can we help get this finished? I'd love to release this with the next major version

@kentcdodds
Copy link
Contributor

I'm going to go ahead and finish this 👍

@kentcdodds
Copy link
Contributor

This is superseded by #255

@kentcdodds kentcdodds closed this Jul 27, 2017
@atticoos
Copy link
Collaborator Author

Thanks for bringing this across the finish line, been a busy week and was on vaca!

@kentcdodds
Copy link
Contributor

Sure thing! Thanks for all the work you've done!

@kentcdodds kentcdodds deleted the with-props branch July 27, 2017 21:00
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants