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

Why are interactions and custom elements global? #128

Closed
KidkArolis opened this issue Jun 4, 2015 · 111 comments
Closed

Why are interactions and custom elements global? #128

KidkArolis opened this issue Jun 4, 2015 · 111 comments

Comments

@KidkArolis
Copy link

I just started playing with Cycle and it looks really cool!

To me, two things immediately surprised me, so I was wondering if I'm missing something.

  1. interactions.get(selector, eventName) in all examples I've seen pass in a selector (not a dom element) which means those selectors are queried globally in the entire DOM. Surely this is really impractical, you want to scope it all down to specific components, the way you can easily do with React's inline handlers? Wouldn't it be better if say you handle events in the view and then emit to the interactions stream the low level event or a higher level intent?
  2. registerCustomElement seems to register elements globally. Again, this is, imho, mush inferior to how something like React does it where you require components and use them by reference. Is there a way to do that in Cycle? `var Button = require("./button"); Button({}). Sorry if this is documented somewhere, I couldn't find an example/docs.
@staltz
Copy link
Member

staltz commented Jun 5, 2015

Hi, nice that you like it.

interactions.get(selector, eventName) in all examples I've seen pass in a selector (not a dom element) which means those selectors are queried globally in the entire DOM. Surely this is really impractical, you want to scope it all down to specific components

The contract in Cycle should be that selectors are not queried globally because they shouldn't inspect inside a custom element. It also doesn't select from the window as a parent, it selects scoped to the container div you gave to applyToDOM. So it should have both upper and lower boundaries.

Wouldn't it be better if say you handle events in the view and then emit to the interactions stream the low level event or a higher level intent?

There is a reason why events are not handled in the "view". View should only be concerned with rendering, i.e. how things look. Event handling is how it works. Read more here. On the other hand, after #116 is ready, it will be possible to make a React adapter which works like you want. But I recommend first thinking why do you say something is impractical, and why is the other way better. For instance inline styles are often seen as a bad idea, but vjeux explained to the community that sometimes such as in the Virtual DOM case it might make more sense, and might be better.

registerCustomElement seems to register elements globally. Again, this is, imho, mush inferior to how something like React does it where you require components and use them by reference.

This will change with #116, custom elements will not be registered globally with a mutable API, but they still will be specified at the top-most level, and given down through the view hierarchy. If you think it is inferior, please explain why. And again, with #116 it will be possible to have a React adapter and do this the way you want.

@ivan-kleshnin
Copy link
Contributor

For instance inline styles are often seen as a bad idea, but vjeux explained to the community that sometimes such as in the Virtual DOM case it might make more sense, and might be better.

I wonder how do they propose to style nested tags with this "inline styles" approach? Anyone knows? CSS works how it does (everything is global) because it allows you to traverse into component and change it's inner styles from your app stylesheet.

@staltz
Copy link
Member

staltz commented Jun 5, 2015

You can give styles as props, for instance.

@ivan-kleshnin
Copy link
Contributor

That's the thing. Your only way to style nested tag without global CSS is to implement an extremely clunky namespacing solution on your own. I doubt anyone will succeed here for anything bigger than hello-div.

Most web components (in broad terms) contains tenths of nested tags.
With CSS I can easily access any tag inside this structure. I need to know only top-level class name (tag name, whatever).

<div class="clockworkOrange">
  <div>
    <div>
      ..
    </div>
  </div>
</div>

Did I miss the moment restyling of 3-rd party components had become uneeded? I guess no.

How to do this through props?

In app:

<ClockWorkOrange 
  firstDivFromTheTopStyle={firstDivFromTheTopStyle} 
  secondDivFromTheTopFourthSiblingStyle={secondDivFromTheTopFourthSiblingStyle} 
...omg/>

And in library:

<div>
  <div style={firstDivFromTheTopStyle}> 
    ...
    <div style={secondDivFromTheTopFourthSiblingStyle}>
      ...
   </div>
  </div>
<div/>

This is still not gonna work for repeating items... 😞
So I'm very curious why React team is so pushy in enforcing us this local-style vision.

Because React native does support only local styles? That's their limitation. Desktop limitation.
They should better emulate CSS stylesheet there somehow. I'm sure this is possible.

Same thing with Webpack CSS "local scopes". They are worthless in practice. I dunno... maybe there are so many technologies today that people are just starting to loose the track of what they do.

@gaearon
Copy link

gaearon commented Jun 5, 2015

@ivan-kleshnin If you haven't found a way to make something work, it doesn't make it "worthless". People including me have built, and are building large apps that don't treat CSS globally and rely on props for everything. It works surprisingly well if you embrace the component mentality and create smaller components. In fact you'd rarely, if ever, need to pass explicit styles, and instead control most of the appearance via vanilla props. Encapsulation is a win, nothing clunky about it. For off the shelf components, if you need that custom rendering, a better idea is to let you provide renderItem method like fixed-data-table does.

@ivan-kleshnin
Copy link
Contributor

@gaearon so you just verified there is no other way except props 😞

I don't believe this is how the future should look like.

small component mentality

are just marketing words. In reality most of the people still use jQuery because it's the only way to have file loaders and other huge libraries available for them. Not every company has a budget and time to just stop everything and begin to reimplement every library they need in React. People use Angular not because it's better. Because it's more production ready and have all that matherial design libraries already developed and ready-to-use.

"Small components" hype is the same thing as "small modules" hype.
I remember you were wise saying "what is better – depends on the context".

For off the shelf components, if you need that custom rendering, a better idea is to let you provide renderItem method like fixed-data-table does.

Everyone will need that at some moment. That's the problem.
I just don't like when people hide drawbacks for PR or marketing reasons.
Don't we deserve to know the truth about both sides of the coin?

In fact you'd rarely, if ever, need to pass explicit styles, and instead control most of the appearance via vanilla props.

Web is about sharing and restyling. How many props would be enough for video player to
get the desired "look and feel"? Are we going to replicate all famous desktop styling issues in web and call this "progress"? Maybe local styles will win. I dunno. I just want an open discussion instead of declarations. For now we're getting declarations mostly.

If you haven't found a way to make something work, it doesn't make it "worthless".

Of course it doesn't. That's why I'm asking people how to solve my problems.

@gaearon
Copy link

gaearon commented Jun 5, 2015

"Small components" hype is the same thing as "small modules" hype.

I'm not selling you anything and I don't care about hype. I'm sharing the experience I gained building a complex webapp with complex styles and UI in a team together with a designer in React. What I'm saying is, if you approach components as large templates, like people usually do Backbone views, you're going to have the problems you described above. If you make smaller components and use composition more, you'll find that the <Button color='red' fill /> is much easier to use across the codebase than <Button styles=... />. And that expressing bigger components in terms of smaller components with existing styling is much more convenient than using global CSS styles. But surely you're free to not take my word and insist “composition” is a marketing buzzword I'm trying to fool you with. Whatever :-).

Web is about sharing and restyling. How many props would be enough for video player to
get the desired "look and feel"?

It's a trick question. This is “smart and dumb components” all over again. “Video player”-like stock components made sense in jQuery land, but I don't think they make any sense in React land.

With React, you probably want a <Player> component that manages <video> without any styling and has isPlaying, currentPosition and onChange props so you can build any custom UI for it in an hour. Somehow I wrote a drag and drop library that makes zero assumption about how your components render. fixed-data-table doesn't make those assumptions either. This is because such components are “controllers”.

On the other side of the spectrum are stock components whose only point is to provide style, not functionality. Think “Material design” components. For them, it doesn't make any sense to override the styles because the only reason you'd use them is for their consistent styling.

So yes, “restyling” a component that tangles functionality and styling is hard. But that's a strawman because such components don't make sense in React. Create <Player> and <MyBeautifulPlayerControls>, or <Player> and <PlayerControlsUsingMaterialDesignUnderTheHood>.

PS If you'd like to continue this argument, please create a Gist so we don't spam the Cycle issue tracker.

@ivan-kleshnin
Copy link
Contributor

I'm not selling you anything and I don't care about hype. I'm sharing the experience I gained building a complex webapp with complex styles and UI in a team together with a designer in React. What I'm saying is, if you approach components as large templates, like people usually do Backbone views, you're going to have the problems you described above. If you make smaller components and use composition more, you'll find that the is much easier to use across the codebase than . And that expressing bigger components in terms of smaller components with existing styling is much more convenient than using global CSS styles. But surely you're free to not take my word and insist “composition” is a marketing buzzword I'm trying to fool you with. Whatever :-).

Imagine that you are a developer and business tells you to integrate this shiny new video player they've just purchased into their site. They require you to be "pixel-perfect". That player is built from small components just as guru teach us.

<VideoPlayer>
  <VideoPlayerHeader>
    ...
  </VideoPlayerHeader>
  <VideoPlayerFooter>
    ...
  </VideoPlayerFooter>
</VideoPlayer>

And then you're starting to tweak it...

<VideoPlayer margin={50} fontFamily="Roboto" lineHeight=...>
  <VideoPlayerHeader pauseIconColorBackground="yellow"...>
    ...
  </VideoPlayerHeader>
  <VideoPlayerFooter ...>
    ...
  </VideoPlayerFooter>
</VideoPlayer>

I wish you good luck doing that.

Don't you extrapolate your specific one-project experience to all cases of all people in the world? Did you use 3-rd party components? What were your styleguides? Did you fallback to global styles every time you met a problem I described and just considered that "edge-cases"?

There is an enterprise world. Like Facebook where they may create everything they need. Even Immutable library instead of Mori. Because of infinite budget. Or take Google+ created just for fun. That's ok. There is also a world of a small and medium business. Which does have limitations. And that is ok again. Problems appear when people start to disguise a strictly enterprise solution for a "normal", "standard" or "definitely better".

“Video player”-like stock components made sense in jQuery land, but I don't think they make any sense in React land.

What are Web Components then? They may be implemented in a "revealing" style for "better control".

<Container><Item/></Container> vs <Component/> 

In any case, that does not provide a 1% of the flexibility you have with CSS stylesheets.

But that's a strawman because such components don't make sense in React.

Are you talking about React or about your own vision of React?

PS If you'd like to continue this argument, please create a Gist so we don't spam the Cycle issue tracker.

I don't think this is a spam. This is important and is related to Cycle components to the same matter as to React. I want to receive more feedback from people about local styles. If you don't have arguments, that's normal. You can unsubscribe from this thread if you're bullet-proof in your opinion.

@ArnoBuschmann
Copy link

I just feel the urge to mention, that I extremely appreciate to read the opinion of you both, @ivan-kleshnin and @gaearon. There is no need at all to attack of feel being attacked. It's all about solutions and many ways lead to rome.

Actually I were thinking about on how to handle CSS best in Cycle these days. Thanks again for this discussion, it is interesting and related, so stay and keep it going right here ;)

@gaearon
Copy link

gaearon commented Jun 5, 2015

Styling third-party component with custom global CSS is an escape hatch. Escape hatches are cool for solving the problem you described (you are given a black box with bad API and you are ordered to use it).

Another example of a similar escape hatch is monkeypatching. Nobody likes monkeypatching but it's essential for some use cases when you're given something that doesn't work exactly like you want, and offers no extension points.

But this doesn't mean monkeypatching should be the primary means of extending something. Overriding third-party CSS is conceptually the same thing as monkeypatching. Suitable in some situations but you should try to avoid it if possible.

Did you use 3-rd party components? What were your styleguides? Did you fallback to global styles every time you met a problem I described and just considered that "edge-cases"?

We had no styleguides. Our designer worked directly on the components. The styleguide was to use that component palette. This ensured consistency.

Sometimes we used “pre-React” third-party components that had their own styling. Yes, we falled back to patching global styles for them, but wrapped them into React components that encapsulated this implementation detail and had a declarative API. The rest of the codebase would then use React components. When we had time, we'd reimplement these components in React.

We never needed to use third-party React components with built-in styling. I never found them valuable. To have consistent styling, we needed to use our own component palette (that includes our <Button>, etc). We used third-party React components for functionality (think data table, actual video player). They had injection points for rendering custom rows / panels / whatever. It's too trivial to implement “player controls” with declarative paradigm that I don't see any value in using stock ones. (Unless you're forced to do so, in which case, as I said above, you're welcome to use global styles, but yes, it's an escape hatch just like monkeypatching is.)

What are Web Components then?

WCs don't solve my problems. I don't know.

If you don't have arguments, that's normal. You can unsubscribe from this thread if you're bullet-proof in your opinion.

This is an interesting argument, I'm just not sure if it matches what the thread was originally about.

cc @markdalgleish

@gaearon
Copy link

gaearon commented Jun 5, 2015

@radubrehar
Copy link

I see the css-loader as a solution for styling components in isolation and also providing a way for users of the component to easily extend them.

Say I have a datepicker (which I really do, check out https://www.npmjs.com/package/react-date-picker). I will add both a autogenerated className and a component className. I illustract this with some code:

:local(.picker)
     //picker styles

     .inner
           //picker inner styles
var ClassName = require('./index.styl')
var DatePicker = React.createClass({
    render(){
             var className="date-picker " + ClassName.picker
             return <div className={className}>
                 <div className="inner">
                      //....
                </div>
            </div>
    }
})

In this way, I am certain that my component styles are really isolated (since all my selectors are nested inside the autogenerated/prefixed one), and people can also customize whatever they want (since I also add the static "date-picker" class.

I see this as a very good mix. What's your opinion.

PS: I also use inline-styles quite a lot :)

@natew
Copy link

natew commented Jun 5, 2015

I think Pete is getting closer, but @ivan-kleshnin is right about the style issue with tags. This is a problem with inline style encapsulation.

In my experience (writing a UI framework) I ended up having to "tag" all of my inner elements and have a syntax for people to style them, for example, say a Post component:

<div {...props()}>
  <h1 {...props('title')}>Title</h1>
</div>

And then built a mixin of sorts that resolves these if you pass in a styles prop:

<Post styles={{ self: { background: 'black' }, title: { fontSize: 20 } }} />

Because even in small components you would lose this power. Now, given it's a UI kit it's on the extreme. But I think this is important. If I want to borrow any visual component from any npm library right now in React, unknown how I'll be able to style it... props, import CSS, some custom library.

I think this is a big gap in React (it's supposed to be the View right?), and Pete's example actually illustrates it. Now, my hack is not great either. You have to explicitly use a method, it's inconsistent, and more.

Now, this sounds off topic but is related: why are we using <div> everywhere? It has no meaning, and so many of our tags are just <div> or <span>. I'd like to do this for the example above:

<post>
  <title>Title</title>
</post>

And have a standardized way for that to be styled. With small components you can easily have everything unique. If you need to use semantic tags like h1 you can (and they can be styled!). Class names should still be used in the right situation and handled, so I can pass in styles and they all apply automatically.

Just thoughts from experience and experiments in this area.

@gaearon
Copy link

gaearon commented Jun 5, 2015

I just can't see “styling” as something separate from “generating markup”. What's the point in being able to attach styles to a <div> if you can't, for example, wrap it into an extra <div> for some flexbox styling?

Thus

If I want to borrow any visual component from any npm library right now in React, it's a total surprise how I'll be able to style it.

becomes

“If I want to borrow any visual component from any npm library right now in React, it's a total surprise how I'll be able to change how it renders.”

And the answer is: use the stock components that let you inject the rendering. <List renderItem={this.renderItem} /> is fine, probably with some default renderItem implementation. Don't use stock components that don't let you inject the rendering if you want to customize it later—it's a recipe for disaster.

No?

@radubrehar
Copy link

I totally agree to using props as render/factory functions. It provides people with the flexibility they need to either change styling or bring in a totally new markup!

@natew
Copy link

natew commented Jun 5, 2015

The view layer should handle style. If it did that properly there would be no argument, I could use anyone's logic and markup, something made with love (and accessibility, browser support, etc) and still style every piece of it I need.

If I want to use a really well designed Button and change one color or tweak the font size, I should be able to, no?

@gaearon
Copy link

gaearon commented Jun 5, 2015

If I want to use a really well designed Button and change one color or tweak the font size, I should be able to, no?

A well-designed <Button> would probably accept a single tint prop and adjust all its colors based on it (e.g. shadow colors, border colors, etc.) This is proper encapsulation. This is how it works in iOS too. If this customization is not enough, why do you need to use that particular <Button> at all? Clearly it isn't what you wanted..

As for the font size or family, a well designed <Button> shouldn't specify it at all. Thus, the parent element (or even <body>) decides its font size and family. I'm not advocating “no CSS inheritance at all”—it can be useful in situations like this. I'm just advocating “no overriding”.

If particular styles are important to component (such as color), it should expose them as props (such as tint). It particular styles are not important to component (such as fontFamily), it shouldn't specify them at all.

Finally, as the ultimate escape hatch, component may accept styles prop with fields like header, border, content, playerButton etc. This makes such overrides explicit part of component's API.

<SuperFlexibleVideoPlayer styles={{
  pauseButton: { backgroundColor: 'yellow' },
  container: { display: 'flex' },
  ...
}} />

@natew
Copy link

natew commented Jun 5, 2015

I can see that point, though I see no downside in a component system that let you style any element inside a component you import. Only upsides, potentially pretty big just from personal experience.

What if I want to add a inner shadow to the Button? Or round some of the edges based on where I put it? It seems there are a thousand little ways I may want to style it, and to make the authors have consider them all puts the burden in the wrong place.

In other words, a let's make it really easy to have a well-designed Button. It should just get the logic right and the markup. If I like how it works I should be able to use it any way I like, without having to inspect if it's "well designed" enough for me to style it or not.

Edit: (made some edits / added last paragraph after publishing)

@ivan-kleshnin
Copy link
Contributor

@ArnoBuschmann I just took a walk yesterday and was striked by the thought about local styles.
I wanted to hold on with a new hot debates after my last one in Ramda repo 😄 but Andre mentioned local styles and I could not refrain myself.

Also relevant: https://github.com/petehunt/jsxstyle#what-about-re-theming-off-the-shelf-components
What about re-theming off-the-shelf components?
This is a bug in CSS, not a feature.

Yes. Pete Hunt is always aware how to make our lifes better. Even when against our wills.

Styling third-party component with custom global CSS is an escape hatch. Escape hatches are cool for solving the problem you described (you are given a black box with bad API and you are ordered to use it).

I believe bad API is exactly what you're defending. Component API that exposes styling through attributes is bad. Global CSS is fundamental. I'll give you one example.

There is a popular organizer service Trello.com.
It's quite good but has some issues with UI. People released a lot of browser plugins which improve it's look and feel. I use one of them. This is something you're fundamentally can't do in desktops.

You can't even dream of turning off annoying icons in OSX or recoloring them unless Apple will be so kind to allow you to do it.

And all this is possible thankfully to the global CSS.

Another fundamental reason. Let's be fair, most of the 3-rd party components are broken to some degree. The are either mobile unfriendly or have some issues with older browsers.
You always need to "tweak" them until they work. Unless you're using only your own components.
But this is again "not-invented-in-microsoft" and all about enterprise.

This approach of "hardcoding" styles and letting them go "encapsulated" is so overconfident...
Approach "Provide only behavior, don't provide styles ever" should be critized separately.

In this way, I am certain that my component styles are really isolated (since all my selectors are nested inside the autogenerated/prefixed one), and people can also customize whatever they want (since I also add the static "date-picker" class. I see this as a very good mix. What's your opinion.

@radubrehar If you expose class names that is a classic solution with global CSS. You leave the chance to fix your errors to us. The question is how to achieve the same effect with local styling only?

I just can't see “styling” as something separate from “generating markup”. What's the point in being able to attach styles to a

if you can't, for example, wrap it into an extra
for some flexbox styling?

CSS has it's limitations, that's true, but it evolves. All that wrapping issues are mostly coming from the broken padding / margin calculations from the early ages of the web. From the broken box model before border-box. One div is enough to do an amazing bunch of things. See how Semantic UI has the same markup for horizontal, vertical and inline forms.

I mean... people created a great tool for us. CSS. It's still underestimated and unexplored in power.
Browser plugins are still just miserable shadows of what they can be. Of what they should be.

Don't use stock components that don't let you inject the rendering if you want to customize it later—it's a recipe for disaster.

You can have default styles now, thank gods to Webpack. This styles should be in a separate file.
Not imported by component, but exposed to you. Than you can: apply this global stylesheet, override it with your own rules. You also can parse this file and replace everything in it with something like PostCSS. The only drawback is a global CSS nature.

We're starting to mix two things here. There are TWO approaches I criticise:

Local styling (you-don't-need-styles)

This is Evil. I already put my arguments against.

Zero styling & Zero markip (don't-provide-css-and-html-just-inject-render)

This approach is ok for a lot of cases.
I don't want to blame it too much.

But still. Time is money. I want to use Matherial Design. I want to have both behavior and styles available from the start.

Because both are extremely hard to get right for all use-cases (which are growing). Demands are growing as well. And I also want to be able to tweak it and to be able to fix what is wrong about visual aspect. Because design is the most specific thing you have. You basically never repeat it.

If all that are "corner cases" then the world is built from them.
I prefer to accept Reality rather than struggle against it.

And I believe Future is about customization of everything.

@gaearon
Copy link

gaearon commented Jun 5, 2015

It's up to you as component author to decide on the granularity of the style overriding that you think makes sense for your component.

Maybe the user will be satisfied with tweaking styles for particular DOM elements so you “tag” them and let user provide styles or classNames per “extension point”. Or maybe anticipate the user to want to wrap your <div>s or change them to <p>s, and you might provide renderButton as a prop to <PlayerControls>.

Or maybe you'll anticipate that the user wants to change a lot of stuff, so you export <Button>, as well as <Border>, <Touchable> that it's composed of, so they can be used independently.

It seems there are a thousand little ways I may want to style it

The whole point of having a consistent UI is not to have a thousand ways to style the same thing. Of course creating arbitrarily styled components is not very convenient. But generally you'll want to have a single <Button> in your project with props that make sense to you (e.g. with a rounded prop), and maybe you'll use a stock component with injectable styles to implement it inside. Throughout the project, however, you won't need to care about this anymore.

@ivan-kleshnin
Copy link
Contributor

The whole point of having a consistent UI is not to have a thousand ways to style the same thing.

Exactly. If your site has a module size (line height) of 16px you do want to override every your component to look the same. Set correct font family. Repeat border radius. I have a good imagination and still can't imagine how to replicate all this through props.

This approach works only if the web is built from granular, perfect, production ready, polished kits of elements. Nothing like I see around. There are a bunch of matherial design implementations. Bunch of Bootstrap-like clones. One of them has buttons you need. Other – dropdowns.
All come with tiny visual differences and quirks you are required to handle. And you can.
Unless they hardcoded it with local styles.

@gaearon
Copy link

gaearon commented Jun 5, 2015

Set correct font family.

I'm not idealistic. Do this in your “reset” stylesheet once for your whole website. (As well as setting the root font size.)

Repeat border radius.

All your components may require a common constants file that has this border radius defined. Whether they set this border radius directly or inject into stock component's styles, is implementation detail of your components.

@natew
Copy link

natew commented Jun 5, 2015

@gaearon seems like you are defending that there couldn't exist a better way to handle styles than there is in React? I think all @ivan-kleshnin (or at least I am) saying is there could be a better way and that would be something interesting to discuss.

There are some 18 CSS-in-JS libraries for React now. Plus hundreds of others all doing things inconsistently. It's an area React doesn't handle, which is totally fine (it makes it very flexible). But Cycle could handle it in some way, and I would argue that could dramatically improve how people share components.

@gaearon
Copy link

gaearon commented Jun 5, 2015

These are three different approaches:

  • Compose your app out of a palette of encapsulated components you fully create yourself.
  • Compose your app out of stock components.
  • Compose your app out of stock components, but customize little things in their appearance.

The first approach works best for products with an established brand and identity. (Can you imagine Facebook or Airbnb using stock visual components?) React's encapsulation model works great here.

The second approach is as easy. If you build an internal dashboard, just use Bootstrap. No need for customization. Again, React's encapsulation model works great.

The third approach is “I want to have my cake and eat it too”. CSS and global styles kinda solve this problem, but in return you get all the drawbacks of wanting to both reuse and extremely customize something. You have harder versioning, things breaking other things for no apparent reason, complex overrides, etc, but get what you want in return. It's a tradeoff you choose.

All approaches are valid. There is no single “future”. Either you choose encapsulation (first two approaches), or you choose fragile flexibility (third approach). You can even mix them.

Of course, I prefer the first approach, but I can see why people might choose the second or the third one. It's just wrong IMO to say “style encapsulation doesn't work!” because it's up to you to choose your tradeoffs.

That's probably all I wanted to say in this thread.

@ivan-kleshnin
Copy link
Contributor

I'm not idealistic. Do this in your “reset” stylesheet once for your whole website. (As well as setting the root font size.)

Ok, that's good. But what about people saying "forgot about global CSS"? I have already meet them.
What about people proposing to make local scopes default in Webpack? They tend to think they can do everything with local styles. And this sounds weird to me.

All your components may require a common constants file that has this border radius defined. Whether they set this border radius directly or inject into stock component's styles, is implementation detail of your components.

Wait. A few posts ago you've said styles are excessive, props are enough. How to pass styles to some nested tag without some mad namespacing solutions, again?


I believe everything is about fallbacks. @gaearon is right that sometimes CSS is not enough and you do want to replace HTML. But should we base our conclusions on special cases or on common cases. I believe common ones are better. I have nothing against replacing "render" functions if it's required.
I'm only (and strongly) against limitations in the spirit of you don't need this.

Ideal solution (I mean all of this should be at the same time):

  1. Provide component with some HTML.
  2. Accept most common (numeric or string, not CSS-specific) settings through props
  3. Provide separate "default" stylesheet.
    Possible to parse and tweak. Possible to replace alltogether. Possible to override with CSS.
  4. Provide ways to replace rendering. Can be solved by monkey-patching.
  5. Provide ways to tweak logic. Use function input arguments
  6. Provide ways to replace logic. Can be solved by monkey-patching.

What's wrong?

Either you choose encapsulation (first two approaches), or you choose fragile flexibility (third approach).

You forgot to put "rigid" adjective before "encapsulation" noun.

@gaearon
Copy link

gaearon commented Jun 5, 2015

seems like you are defending that there couldn't exist a better way to handle styles than there is in React?

No. I just don't see how this is a problem about styles, or about React. The way I see it, it is a problem about passing parameters in the functions, or letting functions read global variables instead for extra customization.

@ivan-kleshnin
Copy link
Contributor

The way I see it, it is a problem about passing parameters in the functions, or letting functions read global variables instead for extra customization.

That's because you're mixing visual and behavior paradigms. Visual is more variable in the orders of magnitude. That is like passing 100 arguments to each function. I bet you will choose globals than.

@gaearon
Copy link

gaearon commented Jun 5, 2015

Visual is more variable in the orders of magniture. That is like passing 100 arguments to each function.

I think working with a designer who found a way to create a few basic components with a few custom props that were used throughout the app really changed the way I see it. Customization may be great, but it's good to put a rigid facade in front of it so the rest of the app doesn't care.

@gaearon
Copy link

gaearon commented Jun 5, 2015

Ok, that's good. But what about people saying "forgot about global CSS"? I have already meet them.
What about people proposing to make local scopes default in Webpack? They tend to think they can do everything with local styles. And this sounds weird to me.

Default doesn't mean you can't override it. Default just means “exceptional cases look exceptional”. When you need a global style, do this explicitly. I think local-by-default is sane because it keeps everyone more aware of what is global, make globals visible in code reviews, so they are less dangerous and likely to bite.

Whatever's default, people will use the most, and in most cases (not the styling-stock-component case which isn't very common in a large app compared to styling internal components), the locals are better.

@peterjoel
Copy link

Thanks for responding.

I understand View as the translation function from state to screen pixels

I don't think this is reasonable in the context of web technologies. We operate on the DOM, not on pixels. The DOM has interactive behaviour intrinsically tied to its elements, whereas pixels do not. Working with existing component structures (e.g. React elements) is a reality for anyone who wants to make something based on real life requirements, as opposed to toy examples.

If I use a 3rd party Grid component, and I need to respond to a cell selection, I want to use a high level event, which is part of the component's API. I cannot imagine trying to figure out a selector to reliably identify cells, and then somehow map click events back to the data they represent. Even if you can pull that off, bug-free, good luck maintaining it one year from now.

I am guessing you haven't watched this: https://youtu.be/1zj7M1LnJV4 it explains the "why" of everything in Cycle.

I haven't. I will watch it before making any more comments.

@pH200
Copy link
Contributor

pH200 commented Jun 16, 2015

If I use a 3rd party Grid component, and I need to respond to a cell selection, I want to use a high level event, which is part of the component's API. I cannot imagine trying to figure out a selector to reliably identify cells, and then somehow map click events back to the data they represent. Even if you can pull that off, bug-free, good luck maintaining it one year from now.

Well, the documentation isn't clear on how to work with events. Let's say you have a grid component like this:

<Grid onCellSelected={...} />

In the latest Cycle.js (not Cycle-React), you will subscribe events like this:

function app({dom}) {
  // '.jsevent-' is just a convention for the key of event mapping
  var cellSelectedObservable$ = dom.get('.jsevent-grid', 'onCellSelected');
  // ...
  return {
    dom: stateObservable$.map(state => <Grid className="jsevent-grid" />)
  };
}

So, you can use the high level event from the component, and it doesn't break as long as you don't change the element's className. Many people have questioned about the usage of className. And Staltz has explained why he took className for events.

@jessehattabaugh
Copy link

I share @peterjoel's concerns over separating intent definition from the view. My complaint is mainly that it isn't ergonomic for the developer. Declaring a button in one place, and what happens when it's pressed in another feels disjointed.

I know you want to treat the user as a function, but users don't take arguments or have return values. They just generate anonymous UI events. We have to label these events and give them context in order to affect the user's intent. Like it or not, web developers have done this inline with the declaration of UI since 1995. That may violate the principles that you set out to build Cycle with, but I'm not sure it really violates the principle of reactivity.

If a View module outputs DOM with event listeners attached and those event listeners interactively change the Model; that's not reactive, but if all the event listeners do is trigger specific action events with context data then the Model can listen for those and react. I suppose you wouldn't want to couple the Model to specific DOM events, but if you think of DOM events like the Observables you'd otherwise be returning from the Intent layer, it's not that different architecturally. It just kinda cuts the User function out of the equation.

@staltz
Copy link
Member

staltz commented Jun 16, 2015

I know you want to treat the user as a function, but users don't take arguments or have return values.

Yes they do and in my presentation I explicitly denotated those arguments. Input argument is an Observable of VTree, and return value is an Observable of DOM events (what you called "anonymous UI events").

We have to label these events and give them context in order to affect the user's intent.

The events are well labeled. It's a pair of selector and eventType. Instead of naming event channels, you just name the container element which emits that event, and you name the event itself. There isn't much difference other than taste/preference.

It just kinda cuts the User function out of the equation.

The User function is there no matter what. Cycle is a framework that acknowledges that instead of using a different abstraction.

@jessehattabaugh
Copy link

I still think that most developers are going to prefer to label the events in the place where they originate, and not after they occur. Especially entry level developers who are often the ones working on the View layer.

@staltz
Copy link
Member

staltz commented Jun 16, 2015

most developers are going to prefer

I'm going to repeat my core argument I mentioned in this thread:

given that inline styles is a better solution for Virtual DOM approaches, we can use classNames (and/or ids) to give names to vtree elements, so that they can be observable on all kinds of interaction events without having to alter the vtree element's implementation. This follows the open-closed principle. If you need to change logic code to grab new kinds of events from the vtree element, you don't need to modify code in the vtree element. Because vtree element implementation should be concerned only with "how it looks", and because we want to follow the open-closed principle. Another problem with naming only specific events on the vtree element is that whenever you want to refactor/modify it, you do not know who is using those specific named events, or if they aren't used at all, hence you need to search throughout the code for all usages of those named events, and this is leakage of responsibility. Event handling responsibility should be separated. None if this is "I feel it is better".


Especially entry level developers who are often the ones working on the View layer.

Cycle.js already has a learning curve and that's RxJS. This isn't a framework that beginners will pick up easily. And that's the disadvantage of this framework. Other frameworks such as Ractive.js are focused on beginner-friendliness. RxJS is all about a mindset change. Once you overcome the paradigm shift, everything starts to make sense. And the selector-based intent events handling is one of these paradigm shifting challenges. The issue itself isn't hard or complex, it's just the mindset change which is hard because people are used to thinking in a certain way. Cycle.js is already a paradigm shift, making this change won't remove that, but it will water down the purpose and goals of the framework.

@jessehattabaugh
Copy link

I appreciate your idealism, seriously :-)

@peterjoel
Copy link

I think discussing learning curves is a distraction. There is a shift happening towards FRP and functional style in general, so this will become less of a problem. The ideas aren't hard, they're just different from what people are used to. Let people make wrappers and adapters if they are concerned about that, but don't compromise the core ideas now.

@ivan-kleshnin
Copy link
Contributor

I would also re-iterate that the general site / app reset / normalisers are just local style to the top level of your html tree.

There are rules like * { box-sizing: border-box } which you have to apply with * selector.
How would you classify them? 😉

Interesting discussion - doesn't look like an ideal solution exists yet. Maybe soon

The important problem, totally not solved at this moment, is how to pass variables between CSS layer and JS layer. A set of a component styles more often than not contains a derivative of a whole site styling. Border radius of component "panel" should repeat the common (global) border radius to give you an example.

Possible approaches are:

  1. Use only local styles and keep all style-related variables in JS. This is what Facebook team promotes. Not solves but "avoids" the problem. I believe this is impractical at the current stage of the web technologies where HTML is still an assembly point. As soon as you would be able to direct your browser to http://your-site.com/page.js and get the whole app running it may change. For now I see this is as an another subjective tradeoff where you give up old (familiar) problems and pick up new.
  2. Find a way to pull LESS / postCSS variables from the stylesheet file into JS to reuse them later in local styles. This is theoretically possible with bundlers like Webpack or task runners like Gulp but I'm not aware of any activity in this direction.
  3. Find a way to pull JS variables to the stylesheet file. This is theoretically possible with postCSS.
  4. Find a way to push JS variables into LESS / postCSS stylesheets. As it boils down to the host language (JS) module system, this approach seems impossible without "parameterized modules" concept. Which exist in only a few programming languages (e.g. Racket)...

Here's a perfect example of a fully customizable player:
https://github.com/soundblogs/react-soundplayer

This project uses "revealing components" pattern I described above. +1 for a real example of it.
What we see in the code is a mix of local styles, classes to hook up global stylesheets and... global stylesheets.

Components may be customizable through props if some argument should influence HTML + CSS and there are solid reasons against further revealing (splitting to subcomponents).
Nothing is wrong about it. But haven't you said that a general approach is to pass raw CSS styles (as objects) to and between components? I don't see here any sign of a raw "style passing".

P.S.
People who complain about globals in CSS should take a look at Unix terminal first. Everything is global there (probably an important reason why package naming in Java spirit org.clojure.your.package.name fails to become standard: you still need to come up with a memorizable "unique" name for CLI).

Same thing with NPM package name. It's global and it's an obvious candidate for "conventional" className prefix.

@haustraliaer
Copy link

There are rules like * { box-sizing: border-box } which you have to apply with * selector.
How would you classify them?

Hah, I'd classify them as hacks - if everyone has to write * { anything } then it probably should be the default... But even still - using local style doesn't prevent you from using those features of css... You could still define a global class and assign it to a bunch of html elements.

But the main argument for local style is that more often than not this will come back to haunt you and should generally be considered bad practice.

@ivan-kleshnin
Copy link
Contributor

Hah, I'd classify them as hacks - if everyone has to write * { anything } then it probably should be the default...

If CSS is broken this is rather a fix...
I may ask it in other way: how would you classify h4 { font-size: 2em }?
It's totally required and it's totally can't be described in terms of "trees" and "components".

If you stylize every paragraph through local styles your designer becomes unable to make live experiments. He needs to make N changes to replace font family in the whole visible area of the screen. My designer needs only 1...

But even still - using local style doesn't prevent you from using those features of css...

How about Facebook team saying "global CSS is a bug"?
Do you agree they're wrong about this?

But the main argument for local style is that more often than not this will come back to haunt you and should generally be considered bad practice.

"Bad practice" is a bad term. Some parts of web app (or page) like text styling are naturally global and so are better described by global styles, and some parts like unique widgets are probably better described by local styles. The question is how to pass constants / variables from one layer to another.
And how not to confuse that global and local parts.

@hugooliveirad
Copy link

how would you classify h4 { font-size: 2em }?

It can be a component. And often you don't want a "heading number 4", but a Subtitle, SectionHeading and so on.

It would even be better for your designer and your markup, as you would be free to use any h* with the semantics without having to take care not to break another places where that h* is used.

In most projects that helped me and the designers.

@haustraliaer
Copy link

+1 @hugobessaa - once you think in components with your whole UI, local styling makes a lot more sense. Global css could be considered a bug because the cons of using it far outweigh the benefits.

@hugooliveirad
Copy link

@haustraliaer it can led to bugs. But as global states in JS apps, they can be useful at some point.

But surely is a beast waiting to bite you.

@ivan-kleshnin
Copy link
Contributor

It would even be better for your designer and your markup, as you would be free to use any h* with the semantics without having to take care not to break another places where that h* is used.

In my experience (and not only mine) the problem is exactly the opposite. The problem is that any long-developed site has 100+ shades of gray, 100+ variants of margins etc. And the question is how to unify them, how to reduce them to minimal set of global settings. Not how to pile one more variation of each.

And the funniest thing of all. This was precisely the Facebook problem. Read how Nicole Sullivan optimized their site along with Yahoo if you care.

once you think in components with your whole UI, local styling makes a lot more sense
Global CSS could be considered a bug because the cons of using it far outweigh the benefits.

"Once you think how God loves you, whole life makes a lot more sense..."
I see it's a matter of faith for a lot of people here. "And then Facebook said..."
So I'm helpless.

@clessg
Copy link

clessg commented Jun 27, 2015

"Once you think how God loves you, whole life makes a lot more sense..."
I see it's a matter of faith for a lot of people here. "And then Facebook said..."
So I'm helpless.

That's a very wrong assertion. I suggest it's a matter of experience. Professional developers have been doing their best for years to avoid the problems of global CSS - specificity wars, code bloat, indeterministic style resolution, non-debuggability, silent clashing of styles, dead code, etc. Local styles merely codify the best practices professionals were already using. These practices weren't created by Facebook and they weren't written in the Bible. They solve a real problem.

The experience of many is that this approach works. I can't imagine going back to global CSS and specificity wars, just like I can't imagine going back to two-way binding and templates.

@hugooliveirad
Copy link

Of course you should take care to not create 100+ shades of gray. There are a number of ways, different than global definitions, to share consistency. Variables are an example.

@ivan-kleshnin there is an infinite number of ways to solve different problems. Not every one has the same problems. On my blog I use global styles like your h4 example, and on other websites I use the one I said before.

As I think you said, there is many solutions. I'm with you when you say global CSS shouldn't be considered a bug. Global vars in JS are quite useful sometimes too.

@haustraliaer
Copy link

And when using a module system in JS - you can access the global vars fairly easily by specifying window object (in a browser).

Once you think how God loves you..

Gimme a break, we're talking about building websites not faith in some higher local power...

@ivan-kleshnin
Copy link
Contributor

@haustraliaer, ok I have this code to style a list of alerts

.index-alert {
  position: fixed;
  z-index: @zindex-modal;
  &.top-right {
    top: @line-height-computed / 2;
    right: @line-height-computed;
  }
  &.top-left {
    top: @line-height-computed / 2;
    left: @line-height-computed;
  }
  &.bottom-left {
    bottom: @line-height-computed / 2;
    left: @line-height-computed;
  }
  &.bottom-right {
    bottom: @line-height-computed / 2;
    right: @line-height-computed;
  }
  .item {
    position: relative;
    margin: @line-height-computed / 2;
  }
}

It can't be placed or passed to <AlertIndex/> component directly because it relies on LESS variables.

This variables are going from (or derive from) Bootstrap variables

@line-height-computed: floor((@font-size-base * @line-height-base)); 

and depend on global settings like font size or line height.

Now all mainstream frameworks we have being it Bootsrap or Foundation or SemanticUI are built by the same principle. They require a looong list of LESS or SASS variables to be defined.

How exactly do you propose to reorganize it with local styles?

@staltz
Copy link
Member

staltz commented Jun 28, 2015

@ivan-kleshnin take a look at some "global" styles and variables in JavaScript that I have in RxMarbles: https://github.com/staltz/rxmarbles/tree/master/src/styles

@ivan-kleshnin
Copy link
Contributor

@staltz this is the "built everything from scratch" kind of solution. Pretty ok when you have no forms and limited number of pages and widgets. Is there any way to generalize it to combine with existing ecosystem and libraries?

@staltz
Copy link
Member

staltz commented Jun 28, 2015

You mean integration with this e.g.?

Now all mainstream frameworks we have being it Bootsrap or Foundation or SemanticUI are built by the same principle. They require a looong list of LESS or SASS variables to be defined.

@ivan-kleshnin
Copy link
Contributor

Yeah. Imagine I'm talking to my boss that we need to refactor our CSS with local styles. I show him an article "Global CSS is dead". He says "ok but is it compatible with Bootstrap or any existing framework? How much time is it going it take? What budget should we reserve for it?"

What should I answer him?

@staltz
Copy link
Member

staltz commented Jun 28, 2015

"Bootstrap and similar frameworks are incompatible with new best practices (refer to article), hence if we refactor/rework Bootstrap to convert it to JavaScript styles, we will benefit in the long-run (refer to article)"

@ivan-kleshnin
Copy link
Contributor

That's what I'm talking about.

  1. Who decided that those practices are "best" if there is still no ecosystem around it?
    "Bootstrap and similar frameworks" are 90% of the web... + 9% of legacy sites.
    Who is so brave to talk for all developers and decide for them what is "better"?

Personally, I admit it may be "better" or even "best". But engineering is all about practice so we need proofs and success stories from common people. Only a few (2-3) people shared their positive experience but without details and with the "we're right, you're wrong" aplomb. How this should decrease my skepticism?

It's obvious that such dramatic change will bring a lot of drawbacks.
Why not a word about them from promoters?
How global CSS can be dead if there is still no practical replacement for it except for
"do it yourself" cases?

  1. Noone can accuse me I'm only criticize. In most of my replies I was trying to shape a bridge between one approach and another. If we could pass variables between JS and LESS the whole transition could be made much more painless and cheap. And maybe the whole transition wouldn't be required at all, because two approaches could converge. Don't see any reaction on those propositions.

@staltz
Copy link
Member

staltz commented Jun 28, 2015

@ivan-kleshnin I can't add much to this discussion other than vjeux's presentation about CSS problems, which I think covers everything technical. You're asking more from the social or dev community perspective, and I don't know what to say about that.

@benoneal
Copy link

benoneal commented Aug 6, 2015

So imagine you live in a hypothetical world where dozens of developers are working on a Cycle.js single page app. Every event is globally scoped and all event selectors are just global CSS selectors.

Let's say there's a bug in the app. All your unit tests are green, but it's behaving erratically, but only in a few contexts. How easy would it be to debug this, to discover that, uh oh, it's a simple namespace collision is causing it to pick up unrelated events from other elements that only appear on some pages?

Do you think you'd pick that up immediately? Or after many hours pouring over everything trying to duplicate and isolate the collision?

@staltz
Copy link
Member

staltz commented Aug 6, 2015

@benoneal If you are referring to DOM.get(selector, eventType) in hierarchies of custom elements, global selection is a bug, should be fixed.

That said, I am starting to bet on hierarchical MVI, a new approach where selectors are locally scoped.

In any case, these two approaches are not the same as global selection bugs that you typically see in large jQuery-spaghetti SPAs.

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

No branches or pull requests