ReactJs / Redux Invariant Violation:在“Connect(LoginContainer)”的上下文或道具中找不到“store”

Posted

技术标签:

【中文标题】ReactJs / Redux Invariant Violation:在“Connect(LoginContainer)”的上下文或道具中找不到“store”【英文标题】:ReactJs/Redux Invariant Violation: Could not find "store" in either the context or props of "Connect(LoginContainer)" 【发布时间】:2017-11-11 12:28:13 【问题描述】:

不知道为什么会出现此错误,它是在我将 connectredux 添加到我的登录组件时发生的,因此我可以连接我的 store

失败 src/components/auth/Login.test.js

● 测试套件无法运行

不变违规:在“Connect(LoginContainer)”的上下文或道具中找不到“store”。要么将根组件包装在 <Provider> 中,要么将“store”作为道具显式传递给“Connect(LoginContainer)”。

Index.js

import React from 'react'
import ReactDOM from 'react-dom'
import  Provider  from "react-redux"
import  createCommonStore  from "./store";
import App from './App'
import css from './manage2.scss'

const store = createCommonStore();
const element = document.getElementById('manage2');
console.log("Index.js Default store", store.getState());

ReactDOM.render(
    <Provider store=store>  // <-- store added here
        <App />
    </Provider>, element);

store.js

import React from "react"
import  applyMiddleware, combineReducers, compose, createStore from "redux"
import thunk from "redux-thunk"
import  userReducer  from "./reducers/UserReducer"
import  authReducer  from "./reducers/AuthReducer"

export const createCommonStore = (trackStore=false) => 
    const reducers = combineReducers(
        user: userReducer,
        user: authReducer
    );

    //noinspection JSUnresolvedVariable
    const store = createStore(reducers,
        compose(
            applyMiddleware(thunk),
            window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
        )
    );

    if (trackStore) 
        store.subscribe((() => 
            console.log("  store changed", store.getState());
        ));
    

    return store;
;

App.js

import React from 'react'
import  BrowserRouter as Router  from 'react-router-dom'
import Routes from './components/Routes'
const supportsHistory = "pushState" in window.history

export default class App extends React.Component 
    render() 
        return (
            <Router forceRefresh=!supportsHistory>
                <Routes />
            </Router>
        );
    

Routes.js

import React from 'react'
import  Route, Switch  from 'react-router-dom'
import LoginContainer from './auth/Login'
import Dashboard from './Dashboard'
import NoMatch from './NoMatch'

const Routes = () => 
    return (
        <Switch>
            <Route exact= true  path="/" component= LoginContainer />
            <Route path="/dashboard" component= Dashboard />
            <Route component= NoMatch  />
        </Switch>
    );


export default Routes

最后是 Login.js(为简洁起见删除了代码

import React from 'react'
import  connect  from "react-redux"
import  bindActionCreators  from 'redux'; 
import  setCurrentUser  from '../../actions/authActions'
import * as api from '../../services/api'

const mapDispatchToProps = (dispatch) => 
    console.log('mapDispatchToProps', dispatch);
    return 
        setUser: (user) => 
            bindActionCreators(setCurrentUser(user), dispatch)
        
    


class LoginContainer extends React.Component 
    constructor(props) 
        super(props)

        this.state = ;

        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
    

    handleChange(e) 
    

    handleSubmit(e) 
    

    render() 
        return (
            <div className="app-bg">
                ...
            </div>
        )
    


export default connect(null, mapDispatchToProps)(LoginContainer); 

登录测试

import React from 'react'
import ReactTestUtils from 'react-dom/test-utils'
import  mount, shallow  from 'enzyme'
import toJson from 'enzyme-to-json'
import  missingLogin  from '../../consts/errors'
import Login from './Login'
import Notification from '../common/Notification'

const loginComponent = shallow(<Login />);
const fakeEvent =  preventDefault: () => '' ;

describe('<Login /> component', () => 
    it('should render', () => 
        const tree = toJson(loginComponent);
        expect(tree).toMatchSnapshot();
    );

    it('should render the Notification component if state.error is true', () => 
        loginComponent.setState( error: true );
        expect(loginComponent.find(Notification).length).toBe(1);
    );
);

describe('User Login', () => 
    it('should fail if no credentials are provided', () => 
        expect(loginComponent.find('.form-login').length).toBe(1);
        loginComponent.find('.form-login').simulate('submit', fakeEvent);
        expect(loginComponent.find(Notification).length).toBe(1);
        const notificationComponent = shallow(<Notification message= missingLogin />);
        expect(notificationComponent.text()).toEqual('Please fill out both username and password.');
    );

    it('input fields should be filled correctly', () => 
        const credentials =  username: 'leongaban', password: 'testpass' ;
        expect(loginComponent.find('#input-auth-username').length).toBe(1);

        const usernameInput = loginComponent.find('#input-auth-username');
        usernameInput.value = credentials.username;
        expect(usernameInput.value).toBe('leongaban');

        const passwordInput = loginComponent.find('#input-auth-password');
        passwordInput.value = credentials.password;
        expect(passwordInput.value).toBe('testpass');
    );
);

你觉得这里有什么问题?

【问题讨论】:

createCommonStore 代码在哪里, @ShubhamKhatri 刚刚添加了抱歉! store.js 在你的 index.js 中做一个 console.log() 看看你是否得到了商店的防御 能否提供login.test.js文件? 测试中如何连接store? 【参考方案1】:

Redux 建议导出未连接的组件以进行单元测试。见他们的docs。

在 login.js 中:

// Named export for tests
export class LoginContainer extends React.Component 


// Default export
export default connect(null, mapDispatchToProps)(LoginContainer); 

在你的测试中:

// Import the named export, which has not gone through the connect function
import  LoginContainer as Login  from './Login';

然后,您可以直接在组件上指定来自商店的任何道具。

【讨论】:

谢谢,这就是我用来将登录组件导入到我的测试中的,但是我目前没有在我的登录代码中使用商店,如上所示。这可能是问题所在吗? @LeonGaban 这不是您在问题代码中所做的。您正在将连接的组件作为默认导出导入。这是将未连接的组件作为命名导出导入。 好的,这样做了,仍然出现错误。我在测试中还需要什么? gist.github.com/leongaban/770713a4d7f542c725c46cf5db47cae2 啊!就是这样!抱歉,我在路线测试中看到错误。所以提供者在测试内部工作!现在得到一个无法读取未定义的属性“应用”,但这是一个新错误。 18 小时前我可以检查你的答案。【参考方案2】:

您需要在测试中将 store 作为 prop 或 context 传递。 mount 方法接受上下文作为另一个参数。

你如何在这里获得商店?您创建商店的方式与您在 app.js 中创建的方式相同

【讨论】:

您能否发布一个如何在开玩笑测试中执行此操作的示例?目前我正在使用 index.js 中的 Provider 组件将商店插入到我的应用程序中。真的不确定如何在我的测试中做到这一点。我的 index.js 位于我的问题的首位。【参考方案3】:

你可以使用 React 的 contextType 或传递 propType。您需要将其声明为 prop 或 contextType。

Provider.contextTypes = 
  Store: React.PropTypes.object.isRequired
;

 Provider.propTypes= 
  Store: React.PropTypes.object.isRequired
;

【讨论】:

这将进入哪个模块?还是在测试中?

以上是关于ReactJs / Redux Invariant Violation:在“Connect(LoginContainer)”的上下文或道具中找不到“store”的主要内容,如果未能解决你的问题,请参考以下文章

reactjs - jest 快照测试嵌套的 redux “连接”组件

reactjs:动作后redux不会重新渲染

ReactJS + Redux + Reduc-thunk 无法调度承诺

Redux/Flux(使用 ReactJS)和动画

如何正确地从 ReactJS + Redux 应用程序进行 REST 调用?

使用 ReactJS + Redux 时需要 componentWillReceiveProps 和 replaceProps 吗?