Testing simple Redux-Form with Enzyme (where is value??) - reactjs

I have the most simple redux-form connected to redux example :
import * as React from 'react';
import { Field, reduxForm } from 'redux-form';
import { connect } from 'react-redux';
class TestForm extends React.PureComponent {
render() {
return (
<form>
<Field component={Input} name={'testField'}/>
</form>
);
}
}
class Input extends React.PureComponent {
render(): React.Node {
let { input } = this.props;
// input = {name: 'testField', value: 'test value'};
return (
<input name={input.name} value={input.value} type='text' onChange={()=>1}/>
);
}
}
const mapStateToProps = ({ testForm }) => {
return {
initialValues: testForm,
};
};
export const TestFormWithReduxForm = reduxForm({ form: 'test form'})(TestForm);
export default connect(mapStateToProps)(TestFormWithReduxForm);
Note the following :
I have my own custom Input (called Input)
I am connecting to reduxForm, and then connecting to redux.
The initial values that are passed in should have 'name' and 'value'.
I have the following test (Jest+Enzyme)
import React from 'react';
import { Provider } from 'react-redux';
import Enzyme, { mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import configureStore from 'redux-mock-store';
import TestForm from './TestForm';
Enzyme.configure({ adapter: new Adapter() });
describe('Redux Form Test', () => {
let wrapper;
let mockStoreData = {
testForm: {
testField: 'test value',
},
};
const mockStore = configureStore();
const store = mockStore(mockStoreData);
beforeEach(() => {
wrapper = mount(
<Provider store={store}>
<TestForm />
</Provider>
);
});
it('Check Value', () => {
let inputs = wrapper.find('input');
expect(inputs.at(0).prop('name')).toBe('testField'); //OK!!!
expect(inputs.at(0).prop('value')).toBe('test value'); //empty string!!
});
});
The Jest test passes in a 'testForm' object with 'testField' (and its value) into the the store.
As expected, the name on the first input is 'testField', however the 'value' is empty (ie. an empty string).
This is not expected, because if I were to render the component in a normal page then 'test value' would appear.
So something seems to be broken here. I am not sure if it has something to do with redux-form or enzyme, but the redux-form Field object seems to be intercepting the properties that are passed into the Input object.
I am starting to question whether it is even possible to test redux form.

I'm not sure why you'd want nor need to test Redux Form's functionality, as it's already been tested by the creators/maintainers. However, redux-mock-store appears to be made for unit tests only (where you'll mock middlewares and call store.dispatch(actionType) and expect the action to be called). It doesn't handle reducer side effects nor track changes in state.
In the case above, you'll only need to do a unit test on your custom Input component, because that's the only component that's different from what would be considered a standard redux form.
That said... for an integration test, you'll need to use a real store that contains the redux-form's reducer and your field state.
Working example: https://codesandbox.io/s/zl4p5w26xm (I've included a Form integration test and an Input unit test -- as you'll notice, the Input unit test covers most of your testing needs)
containers/Form/Form.js
import React, { Component } from "react";
import { Form, Field, reduxForm } from "redux-form";
import { connect } from "react-redux";
import Input from "../../components/Input/input";
const isRequired = value => (!value ? "Required" : undefined);
class SimpleForm extends Component {
handleFormSubmit = formProps => {
alert(JSON.stringify(formProps, null, 4));
};
render = () => (
<div className="form-container">
<h1 className="title">Text Field</h1>
<hr />
<Form onSubmit={this.props.handleSubmit(this.handleFormSubmit)}>
<Field
className="uk-input"
name="testField"
component={Input}
type="text"
validate={[isRequired]}
/>
<button
type="submit"
className="uk-button uk-button-primary uk-button-large submit"
disabled={this.props.submitting}
>
Submit
</button>
<button
type="button"
className="uk-button uk-button-default uk-button-large reset"
disabled={this.props.pristine || this.props.submitting}
onClick={this.props.reset}
style={{ float: "right" }}
>
Clear
</button>
</Form>
</div>
);
}
export default connect(({ field }) => ({
initialValues: { [field.name]: field.value }
}))(
reduxForm({
form: "SimpleForm"
})(SimpleForm)
);
containers/Form/__test__/Form.js
import React from "react";
import { Provider } from "react-redux";
import { mount } from "enzyme";
import SimpleForm from "../Form";
import store from "../../../store/store";
const wrapper = mount(
<Provider store={store}>
<SimpleForm />
</Provider>
);
describe("Redux Form Test", () => {
it("renders without errors", () => {
expect(wrapper.find(".form-container")).toHaveLength(1);
});
it("fills the input with a default value", () => {
expect(wrapper.find("input").prop("name")).toBe("testField");
expect(wrapper.find("input").prop("value")).toBe("Test Value");
});
it("updates input value when changed", () => {
const event = { target: { value: "Test" } };
wrapper.find("input").simulate("change", event);
expect(wrapper.find("input").prop("value")).toBe("Test");
});
it("resets the input value to defaults when the Clear button has been clicked", () => {
wrapper.find("button.reset").simulate("click");
expect(wrapper.find("input").prop("value")).toBe("Test Value");
});
});
stores/stores.js (for simplicity, I lumped reducers and store into one file)
import { createStore, combineReducers } from "redux";
import { reducer as formReducer } from "redux-form";
const initialValues = {
name: "testField",
value: "Test Value"
};
const fieldReducer = (state = initialValues, { type, payload }) => {
switch (type) {
default:
return state;
}
};
const reducer = combineReducers({
field: fieldReducer,
form: formReducer
});
export default createStore(reducer);
Note: Aside from using initialValues, buried in the documentation, there are three other ways to update field values: Utilizing redux-form's reducer.plugin and dispatching an action to update the form, or by using this.props.intialize({ testField: "Test Value" }); with enableReinitialize: true and keepDirtyOnReinitialize: true,, or by using this.props.change("SimpleForm", { testField: "Test Value" });. Important to note because sometimes mapStateToProps is asynchronous.

Related

TypeScript Property 'value' does not exist on type 'HTMLElement'. React Jest Testing

Currently without TypeScript this code is working, but now it is not working unfortunately. It gave me the following error: Property 'value' does not exist on type 'HTMLElement'. Not sure what is wrong with this. Seems it is nagging about the value. In this case I am using Jest testing and React. Not sure if I can ignore this error or should fix this in order to avoid weird bugs in the future.
import React from 'react';
import axios from 'axios';
import { useDispatch } from "react-redux";
import { getData } from '../../../actions/index';;
export const SearchInput : React.FC = () => {
const dispatch = useDispatch();
let input: any;
const getInputValue = (value: string):void => {
let url = `https://api.tvmaze.com/search/shows?q=${value}`
}
return (
<div className="container">
<h1>Keyword</h1>
<form className="form display-inline-flex"
onSubmit={e => {
e.preventDefault()
if(!input.value.trim()) return;
getInputValue(input.value);
}}>
<input className="form-input-field disable-outline display-inline"
ref={node => (input = node)}
placeholder="Search catalog"
aria-label="search-input"
/>
<button type="submit" className="btn btn-grey white-color display-inline">
Search
</button>
</form>
</div>
)
}
export default SearchInput;
// Jest testing
import React from "react"
import { render, fireEvent } from "#testing-library/react";
import { SearchInput } from "./SearchInput";
import { Provider } from "react-redux";
import { store } from "../../../Store";
const setup = () => {
const utils = render(
<Provider store={store}>
<SearchInput/>
</Provider>);
const input = utils.getByLabelText("search-input");
return {
input,
...utils
}
}
test("It should check if input field get the value passed", () => {
const { input } = setup();
fireEvent.change(input, { target: { value: "search-bar-test" } })
expect(input.value).toBe("search-bar-test")
});
You should be good to go if you add a type assertion like:
const input = utils.getByLabelText("search-input") as HTMLInputElement;

Unable to update text area with new value using #testing-library/user-event

I need to test a graphql-react component using #testing-library/react. I want to display text area based on a result value from query. [am able to get that]. But, unable to update the text area with the new value using #testing-library/user-event.
Here's the graphql-react component:
CustomWelcomeMessageContainer.jsx
import React from 'react';
import PropTypes from 'prop-types';
import {Query} from 'react-apollo';
import FormControl from "react-bootstrap/lib/FormControl";
import {FormSection, FormSectionItem} from "../../src/modules/Forms/components/FormSection/FormSection";
import get from "lodash/get";
import customWelcomeMessageQuery from "../../modules/Forms/queries/allowWelcomeMessageQuery";
const CustomWelcomeMessageContainer = ({isStudentBuddy, universitySlug, prospectWelcomeMessage, onProspectMessageChange}) => {
return (<Query
query={customWelcomeMessageQuery}
variables={{
universitySlug,
}}
>
{({loading, data}) => {
let allowWelcomeMessageCustomization;
if (isStudentBuddy) {
allowWelcomeMessageCustomization = get(
data,
"university.allowWelcomeMessageCustomization",
);
} else {
allowWelcomeMessageCustomization = get(
data,
"university.allowWelcomeMessageCustomizationStaff",
);
}
if (
!loading &&
allowWelcomeMessageCustomization
) {
return (
<FormSection header="Custom Welcome Message">
<FormSectionItem>
<FormControl
componentClass="textarea"
typeClass="text"
value={prospectWelcomeMessage}
placeholder="Enter text"
onChange={onProspectMessageChange}
required
rows={5}
/>
</FormSectionItem>
</FormSection>
);
}
return null;
}}
</Query>
);
};
CustomWelcomeMessageContainer.propTypes = {
isStudentBuddy: PropTypes.bool,
universitySlug: PropTypes.string.isRequired,
prospectWelcomeMessage: PropTypes.string.isRequired,
onProspectMessageChange: PropTypes.func.isRequired,
};
CustomWelcomeMessageContainer.defaultProps = {
isStudentBuddy: false,
userDetails: {},
};
export default CustomWelcomeMessageContainer;
This is the test file:
CustomWelcomeMessageContainer.test.jsx
import React from 'react';
import {render, screen} from '#testing-library/react';
import wait from 'waait';
import '#testing-library/jest-dom';
import userEvent from "#testing-library/user-event";
import {MockedProvider} from '#apollo/react-testing';
import CustomWelcomeMessageContainer from './CustomWelcomeMessageContainer';
const mocks = [{
request: {
query: customWelcomeMessageQuery,
variables: {
universitySlug: "staffTest1",
},
},
result: {
data: {
university: {
id: "staffTest1",
allowWelcomeMessageCustomization: false,
allowWelcomeMessageCustomizationStaff: true,
},
},
},
},
];
describe('CustomWelcomeMessageContainer', async() => {
test("type", async () => {
render(<textarea
className="form-control"
placeholder="Enter text"
required=""
rows="5"
typeClass="text"
>Hello world </textarea>);
const val = await screen.findByRole("textbox");
userEvent.clear(val);
await userEvent.type(val, "hi");
expect(val).toHaveValue("hi");
});
test('should display the value entered by the ambassador', async() => {
render(
<MockedProvider
mocks={mocks}
addTypename={false}
>
<CustomWelcomeMessageContainer
isStudentBuddy
universitySlug="studentTest1"
prospectWelcomeMessage="default"
onProspectMessageChange={jest.fn()}
/>
</MockedProvider>,
);
await wait(0);
const customWelcomeMessageTextBox = await screen.findByRole("textbox");
userEvent.clear(customWelcomeMessageTextBox);
await userEvent.type(customWelcomeMessageTextBox, "hi");
expect(customWelcomeMessageTextBox).toHaveValue("hi");
});
});
In the first test, a basic text area is rendered and the text update works successfully [which means there's no problem with the usage of user-event].
Whereas in the second test, the graphql-react component is rendered and the text update fails. Is it because the textarea is initially populated from the props? Unable to understand why the update doesn't work. This is the error I get:
Error: expect(element).toHaveValue(hi)
Expected the element to have value:
hi
Received:
default
Try, this one.
const inputTextArea = getByRole("textbox");
expect(inputTextArea.value).toEqual("hi");

Accept multiple input field in redux?

I'm learning redux and can able to update single input field but when there's more than 1 input field can't able to update state of both input field!
Here's my code please check it.
My main index.js:
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import {FormReducer} from "./reducers/name";
import { createStore } from "redux";
import { Provider } from "react-redux";
const store = createStore(FormReducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
My presentational and input field component:
import React, { Component } from "react";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import * as actions from "./actions";
class App extends Component {
inputData = event => {
this.props.actions.addToDo({ name: event.target.value });
};
submitData = event => {
console.log(this.props.name);
// console.log(this.props.mobile);
event.preventDefault();
}
render() {
return (
<div>
<form onSubmit={this.submitData}>
FirstName:
<input
type="text"
name={this.props.name}
onChange={this.inputData}
/>
<input
type="text"
name={this.props.mobile}
onChange={this.inputData2}
/>
<button type="submit">Submit</button>
</form>
{this.props.name}
{this.props.mobile}
</div>
);
}
}
const mapStateToProps = state => ({
mobile: state.mobile,
name: state.name
});
const mapDispatchToProps = dispatch => {
return {
actions: bindActionCreators(actions, dispatch)
};
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
In my presentational component I want to make input acceptance such like array like [event.target.name]:event.target.value or something like this.
But when I try to do this with redux it's not working properly. Please check my code and give your input!
My reducer function:
let init = {
name: ''
}
export const FormReducer = (state = init, action) => {
switch (action.type) {
case "ADD_NAME":
return {...state, name:action.name, mobile:action.mobile}
default:
return state;
}
};
My action function which takes action on new input:
let init = {
name: ''
}
export const FormReducer = (state = init, action) => {
switch (action.type) {
case "ADD_NAME":
return {...state, name:action.name, mobile:action.mobile}
default:
return state;
}
};
And also as you can see I want to print both name and mobile both but it's only printing on which I'm working. Please give code so that I can able to print both simultaneously.
Thanks in advance!
It should be like this.
Use ecma6#computed property name [event.target.name]
inputData = event => {
this.props.actions.addToDo({ [event.target.name]: event.target.value });
};
EDIT :
in reducers,add this .
let init = {
name: { key : "name", value : "" },
mobile: { key: "mobile", value: "" },
};
reducer :
case "ADD_NAME":
console.log(action,state);
return {
...state,
name: {
...state.name, value: action.name||state.name.value
},
mobile: {
...state.mobile,
value: action.mobile || state.mobile.value
}
};
If you checked in render,both input field need name attribute which is not initialised and coming blank via props mapped in mapStateToProps.
So,you are not getting name attribute in event handler.
Need to initilise it.I assumed mobile and name.
1rst make state and save field inputs there like this.
state = {
username: "",
password: ""
}
where updateValue will save values from inputs to state
updateValue = (e) => {
this.setState({ [e.target.name]: e.target.value });
}
<input type="text" name="username" value={this.state.username} onChange={(e) => this.updateValue(e)} />
<input type="password" name="password" value={this.state.password} onChange={(e) => this.updateValue(e)} />
then you can collect data from state and pass it to your function, for example like this login form.
submitData = event => {
const user = {
username: this.state.username,
password: this.state.password
}
// now pass this `user` to your action
event.preventDefault();
}

With Redux-Form, how to dispatch from a ReduxFormField onChange event?

I'm getting the following error:
'dispatch' is not defined no-undef
What I'm trying to do is make it so whenever the user selects one of the ReduxFormField radio buttons, the form auto submits.
What is the right way to be able to do this..dispatch from onReduxFormFieldChange?
...
import {
Field as ReduxFormField,
reduxForm,
} from 'redux-form';
import { compose } from 'recompose';
...
class MyFormRadioInputs extends React.Component {
onReduxFormFieldChange = (e, newValue, previousValue) => {
if (newValue) {
dispatch(submit(MYFORM));
}
render() {
const { MyItems } = this.props;
return MyItems.map((item) => (
<ReduxFormField
key={item.id}
name="MyItemId"
type="radio"
value={item.id}
label={item.title}
onChange={this.onReduxFormFieldChange}
/>
));
}
}
const withForm = compose(
reduxForm({
form: MYFORM,
...
})
);
const MyForm = (props) => (
<Form>
<MyFormRadioInputs
myItems={props.myItems}
/>
</Form>
);
MyForm.propTypes = {
myItems: PropTypes.shape....
};
export default withForm(MyForm);
You are connecting your component to redux-form, but you are not ever connecting your component to the redux store, which means you don't have the ability to dispatch actions (including redux-form actions such as submit).
I haven't used recompose before, so I'm not sure if/how you get dispatch through it, but with standard react-redux you would need to add something like the following at the bottom of your file:
const mapDispatchToProps = dispatch => ({ submit: (formName) => dispatch(submit(formName))});
export default connect(null, mapDispatchToProps)(withForm(MyForm));
and then your component's onReduxFormFieldChange implementation becomes:
onReduxFormFieldChange = (e, newValue, previousValue) => {
if (newValue) {
this.props.submit(MYFORM);
}
}

Redux not Re-rendering React components even though store is updated

Hi I'm new to Redux and I'm using React and Redux to try to build a UI where I can drag and drop files (invoices in this case) into a portion of the UI, render them in a list and then be able to launch a popover to edit the metadata associated with each invoice. Dragging and dropping is all working fine - Redux is re-rendering the view each time a file is dropped and the list is being updated. However, when I try an click the edit button against each invoice the store is being updated but the props in my popover component are not. Indeed, it doesn't look like any re-rendering is happening at all when I attempt to click the edit invoice button
App.js
import React from 'react'
import AddInvoice from '../containers/AddInvoice'
import CurrentInvoiceList from '../containers/CurrentInvoiceList'
import ControlPopover from '../containers/ControlPopover'
const App = () => (
<div>
<AddInvoice />
<CurrentInvoiceList />
<ControlPopover />
</div>
)
export default App
containers/AddInvoice.js
import React from 'react'
import { connect } from 'react-redux'
import { addInvoice } from '../actions'
const recipientDataDefaults = {
name: '',
surname: '',
address: '',
phone: ''
};
const handleDragOver = event => {
event.stopPropagation();
event.preventDefault();
event.dataTransfer.dropEffect = 'copy';
};
const handleDragEnter = event => {
event.stopPropagation();
event.preventDefault();
};
const handleDragLeave = event => {
event.stopPropagation();
event.preventDefault();
};
let AddInvoice = ({ dispatch }) =>
const styles = {'minHeight': '200px', 'background': 'tomato'}
return (
<div style={styles}
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
onDragOver={handleDragOver}
onDrop={event => {
event.stopPropagation();
event.preventDefault();
const data = event.dataTransfer;
const files = data.files;
const newInvoiceUploads = Object.keys(files)
.map(key => files[key])
.map(file => {
const invoiceObject = {};
invoiceObject.files = [file];
invoiceObject.recipientData = Object.assign({}, recipientDataDefaults);
return invoiceObject;
});
newInvoiceUploads.forEach(invoice => dispatch(addInvoice(invoice)))
}}>
Drag an invoice here to upload
</div>
)
}
AddInvoice = connect()(AddInvoice)
export default AddInvoice
containers/ControlPopover.js
import { connect } from 'react-redux'
import { closePopoverWithoutSave } from '../actions'
import Popover from '../components/Popover/Popover'
const mapStateToProps = (state) => {
return {
isActive: !!state.isActive
}
}
const mapDispatchToProps = {
handleCancel: closePopoverWithoutSave
}
const ControlPopover = connect(
mapStateToProps,
mapDispatchToProps
)(Popover)
export default ControlPopover
containers/CurrentInvoiceList.js
import { connect } from 'react-redux'
import { showInvoiceEditPopover } from '../actions'
import InvoiceList from '../components/InvoiceList/InvoiceList'
const mapStateToProps = state => {
return {
invoices: state.invoices
}
}
const mapDispatchToProps = dispatch => ({
handleEditInvoice: invoice => {
dispatch(showInvoiceEditPopover(invoice))
}
})
const CurrentInvoiceList = connect(
mapStateToProps,
mapDispatchToProps
)(InvoiceList)
export default CurrentInvoiceList
actions/index.js
let nextInvoiceId = 0
export const addInvoice = invoice => ({
type: 'ADD_INVOICE',
id: nextInvoiceId++,
invoiceData: invoice
})
export const showInvoiceEditPopover = invoice => ({
type: 'SHOW_POPOVER',
invoice
})
The popover reducer (combined in app but inlined here for brevity) reducers/index.js
const popover = (state = {}, action) => {
switch (action.type) {
case 'SHOW_POPOVER':
const popoverState = {}
popoverState.isActive = true
popoverState.data = action.invoice
return popoverState
case 'CLOSE_POPOVER_WITHOUT_SAVING':
const inactiveState = {}
inactiveState.isActive = false
inactiveState.data = {}
return inactiveState;
default:
return state
}
}
export default popover
components/InvoiceList.js
import React from 'react'
import PropTypes from 'prop-types'
import Invoice from '../Invoice/Invoice'
const InvoiceList = ({ invoices, handleEditInvoice }) => {
return (
<div>
{invoices.map(invoice =>
<Invoice
key={invoice.id}
invoice={invoice.invoiceData}
onClick={event => {
// here we invoke the action bound by the CurrentInvoiceList
// container
event.preventDefault()
handleEditInvoice(invoice)
}}
/>
)}
</div>
)
}
InvoiceList.propTypes = {
invoices: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.number.isRequired,
invoiceData: PropTypes.object
}).isRequired).isRequired,
handleEditInvoice: PropTypes.func.isRequired
}
export default InvoiceList
components/Invoice.js
import React from 'react'
import PropTypes from 'prop-types'
import TextInput from '../TextInput/TextInput';
import Button from '../Button/Button';
import './invoice.css'
const Invoice = ({ invoice, onClick }) => {
const fileNames = invoice.files.map((file, index) => {
return (<div key={index} className="invoice__file-title-legend">
{file.name}</div>);
});
return (
<div className="invoice">
<form className="invoice__form">
<TextInput id="invoice__input-amount" placeholder="enter invoice amount" label="Invoice Amount" />
<TextInput id="invoice__input-target" placeholder="enter payment target" label="Payment Target" />
<Button value="Add recipient" onClick={onClick} /> // clicking this button updates the store but does NOT re-render. Why?
</form>
<div className="invoice__files">{fileNames}</div>
</div>
)
}
Invoice.propTypes = {
onClick: PropTypes.func.isRequired,
invoice: PropTypes.object
}
export default Invoice
components/Popover/Popover.js
import React from 'react'
import PropTypes from 'prop-types'
import createModifiers from '../../lib/createModifiers';
import './Popover.css';
const Popover = ({ handleCancel, isActive }) => {
console.log('rendering popover component') // does not get called when invoice edit button is pressed
const popoverModifiers = createModifiers('popover', {
'is-active': isActive
})
return (
<div className={popoverModifiers}>
<div className="popover__header">
<button onClick={handleCancel}>x</button>
</div>
<div className="popover__content">
Popover content
</div>
</div>
)
}
Popover.propTypes = {
handleCancel: PropTypes.func.isRequired,
isActive: PropTypes.bool.isRequired
}
export default Popover
And finally the createModifiers Code for posterity. This code is merely producing some BEM modifier CSS classes based on a state boolean value passed in
const createModifiers = (element, modifiers) => {
const classList = Object.keys(modifiers)
.filter(key => modifiers[key])
.map(modifier => `${element}--${modifier}`)
.concat(element)
.join(' ')
return classList
}
export default createModifiers
I know this is a large amount of example code so I tried to keep it a brief and focused as possible whilst giving a comprehensive view of the application. Any help is most appreciated.
The problem is in containers/ControlPopover.js and the mapStateToProps function. The isActive property needs to be assigned to state.popover.isActive
I believe your problem is your mapDispatchToProp functions are not formatted properly.
You need to return an object that has methods. Those methods are what will be given to your connected component as props.
Example:
const mapDispatchToProps = ( dispatch ) => {
return {
doSomething: ( arguments ) => {
// here you can dispatch and use your arguments
}
};
}
doSomething is the prop that would be provided to the connected component.
All of your mapDispatchToProps functions are formatted improperly.
SIDE NOTE / OPINION - TLDR:
In the future if you have a lot of code to post, I believe it would be easier to digest if the pieces were linked together.
I.E.
// App.js
const App = () => (
<div>
<Header />
<Body />
<Footer />
</div>
);
The components appear in the order: header -> body -> footer. Provide the code for them in that order, with their actions, reducer, presentational, and container information in one block.
Header
// header.presentational.js ...
// header.container.js ... ( or where you mapStateToProps and connect )
// header.actions.js ...
// header.reducer.js ...
Body ...
Footer ...
I don't know if the code is different on your end, but your mapStateToDispatch function is still improperly formatted.
Change this...
const mapDispatchToProps = dispatch => ({
handleEditInvoice: invoice => {
dispatch(showInvoiceEditPopover(invoice))
}
})
To this:
const mapDispatchToProps = dispatch => ({
return {
handleEditInvoice: invoice => {
dispatch(showInvoiceEditPopover(invoice))
}
};
})

Resources