Having very simple component:
import PropTypes from 'prop-types'
import React from 'react'
import { connect } from 'react-redux'
class MyComponent extends React.Component {
componentWillMount() {
if (this.props.shouldDoSth) {
this.props.doSth()
}
}
render () {
return null
}
}
MyComponent.propTypes = {
doSth: PropTypes.func.isRequired,
shouldDoSth: PropTypes.bool.isRequired
}
const mapStateToProps = (state) => {
return {
shouldDoSth: state.shouldDoSth,
}
}
const mapDispatchToProps = (dispatch) => ({
doSth: () => console.log('you should not see me')
})
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
I want to test if doSth is called when shouldDoSth is equal true.
I've written a test:
describe('call doSth when shouldDoSth', () => {
it('calls doSth', () => {
const doSthMock = jest.fn()
const store = mockStore({shouldDoSth: true})
shallow(<MyComponent doSth={doSthMock}/>, { context: { store } }).dive()
expect(doSthMock).toHaveBeenCalled()
})
})
but it seems that although I pass doSth as props it gets overridden by mapDispatchToProps as console.log('im not a mock') is executed.
How to properly pass/override/assign doSth function to make component use mock instead of function from mapDispatchToProps. Or maybe I'm doing something which should not be allowed at all and there is 'proper' way of testing my case. Shall I just mock dispatch instead and check if it is called with proper arguments?
I think one thing you need to figure out is whether you want doSth to be a prop, or a redux action connected in mapDispatchToProps.
If it's a prop, then you would connect it to redux in a parent (container). Remove it from this component's mapDispatchToProps. This would make the component more testable.
If you want it to be a redux action connected in this component, then it would make sense to move the action out of this component, somewhere like actions.js, import it in this component, and then mock it in the test jest.mock('actions.js', () => ({doSth: jest.mock()}))
Export the unconnected component and use it in the test and you will be able to override the mapDispatchToProps action.
export class MyComponent extends React.Component {
componentWillMount() {
if (this.props.shouldDoSth) {
this.props.doSth()
}
}
render () {
return null
}
}
MyComponent.propTypes = {
doSth: PropTypes.func.isRequired,
shouldDoSth: PropTypes.bool.isRequired
}
const mapStateToProps = (state) => {
return {
shouldDoSth: state.shouldDoSth,
}
}
const mapDispatchToProps = (dispatch) => ({
doSth: () => console.log('you should not see me')
})
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
import {MyComponent} from '../MyComponent'
describe('call doSth when shouldDoSth', () => {
it('calls doSth', () => {
const doSthMock = jest.fn()
const store = mockStore({shouldDoSth: true})
shallow(<MyComponent doSth={doSthMock}/>, { context: { store } }).dive()
expect(doSthMock).toHaveBeenCalled()
})
})
I think that you should ask yourself if you want to test the unconnected MyComponent or the connected one.
Here you have two discussions that may help you: Am I testing connected components correclty? and Can't reference containers wrapped in a Provider or by connect with Enzyme
If you are not testing the action nor state properly said, you might forget about mapStateToProps and mapDispatchToProps (those processes are already tested by redux) and pass values through props.
Check the following example:
describe('MyComponent', () => {
let wrapper;
const doSthMock = jest.fn();
beforeEach(() => {
const componentProps = {
doSth: true,
};
wrapper = mount(
<MyComponent
{... componentProps}
doSth={doSthMock}
/>
);
});
it('+++ render the component', () => {
expect(wrapper.length).toEqual(1);
});
it('+++ call doSth when shouldDoSth', () => {
expect(doSthMock).toHaveBeenCalled();
});
})
Related
How can I pass the context and extract the data in a helper method?
See the below snippet:
import AppContext from '../../context/AppContext'
import extractDatta from '../../helper';
class App extends Component{
static contextType = AppContext
componentWillMount(){
const resolvedData = extractData("/home",this.context)
}
render(){
return(
)
}
}
helper/index.js:
const extractData = (path, context) => {
// getting context as undefined
}
App.test.js:
describe('App test suites', () => {
let wrapper;
let appContext;
beforeEach(() => {
appContext = {name: "Application"}
wrapper = shallow(<Supplier />, { appContext })
})
it('Should render the App Component', () => {
expect(wrapper).toMatchSnapshot();
})
})
Any help appreciated :)
So here is my hacky workaround whilst the Enzyme team work on finishing [https://github.com/airbnb/enzyme/issues/1553](React 16 support). I'm providing the legacy contextTypes on the component class, allowing the context to be passed as per the docs.
import PropTypes from "prop-types";
describe('App test suites', () => {
let wrapper;
let appContext;
beforeEach(() => {
appContext = {name: "Application"}
// Hack in the legacy context API just for tests. You'll get a warning, but it
// ought to work...
Supplier.contextTypes = {
name: PropTypes.string
}
wrapper = shallow(<Supplier />, { appContext })
})
it('Should render the App Component', () => {
expect(wrapper).toMatchSnapshot();
})
})
That probably wouldn't help. But shouldn't you pass Options object with context parameter name instead of appContext?
wrapper = shallow(<Supplier />, { context: appContext })
I written a custom logic for handling async route loading bundles in react-router-dom .v4. It's work perfectly. But also I heard about useful package with nice API to do the same, like React-Loadable. It has one problem, I cannot get the props/state pushed from Redux on the mount of the component throw this package.
My code is rewritten from the custom style to react-loadable style in two examples below. The last one is react-loadable version, that does not throw state/props.
My personal code:
const asyncComponent = getComponent => {
return class AsyncComponent extends React.Component {
static Component = null;
state = { Component: AsyncComponent.Component };
componentWillMount() {
const { Component } = this.state
if (!Component) {
getComponent().then(({ default: Component }) => {
const { store } = this.props // CAN GET THE REDUX STORE
AsyncComponent.Component = Component;
this.setState({ Component });
});
}
}
render() {
const { Component } = this.state;
if (Component) {
return <Component {...this.props} />
}
return null;
}
};
};
export default withRouter(asyncComponent(() => import(/* webpackChunkName: "chunk_1" */ './containers/Component')))
The same code, but with React-Loadable:
const Loading = () => {
return <div>Loading...</div>;
}
const asyncComponent = Loadable({
loader: () => import(/* webpackChunkName: "" */ './containers/Component')
.then(state => {
const { store } = this.props // CANNOT GET THE REDUX STORE!!
}),
loading: Loading
})
export default withRouter(asyncComponent)
To get the state from Redux store via Provider you should place your asyncComponent in Stateful Component wrapper, like you do in your custom async logic (1st case).
It because Loadable library returns you asyncComponent like a function, not a constructor, that way he cannot get access to current Redux store. So, the working solution will be the next:
const Loading = () => {
return <div>Loading...</div>;
}
const asyncComponent = Loadable({
loader: () => import(/* webpackChunkName: "" */ './containers/Component')
.then(state => {
const { store } = this.props // YOU WILL GET THE REDUX STORE!!
}),
loading: Loading
})
class asyncComponentWrapper extends Component{ // Component wrapper for asyncComponent
render() {
return <asyncComponent {...this.props} />
}
}
export default withRouter(asyncComponentWrapper)
P.S.
I do not know what you try to do, but in case how to make reducer injection inside the current store (probably it's exactly what you trying to do), you need to include you Redux store explicitly by import, not from the Provider state.
In my React + Redux application I am trying to use mapDispatchToProps utility, But whenever I put this inside connect(mapStateToProps, mapDispatchToProps) it gives me an error saying Uncaught TypeError: dispatch is not a function at new ReduxApp (ReduxApp.js:42)
What could be the issue in this?
PS: below is the file
ReduxApp.js
import React from 'react';
import { Router, Route } from 'react-router-dom';
import { connect } from 'react-redux';
import { history } from './_helpers';
import { alertActions } from './_actions'
class ReduxApp extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
const { dispatch } = this.props;
dispatch(alertActions.success("hello world"));
}
handleChange(){
this.props.dispatch(alertActions.clear());
}
render(){
const { alert } = this.props;
return(
<div>
<h1>{alert.message}</h1>
<button onClick={this.handleChange}>clear</button> {/* this is working since this function is declared outside the mapDispatchToProps. */}
<button onClick={this.props.handleClick}>clear</button>
</div>
);
}
}
const mapStateToProps = (state) => ({
alert : state.alert
});
const mapDispatchToProps = (dispatch) => ({
handleClick: () => dispatch(alertActions.clear())
});
const connectedApp = connect(mapStateToProps, mapDispatchToProps)(ReduxApp); // when I add mapDispatchToProps in the connect(), I get thhe issue.
export { connectedApp as ReduxApp }
you first need to pass dispatch as it is not available when using mapDispatchToProps (see this answer by #gaeron Redux's creator: https://github.com/reduxjs/react-redux/issues/255)
const mapDispatchToProps = dispatch => ({
handleClick: () => alertActions.clear(dispatch),
dispatch,
});
Update your actionCreator to dispatch the action now that dispatch's reference is available:
alert.clear = dispatch => {
// your logic
dispatch(ALERT_CLEAR_ACTION) // or whatever you named your action
}
And in your component:
handleChange = () => this.props.handleClick();
From React Redux Official Documentation
Why don't I have this.props.dispatch available in my connected component?
The connect() function takes two primary arguments, both optional. The first, mapStateToProps, is a function you provide to pull data from the store when it changes, and pass those values as props to your component. The second, mapDispatchToProps, is a function you provide to make use of the store's dispatch function, usually by creating pre-bound versions of action creators that will automatically dispatch their actions as soon as they are called.
If you do not provide your own mapDispatchToProps function when calling connect(), React Redux will provide a default version, which simply returns the dispatch function as a prop. That means that if you do provide your own function, dispatch is not automatically provided. If you still want it available as a prop, you need to explicitly return it yourself in your mapDispatchToProps implementation.
The issue got solved after returning dispatch in the mapDispatchToProps implementation
const mapDispatchToProps = (dispatch) => ({
handleClick: () => dispatch(alertActions.clear()),
dispatch, //returning dispatch solves the issue
});
Note: If we use PropTypes no need to retun mapDispatchToProps
Have problem with state in my component.
I'm trying to get status from my reducer but state is empty just getting undefined
Here is my actionCreator
export function checkLogin() {
return function(dispatch){
return sessionApi.authCheck().then(response => {
dispatch(authSuccess(true));
}).catch(error => {
throw(error)
})
}
}
My reducer
export const authStatus = (state = {}, action) => {
switch(action.type){
case AUTH_FALSE:
return{
status: action.status
}
case AUTH_TRUE:
return {
...state,
status: action.status
};
default:
return state;
}
};
And here is my component where i'm trying to get state
const mapStateToProps = (state) => {
return {
status: state.status
}
};
const mapDispatchToProps = (dispatch:any) => {
const changeLanguage = (lang:string) => dispatch(setLocale(lang));
const checkAuth = () => dispatch(checkLogin());
return { changeLanguage, checkAuth }
};
#connect(mapStateToProps, mapDispatchToProps)
I need to get status from the state
Component
import * as React from "react";
import Navigation from './components/navigation';
import { connect } from 'react-redux';
import { setLocale } from 'react-redux-i18n';
import cookie from 'react-cookie';
import {checkLogin} from "./redux/actions/sessionActions";
class App extends React.Component<any, any> {
constructor(props:any) {
super(props);
this.state = {
path: this.props.location.pathname
};
}
componentDidMount(){
this.props.checkAuth();
this.props.changeLanguage(cookie.load('lang'));
}
componentWillUpdate(){
}
render() {
return (
<div>
<Navigation path={this.state.path} />
{this.props.children}
</div>
)
}
}
const mapStateToProps = (state) => {
return {
status: state.status
}
};
const mapDispatchToProps = (dispatch:any) => {
const changeLanguage = (lang:string) => dispatch(setLocale(lang));
const checkAuth = () => dispatch(checkLogin());
return { changeLanguage, checkAuth }
};
#connect(mapStateToProps, mapDispatchToProps)
export class Myapp
extends App {}
You cannot access props that are asynchronous inside of the constructor. As the constructor will be executed only once, when you instantiate your component. When you instantiate your component your asynchronous call has not responded yet, therefore this.props.status is undefined.
You could use componentWillReceiveProps from React lifecycle methods for example:
componentWillReceiveProps(nextProps) {
console.log(nextProps.status);
}
This method will be executed everytime a prop connected, or passed, to the component will change.
You could also use this.props.status inside of the render as this one is also executed everytime a prop changed.
For a better understanding of react lifecycle you could have the look at the different methods available, here : https://facebook.github.io/react/docs/react-component.html
This my util module, and when I use redux action it does not work.
import {openloading} from '../actions/loading'
export default function (e) {
openloading(e.font);
}
But in my react component it does work
Actions themselves do nothing, which is ironic given the name. Actions merely describe what is going to happen and how state will change. Change actually occurs when these instructions are dispatched to the reducer. As Paul said, you need access to the dispatch method.
Typically, you're calling your util module functions from within your components. In that case, you might want to pass the dispatch method as a parameter.
import { openloading } from '../actions/openloading'
export const myFn = (e, dispatch) => {
dispatch(openloading(e.font))
}
Your component should get wired up to Redux like so:
import React from 'react'
import { connect } from 'react-redux'
import { myFn } from 'somewhere'
const myComponent = ({ dispatch }) => {
myFn(e, dispatch)
return (
<div>
{ ...whatever }
</div>
)
}
const mapStateToProps = (state) => {
return { ...stuff }
}
const mapDispatchToProps = (dispatch) => {
return {
dispatch: dispatch
}
}
export default connect(mapStateToProps, mapDispatchToProps)(myComponent)
Note that dispatch is getting passed into the component as a prop in mapDispatchToProps.