I have container components that all look similar to this:
import { connect } from 'react-redux';
import testAction from '../actions/TestComponentActions';
import TestComponent from '../components/TestComponent';
const mapDispatchToProps = {
testAction,
};
const mapStateToProps = (state: any) => state.testInformation;
export const TestContainer: any = connect(mapStateToProps, mapDispatchToProps)(TestComponent);
export default TestContainer;
how can I write unit tests for this component? Other than the simple render() and expect().toBeTruthy();
is that all that needs to be done? or can I get a little more specific here. thank you in advance
Related
How to use redux in class component using react with typescript.
1.) useDispatch,useSelector how to use it in class base components react typescript
And here I get the data from store.tsx using useSelector but this is a class component(App.tsx)
then, I dispatched here(App.jsx)
You have to use higher order component connect which is provided by react-redux library.
first import it
import { connect } from "react-redux";
then to access state use the function
const mapStateToProps = (props) => {
return {
api: props.api,
};
};
and for action use
const mapDispatchToProps = (dispatch) => {
return {
action: () => dispatch(action),
};
};
and at the end export it like
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);
to use it simply access through props, for example
const data = props.api
and same for dispatch
function addData () {
props.action()
}
I know how to to dispatch and get store using functional component. There is a great documentation about it here
But what about class component? How to get store and fire dispatch using class component. There is nothing said about it.
Lets say I defined hooks.ts:
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import { RootState, AppDispatch } from './store/store';
export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
And I need this functional component make as class:
import {useAppDispatch, useAppSelector} from "../../../hooks"
import { setRelease } from "./../store/factorySlice/factorySlice"
const X: React.FC = () => {
const {selectedRelease} = useAppSelector(store => store.factory)
const dispatch = useAppDispatch()
return (
<>
{selectedRelease}
<button onClick={() => dispatch(setRelease())}>Click</button>
</>
)
}
Redux Toolkit is our official recommended approach for writing Redux logic. It wraps around the Redux core, and contains packages and functions that we think are essential for building a Redux app
You can use it with class component by using react-redux's connect API:
import { connect } from "react-redux";
import { setRelease } from "./../store/factorySlice/factorySlice"
class X extends React.Component {
render() {
return (
<>
{this.props.selectedRelease}
<button onClick={() => this.props.dispatch(setRelease())}>Click</button>
</>
);
}
}
const mapStateToProps = (state) => ({
selectedRelease: state.factory.selectedRelease
});
export default connect(mapStateToProps)(X);
This was the first implementation. You would connect the class component to the store like
connect()(MyComponent)
Where Class is MyComponent
I have multiple components (screens) connected like this:
import { setNavigationHeader } from './actions';
const MyScreen = () => {
useEffect(() => {
props.setNavigationHeader('MyScreen title');
});
...
}
const mdtp = {
setNavigationHeader
}
export default connect(null, mdtp)(MyScreen);
Is there any way to "inject" this into the component so I can reuse it for multiple components? Maybe with a HOC?
I'm pretty new to React so I don't know what would be the best course of action here.
Also, I would need to be able add more actions to the mapDispatchToProps or to add a mapStateToProps if I want.
The connect function actually returns a HOC, you just need to create it once and reuse it inside your screen components
// navigation.utils.js
import { setNavigationHeader } from './actions';
export const connectScreen = connect(null, {setNavigationHeader});
// MyScreen.js
import { connectScreen } from './navitation.utils'
const MyScreen = ...
export default connectScreen(MyScreen);
// MyScreen2.js
import { connectScreen } from './navitation.utils'
const MyScreen2 = ...
export default connectScreen(MyScreen2);
[Solved] Check my answers
I'm learning MERN Stack via a youtube playlist https://www.youtube.com/watch?v=TO6akRGXhx8. I'm stuck when i reached the 28:04 where he forgot to connect his component with 'react-redux'. I followed how he resolve it but well, for some reason mine doesn't seem to be connected. No props was pass by to my ItemModal component. So i spent 3hrs to debug and lastly conclude that i found it weird that only when the js is named ShippingList, will connect() works... When I renamed ShippingList to another name and update the references, it doesn't work anymore... Please refer to below for some of the snippet
I dont think i need to identify a component to the store when creating it.. so im stupefied now..
Was wondering if u guys can replicate it, please find my repo
https://github.com/AmeDin/mern
ShoppingList.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
export class ShoppingList extends Component {
render() {
console.log(this.props)
console.log(this.state)
//const { items } = this.props.item;
return (
<div>
</div>
)
}
}
const mapStateToProps = (state) => ({
item: state.item
})
export default connect()(ShoppingList);
ShoppingListOne.js
import React, { Component } from 'react'
import { connect } from 'react-redux';
export class ShoppingListOne extends Component {
render() {
console.log(this.props)
console.log(this.state)
//const { items } = this.props.item;
return (
<div>
</div>
)
}
}
const mapStateToProps = (state) => ({
item: state.item
})
export default connect()(ShoppingListOne);
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { createStore, applyMiddleware, compose } from 'redux'
import rootReducer from './reducers/index'
import thunk from 'redux-thunk'
import { Provider } from 'react-redux'
import * as serviceWorker from './serviceWorker';
const middleware = [thunk];
const store = createStore(rootReducer,
compose(
applyMiddleware(thunk)
)
);
ReactDOM.render(<Provider store={store}><App /></Provider>,
document.getElementById('root'));
serviceWorker.unregister();
Screenshot of console.log: https://i.stack.imgur.com/FPBBs.png
Further testing
ShoppingListOne
const mapStateToProps = (state) => ({
item: state.item
})
const mapDispatchToProps = (dispatch) => {
console.log(dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(ShoppingListOne);
ShoppingList
const mapStateToProps = (state) => ({
item: state.item
})
const mapDispatchToProps = (dispatch) => {
console.log(dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(ShoppingList);
No functions seems to be called for ShoppingListOne.
ShoppingList has a function called line26, 3rd row of console.
https://i.stack.imgur.com/WxwRm.png
You need to pass mapStateToProps function as first argument to connect in order to make these value available to the component connected to redux store
. Connect without any arguments don't do anything except make dispatch available as a prop to the connected component
const mapStateToProps = (state) => ({
item: state.item
})
export default connect(mapStateToProps)(ShoppingListOne);
and
const mapStateToProps = (state) => ({
item: state.item
})
export default connect(mapStateToProps)(ShoppingList);
Also you need to make sure that you are imported the connected component which is ShoppingListOne exported as a default export rather than a named export
You import would look like
import ShoppingListOne from './path/to/ShoppingListOne';
You must pass mapStateToProps and mapDispatchToProps to connect, so that it can create a wrapper which has access to redux store.
export default connect(mapStateToProps, mapDispatchToProps)(ShoppingList);
export default connect(mapStateToProps, mapDispatchToProps)(ShoppingListOne);
Found out the issue...
The import statement seems to play its role to the connection..
Error
import { ShoppingList } from './components/ShoppingList';
import { ItemModal } from './components/ItemModal';
Correct
import ShoppingList from './components/ShoppingList';
import ItemModal from './components/ItemModal';
Anyone know the differences? Is there a post out there answering it i wonder?
I currently have a component like so:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getDataAction } from ' './my-component';
export class MyComponent extends { Component } {
componentWillMount() {
this.props.getData();
}
render(){
<div>
this.props.title
</div>
}
}
const mapStateToProps = (state) => ({
title: state.title
});
const mapDispatchToProps = (dispatch) ({
getData() {
dispatch(getDataAction());
}
});
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
and I am trying to shallow render test it using jest and enzyme.
test:
import React from 'react';
import { shallow } from 'enzyme';
import { MyComponent } from './index';
it('renders without crashing', () => {
shallow(<MyComponent getData={jest.fn()} />);
});
My question is, is this the conventional way to mock? Jest official docs don't mention specifically about mocking props and this post Using Jest to mock a React component with props is about testing with full mounting instead.
Is there another way to mock dispatchToProps? In this example there is only one, but what if I have a lot of functions in dispatchToProps?
Side Question: in my real file, I have a reference to a value like this.props.information.value which I expect to throw an error like cannot get value of undefined since information is not mocked/defined, but it doesn't. It's only when functions are not present that an error is thrown.
You can export mapDispatchToProps and write tests for it by importing it in your tests.
Add export { mapDispatchToProps }; at the end of your MyComponent.js
Create MyComponent.tests.js file beside MyComponent.js
import configureMockStore from 'redux-mock-store';
import thunkMiddleware from 'redux-thunk';
import { mapDispatchToProps } from './MyComponent';
const configMockStore = configureMockStore([thunkMiddleware]);
const storeMockData = {};
const mockStore = configMockStore(storeMockData);
describe('mapDispatchToProps', () => {
it('should map getDataAction action to getData prop', () => {
// arrange
const expectedActions = [getDataAction.type];
const dispatchMappedProps = mapDispatchToProps(mockStore.dispatch);
// act
dispatchMappedProps.getData();
// assert
expect(mockStore.getActions().map(action => action.type)).toEqual(expectedActions);
}
});
Here I have used thunk, just to let you know that how to do it if there are middlewares configured in your store setup.
Here getDataAction can also be a function instead of a simple action like { type: 'FETCH_DATA' } if you are using middlewares like thunks. However, the approach to test is same except that you will create expectedActions with explicit action types like const expectedActions = ['FETCH_CONTACTS']
Here FETCH_CONTACT is another action dispatched in your thunk i.e getDataAction