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

React setState() fails when called from UI-Router Transition Hook #59

Closed
kboucher opened this issue Jun 14, 2017 · 8 comments
Closed

React setState() fails when called from UI-Router Transition Hook #59

kboucher opened this issue Jun 14, 2017 · 8 comments

Comments

@kboucher
Copy link

kboucher commented Jun 14, 2017

I am attempting to pass stateParams from a child route to its parent route:

Child.js

this.props.transition.router.stateService.go('parent', {
    sampleMember: 'sample text data',
});

I am attempting to capture this data in the parent route component:

Parent.js

onSuccess(trans, state) {
    const sampleMember = trans.targetState().params().sampleMember;

    this.setState({
        sampleFromChild: sampleMember
    });
}

onSuccess is registered in the Parent component's constructor and fires as expected. I am also successfully retrieving sampleMember from the params().

However, setState is consistently aborted by React due to the component's _reactInternalInstance member being undefined. setState calls enqueueSetState and enqueueCallback which both fail (noop) if this._reactInternalInstance is falsey.

I am using this same pattern in an Angular version of this sample application (with UI Router) and it works as expected. Am I missing something here, or is this a legitimate bug?

My state definitions (in case it matters):

var app_states = [{
        name: "parent",
        url: "/parent",
        component: Parent
    },
    {
        name: "child",
        parent: "parent",
        url: "/child",
        views: {
            "!$default": Child
        }
    },
];
@Kukkimonsuta
Copy link
Contributor

I'm not certain, but it seems to me that the component is not yet mounted. Try registering your callbacks in componentDidMount (and unregistering in componentWillUnmount)

@elboman
Copy link
Member

elboman commented Jun 30, 2017

yeah, it sounds like the component is not mounted. Placing the code in componentDidMount ensures that the component is actually mounted. It may also be that the onSuccess method is not bound to the instance, buy I'd need to see more code to understand better.

@kboucher
Copy link
Author

kboucher commented Jul 6, 2017

I tried registering the onSuccess transition hook handler in the componentDidMount lifecycle hook. However, this did not cure the problem.

Which makes sense in retrospect. There was no issue registering the hook. The onSuccess handler fired as expected. The issue is that the setState() call within the onSuccess handler fails because the component is not yet mounted when the onSuccess handler is fired. (Are there transition hooks that definitely fire after the component is mounted? And would I still have access to the passed state params at that time?)

I've isolated the issue in this project: https://github.com/kboucher/uirouter-test

(I'm also trying to setup a plunker, but so far the handler doesn't fire in that context at all.)

@Kukkimonsuta
Copy link
Contributor

The issue is that the component gets unmounted. See lifecycle in pr:

https://github.com/kboucher/uirouter-test/pull/1

@kboucher
Copy link
Author

kboucher commented Jul 6, 2017

@Kukkimonsuta Not sure how that helps

I merged your changes and the onSuccess handler no longer fires at all. As I stated above, all I'm trying to do is utilize transition hooks to capture state params passed from one route to another.

This works in Angular, but not in React. If this is not a bug, how can I achieve this functionality?

@Kukkimonsuta
Copy link
Contributor

It's not fix, more of an explanation why it's not working.

From my tests I believe ( @elboman correct me if I'm wrong )

a) params can only hold values defined in URL
b) you can pass any other values in options.custom

There seem to be however a bug/feature, where the transition prop is always the same for retained states - meaning in this case that transition prop on parent has always #id=0. @elboman Please check the repro https://github.com/Kukkimonsuta/uirouter-test

@elboman
Copy link
Member

elboman commented Jul 7, 2017

If the handle doesn't fire anymore it sounds like it shouldn't be fired at all, as the parent components gets unmounted during the transition.

I'm trying to understand what you want to achieve with this, as I'm not even sure you need a hook here. Transition hooks are useful for modifying a transition while it's happening, or to interact with the rest of the application (outside of the router scope).


As @Kukkimonsuta already mentioned, there are to ways to pass data from one state to another, and it depends on your needs and how integrated it needs to be in the app architecture:

State params

Each state param has a type:

  • Path parameters (/:fooParam): path
  • Query parameters (?queryParam): query
  • Non-url parameters (param: { foo: null }): any

Or you can define custom param types.

Transition options

You can specify custom data in the transition options, and it will be passed along with the transition, using the options.custom property.

Differences

The difference between the two rely on the use case: state params are more verbose but give you a lot of useful tools. They are explicit and declarative by nature, they are typed, encoded/decoded by default (in the url for example), you can have default values, and so on. (more info here)

The option.custom is a quicker way to achieve this but you won't get all the goodies mentioned above, so I encourage you to use it wisely and only when you need to pass data into some specific transition hook.


The transition prop is not always the same for retained states, if anything changes then the component is re-rendered with the new transition prop. What you are experiencing is the inherit: true default option provided in both the UISref and the stateService.go() transition methods. Since the transition is inheriting any param, the value will stay the same until you override it.

Anyway, I created a simple codesandbox to better illustrate what I mean, let me know if you have any doubts.

@kboucher
Copy link
Author

@elboman Thanks for the clarification and posting that example.

As you indicated by your statement, I apparently did not need a transition hook to simply pass data from the child to the parent.

Transition hooks are useful for modifying a transition while it's happening, or to interact with the rest of the application (outside of the router scope).

I forget now, but I think I went down that path after unsuccessfully trying to the more direct route of accessing the transition params first. However, I think the key ingredient that I was missing was the declaration of the param on the state definition:

    var appStates = [{
        name: "parent",
        url: "/parent",
        component: Parent,

        // Needed this declaration
        params: {
          dataObj: '[null]',
        }
    },

(It was also not necessary to set inherit: false on the call to transition back to Parent.)

I'm still a little fuzzy on some of this, but your responses have helped immensely. Thanks!

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

No branches or pull requests

3 participants