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

Compose routes and deep nested components #59

Closed
jgoux opened this issue Jul 27, 2016 · 6 comments
Closed

Compose routes and deep nested components #59

jgoux opened this issue Jul 27, 2016 · 6 comments

Comments

@jgoux
Copy link
Contributor

jgoux commented Jul 27, 2016

Hello ✋,

I'd like to organize my application in a fractal manner.
To do so, I need to be able to declare routes locally with their corresponding component. I was able to do it in JS using react + react-router's route object.

Here is an example splitting the current page into components :

image

So we have this hierarchy of components for the current page https://github.com/alexmingoia/purescript-pux/issues/new :
Application → Repository → Issues → NewIssue

The idea here is to organize these components according to their relation Parent → Child :

├── Application
│   ├── Repository
│   │   ├── Issues
│   │   │   ├── IssueList.purs
│   │   │   └── NewIssue.purs
│   │   └── Issues.purs
│   └── Repository.purs
└── Application.purs

We can identify "routable" components which display different children based on the current URI. If we imagine that each "routable" component consume a part of the URI as we go deep in the hierarchy we can say that :

  • Application displays its child Repository when the URI is :username/:projectname
  • Repository displays its child Issues when the URI is issues
  • Issues displays its child NewIssue when the URI is new and its child IssueList when the URI is / or `` (this case can be considered as its index route)

In that way, a "routable" component just needs to care about its own part of the URI, and know nothing outside of its direct children.

For the routes definition, we could add a Route data type to each "routable" component definition in addition to the usual State, Action, init, update, view.

-- Application.purs
data Route = Repository

-- Repository.purs
data Route = Issues

-- Issues.purs
data Route = IssueList | NewIssue

This is where I stopped, now I'm looking for a way to declare local match functions, consume and pass the current URI to children so I can repeat the pattern down the hierarchy. 🆘

@mrtnbroder
Copy link

I'm missing exactly this.

I'm structuring my components just like @jgoux does, you can see a working example here: https://github.com/mrtnbroder/universal-react-webpack-boilerplate (in js)

take a look at the src/Application folder, where each component exposes it's route, update, view etc. functions and where react-router consumes it at the top level.

This ported to purescript-pux would be awesome!

@spencerjanssen
Copy link

spencerjanssen commented Jul 28, 2016

Routes could be composed with the Match applicative functor? Pretend I used qualified modules instead of ugly names:

-- App.purs
data AppR
    = WrapRepo RepoR
    | WrapDashboard DashboardR
    | NotFoundR

appMatch :: Match AppR
appMatch =
    WrapRepo <$> repoMatch
    <|> WrapDashboard <$> dashboardMatch
    <|> pure NotFoundR

-- Repo.purs
data RepoR = Repo {owner :: String, name :: String, subroute :: RepoSubR}

data RepoSubR
    = Issue IssueR
    | ViewFile String

repoMatch :: Match RepoR
repoMatch = lit "repo" *> (mk <$> str <*> str <*> (Issue <$> issueMatch <|> fileMatch))
 where
    mk owner name subroute = Repo {owner, name, subroute}
    fileMatch = lit "view-file" *> (ViewFile <$> str)

-- Issue.purs
data IssueR
    = New
    | View Int
    | List

issueMatch :: Match IssueR
issueMatch = lit "issue" *>
    ((lit "new" *> pure New)
    <|> (lit "view" *> (View <$> int))
    <|> (lit "list" *> pure List))

-- Dashboard.purs
data DashboardR = Dashboard -- TODO

dashboardMatch :: Match DashboardR
dashboardMatch = lit "dashboard" *> pure Dashboard

@sloosch
Copy link

sloosch commented Jul 30, 2016

With inspirations from the react router and ui-router i've come up with the following prototype:
Define the routes in a nested structure:

route ::  e. Route AppAction AppState e
route =
    Route "bla" (C.wrapManyWith navigationBar) [
        Route "blub" nestedStatefulRoute [
            Route "here" (const typeHereComp) [],
            Route "here" (const otherComponent) []
        ],
        Route "bar/foo" (const helloComponent) [],
        Route "something" (C.wrapManyWith $ caption "Here is something") [
            Mount $ map implantInApp <<< childRouter
        ]
    ]
....

Every route has a component assigned (Route <path> <component> <children>). The component is responsible to view and update the sub-route components.
A component may defines its own route-tree which then can be mounted in any other route (Mount $ map implantInApp <<< childRouter) a.k.a. composition.

Finally a root-router is needed to handle the url-changed event and display/update the current active routes:

main :: Eff _ Unit
main = do
    url <- PuxRouter.sampleUrl
    let rootComp = rootRouter route
    app <- Pux.start {
      initialState : init,
      update: C.update rootComp,
      view : C.view rootComp,
      inputs: [UrlChanged <$> url]
    }
    PuxRouter.navigateTo "bla/blub"
    Pux.renderToDOM "#app" app.html

To make this work we need the concept of a component (update + view function) in pux, this is the same problem as described in #31 (Nesting arbitrary components). So i've reused https://gist.github.com/sloosch/ea98c0c58f9440903c98b952265b556e#file-component-purs

Here is the working prototype in action:
test
and the full source: https://gist.github.com/sloosch/72c62d30800f1f27a1bc55095c40cd60

Is this what you were asking for?
Anyone interested in putting this into a purescript-pux-nested-router?
Does anyone know how this is handled in Elm?

Maybe the solution described by spencerjanssen is a better fit. Hadn't time to explore it further to the point where states and actions come into play.

@paluh
Copy link

paluh commented Jul 30, 2016

Here is my proof of concept library which tackles this problem from different perspective - alternative (bidirectional - all urls are generated) routing engine:

https://github.com/paluh/purescript-puxing-bob

Please look into examples/multiple-components-routing for working example. Unfortunately view's type became a bit complicated in my approach, because we need to somehow add parent's context to generated urls.

@jgoux
Copy link
Contributor Author

jgoux commented Aug 17, 2016

Thanks all for the answers ! 👍

I like all the approaches proposed, even if I don't fully understand them (yet).
I think @sloosch is my favorite because it's the closest to react-router. 😄
I'd be more than interested to see a purescript-pux-nested-router.

Questions for @sloosch :

  • What is the implementation of import Component as C ?
  • Is the state of a routed child erased when switching view ?
  • How would you implement "hooks" like entering / leaving a view ? (Thinking about starting/clearing a timeout for example)

Questions for @spencerjanssen :

  • How would you write the view function of each component here ? You'd have to also run a match on each sub component to pick a route locally and being able to render the right sub-sub-component ?
  • Somehow the top component containing a NotFound view has to know that the current url is reachable, so having this kind of composition in it makes total sense. How would you implement intermediate NotFound in a sub-component ?

Question for all :

  • Wouldn't the child state in a parent be something like a child :: Maybe Child.State to handle the case when the child isn't mounted ?

I also think setting up a little app showing routing capabilities of each solution would be great, so we can compare from a common ground.

@alexmingoia
Copy link
Owner

I'm archiving most of the discussion threads. If you'd like to continue the discussion I'd prefer if you posted a question on StackOverflow or Gitter. Thanks.

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

No branches or pull requests

6 participants