dabbott / react-styles-provider

Provide dynamic styles to your React components in a performant way.
24 stars 3 forks source link

Goals for v1 #4

Open dabbott opened 7 years ago

dabbott commented 7 years ago

The goal of this library is to be a complete solution to using inline styles instead of CSS.

Everything should be done "the React way" when possible. We're not going to invent new language features like inlining CSS in our JSX, we're not going to introduce new lifecycle methods or static properties for configuration, etc. We're going to use patterns like state and props, higher order components, ...context.

Where possible, it should be compatible with both React and React Native, with the necessary features and optimizations for both.

(After this point, I assume you've read the README already, so take a look at it if you haven't yet.)

Imagine this library as the future version of StyleSheet.create() that will go into React Native, and that will be used in React DOM for superior performance (styles will be converted into stylesheets behind the scenes, similar in spirit to what happens on Native). Then we won't need to use HoCs and decorator syntax, but can pass the styles we create to StyleSheet.create() and use them as the style prop, e.g. <Component style={styles.myStyles}>. We should try to minimize what goes into the core styling APIs, keeping them to the bare minimum needed to replace CSS. Convenience utilities in separate packages can make the API easier/simpler to work with for specific use cases (e.g. responsiveness).


The main idea: We have an API to compute dynamic styles from props

What this idea solves:
  1. It should be easy and intuitive to create styles configured by component props

    The API for styling a component based on its props should be a logical extension of the normal styling APIs. Maybe a button has a "type" prop, or an activity indicator has a "size" prop. These will most likely need to affect styling, but right now the API is too flexible: you just do anything you want in JS logic, and then pass a style object to a child component. This is very manual, and most apps/authors use different techniques to create and merge, e.g. {...this.props.style, color: this.props.type === 'warning' ? 'yellow' : 'green'}

  2. Styles are all in one place

    There shouldn't be any difference defining dynamic styles and static styles; we shouldn't suddenly have to start creating style objects in the render function just because we decide to make our component more flexible.

  3. Dynamic styles are performant automatically

    Normally creating dynamic style objects in the render function and passing them to children as props causes unnecessary re-renders. This happens because a new style object is created each render, and children are probably PureComponents that only test shallow equality of props.

  4. Opt-in on a per-component basis

    At least for the basics, there shouldn't be any global setup. You shouldn't have to create a context provider. A component should be able to use this API completely standalone.


Use cases

We need to solve nearly all use cases for this to be a serious replacement for CSS, since CSS gives us a lot of power and flexibility.

Here are a few, and potential solutions:

a. Vendor prefixing

This is pretty easy. We just (on web) vendor prefix the returned styles created by @createStyles.

b. Responsiveness

Use the Provider/connect pattern to provide some kind of description of the window/device, as a prop. This should likely be user-configurable, since "responsive" could mean a lot of different things to different apps. However, we should provide a sensible default for web and native. this.props.responsive could be an object or string containing things like isMobile. Or perhaps we have a helper function for the developer to define breakpoints. Implementation: we'll update context when the screen size changes.

This nice thing about using a prop for responsiveness is that you can render totally different components, not just different styles. This allows for much more flexible designs for different platforms.

return this.props.responsive.mobile ? this.renderForMobile() : this.renderForDesktop()

c. Compute styles based on arbitrary app state

Add a function to either the props of the component wrapped with @createStyles or to the styles prop called getStyles, which you can call to compute styles based on state. (This is described more in the readme) I think this API could easily be abused to do lots of crazy stuff, but sometimes you do want to compute styles based on arbitrary state. Most often, "state" in this case means component state, e.g. when a menu is open, but you could call this with anything really. It's a useful escape hatch. But that also makes it dangerous.

d. Hover/active state

hover and active can be state or props, and then styles can be computed based on these. Two solutions come to mind.

  1. Use the getStyles mentioned for computing arbitrary app state, and always store hover and active in component state (a lot of boilerplate/manual effort)

  2. Make an HoC that adds hover/active to props.

import React, { Component } from 'react'

export default () => (WrappedComponent) => {
  return class hoverState extends Component {

    state = {
      hover: false,
    }

    onMouseEnter = () => this.setState({hover: true})

    onMouseLeave = () => this.setState({hover: false})

    render() {
      const {hover} = this.state

      return (
        <WrappedComponent
          hover={hover}
          onMouseEnter={this.onMouseEnter}
          onMouseLeave={this.onMouseLeave}
          {...this.props}
        />
      )
    }
  }
}

Then this can be used like

@hoverState()
@createStyles({
  opacity: (props) => props.hover ? 0.7 : 1,
})
class Button extends Component {
  render() {
    const rest = {...this.props}

    // unrecognized prop
    delete rest.styles
    delete rest.hover

    // We need the click handlers
    return <div {...rest} />
  }
}
e. Styling ::placeholder for text input placeholder styling

Since AFAIK it's impossible to set this placeholder color via JS, we need to have a way to add CSS to the page for this.

  1. Handle it automagically (how?) by inventing a new style

    @createStyles({
    input: { 
    color: 'black'
    placeholderColor: 'grey',
    },
    })
  2. An escape hatch to add arbitrary CSS.

import createStyles, { appendStyleSheet } from 'react-styles-provider'

@createStyles((props) => {
  // idk, how does Radium/glamor do this sort of thing?
  appendStyleSheet(`input::placeholder { color: 'red' }`)

  return {
    input: { color: 'black' },
  }
})
f. Styling :before and :after

These are low priority, since you can't really create pseudo elements via React anyway. We don't need to be super compatible with other libraries that use classnames. If we have an arbitrary CSS escape hatch, that would solve.

dabbott commented 7 years ago

@alinz here are my thoughts so far