How do you snapshot test components with actions from Redux connect? - redux

I want to snapshot test a react component that dispatches a redux action in its componentDidMount() handler. The action is given to the component through Redux's connect().
How should I mock the action?
Right now the action is imported in the component (see Newsfeed.js below). So how would I swap it out for a mock action in my test?
I'm using redux-thunk for my async actions, but that shouldn't matter much.
app/components/Newsfeed.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
// An action that makes an async request to get data
import { loadNewsfeedStories } from '../actions/Newsfeed';
class Newsfeed extends Component {
componentDidMount(){
this.props.loadNewsfeedStories();
}
...
}
export default connect(state => ({
stories: state.newsfeed.stories
}),
{
loadNewsfeedStories
})(Newsfeed)
app/tests/Newsfeed.test.js
import React from 'react';
import { Provider } from 'react-redux';
// This creates a mockStore using my reducers and a saved JSON state.
import { mockStore } from './MockState';
// The component to test
import Newsfeed from '../components/Newsfeed';
test('Newsfeed matches snapshot', () => {
const wrapper = mount(
<Provider store={mockStore}>
<Newsfeed />
</Provider>
);
expect(wrapper).toMatchSnapshot();
});
Possible Solutions
Export the unconnected component, and manually pass in all props & mock actions. Will be a lot of extra coding compared to just using the mockStore & provider. Also we won't be testing the 'connected' component.
Use something like Nock? Seems to intercept HTTP calls, so the ajax requests wouldn't actually go anywhere.
For axios, there is a lib called moxios - https://github.com/mzabriskie/moxios

You could try exporting a different connected component, but with a mocked "mapDispatchToProps" function. This way, no action will get through.
But personally, when testing components. I write a mock reducer, these should not change the state but record all dispatched actions (Jests mock functions are very useful for this). This way, you can also test if the correct action is dispatched when clicking a button,...

It's a bad idea to test the component, redux store, and mock http requests at the same time because unit tests should be small.
Another option would be to avoid executing business logic in componentDidMount.
Instead of writing
componentDidMount(){
this.props.loadNewsfeedStories();
}
You can move this logic to the redux module (depends on libs you use).
If you use only redux-thunk without libraries like saga or redux-logic, you can use this https://github.com/Rezonans/redux-async-connect.
but record all dispatched actions
Redux thunks are hard to test. Your mock dispatcher will receive only an array of functions. And you can't verify if a function was created by loadNewsfeedStories() or by someAnotherThunk().

Related

Can we use redux states in a react component without using {connect}?

I usually import {connect} from react-redux to connect my React component to a Redux store.
Is there any other way to access the redux store from react components without using {connect}?
There is useSelector from React Redux Hooks
The selector is approximately equivalent to the mapStateToProps argument to connect conceptually. The selector will be called with the entire Redux store state as its only argument. The selector will be run whenever the function component renders (unless its reference hasn't changed since a previous render of the component so that a cached result can be returned by the hook without re-running the selector). useSelector() will also subscribe to the Redux store, and run your selector whenever an action is dispatched.
import React from 'react'
import { useSelector } from 'react-redux'
export const CounterComponent = () => {
const counter = useSelector(state => state.counter)
return <div>{counter}</div>
}

Redux Connect: mapDispatchToProps pass in the result of an action creator

I'm learning redux and and wanted to know how dispatch passes in result of an action creator as stated in the screenshot below taken from redux doc.
The learning app code is available here: https://github.com/ZhangMYihua/lesson-12/tree/master/src
As per the course instructor the below mapDispatchToProps code updates the header component state using user.action.js. I'm not able to understand how all this works even after reading the redux documentation.https://react-redux.js.org/using-react-redux/connect-mapdispatch
const mapDispatchToProps = dispatch => ({
setCurrentUser: user => dispatch(setCurrentUser(user))
});
The initial state of the currentUser in the header component is set using the user.reducer.js file in the redux folder. This part is clear.
When we sign in using Google SignIn, the createUserProfileDocument(userAuth) function in App.js will check if the google login is available in the firestore users collections. If not it will create a copy of the account with the required fields. The user login details are fetched from the firestore users collections which are passed in the userRef in App.js.
What i do not understand is how data from userRef in componentDidMount() gets passed to header component using dispatch when we login using google signin?
componentDidMount() {
const {setCurrentUser} = this.props;
this.unsubscribeFromAuth = auth.onAuthStateChanged(async userAuth => {
if (userAuth) {
const userRef = await createUserProfileDocument(userAuth);
userRef.onSnapshot(snapShot => {
setCurrentUser({
id: snapShot.id,
...snapShot.data()
});
});
}
Below is the example from the react-redux documentation which is similar to the above.
This part in your componentDidMount is passing userRef data to the redux store:
const {setCurrentUser} = this.props;
// ...
setCurrentUser({
id: snapShot.id,
...snapShot.data()
});
mapDispatchToProps
The code calls this.props.setCurrentUser and that is function which you define in your mapDispatchToProps.
const mapDispatchToProps = dispatch => ({
setCurrentUser: user => dispatch(setCurrentUser(user))
});
So this.props.setCurrentUser function definition is simple like this (where dispatch is defined in upper scope):
this.props.setCurrentUser = user => dispatch(setCurrentUser(user))
The function takes a single parameter user and calls dispatch(setCurrentUser(user)). Here the setCurrentUser(user) is your action creator that produce some action (something like this {action: 'SET_CURRENT_USER', payload: {user}}). This action is dispatched to redux store with dispatch function (the react-redux passes correct dispatch function to your mapDispatchToProps through react-redux connect, see bellow).
connect
The mapDispatchToProps is useless if you are not use it as an argument of the react-redux connect function. You usually wrap your component with connect and export the wrapped component not just the component itself. So whenever you import your App.js module and use the default export from it as component in some JSX tree, it is not just your component, but it is your component wrapped with react-redux magic. This magic ensures, that the mapDispatchToProps is called with correct dispatch argument (store.dispatch from redux library) and enhances your component props object with another properties, the ones form mapStateToProps and the ones from mapDispatchToProps.
// here you are wrapping your App component with `connect` from `react-redux`
export default connect(
null,
mapDispatchToProps
)(App);
In next file you will import and use the wrapped component like this:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './redux/store';
import App from './App'; // here you are importing the wrapped component
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
It is necessary to have your components which uses react-redux have wrapped with Provider in the app component tree. Otherwise connect will not have access to redux store so connecting component to redux store won't work.
EDIT
mapStateToProps
this.props.setCurrentUser is used to dispatching action to your redux store which just MODIFIES it (how the store is modified is up to your implementation of reducer - see user.reducer.js in your example).
If you want to access some part of your redux store, you need to pass the mapStateToProps by the first argument (see above that in example of your App component the first argument of connect was null).
You can imagine the redux store as a single javascript object. The first definition of this object you can see in your root-reducer.js:
export default combineReducers({
user: userReducer
});
This defines that your redux store (object) has the property user on the top level. The value of property user handles user.reducer.js. That reducer defines initial value of the user part of the store and also defines how it can be modified (by redux actions). So according to your example your initial store is this:
{
user: {
currentUser: null
}
}
If you want access it in your component (props) you need to use mapStateToProps as I stated above, like this (see the file header.component.jsx in your example):
// header.component.jsx
const mapStateToProps = state => ({ // react-redux passes here the whole redux store to the argument state
/*
you want access currentUser of user reducer,
so you just access it the same way as you would do it
in javascript object, with use of dots.
*/
currentUser: state.user.currentUser //
});
export default connect(mapStateToProps)(Header);
And then you access the connected store value in your component by props:
// whenever in your component code
console.log(this.props.currentUser);

Does Next.js not need browser history when using Redux?

I'm trying to migrate my React app to Next.js. I keep having below error from export const history = createBrowserHistory(); in my configureStore.js.
Invariant Violation: Browser history needs a DOM
configureStore.js
import { createStore, combineReducers, compose, applyMiddleware } from "redux";
import { connectRouter, routerMiddleware } from "connected-react-router";
import { createBrowserHistory } from "history";
import thunk from "redux-thunk";
import user from "./modules/user";
import stores from "./modules/stores";
import info from "./modules/info";
export const history = createBrowserHistory();
const middlewares = [thunk, routerMiddleware(history)];
const reducer = combineReducers({
user,
stores,
info,
router: connectRouter(history)
});
export default function configureStore(preloadedState) {
return createStore(
reducer,
preloadedState,
compose(
applyMiddleware(
...middlewares
)
)
);
}
I've found that many things need to be changed when migrating React to Next.js as Next is a framework which requires its own code architecture as well as the difference between SSR and CSR. When I studied Next.js tutorials, there is a routing section which says that routing is differentiated based on CSR or SSR. Does that mean I cannot use browser history in Next.js? That's what I'm guessting. I'm still confused.
You are right. There is different between SSR and CSR. Next.js use Next Router for routing for CSR and if you need custom SSR, then you should ask for some help from frameworks like express.
By doing the CSR, nextjs will remember browser history and back button works as expected. However if you need to change the route to a very diffrernt route you can use any of these solutions:
import Router from 'next/router';
...
Router .push('/about');
Or
import Link from 'next/link';
...
<Link href="/about"><a>about</a></Link>
and if you need to do some extra work before routing then you should use:
Router.beforePopState(({ url, as, options }) => {...}
Migration would take some time and you need to remember next.js will take over the charge for routing and browser history automatically. Unless you customise it.

Redux --I want to render SimpleModal component in handleClick , how can I achieve it through redux

I want to render SimpleModal component in handleClick , how can I achieve it through redux
can I do this way??
//ReactDOM.render(, document.getElementById("123"));
import React, { Component } from 'react';
import ReactDOM from 'react-dom'
import Redux,{createStore,combineReducers } from 'redux';
import SimpleModal from './modal.js';
import {Provider, connect} from 'react-redux';
import {displayItems} from './reducers.js';
const ecommerceAppReducer = require('./reducers.js').default;
const store = createStore(ecommerceAppReducer);
const EcommerceApp = React.createClass({
componentDidMount(){
store.dispatch({
type: 'LIST_DATA',
id: 12
});
},
handleClick: function(entity){
this.props.dispatch({
type: 'DISPLAY_INFORMATION',
entity:entity
});
**Want to render a SimpleModal here**
},
render() {
return (
<div>
<ul>{
this.props.state.displayItems.map(function(e) {
return <li><a onClick={this.handleClick.bind(this,e) }>{e.name}</a></li>
}.bind(this))
}
</ul>
</div>
);
}
});
const mapStateToProps = function (state) {
return {state};
}
const Eapp = connect(mapStateToProps)(EcommerceApp);
class App extends React.Component {
render() {
return (
<Provider store={store}>
<Eapp />
</Provider>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'));
In React, your render function should return what the app looks like currently. As in right now. React will take care of updating and rendering and so forth as long as you use one of the methods to inform React when it needs to rerender something.
One method is to call React.render on the root of your app. This is the worst way, but not terrible for small apps. Only recommended if you know what you're doing and even then there are probably better methods.
The next is to use setState() in your component. React will call that particular component's render method sometime after that. It's much more precise in that not your entire app gets rerendered (although you can always stop the rendering cascade by implementing shouldComponentUpdate judiciously).
Next is to call forceUpdate which is terrible to use unless you are really sure of what it is you're getting yourself into. React-Redux uses this because they do know what they're getting into.
And finally, there's React-Redux, which isn't really another way for React to render your component. But it is a new way for the developer. This is by far the recommended way to do things.
To use it, you just follow the connect prescribed method for transforming your Redux state into props for your component.
This requires reading the a Redux docs. It's a long and arduous process that is guaranteed to make anyone a better developer.
In your mapStateToProps implementation it's important to be very selective with what parts of the state you pass along to your component.
Don't just go and pass the entire Redux state. This would cause your entire app to rerender if anything at all changed anywhere in your app. Less than optimal. Only pass what you need. Don't even pass what child components need. They get their own connect.
Now onwards and forwards we go.
You want handleClick to pop up some stuffs and show it to the user.
Method 1: Use alert. It's ugly and super simple. It provides a terrible user experience so it's not recommended.
Method 2: Use React-Redux. dispatch an action that causes your reducer to put some data in the state that lets your app know to show the data. Looks like you are already doing that!
Redux will then inform React-Redux that something has changed.
React-Redux will then check if any of your components use the information in the state that was just changed. It knows what you use because this is what you returned from your mapStateToProps function.
React-Redux will then tell React to rerender any of the components that it finds need updating.
When your component's render method gets called, you'll get the new info in the props. So do:
render() {
return (
<div>
{Boolean(this.props.modalOpen) && <MyConnectedModal />}
<ul>{
this.props.displayItems.map(function(e) {
return <li key={e.name}><a onClick={this.handleClick.bind(this, e) }>{e.name}</a></li>
}.bind(this))
}
</ul>
</div>
);
}
There's still plenty wrong with the above code. You should, for instance, never bind in render.
Note that the modal is a component apart. It gets its data from React-Redux and not from props passed by the parent. This means your EcommerceApp component does not have to be responsible for updating the modal if any data it's displaying changes. Redux will take care of that. Actually with React-Redux's help of course. And React, naturally. Not necessarily in that order.
To recap what's going on here: Your render method tells React not what to pop up, but what the final result should look like. This is an enormous difference and pretty much the entire point of React.
You never tell React what changed. You always tell it what the final result should look like. React will then go and figure out what happened and will find an efficient way to show it in your browser window or electron or nw.js desktop app or native mobile app or anywhere else React worx.

Make api calls from dumb components with Redux

I want to implement dropdown with react and redux. Dropdown will be a part of other component, so, it it really "Dumb" component. But dropdown should call to api to fetch items, apply custom filters and etc. Api calls should be authenticated, tokens stored it global state. Should I pass tokens to component props? Or there is a better way to do this?
A dumb component, by definition, should be dumb: it means that it should take everything it needs "from the top", i.e. via the props.
Only a "Smart" ("connected" to Redux) Component, up the hierarchy, would deal with fetching the data using a new (async) Action, which would then modify the state when the API call returns, which would re-render your Dumb Component with its new props.
So in Redux:
Your Dumb component takes two props: one with the values coming from your API (which actually are part of your "state"), the other one a function that is called when your dropdown selected item changes. <MyDumbComponent data={this.props.data} onChange={this.onChange.bind(this)} />
Then a "Smart" component up the hierarchy will listen to that onChange function, and dispatch an Action (FETCH_DATA)
The (async) Action will call the API, and when receiving the data, call another Action (FETCH_DATA_SUCCESS) with the data
Then Redux, with a reducer, would update its state from the Action payload
Which will re-render your Component with its new props (new data), coming from the current state.
You might want to read this: http://redux.js.org/docs/basics/UsageWithReact.html
And regarding async actions: http://redux.js.org/docs/advanced/AsyncActions.html
Dumb component doesn't mean it can do anything like fetch updates, it means it's 'dumb' to the concept of redux, and knows nothing about your store or what library you're using. The benefit is that you can change your flux implementation and you only have the small bit of work to update the smart components.
For the type of scenario you're describing, you would give your <Menu> a function via props that would run when <Menu> wants to update the data. <Menu> knows nothing about Redux - it's just executing a function - but it's still able to fetch new data. Depending on the complexities, you could pass through the raw action creator (bound to dispatchAction) and have it run that.
import * as dataActions from './actions';
// We have dataActions.fetchItems() which is the action creater to get new items.
// Attach items, and the action creator (bound to dispatch) to props
#connect((store) => {
return {items: store.data.items}
}, dataActions)
class ItemsPage extends Component {
static propTypes = {
items: PropTypes.object, // the items from the store, from #connect
fetchItems: PropTypes.func // action dispatcher, from #connect
}
render() {
return (<Menu onChange={this.props.fetchItems} /> )
}
}
// Our 'dumb' component that doesnt know anything about Redux,
// but is still able to update data (via its smart parent)
class Menu extends Component {
static propTypes = {
onChange: PropTypes.func // same as fetchItems
}
render() {
return (<button onClick={this.props.onChange}>Update items</button>);
}
}

Resources