Passing additional state to a Formless component

I’m migrating some components to the latest versions of Halogen and Formless, and I noticed that a (probably not efficient, but still useful) pattern I was using to pass additional state to a form is not working any more.

For example, to disable all the fields of a form when the parent component was performing an async operation I used to do something like this:

slot unit F.component { render: renderForm isDoingAsyncStuff, ... }

Then in the form component I would simply use that value to set the disabled property.

This doesn’t work with the latest formless version: when isDoingAsyncStuff changes the form is not re-rendered.

slot F._formless unit (renderForm isDoingAsyncStuff) unit HandleForm

The closest thing I could think of is adding a non-rendered additional field to the form to hold that value and then use the standard input/receiver code to handle it, but it’s not a great solution.

What’s the correct way to handle cases like this?

Thanks! :slight_smile:

2 Likes

The form spec is passed to the Formless component as an argument, not as part of its Halogen input, which means that it will only be constructed once and then used for the lifetime of the component.

(This is the same as any other Halogen component.) If you want to pass through a value from a parent that can update many times over the lifetime of the component then you’ll need to pass via the component’s Input and retrieve it from the component’s State when rendering.

For example, the “real world” example has a field with extra state:

and that state is provided as part of the component input:

1 Like

Thanks for the response!

This was actually pretty simple and very similar to how it is for standard components, thanks :slight_smile:

I was assuming this to be much more complicated than it actually is.

@thomashoneyman I have found a similar issue in my codebase, but regarding validators.

I think validators have no access to the state, but only to the form fields, so I’m not sure of how to handle cases where they need some extra dynamic information.

Let’s say for example I had something similar to:

formInput :: Input -> F.Input Form State Aff
formInput i =
  { initialInputs: Nothing
  , validators: validators i
  ... etc. state fields
  }

 validators i = Form
  { foo: nonEmptyStr
  , bar: checkBar i
  }

In this example checkBar uses some values from the input to perform the validation for bar.
This code works on first load, but obviously stops working as soon as the input changes since the validation is still performed on the old input values.

I have thought of a few solutions, but they are all sub-optimal. I’m wondering if I’m missing again something obvious here.

Thanks!

1 Like

That’s a good question. As you noticed, the Validation module lets you write validation that relies on the entire form state, but they don’t have access to other arbitrary state.

Some options come to mind, but I agree they aren’t exactly optimal.

My gut sense is that you probably want to adjust the form field that needs the extra state so that it is part of the form field’s input (and is passed in via modify):

render state =
  ...
    [ HE.onValueInput \str -> F.modifyValidate _bar { input: str, otherField: state.otherField }

Alternately, perhaps the validators should be constructed from the input each render.

1 Like

I thought about that, but in the end I opted for forcing a rerender of the form when that data required by the validation changes.
Mostly because this allowed to keep most of my old code unchanged, but also because the data is a biggish map and I didn’t want to carry it around in multiple fields (which would almost surely be better than rerendering lol).

Thank you very much!