Open halcwb opened 5 years ago
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).
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
?
Yes sorry, I copied that part from your example but forgot to change it.
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
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:
The example in this thread is much more insightful.
Originally posted by @halcwb in https://github.com/mvsmal/fable-material-ui/issues/4#issuecomment-462112975