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

Updates for 0.12 #149

Merged
merged 13 commits into from
Jun 9, 2018
Merged

Conversation

natefaubion
Copy link
Contributor

Also includes some renames for consistency.

@natefaubion
Copy link
Contributor Author

Are we interested in adding the new lifecycle methods?

@ethul
Copy link
Contributor

ethul commented Jun 2, 2018 via email

@natefaubion
Copy link
Contributor Author

One thing that's weird is the interplay between getSnapshotBeforeUpdate and componentDidUpdate. https://reactjs.org/docs/react-component.html#getsnapshotbeforeupdate Both are optional, but if you provide getSnapshotBeforeUpdate, then the return value will be provided to componentDidUpdate. Not sure of the best way to encode this. One option is

foreign import data ReactUnusedSnapshot :: Type

-- | Required fields for constructing a ReactClass.
type ReactSpecRequired state r =
  ( state :: state
  , render :: Render
  | r
  )

type ReactSpecUnsafe props state r =
  ( unsafeComponentWillMount :: ComponentWillMount
  , unsafeComponentWillReceiveProps :: ComponentWillReceiveProps props
  , unsafeComponentWillUpdate :: ComponentWillUpdate props state
  | r
  )

-- | Optional fields for constructing a ReactClass.
type ReactSpecOptional props state snapshot r =
  ( componentDidMount :: ComponentDidMount
  , componentDidCatch :: ComponentDidCatch
  , componentWillUnmount :: ComponentWillUnmount
  | ReactSpecSnapshot props state snapshot r
  )

type ReactSpecSnapshot props state snapshot r =
  ( componentDidUpdate :: ComponentDidUpdate props state snapshot
  , getSnapshotBeforeUpdate :: GetSnapshotBeforeUpdate props state snapshot
  | r
  )

type ReactSpecShouldComponentUpdate props state =
  ( shouldComponentUpdate :: ShouldComponentUpdate props state
  )

type ReactSpecAll props state snapshot
  = ReactSpecRequired state
  + ReactSpecOptional props state snapshot
  + ReactSpecShouldComponentUpdate props state

component
  :: forall props state snapshot r spec
   . Row.Union
      (ReactSpecRequired (Record state) r)
      (ReactSpecAll (Record props) (Record state) ReactUnusedSnapshot)
      spec
  => Row.Nub spec (ReactSpecAll (Record props) (Record state) snapshot)
  => String
  -> ReactClassConstructor (Record props) (Record state) r
  -> ReactClass (Record props)

This type signature is kind of bonkers, but the inference is actually decent.

testChildren :: ReactClass { label :: String, children :: Children }
testChildren = component "TestChildren" \this -> do
  let
    getSnapshotBeforeUpdate _ _ = do
      pure { foo: 42 }

    render = do
      props <- getProps this
      pure $ div'
        [ text props.label
        , div' (childrenToArray props.children)
        ]
  pure
    { state: { a: "start", b: 0 }
    , render
    , getSnapshotBeforeUpdate
    }
  Could not match type

    { foo :: Int
    }

  with type

    ReactUnusedSnapshot


while trying to match type t2
                             { foo :: Int
                             }
  with type Effect ReactUnusedSnapshot
while solving type class constraint

  Prim.Row.Nub t0
               ( state :: { a :: String
                          , b :: Int
                          }
               , render :: Effect ReactElement
               , componentDidMount :: Effect Unit
               , componentDidCatch :: Error
                                      -> { componentStack :: String
                                         }
                                         -> Effect Unit
               , componentWillUnmount :: Effect Unit
               , componentDidUpdate :: { label :: String
                                       , children :: Children
                                       }
                                       -> { a :: String
                                          , b :: Int
                                          }
                                          -> t1 -> Effect Unit
               , getSnapshotBeforeUpdate :: { label :: String
                                            , children :: Children
                                            }
                                            -> { a :: String
                                               , b :: Int
                                               }
                                               -> Effect t1
               , shouldComponentUpdate :: { label :: String
                                          , children :: Children
                                          }
                                          -> { a :: String
                                             , b :: Int
                                             }
                                             -> Effect Boolean
               )

while inferring the type of component "TestChildren"
in value declaration testChildren

where t0 is an unknown type
      t1 is an unknown type
      t2 is an unknown type

And adding a corresponding componentDidUpdate works and infers correctly.

testChildren :: ReactClass { label :: String, children :: Children }
testChildren = component "TestChildren" \this -> do
  let
    componentDidUpdate :: _
    componentDidUpdate _ _ _ = do
      pure unit

    getSnapshotBeforeUpdate _ _ = do
      pure { foo: 42 }

    render = do
      props <- getProps this
      pure $ div'
        [ text props.label
        , div' (childrenToArray props.children)
        ]
  pure
    { state: { a: "start", b: 0 }
    , render
    , getSnapshotBeforeUpdate
    , componentDidUpdate
    }
  Wildcard type definition has the inferred type

    { label :: String
    , children :: Children
    }
    -> { a :: String
       , b :: Int
       }
       -> { foo :: Int
          }
          -> Effect Unit

  in the following context:

    getSnapshotBeforeUpdate :: { label :: String
                               , children :: Children
                               }
                               -> { a :: String
                                  , b :: Int
                                  }
                                  -> Effect
                                       { foo :: Int
                                       }
    render :: Effect ReactElement
    this :: ReactThis
              { label :: String
              , children :: Children
              }
              { a :: String
              , b :: Int
              }


in value declaration testChildren

With the inverse (componentDidUpdate without getSnapshotBeforeUpdate) inferring ReactUnusedSnapshot is fine, because it's equivalent to a Unit type.

I'm open to suggestions here.

@ethul
Copy link
Contributor

ethul commented Jun 3, 2018

All of the updates look great. Thanks for adding context. Resolves #64 . I like your proposal for getSnapshotBeforeUpdate and componentDidUpdate, but I will have to put a bit more thought into this.

@natefaubion
Copy link
Contributor Author

I've gone ahead and pushed that approach. I've just consolidated it into a separate class so the signature doesn't look too bad.

@natefaubion
Copy link
Contributor Author

Also, I should point out I haven't tested this thoroughly beyond type checking/inference 😆

@ethul
Copy link
Contributor

ethul commented Jun 3, 2018 via email

@paf31
Copy link
Contributor

paf31 commented Jun 7, 2018

Something seems off, since I can set render: 42 in my spec, and things compile happily:

R.pureComponent "bad" \this -> 
    pure { state: { }
         , render: 42
         , shouldComponentUpdate: \_ _ -> pure true
         } 

@natefaubion
Copy link
Contributor Author

natefaubion commented Jun 7, 2018

@paf31 Thanks! I had introduced a bug when I refactored it to use Nub. It was done in such a way that the provided state and render were removed from the spec before unifying. Also, pureComponent should not allow shouldComponentUpdate since that's the whole reason it exists.

@natefaubion
Copy link
Contributor Author

I've added unsafe variations of the createElement functions which do not use ReactPropFields.

@ethul
Copy link
Contributor

ethul commented Jun 9, 2018

Thanks! Is this PR about ready to go? If so, I will give the changes a test and merge.

@natefaubion
Copy link
Contributor Author

I don't think I have anything else to add to this PR.

@ethul
Copy link
Contributor

ethul commented Jun 9, 2018

Looks great and works well! Thanks for all of your work on this.

@ethul ethul merged commit dac7f46 into purescript-contrib:master Jun 9, 2018
@vyorkin vyorkin mentioned this pull request Aug 9, 2018
33 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants