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

Improve documentation how to create HOCs with Styles #53

Open
halcwb opened this issue Feb 10, 2019 · 4 comments
Open

Improve documentation how to create HOCs with Styles #53

halcwb opened this issue Feb 10, 2019 · 4 comments

Comments

@halcwb
Copy link

halcwb commented Feb 10, 2019

This thread is closed but is very important to figure out how the creation of HOCs work. Especially the part where you need to pass the model and the dispatch function through a props object. Maybe put this in the documentation as well? The current example just says:

open Fable.Core.JsInterop
open Fable.Helpers.React
open Fable.Helpers.React.Props
open Fable.MaterialUI.Core
open Fable.MaterialUI.Props

let styles : IStyles list = [
    Styles.Root [
        CSSProp.BackgroundColor "red"
    ]
]

let myFun (props : IClassesProps) =
    div [ HTMLAttr.Class !!props.classes?root ] []

let withStylesFun = withStyles<IClassesProps> (StyleType.Styles styles) [] myFun

let view () =
    from withStylesFun createEmpty []

The example in this thread is much more insightful.

Originally posted by @halcwb in #4 (comment)

@cmeeren
Copy link
Contributor

cmeeren commented Feb 10, 2019

For the record, here's how I do it:

let private styles (theme: ITheme) : IStyles list =
  Styles.Root [
        CSSProp.BackgroundColor "red"
    ]

let private view' (classes: IClasses) model dispatch =
  div [ HTMLAttr.Class !!classes?root ] []

// Boilerplate below to support JSS with Elmish

type private IProps =
  abstract member model: Model with get, set
  abstract member dispatch: (Msg -> unit) with get, set
  inherit IClassesProps

type private Component(p) =
  inherit PureStatelessComponent<IProps>(p)
  let viewFun (p: IProps) = view' p.classes p.model p.dispatch
  let viewWithStyles = withStyles (StyleType.Func styles) [] viewFun
  override this.render() = ReactElementType.create !!viewWithStyles this.props []

let view (model: Model) (dispatch: Msg -> unit) : ReactElement =
  let props = jsOptions<IProps>(fun p ->
    p.model <- model
    p.dispatch <- dispatch)
  ofType<Component,_,_> props []

IProps, Component and view are always defined like this (copy-paste). Only styles and view' varies per component, and view looks like normal Elmish (except it's private and also takes an IClasses parameter).

@halcwb
Copy link
Author

halcwb commented Feb 10, 2019

I think this code:

let private view' (classes: IClasses) model dispatch =
  div [ HTMLAttr.Class !!props.classes?root ] []

Should be:

let private view' (classes: IClasses) model dispatch =
  div [ HTMLAttr.Class !!classes?root ] []

So classes?root instead of props.classes?root ?

@cmeeren
Copy link
Contributor

cmeeren commented Feb 10, 2019

Yes sorry, I copied that part from your example but forgot to change it.

@Luiz-Monad
Copy link

Luiz-Monad commented Jun 9, 2019

I found a better way to do this. (related to #4)

I though withStyles was causing performance problems in my App, so I dediced to implement my own HoC to do styling.
Now I think the problem was the way Fable-React is rendering, it didn't solve my problem, but now at least I have a better way to style the components and have the JSS cached.

The way I envisioned usage.

let styles ( theme: Theme ) = {|
    Button = style [
        S.Margin "5vh 0 5vh 0"
    ]
|}
let render styled dispatch model =
    let styled = inferType styles styled
    div styled.Background [   .... omited   ]
let view = render |> withStyles styles 

You need some adapters

/// Declares a style class.
let style cssProps =
    Some { Props = cssProps; ClassName = "" }

/// Runtime generated style class name.
let styleClass style : IHTMLProp list =
    match style with
    | Some s -> [ HTMLAttr.ClassName s.ClassName ]
    | _ -> []

// mui Helper
let div style =
    div <| styleClass style

Code follow:
I better make a PR, what do you think ?

// Material UI makeStyles
let makeStyles'<'S, 'O, 'P> ( styles: 'S ) ( options: 'O ) 
    : 'P -> IClassesProps =
    !!((import "makeStyles" "@material-ui/core/styles") $ (styles, options))

// Material UI makeStyles
let makeStyles ( styles : StyleType ) ( options: StyleOption seq ) =
    let opt = keyValueList CaseRules.LowerFirst options
    let styles' =
        match styles with
        | StyleType.Styles styles -> (keyValueList CaseRules.LowerFirst styles |> unbox)
        | StyleType.Func func -> func >> keyValueList CaseRules.LowerFirst
    makeStyles'<_, _, unit> styles' opt 

// Material UI useTheme
let useTheme<'T> () : 'T =
    !!((import "useTheme" "@material-ui/core/styles") $ ())

/// Convert our CssStyle to IStyles list
let createSheet ( styleSheet: ITheme -> 'StyleSheet ) =
    fun theme ->
        let css = styleSheet theme
        let idx k = ( k, css?(k) )
        let conv ( k, v: CssStyle ) = Styles.Custom ( k, v.Props ) :> IStyles    
        JS.Object.keys css |> Seq.map ( idx >> conv ) |> List.ofSeq

/// Convert the IClassesProps to our CssStyle
let applySheet styleSheet ( classes: IClassesProps ) =
    let css: 'StyleSheet = styleSheet ( useTheme<ITheme> () )
    let convBack k v = Some { Props = v.Props; ClassName = k }
    let styleIt k = css?(k) <- convBack classes?(k) css?(k)
    JS.Object.keys css |> Seq.iter styleIt
    css

let inline private uncurryView3 f key dispatch model = 
    f {| pkey = key; dispatch = dispatch; model = model |}

let inline private curryView3 f props =
    let inline inferType ( fn : ( 's -> _ ) -> _ ) ( _ : 's ) = ()
    inferType uncurryView3 props
    f props.pkey props.dispatch props.model 

/// Create a new styled component from a component and a style sheet.
let withStyles styleSheet ( fn: 'StyleSheet -> 'Dispatch -> 'Model -> ReactElement ) =
    let name = ( box styleSheet ).GetHashCode().ToString()
    let sheet = styleSheet |> createSheet |> StyleType.Func |> makeStyles 
    let useStyles = sheet [ StyleOption.Name name ]
    let applyStyles f _ dispatch model = 
        f ( useStyles () |> applySheet styleSheet ) dispatch model
    let fnC = fn |> applyStyles |> curryView3
    let memoComp = 
        FunctionComponent.Of ( fnC, "styled", 
            fun x y -> equalsButFunctions ( x.model ) ( y.model ) )    
    // Defeat Fable currying optimization, so things can be cached.
    fun ( key: string ) ->        
        uncurryView3 memoComp key

/// Useful helper for getting a copy of the anonymous record type to a parameter.
let inline inferType ( fn : _ -> 's ) ( s : 's ) = s

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

3 participants