I've pasted my Component below which is very, very basic. When the Component is mounted, it will basically call the fetchMessage Action, which returns a message from an API. That message will in turn get set as state.feature.message in the mapStateToProps function.
I'm at a complete loss on where to begin testing this Component. I know that I want to test that:
1) The Feature Component is rendered
2) The fetchMessage function in props is called
3) It displays or has the correct message property when rendered using that
I've tried setting my test file up as you can see below, but I just end up with repeated error after error with everything that I try.
Could anyone point me in the right direction with what I'm doing wrong?
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import * as actions from './actions';
class Feature extends Component {
static propTypes = {
fetchMessage: PropTypes.func.isRequired,
message: PropTypes.string
}
componentWillMount() {
this.props.fetchMessage();
}
render() {
return (
<div>{this.props.message}</div>
);
}
}
function mapStateToProps(state) {
return { message: state.feature.message };
}
export default connect(mapStateToProps, actions)(Feature);
Test file:
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
import expect from 'expect';
import { shallow, render, mount } from 'enzyme';
import React from 'react';
import sinon from 'sinon';
import Feature from '../index';
const mockStore = configureStore([thunk]);
describe('<Feature />', () => {
let store;
beforeEach(() => {
store = mockStore({
feature: {
message: 'This is the message'
}
});
});
it('renders a <Feature /> component and calls fetchMessage', () => {
const props = {
fetchMessage: sinon.spy()
};
const wrapper = mount(
<Provider store={store}>
<Feature {...props} />
</Provider>
);
expect(wrapper.find('Feature').length).toEqual(1);
expect(props.fetchMessage.calledOnce).toEqual(true);
});
});
You can use shallow() instead of mount() to test your component. The shallow() method calls the componentWillMount() life-cycle method so there is no reason to use mount(). (Disclaimer: I am not quite well at mount() yet.)
For connected components, you can pass a store object like this:
<connectedFeature {...props} store={store} />
And you should call shallow() method twice to make it work for connected components:
const wrapper = shallow(<connectedFeature {...props} store={store} />).shallow()
Testing Connected React Components
Use separate exports for the connected and unconnected versions of the components.
Export the unconnected component as a named export and the connected as a default export.
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import * as actions from './actions';
// export the unwrapped component as a named export
export class Feature extends Component {
static propTypes = {
fetchMessage: PropTypes.func.isRequired,
message: PropTypes.string
}
componentWillMount() {
this.props.fetchMessage();
}
render() {
return (
<div>{this.props.message}</div>
);
}
}
function mapStateToProps(state) {
return { message: state.feature.message };
}
// export the wrapped component as a default export
export default connect(mapStateToProps, actions)(Feature);
Remember connected components must be wrapped in a Provider component as shown below.
Whereas unconnected components can be tested in isolation as they do not need to know about the Redux store.
Test file:
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
import expect from 'expect';
import { shallow, render, mount } from 'enzyme';
import React from 'react';
import sinon from 'sinon';
// import both the wrapped and unwrapped versions of the component
import ConnectedFeature, { feature as UnconnectedFeature } from '../index';
const mockStore = configureStore([thunk]);
describe('<Feature />', () => {
let store;
beforeEach(() => {
store = mockStore({
feature: {
message: 'This is the message'
}
});
});
it('renders a <Feature /> component and calls fetchMessage', () => {
const props = {
fetchMessage: sinon.spy()
};
const wrapper = mount(
<Provider store={store}>
<connectedFeature {...props} />
</Provider>
);
expect(wrapper.find('Feature').length).toEqual(1);
expect(props.fetchMessage.calledOnce).toEqual(true);
});
});
Related
The translation only work on refresh. It seems because i have used a wrapper around the App.js that why its not working.
Also i tried to add a key to intlprovider the translation worked but now all my inner components get refresh.
Could there be a way to used reactintl when using an app wrapper without refreshing all inner components??
Below you can find the app.js, index.js and the app wrapper:
Index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import './styles/global.css';
import registerServiceWorker from './registerServiceWorker';
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
import rootReducer from './redux/rootReducers';
import AppWrapperContainer from './containers/appWrapperContainer/appWrapperContainer';
import {localeSet} from './redux/actions/localeActions';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(rootReducer, /* preloadedState, */ composeEnhancers(
applyMiddleware(thunk)
));
if(localStorage.H24Lang)
{
store.dispatch(localeSet(localStorage.H24Lang));
}
ReactDOM.render((
<Provider store={store}>
<AppWrapperContainer/>
</Provider>
),
document.getElementById('root'));
registerServiceWorker();
AppWrapperContainer.js
import React, { Component } from 'react';
import { IntlProvider, addLocaleData} from "react-intl";
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import messages from '../../messages';
import en from "react-intl/locale-data/en";
import fr from "react-intl/locale-data/fr";
import App from "../../App";
addLocaleData(en);
addLocaleData(fr);
class AppWrapperContainer extends Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
const {lang} = this.props
let locale =
(navigator.languages && navigator.languages[0])
|| navigator.language
|| navigator.userLanguage
|| lang
return (
// <IntlProvider locale={lang} messages={messages[lang]} key={lang}></IntlProvider>
<IntlProvider locale={lang} messages={messages[lang]} >
<App/>
</IntlProvider>
);
}
}
AppWrapperContainer.propTypes = {
lang: PropTypes.string.isRequired
}
//what reducer you need
function mapStateToProps(state) {
console.log("State is", state);
return {
lang: state.locale.lang
};
}
export default connect(mapStateToProps,null)(AppWrapperContainer);
App.js
import React, { Component } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import Home from './screens/home/home';
import { connect } from 'react-redux';
import { BrowserRouter as Router, Route,Redirect, Switch } from 'react-router-dom';
class App extends Component {
constructor(props) {
super(props);
this.state = {
};
}
componentWillMount() {
}
componentDidMount() {
}
render() {
return (
<div className="App">
<Router>
<div className = "app-main-content">
<Route exact path='/' component={Home} />
</Router>
</div>
);
}
}
//what reducer you need
function mapStateToProps(state) {
return {
};
}
function mapDispatchToProps(dispatch) {
return {
};
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
The key prop in IntlProvider forces React to remount the component (and all its children), but you just want them to be re-rendered (not remounted).
First confirm that your stored state is changing its locale/lang value as expected and that this change goes thougth mapStateToProps to your AppWrapperContainer component.
Then, make sure it is received in componentWillReceiveProps method and that a re-render is fired when its value changes. Then, all children will be re-rendered (if not blocked by shouldComponentUpdate method).
By the way, what is locale variable in AppWrapperContainer for?
Problem:
I can't display the value from the state of redux, which is delivered by mapStateToProps function to the component.
Project structure:
Create-react-app CLi application built the project.
Inside of the src/ I have the following code structure
Necessary code:
The main page which we are interacting with looks like this:
Underneath it is planned to post the result of the clicking on the buttons.
So how do I bind the redux state and actions to those two components: Calculator and ResultLine?
Let me show the index.js code, where I create the store:
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore } from "redux";
import reducers from './reducers/';
import App from './components/App';
ReactDOM.render(
<Provider store={createStore(reducers)}>
<App />
</Provider>,
document.getElementById("root")
);
There are only three actions:
import {CALCULATE, ERASE, PUT_SYMBOL} from "./types";
export const putSymbol = (symbol) => {
return {
type: PUT_SYMBOL,
payload: symbol
}
};
export const calculate = () => {
return {
type: CALCULATE
}
};
export const erase = () => {
return {
type: ERASE
}
};
And in the App.js I pass reducers, which are binded to those actions to the Calculator component:
import React, {Component} from 'react';
import Calculator from './Calculator';
import ResultLine from "./ResultLine";
import {calculate, erase, putSymbol} from "../actions/index";
import {connect} from "react-redux";
class App extends Component {
render() {
return (
<div>
<Calculator
onSymbolClick={this.props.onSymbolClick}
onEqualsClick={this.props.onEqualsClick}
onEraseClick={this.props.onEraseClick}/>
<br/>
<ResultLine result={this.props.result}/>
</div>
);
}
}
const mapStateToProps = (state) => {
console.log('mapState', state.calc.line);
return {
result: state.line
}
};
const mapDispatchToProps = {
onSymbolClick: putSymbol,
onEqualsClick: calculate,
onEraseClick: erase
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
And that works fine. Whenever I click the button the state changes, and I observe it in the console log, called in mapStateToProps function.
So I expect, that I can deliver result prop to the Result line easily, and I pass it into the ResultLine component as a parameter. So, let's look at that element:
import React from 'react';
const ResultLine = ({result}) => {
return (
<p>{result}</p>
);
};
export default ResultLine;
And I can see no changes in a result line. Maybe, something wrong with the React/Redux lifecycle management and ResultLine component just does not update on changes in state?
There's an error on mapStateToProps.
Instead of:
const mapStateToProps = (state) => {
return {
result: state.line
}
}
Please use:
const mapStateToProps = (state) => {
return {
result: state.calc.line // calc was missing here
}
}
I'm just getting started with React/Redux and have everthing working fine with Redux when I test it.
However, I am not able to connect it into my actual application.
I assume I should use connect(), but I don't know how/where to.
// libraries
import React from 'react';
import ReactDOM from 'react-dom';
// redux
import { Provider } from 'react-redux';
import store from './redux/store';
import './redux/test.js';
import {connect} from 'react-redux'
class App extends React.Component {
constructor(props, test) {
super(props);
}
render () {
return (
<div id = 'contents'>
</div>
)
}
}
const app = document.getElementById('app');
ReactDOM.render(
<Provider store={store}>
<App></App>
</Provider>
, app);
You need to connect your component App.
By example let's assume you have value in your reducer named reducer in your store :
// libraries
import React from 'react';
import ReactDOM from 'react-dom';
// redux
import { Provider } from 'react-redux';
import store from './redux/store';
import './redux/test.js';
import {connect} from 'react-redux'
class App extends React.Component {
constructor(props, test) {
super(props);
}
render () {
// You can use data from props
return (
<div id = 'contents'>
{this.props.data}
</div>
)
}
}
const mapStateToProps = (state, ownProps) => {
// Bind your store state to the component data
return {
date: state.reducer.value,
};
}
const mapDispatchToProps = (dispatch) => {
// Bind actions to your component
}
const ConnectedApp = connect(mapStateToProps, mapDispatchToProps)(App);
const app = document.getElementById('app');
ReactDOM.render(
<Provider store={store}>
<ConnectedApp></ConnectedApp >
</Provider>
, app);
so what you need is to wrap App with the function returned by connect:
const AppWithRedux = connect()(App);
ReactDOM.render(
<Provider store={store}>
<AppWithRedux/>
</Provider>,
app);
But you can find a good example about how to integrate redux with react in the following link:
http://redux.js.org/docs/basics/UsageWithReact.html
I'am using redux, react-router-redux and redux-form in my code. Code has a Provider, Connected router and Mini component. Mini component includes Switch and some components, which depends on route.
Index.js
...
import { Provider } from 'react-redux'
import { ConnectedRouter, routerMiddleware } from 'react-router-redux'
import createBrowserHistory from 'history/createBrowserHistory'
import Reducers from './reducers'
const history = createBrowserHistory({ basename: 'mini' })
const middlewareRouter = routerMiddleware(history)
const store = createStore(Reducers, applyMiddleware(middlewareRouter))
render(<Provider store={store}>
<ConnectedRouter history={history}>
<Mini/>
</ConnectedRouter>
</Provider>, document.getElementById('root'))
Mini.js
import React, { Component } from 'react'
import { Switch, Route } from 'react-router-dom'
...
import NavigationContainer from './containers/navigation'
import CategoryContainer from './containers/category'
class Mini extends Component {
render () {
return (<main>
<Switch>
<Route path="/navigation" component={NavigationContainer}/>
<Route path="/category" component={CategoryContainer}/>
...
</Switch>
<LoadContainer/>
<div id="form"></div>
</main>)
}
}
All components in Switch section has a button. Clicking on the button can render a form.
...
import FormCreate from './formcreate'
class Topbar extends Component {
constructor (props) {
super(props)
this.handleClickCreate = this.handleClickCreate.bind(this)
}
handleClickCreate (e) {
e.preventDefault()
render(<FormCreate/>, document.getElementById('form'))
}
...
}
But when I click on button error appear Uncaught Error: Could not find "store" in either the context or props of "Connect(Form(FormCreate))"
How can I fix the problem? Thanks in advance!
PS Reducers.js
import { combineReducers } from 'redux'
import { routerReducer as reducerRouter } from 'react-router-redux'
import { reducer as reducerForm } from 'redux-form'
const Reducers = combineReducers({
...
router: reducerRouter,
form: reducerForm
})
PSS FormCreate.js
import React from 'react'
import { Field, reduxForm } from 'redux-form'
...
const FormCreate = (props) => {
const { error, handleSubmit, pristine, reset, submitting } = props
return (
...
)
}
export default reduxForm({
form: 'create',
validate
}) (FormCreate)
I think the problem here is that you are trying to render FormCreate create another app within html element form that does not have access to the redux store, resulting in the error that you see.
What I would do is set up a reducer that handle whether or not I should render FormCreate then render it in component in your app like in LoadContainer.
Topbar.js
class Topbar extends Component {
constructor (props) {
super(props)
this.handleClickCreate = this.handleClickCreate.bind(this)
}
handleClickCreate (e) {
e.preventDefault()
// dispatch action to reducer to tell store to display FormCreate
}
...
}
LoadContainer.js
class LoadContainer extends Component {
// ... rest of code
render() {
// get shouldDisplayForm from redux store
const { shouldDisplayForm } = this.props;
return (
//... rest of component
{ shouldDisplayForm && <FormCreate> }
);
}
}
Alternatively, if you want to render FormCreate in html element 'form', you can put the store in a file so that you can require it in many files. Then render FormCreate with Provider like what you've done Index.js.
Trying out React + Redux, and probably am doing something obviously stupid, because a component that fires an action to fetch data over the network does not get updated (re-rendered) when the data is fetched.
Here are the relevant bits of my code:
The top-level index.js serving as an entry point for the app:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { Router, browserHistory } from 'react-router';
import reduxPromise from 'redux-promise';
import createLogger from 'redux-logger';
const logger = createLogger();
import routes from './routes';
import reducers from './reducers';
const createStoreWithMiddleware = applyMiddleware(reduxPromise, logger)(createStore);
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<Router history={browserHistory} routes={routes} />
</Provider>
, document.querySelector('.container'));
Top-level container App:
import React, {Component} from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as Actions from '../actions';
import Header from '../components/header';
import Showcase from '../components/showcase';
function mapStateToProps(state) {
return {
resources: state.resources
}
}
function mapDispatchToProps(dispatch) {
return {
fetchResources: () => {
dispatch(Actions.fetchResources());
}
}
}
class App extends Component {
render() {
console.log('props in App', this.props);
return (
<div>
<Header/>
<Showcase
fetchResources={this.props.fetchResources}
resources={this.props.resources}
/>
</div>
);
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App)
Component that triggers an action to sends a request for data when it is about to mount and is supposed to show the fetched data:
import React, {Component} from 'react';
import {connect} from 'react-redux';
class Showcase extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.fetchResources();
}
render() {
console.log('resources', this.props);
return (
<div>
This is showcase
</div>
);
}
}
export default connect(state => ({resources: state.resources}))(Showcase)
Action Creator:
import * as types from '../constants/ActionTypes';
import axios from 'axios';
export function fetchResources() {
return {
type: types.FETCH_FIRST,
payload: axios.get('/sampledata/1.json')
}
}
Reducer for the fetch action:
import * as types from '../constants/ActionTypes';
export default function resourcesReducer (state={}, action) {
switch (action.type) {
case types.FETCH_FIRST:
console.log('about to return', Object.assign (state, {resources: action.payload.data }))
return Object.assign (state, {resources: action.payload.data });
default:
return state
}
};
and finally the root reducer:
import { combineReducers } from 'redux';
import navigationReducer from './navigation-reducer';
import resourcesReducer from './resources-reducer';
const rootReducer = combineReducers({
navigationReducer,
resourcesReducer
});
export default rootReducer;
So, here is what I am observing. The action to request data is successfully triggered, a request is sent, the reducer receives it when the promise is resolved, and updates the state with the fetched data. At this point, I would expect the top-level App component and the Showcase component to detect that the store has updated, and to re-render, but I do not see it in the console.
Also, I am confused by redux-logger’s console output:
Specifically, I am surprized to see that the state contains reducers from the rootReducer — I don't know if it's right (an example on Redux logger Github page shows a state without reducers). It also seems surprising that the prev state as reported by redux-logger contains the same resourcesReducer object as the next state, although intuitively I would expect prev state to be more or less empty.
Could you please point out what I am doing wrong and how to get React components respond to the state changes?
==================================================
UPDATED:
1) Changed the mapStateToProps function in the App component so that it correctly maps to reducer states:
function mapStateToProps(state) {
return {
resources: state.resourcesReducer
}
}
2) Still passing the resources down to the `Showcase component:
render() {
console.log('props in App', this.props);
return (
<div>
<Header navigateActions={this.props.navigateActions}/>
React simple starter
<Showcase
fetchResources={this.props.fetchResources}
resources={this.props.resources}
/>
</div>
);
3) Trying to display resources on the screen by stringifying it to see what’s actually inside this object:
render() {
console.log('resources', this.props);
return (
<div>
This is showcase {JSON.stringify(this.props.resources)}
</div>
);
}
See this on the screen: This is showcase {}. The component does not seem to re-render.
Here’s the screenshot of the console showing that App’s props have updated with the values from the next state. Still, that did not cause the component to re-render:
UPDATED AGAIN: And my javascript-fu was poor, too. I did not quite realize that by returning Object.assign (state, {resources: action.payload.data }); I was in fact mutating the state, and that a simple inversion of arguments would let me achieve what I intended. Thanks to this discussion on SO for enlightenment.
I am surprized to see that the state contains reducers from the rootReducer
This is how it works. Take a closer look at combineReducers().
const rootReducer = combineReducers({
navigationReducer,
resourcesReducer
});
Recognise that it's not a list of parameters; it's a single object parameter. Perhaps it is clearer in verbose syntax:
var rootReducer = combineReducers({
navigationReducer: navigationReducer,
resourcesReducer: resourcesReducer
});
The resourcesReducer key points to the state returned by the resourcesReducer() function. That is, the state variable within the resourcesReducer() is just one part of the entire state.
The functions passed to connect() take the entire state as an argument. What yours should actually look like is this:
export default connect(state => ({
resources: state.resourcesReducer.resources
}))(Showcase);