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

Best practices, simple use case #34

Open
gaku-sei opened this issue Nov 11, 2019 · 2 comments
Open

Best practices, simple use case #34

gaku-sei opened this issue Nov 11, 2019 · 2 comments
Labels
documentation Documentation or examples

Comments

@gaku-sei
Copy link

gaku-sei commented Nov 11, 2019

Hello,

So far I really enjoy using Concur for some personal projects!

But I'm often wondering what the "Concur way" of doing things should be. There are many examples, using Elm architecture, Signal, StateT, simple Widget, etc... and to be honest I'm a bit confused on which one is the best for which case.

For instance I was implementing a simple "fetch" button, which ended up looking like the following:

data State
  = Init
  | Loading
  | Error
  | Success (Array User)

fetchUsersSignal :: State -> Signal HTML State
fetchUsersSignal state =
  step state
    $ case state of
        Init -> return =<< fetchButton
        Loading -> do
          resp <-
            (liftAff $ Ajax.get ResponseFormat.string "https://jsonplaceholder.typicode.com/users")
              <|> D.button' [ D.text "Loading..." ]
          return $ maybe Error Success $ (hush <<< runExcept <<< decodeJSON <<< _.body) =<< hush resp
        Error -> return =<< fetchButton <|> D.div' [ D.text "An error occured, please try again" ]
        Success users -> do
          let
            names = (_.name <<< unwrap) <$> users
          liftEffect $ log $ show names
          void $ D.div' [ D.text "It worked!", D.div' $ (D.div' <<< pure <<< D.text) <$> names ]
          return state
  where
  fetchButton = D.button [ P.onClick $> Loading ] [ D.text "Fetch users" ]

  return = pure <<< fetchUsersSignal

As you can see it's using Signal, and some sort of "Elm-ish" architecture. I find this approach quite easy to read, but about the performances, and in general the downsides I could encounter with this approach? Would Widget be better? Should I put the logic in a separate case/of like in https://github.com/purescript-concur/purescript-concur-react/blob/master/examples/src/Test/TheElmArchitecture.purs?

In general, do you think there could be a page in the documentation listing some good/best practices, with some common use cases?

Thank you

@ajnsit
Copy link
Member

ajnsit commented Nov 23, 2019

That's a great example! Thanks for sharing it. I am still discovering how the API is best used, and examples of code written by others are immensely helpful. The more people use it, the sooner we'll have a best practices document :)

Currently, the base Widget API is solid and easily refactorable. However the Signal API is still brittle and not as composable as I would like. For that reason, while it's fun to use Signals, I don't recommend writing all components with them.

A good rule of thumb is, do you really need to access the intermediate state of a running widget from other parts of the UI. If not, then it's likely not appropriate for a signal. Even if you do need it, perhaps you should refactor out the individual widgety parts of the signal into their own functions. Here that would be 4 widgets, 1 to display the UI for each of the states. And then you can compose the widgets into one signal. This would ensure that when trying to reuse this button code elsewhere, you don't feel handicapped by not being able to access the individual state GUIs and/or being able to compose them in slightly different flows.

@ajnsit ajnsit added the documentation Documentation or examples label Nov 23, 2019
@dariooddenino
Copy link

I've been trying Concur for the past couple of days and I got to the same exact doubts and, weirdly enough, to the same exact example scenario and code solution 😄

widget :: forall a. State -> Widget HTML a
widget init = dyn $ loopS init $ \i -> do
  display $ D.p' [ D.text $ "Current results: " <> show i.results ]
  display $ D.p' [ D.text $ "STATUS: " <> show i.status ]
  button i

button :: State -> Signal HTML State
button init = loopW init $ \i ->
  if isLoading i.status
  then liftAff $ fetchResults i
  else do
    _ <- D.button [P.onClick]
        [ D.text "Fetch results" ]
    pure $ i { status = Loading }

It took me a lot of tries with widgets, signals, state, etc to finally get to a working solution.

I can't wait for this library to grow to a more final release, as I'd really like to migrate all my halogen 4 components to it :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Documentation or examples
Projects
None yet
Development

No branches or pull requests

3 participants