paf31 / purescript-thermite

A simple PureScript wrapper for React
MIT License
350 stars 55 forks source link

Updating state in child components #42

Open AlexeyRaga opened 8 years ago

AlexeyRaga commented 8 years ago

Hi,

I am using the following lens-based approach to propagate a part of the state to the downstream components:

fold
   [ T.focus _items _ListItemAction $ T.foreach \_ -> listItemSpec
   , listActions
   ]

The first component renders a list of items, the second doesn't render anything but defines performAction.

When listItemSpec is updating the state the change is correctly propagated to the parent component through the lens, but the performAction in the second component (listActions) always receives the "old" state, the one "before" changes are applied through the _items lense.

How can a parent component update the state after it has been altered by its children?

Thanks.

paf31 commented 8 years ago

The assumption right now is that only one component will handle any action. There is no routing, since the callback you get really just updates the state of the React component.

Maybe the solution is to add some way to getState as well.

kozak commented 8 years ago

This seems to work in my simple example:newState < T.cotransform id but I have no idea if this is the correct approach.

listActions :: T.Spec _ State _ Action
listActions = T.simpleSpec performAction T.defaultRender
  where
    performAction :: T.PerformAction _ State _ Action
    performAction (ItemAction i Increment) _ oldState = do
      newState <- T.cotransform id
      lift $ liftEff $ log $ "newState: " <> (show newState) <> " oldState: " <> (show oldState)
      pure unit

    performAction _ _ _ = pure unit

I am not sure about the exact order in which the cotransforms will be applied (can this even be guaranteed, if there is an async action in the child for example ?).

One of the use cases where I feel I need this feature, are form components ( each field is a separate component, when the entire form is filled out, additional async validation needs to be performed)

Thank you!

kozak commented 8 years ago

To answer one of my questions: I've added delays from counter example in the child component:

delay :: Int -> Aff _ Unit
delay ms = later' ms (pure unit)

making it async, so the parent's action will be called after the child's action has finished

sudhirvkumar commented 7 years ago

@kozak We had the exact same issue.

delay is approximate solution and if there is any ajax call involved we will not be able to guarantee that parent's action will be called after the child's action.

The best way is to use the Prism to avoid sending that particular call to the child and handle that particular action in the parent itself, using pattern matching over the child's action!

Child.purs

data Action 
  = Increment
  | Decrement
  | HandleInParent

Parent.purs

data Action 
  = MyAction
  | ChildAction Child.Action

performAction (ChildAction HandleInParent) _ _ = 
  <do stuff>

performAction (ChildAction _ ) _ _ = 
  pure unit

Prism example... the following is not related to the above code... just an example to show how to avoid a particular action from reaching the child spec!

_TaskAction :: Prism' TaskListAction (Tuple Int TaskAction)
_TaskAction = prism (uncurry TaskAction) \ta ->
  case ta of
    TaskAction i HandleInParent -> Left ta
    TaskAction i a -> Right (Tuple i a)
    _ -> Left ta

@paf31 What do you think about this idea? too complex/confusing? can it be simplified?

kozak commented 7 years ago

@sudhirvkumar If I remember correctly (it has been a while) what I meant by saying that I've added delayis that I've added them to check the async behaviour of child vs parent (not as a workaround, but to check the resulting order of performAction calls in presence of async cotransforms). The result was that even with async action the parent performAction is called after the child has finished. That said, what you suggest seems to be in agreement with Paf's statement:

The assumption right now is that only one component will handle any action

and is also intuitive ;)

sudhirvkumar commented 7 years ago

@kozak Thanks for your quick reply. Do you have any suggestions to improve the code example? or what I have is good enough?

kozak commented 7 years ago

I am not experienced enough to be able to help you here - but you could probably just ignore the actions you are not interested in (in the child):

performAction _ _ _ = pure unit

Have a look at lines 75-79 : http://try.purescript.org/?gist=f5f273e4c5e4161fceff&backend=thermite