facebook / componentkit

A React-inspired view framework for iOS.
http://www.componentkit.org/
Other
5.76k stars 587 forks source link

Communicating between sibling components using "state" #357

Closed avnerbarr closed 9 years ago

avnerbarr commented 9 years ago

I am reusing complex components to create a "form" construct.

I cam across a situation in which one component's state change needs to cause its "sibling" component to change state.

So for instance a change in a value in one field should update the "enabled" state of a different component

The form items and the submit button do not share a "context" on their creation which rules out passing in the "enabled" state at creation time.

The components share a distant common component (and its corresponding controller) in the hierarchy.

In this type of situation how can a change the state of a descendant component (Not the direct component of the controller)

adamjernst commented 9 years ago

The distant common ancestor is probably the way to go. This blog post was really helpful to me—check out "Step 4: Identify where your state should live."

https://facebook.github.io/react/docs/thinking-in-react.html

Then it's just a matter of restructuring to pass all that information down cleanly.

This is admittedly tricky because text fields in ComponentKit don't lend themselves to storing their current state anywhere other than locally. (Simply because the text input APIs on iOS are so mutative/stateful.) Still, you could e.g. send a CKComponentAction when the relevant information about the field changes (for example, maybe empty vs non-empty) and have the common ancestor modify the other field accordingly.

Good luck. Feel free to reopen if you need more help — a little sketch or some sample code always helps too.

avnerbarr commented 9 years ago

Thanks for the quick response.

I read the article but still have a couple issues.

Question about passing the props from the ancestor : Since the components are being reused and don't have an "interface" for receiving the props from the parent , I'm not sure how the article example is implemented.

The setup is something like:

-RootComponent
--Child1
--Child2
-The Form
----FormChild1
----FormChild2
----Done Button

Each FormChild can send a message to the parent using CKComponentActionSend but not so sure how the form can change the state of the "done button" down the hierarchy

So for instance

// the done button
+(instancetype)doneButton... {
    CKComponentScope scope(self, @(hash));
    BOOL *enabled = scope.state().boolValue; // how can the form "inject" the value here?
}
+ (id)initialState
{
    return @NO; //disabled by default
}
// button pressed callback
- (IBAction)didPress:(Component *)sender {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
    CKComponentActionSend(@selector(componentPressed:), self);
#pragma clang diagnostic pop
    [self updateState:^id(NSNumber *state) {
           //?
    }];
}

// form child

+(instancetype)newWithModel:(Model *)model {
// scope + state + etc.

}

// callback for some action on the item
-(void)somethingHappenedToItem:(Component *) {
    // state change on the item + send message up the responder chain
    CKComponentActionSend(@selector(formAction....:), self);
}

// the form controller 
-(void)formAction...:(CKComponent *)theSenderOfTheFormAction {
    // do some logic 

   // if enable the button?
   button updateState:... /// enable the button 
}
adamjernst commented 9 years ago

I think the done button's enabled state would have to be controlled entirely by the form component or root component. That is, instead of storing whether it's enabled in state, it would be passed in like this:

@interface DoneButtonComponent : CKCompositeComponent
+ (instancetype)newWithEnabled:(BOOL)enabled;
@end

Whether the done button is enabled or not would become part of the Form component's state.