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

NavigatorIOS: Accessing onRightButtonPress from within child component #31

Closed
nick opened this Issue Feb 2, 2015 · 20 comments

Comments

Projects
None yet
@nick

nick commented Feb 2, 2015

I'm creating a TodoList example app using Flux. Right now I have three components: TodoListApp, TodoItems and AddItem. TodoListApp is simply a NavigatorIOS component which shows TodoItems by default and a method to push the AddItem page to the navigator stack when the user taps the '+' button.

My question is: is there a way for my AddItem component (child of NavigatorIOS) to handle the onRightButtonPress event? Right now I've had to put in a bunch of hacks to accommodate adding a todo item when the user taps the 'Save' button from the AddItem page.

@nick

This comment has been minimized.

Show comment
Hide comment
@nick

nick Feb 2, 2015

btw I'd love to contribute this example to the official repo once it's done :-)

nick commented Feb 2, 2015

btw I'd love to contribute this example to the official repo once it's done :-)

@vjeux

This comment has been minimized.

Show comment
Hide comment
@vjeux

vjeux Feb 2, 2015

Contributor

You are right, we need to have a todo app example otherwise we're not a real js lib :p

Contributor

vjeux commented Feb 2, 2015

You are right, we need to have a todo app example otherwise we're not a real js lib :p

@nick

This comment has been minimized.

Show comment
Hide comment
@nick

nick Feb 2, 2015

My thoughts exactly. I'm trying to give it the exact same functionality as the iOS demo app you build in the first tutorial. Except the tutorial for this will be about 1/10th the length ;-)

nick commented Feb 2, 2015

My thoughts exactly. I'm trying to give it the exact same functionality as the iOS demo app you build in the first tutorial. Except the tutorial for this will be about 1/10th the length ;-)

@zertosh

This comment has been minimized.

Show comment
Hide comment
@zertosh

zertosh Feb 2, 2015

Member

@nick I've got a similar issue - I need to be notified that a "back" happened #26. I'm currently hacking around it by having the NavigatorIOS children call a prop callback on componentWillUnmount.

Member

zertosh commented Feb 2, 2015

@nick I've got a similar issue - I need to be notified that a "back" happened #26. I'm currently hacking around it by having the NavigatorIOS children call a prop callback on componentWillUnmount.

@ericvicenti

This comment has been minimized.

Show comment
Hide comment
@ericvicenti

ericvicenti Feb 3, 2015

Contributor

Currently the best way to do that is to create an EventEmitter in the owner of the NavigatorIOS, then you can pass it down to children using route.passProps. The child can mix in Subscribable.Mixin and then in componentDidMount, you can this.addListenerOn(this.props.events, 'myRightBtnEvent', this._handleRightBtnPress);

It is clear that this API needs improvement. We are actively working the routing API in Relay, and hopefully react-router, but we want NavigatorIOS to be usable independently. Maybe we should add an event emitter inside the navigator object, so child components can subscribe to various navigator activity: this.addListenerOn(this.props.navigator.events, 'rightButtonPress', this._handleRightBtnPress);

Feedback welcome!

Contributor

ericvicenti commented Feb 3, 2015

Currently the best way to do that is to create an EventEmitter in the owner of the NavigatorIOS, then you can pass it down to children using route.passProps. The child can mix in Subscribable.Mixin and then in componentDidMount, you can this.addListenerOn(this.props.events, 'myRightBtnEvent', this._handleRightBtnPress);

It is clear that this API needs improvement. We are actively working the routing API in Relay, and hopefully react-router, but we want NavigatorIOS to be usable independently. Maybe we should add an event emitter inside the navigator object, so child components can subscribe to various navigator activity: this.addListenerOn(this.props.navigator.events, 'rightButtonPress', this._handleRightBtnPress);

Feedback welcome!

@nick

This comment has been minimized.

Show comment
Hide comment
@nick

nick Feb 3, 2015

That last api looks good to me. In the meanwhile I'll try your suggestion. Can't wait to see what @ryanflorence and @mjackson do with support in react-router! Already using it in all my other projects.

nick commented Feb 3, 2015

That last api looks good to me. In the meanwhile I'll try your suggestion. Can't wait to see what @ryanflorence and @mjackson do with support in react-router! Already using it in all my other projects.

@ReadingSteiner

This comment has been minimized.

Show comment
Hide comment
@ReadingSteiner

ReadingSteiner Apr 28, 2015

@ericvicenti
Why not use

this.props.event.addListener('myRightBtnEvent',this._handleRightBtnPress)` ?

directly in the componentDidMount?

What is Subscribable.Mixin's job here?

ReadingSteiner commented Apr 28, 2015

@ericvicenti
Why not use

this.props.event.addListener('myRightBtnEvent',this._handleRightBtnPress)` ?

directly in the componentDidMount?

What is Subscribable.Mixin's job here?

@ericvicenti

This comment has been minimized.

Show comment
Hide comment
@ericvicenti

ericvicenti Apr 28, 2015

Contributor

Subscribable.Mixin is something we use internally to make sure nobody forgets to unsubscribe to an emitter from a component on unmount. The mixin will automatically remove subscriptions added with this.addListenerOn. We are currently phasing it out, though, because we will be transitioning to observables in an upcoming version of React.

For more info on the existing EventEmitter, see the github project here: https://github.com/facebook/emitter

Contributor

ericvicenti commented Apr 28, 2015

Subscribable.Mixin is something we use internally to make sure nobody forgets to unsubscribe to an emitter from a component on unmount. The mixin will automatically remove subscriptions added with this.addListenerOn. We are currently phasing it out, though, because we will be transitioning to observables in an upcoming version of React.

For more info on the existing EventEmitter, see the github project here: https://github.com/facebook/emitter

@ReadingSteiner

This comment has been minimized.

Show comment
Hide comment
@ReadingSteiner

ReadingSteiner Apr 29, 2015

@ericvicenti
Thanks a lot for ur reply :)

ReadingSteiner commented Apr 29, 2015

@ericvicenti
Thanks a lot for ur reply :)

@brentvatne brentvatne closed this May 31, 2015

rozele added a commit to rozele/react-native that referenced this issue Feb 15, 2016

Fixes #31 - Adds idle/busy events to the CatalystInstance for JavaScript
When we have a use case for these events, we can wire them up further.

During implementation, also discovered that the ReactBridge should also flush the JS queue immediately after loading the bundle. Currently, the WebSockets module is activated for debugging purposes immediately after loading the bundle.

rozele added a commit to rozele/react-native that referenced this issue Feb 15, 2016

Merge pull request #130 from CatalystCode/issue31
Fixes #31 - Adds idle/busy events to the CatalystInstance for JavaScript
@backslash112

This comment has been minimized.

Show comment
Hide comment
@backslash112

backslash112 commented Mar 25, 2016

@nick
Checkout this demo: react-native_flux_demo

@tbolt

This comment has been minimized.

Show comment
Hide comment
@tbolt

tbolt Mar 26, 2016

Flux being the only solution for this isn't ideal.

tbolt commented Mar 26, 2016

Flux being the only solution for this isn't ideal.

@backslash112

This comment has been minimized.

Show comment
Hide comment
@backslash112

backslash112 Mar 28, 2016

@teebolt
I agree with you, because I met a new problem when I trying to use Flux:
I created a TodoItems app too, When I succeeded in pushing a notification to the child component, and the child component will save the new item to the db, after this, the child component will push another notification to the list component for reload the list, the problem is, there are two dispatches in this process and it will cause a error: Invariant Violation: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.

backslash112 commented Mar 28, 2016

@teebolt
I agree with you, because I met a new problem when I trying to use Flux:
I created a TodoItems app too, When I succeeded in pushing a notification to the child component, and the child component will save the new item to the db, after this, the child component will push another notification to the list component for reload the list, the problem is, there are two dispatches in this process and it will cause a error: Invariant Violation: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.

@tbolt

This comment has been minimized.

Show comment
Hide comment
@tbolt

tbolt Mar 28, 2016

I was able to solve this using a simple event emitter. I can post more details if anyone needs.

tbolt commented Mar 28, 2016

I was able to solve this using a simple event emitter. I can post more details if anyone needs.

@backslash112

This comment has been minimized.

Show comment
Hide comment
@backslash112

backslash112 Mar 29, 2016

@teebolt Please post more details about simple event emitter, thank you.

backslash112 commented Mar 29, 2016

@teebolt Please post more details about simple event emitter, thank you.

@tbolt

This comment has been minimized.

Show comment
Hide comment
@tbolt

tbolt Mar 29, 2016

@backslash112 Here is how I was able to have the "onRightButtonPress" trigger a function in the sub-component.

The only dependency I used is https://github.com/Olical/EventEmitter which can be added:
var EventEmitter = require('wolfy87-eventemitter');

In my main file (index.io.js) I created a new event emitter outside of the class:
var rightButtonHandler = new EventEmitter();

Then, inside the class I created a method to be called when the button is pressed:
handleSaveButton() { rightButtonHandler.emitEvent('saveButtonPressed'); }

And right below that I am passing the event emitter as a property to the sub-component:
passProps: { events: rightButtonHandler }

Now, switching to the sub-component class, add this or update your componentDidMount method:
componentDidMount() { this.props.events.addListener('saveButtonPressed', this.saveConcert.bind(this)); }

The above will be calling this saveConcert:
saveConcert() { /// This is now called when the navigatorIOS right button is pressed. }

Hope this helps!

tbolt commented Mar 29, 2016

@backslash112 Here is how I was able to have the "onRightButtonPress" trigger a function in the sub-component.

The only dependency I used is https://github.com/Olical/EventEmitter which can be added:
var EventEmitter = require('wolfy87-eventemitter');

In my main file (index.io.js) I created a new event emitter outside of the class:
var rightButtonHandler = new EventEmitter();

Then, inside the class I created a method to be called when the button is pressed:
handleSaveButton() { rightButtonHandler.emitEvent('saveButtonPressed'); }

And right below that I am passing the event emitter as a property to the sub-component:
passProps: { events: rightButtonHandler }

Now, switching to the sub-component class, add this or update your componentDidMount method:
componentDidMount() { this.props.events.addListener('saveButtonPressed', this.saveConcert.bind(this)); }

The above will be calling this saveConcert:
saveConcert() { /// This is now called when the navigatorIOS right button is pressed. }

Hope this helps!

@backslash112

This comment has been minimized.

Show comment
Hide comment
@backslash112

backslash112 Mar 30, 2016

@teebolt
Thank you very much, it works.
But if you want to start another dispatch in a listener that is triggered by emitChange() of a Store, you will get the same error like I said above: Invariant Violation: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.

backslash112 commented Mar 30, 2016

@teebolt
Thank you very much, it works.
But if you want to start another dispatch in a listener that is triggered by emitChange() of a Store, you will get the same error like I said above: Invariant Violation: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.

rozele added a commit to rozele/react-native that referenced this issue Apr 11, 2016

Fixes #31 - Adds idle/busy events to the CatalystInstance for JavaScript
When we have a use case for these events, we can wire them up further.

During implementation, also discovered that the ReactBridge should also flush the JS queue immediately after loading the bundle. Currently, the WebSockets module is activated for debugging purposes immediately after loading the bundle.

rozele added a commit to rozele/react-native that referenced this issue Apr 21, 2016

Fixes #31 - Adds idle/busy events to the CatalystInstance for JavaScript
When we have a use case for these events, we can wire them up further.

During implementation, also discovered that the ReactBridge should also flush the JS queue immediately after loading the bundle. Currently, the WebSockets module is activated for debugging purposes immediately after loading the bundle.

rozele added a commit to rozele/react-native that referenced this issue May 17, 2016

Fixes #31 - Adds idle/busy events to the CatalystInstance for JavaScript
When we have a use case for these events, we can wire them up further.

During implementation, also discovered that the ReactBridge should also flush the JS queue immediately after loading the bundle. Currently, the WebSockets module is activated for debugging purposes immediately after loading the bundle.
@chetankothari

This comment has been minimized.

Show comment
Hide comment
@chetankothari

chetankothari May 23, 2016

@ericvicenti I tried the approach you mentioned in one of the replies, thanks for the detailed explanation. Just wanted to check if the same holds true now or is there another way of solving it in a better way. If so could you please help me out with that.

Thanks.

chetankothari commented May 23, 2016

@ericvicenti I tried the approach you mentioned in one of the replies, thanks for the detailed explanation. Just wanted to check if the same holds true now or is there another way of solving it in a better way. If so could you please help me out with that.

Thanks.

@ericvicenti

This comment has been minimized.

Show comment
Hide comment
@ericvicenti

ericvicenti May 23, 2016

Contributor

These days I would just go with a redux-style control flow, but its not too easy to pass props down into the inside of NavigatorIOS scenes. But it can probably be done with a redux provider, and that would be a much cleaner solution

Contributor

ericvicenti commented May 23, 2016

These days I would just go with a redux-style control flow, but its not too easy to pass props down into the inside of NavigatorIOS scenes. But it can probably be done with a redux provider, and that would be a much cleaner solution

@chetankothari

This comment has been minimized.

Show comment
Hide comment
@chetankothari

chetankothari May 23, 2016

@ericvicenti Thanks for the prompt response. Will try out getting redux-style control flow.

chetankothari commented May 23, 2016

@ericvicenti Thanks for the prompt response. Will try out getting redux-style control flow.

rozele added a commit to rozele/react-native that referenced this issue May 25, 2016

Fixes #31 - Adds idle/busy events to the CatalystInstance for JavaScript
When we have a use case for these events, we can wire them up further.

During implementation, also discovered that the ReactBridge should also flush the JS queue immediately after loading the bundle. Currently, the WebSockets module is activated for debugging purposes immediately after loading the bundle.

dustturtle added a commit to dustturtle/react-native that referenced this issue Jul 6, 2016

JSONKit usage here may cause serious crash hard to debug(I found the …
…crash on simulator, on device I got nothing but app freezed)!

My app has an old version of JSONKit which is still using MRC. I think JSONKit is not needed if system version is available. Kicking out of JSONKit will make react native stronger.
Crash stack:
* thread #11: tid = 0xbd672f, 0x000000010a10edeb imobii-waiqin`jk_encode_add_atom_to_buffer(encodeState=0x00007f9b820a1000, objectPtr=22 key/value pairs) + 16971 at JSONKit.m:2807, name = 'com.facebook.React.JavaScript', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
    frame #0: 0x000000010a10edeb imobii-waiqin`jk_encode_add_atom_to_buffer(encodeState=0x00007f9b820a1000, objectPtr=22 key/value pairs) + 16971 at JSONKit.m:2807
    frame #1: 0x000000010a10ef67 imobii-waiqin`jk_encode_add_atom_to_buffer(encodeState=0x00007f9b820a1000, objectPtr=2 key/value pairs) + 17351 at JSONKit.m:2811
    frame #2: 0x000000010a10ef67 imobii-waiqin`jk_encode_add_atom_to_buffer(encodeState=0x00007f9b820a1000, objectPtr=25 key/value pairs) + 17351 at JSONKit.m:2811
    frame #3: 0x000000010a10e768 imobii-waiqin`jk_encode_add_atom_to_buffer(encodeState=0x00007f9b820a1000, objectPtr=@"3 elements") + 15304 at JSONKit.m:2778
  * frame #4: 0x000000010a10a26a imobii-waiqin`-[JKSerializer serializeObject:options:encodeOption:block:delegate:selector:error:](self=0x00007f9b831fbc80, _cmd="serializeObject:options:encodeOption:block:delegate:selector:error:", object=@"3 elements", optionFlags=0, encodeOption=10, block=0x0000000000000000, delegate=0x0000000000000000, selector=<no value available>, error=domain: class name = NSInvocation - code: 0) + 2250 at JSONKit.m:2876
    frame #5: 0x000000010a109992 imobii-waiqin`+[JKSerializer serializeObject:options:encodeOption:block:delegate:selector:error:](self=JKSerializer, _cmd="serializeObject:options:encodeOption:block:delegate:selector:error:", object=@"3 elements", optionFlags=0, encodeOption=10, block=0x0000000000000000, delegate=0x0000000000000000, selector=<no value available>, error=domain: class name = NSInvocation - code: 0) + 178 at JSONKit.m:2831
    frame #6: 0x000000010a10f700 imobii-waiqin`-[NSArray(self=@"3 elements", _cmd="JSONStringWithOptions:error:", serializeOptions=0, error=domain: class name = NSInvocation - code: 0) JSONStringWithOptions:error:] + 112 at JSONKit.m:2985
    frame #7: 0x000000010ac13c02 imobii-waiqin`_RCTJSONStringifyNoRetry(jsonObject=@"3 elements", error=domain: class name = NSInvocation - code: 0) + 338 at RCTUtils.m:49
    frame #8: 0x000000010ac13990 imobii-waiqin`RCTJSONStringify(jsonObject=@"3 elements", error=0x0000000000000000) + 128 at RCTUtils.m:77
    frame #9: 0x000000010ab5fdfa imobii-waiqin`__27-[RCTContextExecutor setUp]_block_invoke_2(.block_descriptor=<unavailable>, moduleName=@"UIManager") + 218 at RCTContextExecutor.m:363
    frame #10: 0x00000001134495cc CoreFoundation`__invoking___ + 140
    frame #11: 0x000000011344941e CoreFoundation`-[NSInvocation invoke] + 286
    frame #12: 0x000000010db13db3 JavaScriptCore`JSC::ObjCCallbackFunctionImpl::call(JSContext*, OpaqueJSValue*, unsigned long, OpaqueJSValue const* const*, OpaqueJSValue const**) + 451
    frame #13: 0x000000010db13926 JavaScriptCore`JSC::objCCallbackFunctionCallAsFunction(OpaqueJSContext const*, OpaqueJSValue*, OpaqueJSValue*, unsigned long, OpaqueJSValue const* const*, OpaqueJSValue const**) + 262
    frame #14: 0x000000010db14bad JavaScriptCore`long long JSC::APICallbackFunction::call<JSC::ObjCCallbackFunction>(JSC::ExecState*) + 573
    frame #15: 0x000000010dade340 JavaScriptCore`JSC::LLInt::setUpCall(JSC::ExecState*, JSC::Instruction*, JSC::CodeSpecializationKind, JSC::JSValue, JSC::LLIntCallLinkInfo*) + 528
    frame #16: 0x000000010dae535d JavaScriptCore`llint_entry + 22900
    frame #17: 0x000000010dadf7d9 JavaScriptCore`vmEntryToJavaScript + 326
    frame #18: 0x000000010d9b1959 JavaScriptCore`JSC::JITCode::execute(JSC::VM*, JSC::ProtoCallFrame*) + 169
    frame #19: 0x000000010d9985ad JavaScriptCore`JSC::Interpreter::executeCall(JSC::ExecState*, JSC::JSObject*, JSC::CallType, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) + 493
    frame #20: 0x000000010d76cb7e JavaScriptCore`JSC::call(JSC::ExecState*, JSC::JSValue, JSC::CallType, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) + 62
    frame #21: 0x000000010d929a55 JavaScriptCore`JSC::callGetter(JSC::ExecState*, JSC::JSValue, JSC::JSValue) + 149
    frame #22: 0x000000010dad49fb JavaScriptCore`llint_slow_path_get_by_id + 2203
    frame #23: 0x000000010dae22f0 JavaScriptCore`llint_entry + 10503
    frame #24: 0x000000010dae5368 JavaScriptCore`llint_entry + 22911
    frame #25: 0x000000010dae52fd JavaScriptCore`llint_entry + 22804
    frame #26: 0x000000010dae5368 JavaScriptCore`llint_entry + 22911
    frame #27: 0x000000010dae5368 JavaScriptCore`llint_entry + 22911
    frame #28: 0x000000010dae52fd JavaScriptCore`llint_entry + 22804
    frame #29: 0x000000010dae5368 JavaScriptCore`llint_entry + 22911
    frame #30: 0x000000010dae5368 JavaScriptCore`llint_entry + 22911
    frame #31: 0x000000010dae5368 JavaScriptCore`llint_entry + 22911
    frame #32: 0x000000010dae552a JavaScriptCore`llint_entry + 23361
    frame #33: 0x000000010dae5368 JavaScriptCore`llint_entry + 22911
    frame #34: 0x000000010dae5368 JavaScriptCore`llint_entry + 22911
    frame #35: 0x000000010dadf7d9 JavaScriptCore`vmEntryToJavaScript + 326
    frame #36: 0x000000010d9b1959 JavaScriptCore`JSC::JITCode::execute(JSC::VM*, JSC::ProtoCallFrame*) + 169
    frame #37: 0x000000010d998264 JavaScriptCore`JSC::Interpreter::execute(JSC::ProgramExecutable*, JSC::ExecState*, JSC::JSObject*) + 10404
    frame #38: 0x000000010d7a8786 JavaScriptCore`JSC::evaluate(JSC::ExecState*, JSC::SourceCode const&, JSC::JSValue, WTF::NakedPtr<JSC::Exception>&) + 470
    frame #39: 0x000000010d9f6fb8 JavaScriptCore`JSEvaluateScript + 424
    frame #40: 0x000000010ab6379e imobii-waiqin`__68-[RCTContextExecutor executeApplicationScript:sourceURL:onComplete:]_block_invoke.264(.block_descriptor=<unavailable>) + 414 at RCTContextExecutor.m:589
    frame #41: 0x000000010ab63262 imobii-waiqin`__68-[RCTContextExecutor executeApplicationScript:sourceURL:onComplete:]_block_invoke(.block_descriptor=<unavailable>) + 498 at RCTContextExecutor.m:589
    frame #42: 0x000000010ab63df8 imobii-waiqin`-[RCTContextExecutor executeBlockOnJavaScriptQueue:](self=0x00007f9b832f6040, _cmd="executeBlockOnJavaScriptQueue:", block=0x00007f9b80c92970) + 248 at RCTContextExecutor.m:627
    frame #43: 0x000000010eb1d7a7 Foundation`__NSThreadPerformPerform + 283
    frame #44: 0x0000000113486301 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    frame #45: 0x000000011347c22c CoreFoundation`__CFRunLoopDoSources0 + 556
    frame #46: 0x000000011347b6e3 CoreFoundation`__CFRunLoopRun + 867
    frame #47: 0x000000011347b0f8 CoreFoundation`CFRunLoopRunSpecific + 488
    frame #48: 0x000000010ab5e41b imobii-waiqin`+[RCTContextExecutor runRunLoopThread](self=RCTContextExecutor, _cmd="runRunLoopThread") + 363 at RCTContextExecutor.m:284
    frame #49: 0x000000010ebc012b Foundation`__NSThread__start__ + 1198
    frame #50: 0x00000001140869b1 libsystem_pthread.dylib`_pthread_body + 131
    frame #51: 0x000000011408692e libsystem_pthread.dylib`_pthread_start + 168
    frame #52: 0x0000000114084385 libsystem_pthread.dylib`thread_start + 13
@littlehome-eugene

This comment has been minimized.

Show comment
Hide comment
@littlehome-eugene

littlehome-eugene Jul 23, 2017

@ericvicenti @chetankothari
Can you elaborate what you mean by redux-style control flow?

Notify the click event through redux state?

littlehome-eugene commented Jul 23, 2017

@ericvicenti @chetankothari
Can you elaborate what you mean by redux-style control flow?

Notify the click event through redux state?

@facebook facebook locked as resolved and limited conversation to collaborators May 31, 2018

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.