ihp icon indicating copy to clipboard operation
ihp copied to clipboard

SSC: Support Passing In `initialValue` During Instantiation

Open s0kil opened this issue 3 years ago • 11 comments

componentFromState @Counter (Counter { value = 100 }) should use the provided initial state instead of relying on the initialValue defined in the Counter component module.

https://ihpframework.slack.com/archives/C01DQE0F4F8/p1624537056154200

s0kil avatar Jun 24 '21 14:06 s0kil

any updated here?

MurakamiKennzo avatar Dec 08 '21 11:12 MurakamiKennzo

Not yet 👍

mpscholten avatar Dec 08 '21 11:12 mpscholten

😭, What's the main problem here, I read some ssc code just now and think It's may not a tricky.

image

I think the main problem is the type of run function, what about change like this:

run :: (?state :: IORef state, ?context :: ControllerContext, ?applicationContext :: ApplicationContext, ?modelContext :: ModelContext, ?connection :: Websocket.Connection) => state -> IO ()

and change initState like this:

initialState :: Maybe State -> State

Help wanted!

MurakamiKennzo avatar Dec 08 '21 14:12 MurakamiKennzo

What about something as this:

class Component state action configData | state -> action, state -> configData where
  initialState :: configData -> state
  ...

Then you may have both static and dynamic configuration data for the components (as you are able to choose the configData...). For example

instance Component Counter CounterController Int where
  initialState n = Counter { value = n }

fidel-ml avatar Apr 01 '22 08:04 fidel-ml

I like this idea. It's very close to how react.js works as well 👍

mpscholten avatar Apr 01 '22 17:04 mpscholten

With class Component state action configData | state -> action, state -> configData where it will require ConfigData to be provided on every instance of the component, correct? component @Counter configData, I can no longer do component @Counter, or am I misunderstanding something?

s0kil avatar Apr 03 '22 17:04 s0kil

yep that's correct. If you set configData = () you could use component @Counter () as a workaround to get the old behaviour

mpscholten avatar Apr 03 '22 17:04 mpscholten

You may also provide a defaultConfig, and let component use it, and another componentWith for the new behaviour...

fidel-ml avatar Apr 06 '22 16:04 fidel-ml

Hi @fidel-ml, I had tested what you have suggested. But notice that the idea does not work quite as thought. @MurakamiKennzo is right, the problem is the run method where the initialState which is executed at ComponentsController initialization. At that point I don't have the props value yet. Somehow when I call IHP.ServerSideComponent.ViewFunctions componentWithProps the value I pass must be stored in the ControllerContext or somehow? But I have too little understanding of haskell that I probably can't solve this. Who can help?

Here see you what I have: https://github.com/leobm/ihp/commit/13a4de7aae1b682642bf9597e26f91d7cc839f3a

Edit: You could maybe put the props as data attribute into the html and send it somehow with the first websocket call and set it before you change the state. But this is probably a stupid idea....

leobm avatar Dec 11 '22 16:12 leobm

You could maybe put the props as data attribute into the html and send it somehow with the first websocket call and set it before you change the state. But this is probably a stupid idea....

I think this might actually be a good option here.

The ControllerContext cannot be used as we're having two requests here. The first that renders the SSC componentWithProps Counter { value = 100 }. Then the JS connects via the WebSocket which is a second request.

We've had a similiar problem with IHP AutoRefresh (keeping state across multiple requests). AutoRefresh solves this by having a server-global session store. On the first request it creates a new AutoRefreshSession data type and stores it into the session store. It also puts a unique ID of the AutoRefreshSession into the first response via a meta tag. The AutoRefresh JS now passes the ID onto the WebSocket. The WebSocket handler looks up the correct session by the ID. This way we share state across multiple requests. Passing the state around here explicitly via JSON doesn't work here as the state here is the ControllerContext which cannot be serialized as JSON.

mpscholten avatar Dec 12 '22 07:12 mpscholten

Hello Marc @mpscholten ,

I have played around a bit today. I now pass the props in the websocket URL on the first request and then set the state in the ComponentsController over it.

My example runs with it. But I really only have rudimentary Haskell knowledge, so the code is probably really bad. I've somehow managed to get there with a lot of trial and error. Here is the branch with my changes. https://github.com/leobm/ihp/tree/feature-component-props

I also wrote a test counter component. https://github.com/leobm/ihp-test-component-with-props

but somehow I didn't manage to link to my feature branch in default.nix. I have included my feature branch for testing simply over an IHP subdirectory.

Both projects compile. I have only strangely in Visual Code (Haskell plugin) with the language server problems. Get then e.g. Web.Component.Counter File the following error displayed: "- Expected kind '* -> Constraint', but 'Component Counter CounterController' has kind 'Constraint'.

  • In the instance declaration for 'Component Counter CounterController CounterProps'typecheck "

do you know where the problem is?

Edit: The way I solved it is of course a bit of a hack. In the URL I can't send any amount of data, maybe 2000 characters. For all cases this will not be enough of course.

leobm avatar Dec 12 '22 22:12 leobm