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

ReactWrapper::setState() can only be called on the root #1289

Closed
wongjiahau opened this issue Oct 21, 2017 · 19 comments
Closed

ReactWrapper::setState() can only be called on the root #1289

wongjiahau opened this issue Oct 21, 2017 · 19 comments
Labels
API: mount feature request Issues asking for stuff that would be semver-minor help wanted

Comments

@wongjiahau
Copy link

Preface

I had went through #814 and #361 , but I still don't get the answer I want.

Explanation

I know you may say that in practice you should only test a child component in isolation, but I'm in a situation where the child component cannot be rendered without the parent component.
For example, because I'm using React Material-UI, I got to mount the element-to-be-tested inside a theme provider, if not error will be thrown during mounting :

const wrapper = mount(
        <MuiThemeProvider>
            <SubjectListView subjects={subjects}/>
        </MuiThemeProvider>);

Problem

I need to set the state of SubjectListView but then I got this ReactWrapper::setState() can only be called on the root error.

Conclusion

I really hope I can call setState() on child element, if not I have to wrap all my components with a <MuiThemeProvider> which is obviously not a good thing.

@ljharb
Copy link
Member

ljharb commented Oct 21, 2017

You can use shallow, and .dive() - is there a reason you need to use mount?

@wongjiahau
Copy link
Author

@ljharb Thanks for the reply. I need to use mount because I have to check on element which are quite deep in the rendered DOM.

@ljharb
Copy link
Member

ljharb commented Oct 22, 2017

Fair.

However I'd say a series of shallow-rendering tests, for each component in your tree, would get you more coverage and be easier to write.

@wongjiahau
Copy link
Author

Anyway, I still think that allowing client to call setState() on child element is a must have in the future.

@ljharb ljharb added the feature request Issues asking for stuff that would be semver-minor label Oct 22, 2017
@wolfgangmeyers
Copy link

wolfgangmeyers commented Oct 31, 2017

There is another use case I've run into. In the case of a single page app, when rendering links from react-router-dom, it's necessary to nest a component inside of a router. This prevents the test from being able to access the state of the component being tested, because ReactWrapper.state() returns the state of the router component at the root.

@webdevbyjoss
Copy link

Have exactly the same issue with "react-router-dom" as above. Can't test my components due to router wrapper.

@wolfgangmeyers
Copy link

The workaround I've used now is to assign the component instance to a ref and use that to get access to the state.

<HashRouter><MyComponent ref={ref => this.componentInstance = ref} /></HashRouter>

Then you can read and manipulate state directly:

this.componentInstance.getState();
this.componentInstance.setState({});

@gsantiago
Copy link

I had a problem with React Router too and solve it by using the dive method.

const getComponent = props =>
  <MemoryRouter>
    <AppHeader {...props} />
  </MemoryRouter>

it('should set the state `isMenuOpened` to `true`', () => {
  const component = shallow(getComponent()).find(AppHeader).dive()
  expect(component.state('isMenuOpened')).toBeFalsy()

  component.find(Toggle).simulate('click')
  expect(component.state('isMenuOpened')).toBeTruthy()
})

@robwierzbowski
Copy link

robwierzbowski commented May 30, 2018

Note in the above, you're really testing the AppHeader component without testing the getComponent at all, since component is assigned the value of find(AppHeader).dive().

I have a situation where a third party component does some work and then triggers an onChange method. I'd like to be able to set that third party component state to see the changes in my shallow rendered component. Simplified code:

render() {
  <div>
    <3rdParty onChange={setClass}>
      <some html...>
    </3rdParty>

    <div styleName={getClass()} />
  </div>
}

If I could set state on 3rdParty, I could observe the changes in the rest of the component in a snapshot.

@ljharb
Copy link
Member

ljharb commented Jul 4, 2018

I'd be happy to review a PR that allows setState to be called on non-root custom component instances in mount.

@ljharb
Copy link
Member

ljharb commented Sep 3, 2018

Duplicate of #635.

@maxcrystal
Copy link

I found this works to set state of the inner component:

const wrapper = mount(
        <MuiThemeProvider>
            <SubjectListView subjects={subjects}/>
        </MuiThemeProvider>);
wrapper.find(SubjectListView).instance().setState({ foo: bar });
wrapper.update();

@robwierzbowski
Copy link

@maxcrystal Great solution.

@BramDecuypere
Copy link

@maxcrystal I was starting to wonder why on earth would it be so hard to do something so simple? Great job 👍 👍

@joepuzzo
Copy link

joepuzzo commented Oct 2, 2018

So how can i set props on non root elements?

@ljharb
Copy link
Member

ljharb commented Oct 2, 2018

You can wait until the updated version is released, and then update to it.

@joepuzzo
Copy link

joepuzzo commented Oct 2, 2018

It looks like this merge only fixed setting state on child comps though? Or is there another issue for setProps?

@ljharb
Copy link
Member

ljharb commented Oct 2, 2018

ahh sorry, yes, this is only for setState. It doesn't really make sense to arbitrarily set props on a non-root element - the next render would just wipe that out with the props the parent passed. Happy to discuss this in a new issue, though.

@lucasteixeira2
Copy link

I found this works to set state of the inner component:

const wrapper = mount(
        <MuiThemeProvider>
            <SubjectListView subjects={subjects}/>
        </MuiThemeProvider>);
wrapper.find(SubjectListView).instance().setState({ foo: bar });
wrapper.update();

Thank you! Your solution helped me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API: mount feature request Issues asking for stuff that would be semver-minor help wanted
Projects
None yet
Development

No branches or pull requests

10 participants