Skip to content

Commit 91ef43a

Browse files
Separate FormBuilder props from component and render props (#81)
* Remove unnecessary props in new form hooks * Fix behavior of success dialog in forms example * Separate FormBuilder props from component and render props * Simpler formState component; remove unnecessary constraints
1 parent eb0d11e commit 91ef43a

File tree

2 files changed

+61
-97
lines changed

2 files changed

+61
-97
lines changed

docs/Examples/Form.example.purs

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import Data.Foldable (foldMap)
99
import Data.Int as Int
1010
import Data.Lens (iso)
1111
import Data.Lens.Record (prop)
12-
import Data.Maybe (Maybe(..), isJust, maybe)
12+
import Data.Maybe (Maybe(..), maybe)
1313
import Data.Monoid as Monoid
1414
import Data.Newtype (class Newtype, un)
1515
import Data.Nullable as Nullable
@@ -38,7 +38,7 @@ import React.Basic.DOM (css)
3838
import React.Basic.DOM as R
3939
import React.Basic.DOM.Events (preventDefault)
4040
import React.Basic.Events (handler, handler_)
41-
import React.Basic.Hooks (JSX, CreateComponent, component, element, useEffect, useState, (/\))
41+
import React.Basic.Hooks (JSX, CreateComponent, component, element, useState, (/\))
4242
import React.Basic.Hooks as React
4343
import Web.File.File as File
4444

@@ -50,9 +50,9 @@ docs = flip element {} $ unsafePerformEffect do
5050
component "FormExample" \_ -> React.do
5151
{ formData, form } <- F.useForm metaForm
5252
{ initialState: formDefaults
53-
, readonly: false
5453
, inlineTable: false
5554
, forceTopLabels: false
55+
, formProps: { readonly: false }
5656
}
5757

5858
pure $ column_
@@ -105,26 +105,28 @@ mkUserFormExample
105105
}
106106
mkUserFormExample = do
107107
component "UserFormExample" \props -> React.do
108-
modalOpen /\ setModalOpen <- useState false
108+
userDialog /\ setUserDialog <- useState Nothing
109109

110110
{ setModified, reset, validated, form } <- F.useForm userForm
111111
{ initialState: formDefaults
112-
, readonly: props.readonly
113112
, inlineTable: props.inlineTable
114113
, forceTopLabels: props.forceTopLabels && not props.inlineTable
115-
, simulatePauses: props.simulatePauses
114+
, formProps:
115+
{ readonly: props.readonly
116+
, simulatePauses: props.simulatePauses
117+
}
116118
}
117119

118-
let hasResult = isJust validated
119-
useEffect hasResult do
120-
setModalOpen $ const hasResult
121-
mempty
122-
123120
pure $ R.form -- Forms should be enclosed in a single "<form>" element to enable
124121
-- default browser behavior, such as the enter key. Use "type=submit"
125122
-- on the form's submit button and `preventDefault` to keep the browser
126123
-- from reloading the page on submission.
127-
{ onSubmit: handler preventDefault \_ -> setModified
124+
{ onSubmit: handler preventDefault \_ ->
125+
case validated of
126+
Nothing ->
127+
setModified
128+
Just { firstName, lastName } ->
129+
setUserDialog \_ -> Just { firstName, lastName }
128130
, style: R.css { alignSelf: "stretch" }
129131
, children:
130132
[ form
@@ -142,13 +144,15 @@ mkUserFormExample = do
142144
}
143145
]
144146
}
145-
, case validated of
147+
, case userDialog of
146148
Nothing ->
147149
mempty
148150
Just { firstName, lastName } ->
149151
dialog
150-
{ modalOpen
151-
, onRequestClose: reset
152+
{ modalOpen: true
153+
, onRequestClose: do
154+
reset
155+
setUserDialog \_ -> Nothing
152156
, onActionButtonClick: Nullable.null
153157
, actionButtonTitle: ""
154158
, size: Medium

src/Lumi/Components/Form.purs

Lines changed: 42 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,14 @@ import Effect (Effect)
7171
import Effect.Aff (Aff)
7272
import Effect.Class (liftEffect)
7373
import Effect.Unsafe (unsafePerformEffect)
74-
import Heterogeneous.Mapping (class Mapping)
7574
import JSS (JSS, jss)
7675
import Lumi.Components.Color (colors)
7776
import Lumi.Components.Column (column)
7877
import Lumi.Components.FetchCache as FetchCache
7978
import Lumi.Components.Form.Defaults (formDefaults) as Defaults
8079
import Lumi.Components.Form.Internal (Forest, FormBuilder'(..), FormBuilder, SeqFormBuilder, Tree(..), formBuilder, formBuilder_, invalidate, pruneTree, sequential)
8180
import Lumi.Components.Form.Internal (Forest, FormBuilder', FormBuilder, SeqFormBuilder', SeqFormBuilder, formBuilder, formBuilder_, invalidate, listen, parallel, revalidate, sequential) as Internal
82-
import Lumi.Components.Form.Validation (ModifyValidated, setModified)
81+
import Lumi.Components.Form.Validation (setModified)
8382
import Lumi.Components.Form.Validation (Validated(..), Validator, _Validated, fromValidated, mustBe, mustEqual, nonEmpty, nonEmptyArray, nonNull, validNumber, validInt, validDate, optional, setFresh, setModified, validated, warn) as Validation
8483
import Lumi.Components.Input (alignToInput)
8584
import Lumi.Components.Input as Input
@@ -94,14 +93,13 @@ import Lumi.Components.Select as Select
9493
import Lumi.Components.Text (body, body_, subsectionHeader, text)
9594
import Lumi.Components.Textarea as Textarea
9695
import Lumi.Components.Upload as Upload
97-
import Prim.Row (class Lacks, class Nub, class Union)
96+
import Prim.Row (class Nub, class Union)
9897
import React.Basic (JSX, createComponent, element, empty, fragment, keyed, makeStateless)
9998
import React.Basic.Components.Async (async, asyncWithLoader)
10099
import React.Basic.DOM as R
101100
import React.Basic.DOM.Events (capture, stopPropagation, targetChecked, targetValue)
102101
import React.Basic.Events as Events
103102
import React.Basic.Hooks as Hooks
104-
import Record as Record
105103
import Unsafe.Coerce (unsafeCoerce)
106104

107105
-- | Create a React component for a form from a `FormBuilder`.
@@ -111,25 +109,12 @@ import Unsafe.Coerce (unsafeCoerce)
111109

112110
build
113111
:: forall props unvalidated result
114-
. Union
115-
( forceTopLabels :: Boolean
116-
, inlineTable :: Boolean
117-
)
118-
( readonly :: Boolean
119-
| props
120-
)
121-
( forceTopLabels :: Boolean
122-
, inlineTable :: Boolean
123-
, readonly :: Boolean
124-
| props
125-
)
126-
=> FormBuilder { readonly :: Boolean | props } unvalidated result
112+
. FormBuilder { readonly :: Boolean | props } unvalidated result
127113
-> { value :: unvalidated
128114
, onChange :: (unvalidated -> unvalidated) -> Effect Unit
129115
, forceTopLabels :: Boolean
130116
, inlineTable :: Boolean
131-
, readonly :: Boolean
132-
| props
117+
, formProps :: { readonly :: Boolean | props }
133118
}
134119
-> JSX
135120
build = build' defaultRenderForm
@@ -140,37 +125,30 @@ build = build' defaultRenderForm
140125
-- | _Note_: this function should be fully applied, to avoid remounting
141126
-- | the component on each render.
142127
build'
143-
:: forall ui renderProps formProps props unvalidated result
144-
. Union renderProps formProps props
145-
=> ({| props } -> ui -> JSX)
146-
-> FormBuilder' ui {| formProps } unvalidated result
128+
:: forall ui renderProps formProps unvalidated result
129+
. ({| renderProps } -> formProps -> ui -> JSX)
130+
-> FormBuilder' ui formProps unvalidated result
147131
-> { value :: unvalidated
148132
, onChange :: (unvalidated -> unvalidated) -> Effect Unit
149-
| props
133+
, formProps :: formProps
134+
| renderProps
150135
}
151136
-> JSX
152137
build' render editor =
153-
makeStateless (createComponent "Form") \props@{ value, onChange } ->
138+
makeStateless (createComponent "Form") \props@{ value, onChange, formProps } ->
154139
let
155-
{ edit } = un FormBuilder editor (contractFormProps props) value
140+
{ edit } = un FormBuilder editor formProps value
156141
in
157-
render (contractProps props) (edit onChange)
142+
render (contractRenderProps props) formProps (edit onChange)
158143
where
159-
contractFormProps
160-
:: { value :: unvalidated
161-
, onChange :: (unvalidated -> unvalidated) -> Effect Unit
162-
| props
163-
}
164-
-> {| formProps }
165-
contractFormProps = unsafeCoerce
166-
167-
contractProps
144+
contractRenderProps
168145
:: { value :: unvalidated
169146
, onChange :: (unvalidated -> unvalidated) -> Effect Unit
170-
| props
147+
, formProps :: formProps
148+
| renderProps
171149
}
172-
-> {| props }
173-
contractProps = unsafeCoerce
150+
-> {| renderProps }
151+
contractRenderProps = unsafeCoerce
174152

175153

176154
-- | The default Lumi implementation for rendering a forest of JSX
@@ -179,12 +157,13 @@ defaultRenderForm
179157
:: forall props
180158
. { forceTopLabels :: Boolean
181159
, inlineTable :: Boolean
182-
, readonly :: Boolean
160+
}
161+
-> { readonly :: Boolean
183162
| props
184163
}
185164
-> Forest
186165
-> JSX
187-
defaultRenderForm { inlineTable, forceTopLabels, readonly } forest =
166+
defaultRenderForm { inlineTable, forceTopLabels } { readonly } forest =
188167
element (R.unsafeCreateDOMComponent "lumi-form")
189168
{ class:
190169
String.joinWith " " $ fold
@@ -224,21 +203,16 @@ defaultRenderForm { inlineTable, forceTopLabels, readonly } forest =
224203
-- | Render a form with state managed automatically.
225204
useForm
226205
:: forall props unvalidated result
227-
. Mapping ModifyValidated unvalidated unvalidated
228-
=> FormBuilder
229-
{ initialState :: unvalidated
230-
, readonly :: Boolean
231-
, inlineTable :: Boolean
232-
, forceTopLabels :: Boolean
206+
. FormBuilder
207+
{ readonly :: Boolean
233208
| props
234209
}
235210
unvalidated
236211
result
237212
-> { initialState :: unvalidated
238-
, readonly :: Boolean
239213
, inlineTable :: Boolean
240214
, forceTopLabels :: Boolean
241-
| props
215+
, formProps :: { readonly :: Boolean | props }
242216
}
243217
-> Hooks.Hook (Hooks.UseState unvalidated)
244218
{ formData :: unvalidated
@@ -251,30 +225,22 @@ useForm
251225
useForm editor props = Hooks.do
252226
let
253227
renderer = defaultRenderForm
254-
{ readonly: props.readonly
255-
, inlineTable: props.inlineTable
228+
{ inlineTable: props.inlineTable
256229
, forceTopLabels: props.forceTopLabels
257230
}
231+
props.formProps
258232

259-
f <- useForm' editor props
233+
f <- useForm' editor props.initialState props.formProps
260234
pure f { form = renderer f.form }
261235

262236

263237
-- | Like `useForm`, but allows an alternative render implementation
264238
-- | to be provided as an additional argument.
265239
useForm'
266240
:: forall ui props unvalidated result
267-
. Mapping ModifyValidated unvalidated unvalidated
268-
=> FormBuilder'
269-
ui
270-
{ initialState :: unvalidated
271-
| props
272-
}
273-
unvalidated
274-
result
275-
-> { initialState :: unvalidated
276-
| props
277-
}
241+
. FormBuilder' ui props unvalidated result
242+
-> unvalidated
243+
-> props
278244
-> Hooks.Hook (Hooks.UseState unvalidated)
279245
{ formData :: unvalidated
280246
, setFormData :: (unvalidated -> unvalidated) -> Effect Unit
@@ -283,8 +249,8 @@ useForm'
283249
, validated :: Maybe result
284250
, form :: ui
285251
}
286-
useForm' editor props = Hooks.do
287-
formData /\ setFormData <- Hooks.useState props.initialState
252+
useForm' editor initialState props = Hooks.do
253+
formData /\ setFormData <- Hooks.useState initialState
288254

289255
let
290256
{ edit, validate: validated } = un FormBuilder editor props formData
@@ -294,7 +260,7 @@ useForm' editor props = Hooks.do
294260
{ formData
295261
, setFormData
296262
, setModified: setFormData setModified
297-
, reset: setFormData \_ -> props.initialState
263+
, reset: setFormData \_ -> initialState
298264
, validated
299265
, form: ui
300266
}
@@ -308,22 +274,12 @@ useForm' editor props = Hooks.do
308274
-- | the component on each render.
309275
formState
310276
:: forall props unvalidated result
311-
. Lacks "render" props
312-
=> Mapping ModifyValidated unvalidated unvalidated
313-
=> FormBuilder
314-
{ initialState :: unvalidated
315-
, readonly :: Boolean
316-
, inlineTable :: Boolean
317-
, forceTopLabels :: Boolean
318-
| props
319-
}
320-
unvalidated
321-
result
322-
-> Hooks.ReactComponent
277+
. Hooks.ReactComponent
323278
{ initialState :: unvalidated
324-
, readonly :: Boolean
279+
, form :: FormBuilder { readonly :: Boolean | props } unvalidated result
325280
, inlineTable :: Boolean
326281
, forceTopLabels :: Boolean
282+
, formProps :: { readonly :: Boolean | props }
327283
, render
328284
:: { formData :: unvalidated
329285
, setFormData :: (unvalidated -> unvalidated) -> Effect Unit
@@ -333,11 +289,15 @@ formState
333289
, form :: JSX
334290
}
335291
-> JSX
336-
| props
337292
}
338-
formState editor = unsafePerformEffect do
293+
formState = unsafePerformEffect do
339294
Hooks.component "FormState" \props -> Hooks.do
340-
state <- useForm editor (Record.delete (SProxy :: SProxy "render") props)
295+
state <- useForm props.form
296+
{ initialState: props.initialState
297+
, inlineTable: props.inlineTable
298+
, forceTopLabels: props.forceTopLabels
299+
, formProps: props.formProps
300+
}
341301
pure (props.render state)
342302

343303

0 commit comments

Comments
 (0)