Can you help to undestand why my container doesn't transfer dispatch prop saveInfo into component?
My container:
import { connect } from 'react-redux'
import AddCarWashForm from './../components/carwash/subComponents/AddCarWashForm.jsx'
import {addInfo} from './../actions'
const mapDispatchToProps = (dispatch) => {
return {
saveInfo: (info) => {
dispatch(addInfo(info))
}
}
}
const AddCarWashFormContainer = connect(
null,
mapDispatchToProps
)(AddCarWashForm)
export default AddCarWashFormContainer
Here is part of AddCarWashForm.jsx
export default class AddCarWashForm extends React.Component{
static contextTypes = {
saveInfo: React.PropTypes.func.isRequired
};
And when I execute this code, I see in console: Warning: Failed context type: Required context 'saveInfo' was not specified in 'AddCarWashForm'.
You're mapping to props, not to context. Define it as a propType instead and it'll work as you expect it.
Related
I'm following this tutorial to build a chat app with React and Redux on Typescript, but I'm getting an error when exporting my connected component:
Type 'InferableComponentEnhancerWithProps<ConnectedState & ConnectedDispatch, OwnProps>'
is missing the following properties from type 'Component<OwnProps, {}, any>':
context, setState, forceUpdate, render, and 3 more.
This is the code from the tutorial. In which sense should I update it to get rid of this error and be able to use the component normally.
import * as React from 'react'
import * as redux from 'redux'
import { connect } from 'react-redux'
import { Message as MessageModel, UserMessage} from 'rcserver/src/models'
import { ChatState } from '../state'
import { Action } from '../actions'
import { Messages } from './Messages'
import { ChatInput } from './ChatInput'
interface OwnProps {
socket: WebSocket,
username: string
}
interface ConnectedState {
messages: MessageModel[]
}
interface ConnectedDispatch {}
interface OwnProps {}
const mapStateToProps = (state: ChatState, ownProps: OwnProps): ConnectedState => ({
messages: state.messages
})
const mapDispatchToProps = (dispatch: redux.Dispatch<Action>): ConnectedDispatch => ({})
export class ChatAppComponent extends React.Component<ConnectedState & ConnectedDispatch & OwnProps> {
sendHandler = (message: string) => {
const messageObject: MessageModel = {
name: this.props.username,
message: message
}
this.props.socket.send(JSON.stringify(messageObject))
}
render() {
return (
<div className="container">
<h3>React Chat App</h3>
<Messages username={this.props.username} messages={this.props.messages} />
<ChatInput onSend={this.sendHandler} />
</div>
)
}
}
export const ChatApp: React.Component<OwnProps> = connect(mapStateToProps, mapDispatchToProps)
//This last line is the one triggering the error
It's almost correct, but you forgot to apply connect(mapStateToProps, mapDispatchToProps) to your ChatAppComponent. This should work:
export const ChatApp = connect(mapStateToProps, mapDispatchToProps)(ChatAppComponent)
The type of ChatApp will be inferred correctly, so you don't need a type signature. If you do want the signature, you'll need React.FunctionComponent<OwnProps> though, as the connected component is not a class. Alternatively, you could also use the more general React.ComponentType<OwnProps> which works for both classes and function components.
I am using redux/connect to get state and actions creators as props in my component. This, however, causes an error with Typescript when rendering the component as the props i'm receiving via connect are not explicitly written.
Is there a way to get around this without making the types optional or is there something else I am missing?
//FetchData Component
import React, {Component} from 'react';
import { connect } from "react-redux";
import {
getData,
filterMaterial,
filterSize,
resetMap
} from "../js/actions/index";
import { MapProperties } from '../types/state';
import { Appstate } from '../js/store';
type DispatchProps = {
getData: () => void,
filterMaterial: (name:string) => void,
filterSize: (name:string) => void,
resetMap: () => void
}
type Props = DispatchProps & LinkStateProps
class FetchData extends Component<Props> {
constructor(props: Props) {
super(props);
this.handleReset = this.handleReset.bind(this)
}
handleReset = () => {
if(this.props.resetMap) this.props.resetMap();
}
componentDidMount() {
if (this.props.getData) this.props.getData()
}
render() {
return (
...
);
}
}
type LinkStateProps = {
geoJSON: MapProperties,
mapJSON: MapProperties
}
const mapStateToProps = (state: Appstate, props:Props):LinkStateProps => {
return {
geoJSON: state.geoJSON,
mapJSON: state.mapJSON
};
};
export default connect(mapStateToProps, {
getData,
filterMaterial,
filterSize,
resetMap
})(FetchData);
//App Component
import React from 'react';
import './App.scss';
import FetchData from './components/FetchData';
function App() {
return (
<div className="App">
//error = Type '{}' is missing the following properties from type 'DispatchProps': getData, filterMaterial, filterSize, resetMap
<FetchData />
</div>
);
}
export default App;
It's because you're passing Props into your mapToState function.
try:
- const mapStateToProps = (state: Appstate, props:Props):LinkStateProps => {
+ const mapStateToProps = (state: Appstate):LinkStateProps => {
return {
geoJSON: state.geoJSON,
mapJSON: state.mapJSON
};
};
The second argument to mapToState is an optional ownProps argument that is only needed if you need any of the component's "own" props (not connected props) to create the proper mapping from state to props. Providing Props as you did makes TypeScript assume that those props must be provided explicitly when using the component, hence the error you were seeing.
Also, you might consider using some built-in features that TypeScript offers to save you from typing things that TypeScript can infer types from automatically.
Consider this:
import React, {Component} from 'react';
import { connect } from "react-redux";
import {
getData,
filterMaterial,
filterSize,
resetMap
} from "../js/actions/index";
import { MapProperties } from '../types/state';
import { Appstate } from '../js/store';
const mapStateToProps = (state: Appstate) => ({
geoJSON: state.geoJSON,
mapJSON: state.mapJSON
});
const dispatchProps = {
getData,
filterMaterial,
filterSize,
resetMap
}
// You could do:
// type LinkStateProps = ReturnType<typeof mapStateToProps>;
// type DispatchProps = typeof dispatchProps;
// type Props = LinkStateProps & DispatchProps;
// or in one line:
type Props = ReturnType<typeof mapStateToProps> & typeof dispatchProps;
class FetchData extends Component<Props> {
constructor(props: Props) {
super(props);
this.handleReset = this.handleReset.bind(this)
}
handleReset = () => {
if(this.props.resetMap) this.props.resetMap();
}
componentDidMount() {
if (this.props.getData) this.props.getData()
}
render() {
return (
<p>something</p>
);
}
}
export default connect(mapStateToProps, dispatchProps)(FetchData);
Creating an object named dispatchProps (which you do anyway to supply to connect) lets you use Typescript's typeof operator to auto-generate the type signature. And since mapStateToProps returns an object, you can use ReturnType<typeof mapStateToProps> to get the type signature of that object. Combining these two with the '&' operator gives you your Props type. It's essentially a more concise (and less error-prone) way to do the exact same thing you were doing.
I have a connected container like so:
import React from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { autobind } from 'core-decorators';
const propTypes = {
data: PropTypes.object,
userData: PropTypes.object,
};
class ConnectedContainer extends React.Component {
#autobind
doSomethingImportant() {
...
}
render() {
....
}
}
ConnectedContainer.propTypes = propTypes;
const mapStateToProps = state => ({ data, userData });
const mapDispatchToProps = (dispatch) => {
return { actions: bindActionCreators(actions, dispatch) };
};
export default connect(mapStateToProps, mapDispatchToProps)(ConnectedContainer);
I want to test doSomethingImportant, so I have test like so:
import React from 'react';
import { shallow } from 'enzyme';
import ConnectedContainer from '.....'';
import configureStore from 'redux-mock-store';
const mockStore = configureStore();
const store = mockStore({ getState: () => null, dispatch: () => null, data: { data from fixture }, userData: { data from fixture } });
const container = (
<ConnectedContainer
store={store}
actions={{}}
/>
);
describe('ConnectedContainer', () => {
describe('doSomethingImportant', () => {
it('returns something important', () => {
const wrapper = shallow(container);
expect(wrapper.instance().doSomethingImportant()).to.equal( ... some output here );
});
});
});
And no matter what I do, I get this error:
TypeError: wrapper.instance(...). doSomethingImportant is not a function
What is happening with my container that I'm unable to access its instance methods?
Test the unwrapped component
Export the ConnectedContainer class itself, not the wrapped version. You want to test your code, not the connect() function.
You can keep the default export as the wrapped component, and then add the word export in front of the class definition:
export class ConnectedContainer extends React.Component { // Named export, for testing purposes only
...
}
Then in your test, import { ConnectedContainer } from '....'
Render that with shallow instead of the default export.
Naming conventions
Also, naming your component ConnectedContainer is very confusing! It only becomes connected after it is wrapped with the connect function. So when you export ConnectedContainer (as I've suggested) you're actually exporting a non-connected component class. (And is it actually a container? That's also pretty vague.)
One naming convention people use is to define a constant that holds the return value of connect(), and name that xxxContainer, like so:
export class IconView extends React.Component { // Exported for testing
// ... class implementation
}
function mapStateToProps(){...}
function mapDispatchToProps(){...}
const IconViewContainer = connect(mapStateToProps, mapDispatchToProps)(IconView);
export default IconViewContainer; // Exported for use as a component in other views
Try:
const Container = (
<ConnectedContainer
store={store}
actions={{}}
/>
);
Note, the only difference is the C uppercase in container. The thing is that React only treats classes and methods as components if they start with a capital letter. That could be your problem.
I am new to react-redux and I am having some difficulty in understanding the syntax. I am pasting my sample code below... please help me understand if there are any syntactical errors.
SampleParent.js:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { fetchNames, fetchDownloadLink } from '../../actions/actions'
import SampleChild from '../ui/SampleChild'
class SampleParent extends Component {
constructor(props) {
super(props) ;
}
componentDidMount() {
const { dispatch } = this.props
dispatch(fetchNames());
}
render() {
return(<div><ul id="myUL">{this.props.reports.map((report) => (
<li>
<SampleChild
key={report.id}
label={report.label}
uri={() => fetchDownloadLink("http://localhost:8080/sample"+this.props.uri+".pdf")}
/>
</li>))}</ul></div>)}
}
function mapStateToProps(state) {
const { reports } = state
return {
reports
}
}
const mapDispatchToProps = dispatch => {
return {
fetchDownloadLink(url) {
dispatch(
fetchDownloadLink(url)
)
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ReportsApp)
SampleChild.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { fetchDownloadLink } from '../../actions/actions'
class OpenReport extends Component {
constructor(props) {
super(props) ;
}
render(){
return(<div className="in_sample" id={this.props.label}>
{this.props.label}
<a href={this.props.uri}>
<img src="../images/pdf-file_128.png" height="25px" width="25px"></img></a><br></br></div>
)
}
}
module.exports = OpenReport;
Currently I am getting this error:
Uncaught TypeError: dispatch is not a function
at ReportsApp.componentDidMount (bundle.js:39883)
Basically what I need to do is get a url as a string from the 'fetchDownloadLink ' function and pass this string to my child component. Is there any other way to do that?
Please suggest...
Thanks in advance!
According to the Documentaion:
mapDispatchToProps returns an object that somehow uses dispatch to bind
action creators in your own way.
However in your case you are returning an object without keys. Change your function to
const mapDispatchToProps = dispatch => {
return {
fetchDownloadLink: (url) => dispatch(fetchDownloadLink(url))
}
}
}
MoreOver, using connect function you need to connect mapStateToProps and mapDispatchToProps function to the component in which you will be using the action creators which in your case is SampleParent
Also if you pass mapDispatchToProps as the second parameter to connect, then dispatch is not available as a prop to your component.
So change your code to the following
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { fetchNames, fetchDownloadLink } from '../../actions/actions'
import SampleChild from '../ui/SampleChild'
class SampleParent extends Component {
constructor(props) {
super(props) ;
}
componentDidMount() {
const { dispatch } = this.props
this.props.fetchNames();
}
render() {
return(<div><ul id="myUL">{this.props.reports.map((report) => (
<li>
<SampleChild
key={report.id}
label={report.label}
uri={() => this.props.fetchDownloadLink("http://localhost:8080/sample"+this.props.uri+".pdf")}
/>
</li>))}</ul></div>)}
}
}
function mapStateToProps(state) {
const { reports } = state
return {
reports
}
}
const mapDispatchToProps = dispatch => {
return {
fetchDownloadLink: (url) => dispatch(fetchDownloadLink(url)),
fetchNames: () => dispatch(fetchNames)
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SampleParent)
The problem is that you are passing your action creator as a prop uri to your child component, and then using that prop as an HREF tag.
You should instead pass it as onChildClick (example name) prop to your report item component, and call it on the onClick prop of the <a>.
<a onClick={ this.props.onChildClick }>xxxx</a>
The previous answer about the action creator mapping is fine, but you don't even need a function: if you use an object with functions as keys, they will be wrapped with dispatch for you.
const mapDispatchToProps = {
fetchDownloadLink
}
EDIT after your updated question
I see the problem now. You don't have dispatch as prop because you're using mapDispatchToProps to provide some action creators as props. It doesn't make sense to map dispatch to fetchDownloadLink and not doing it as well for fetchNames. Map both or neither, but you shouldn't mix and match.
Looking at the docs for react-async-poll I'm following the Usage example to integrate asyncPoll into my component, but I'm getting a Uncaught TypeError: dispatch is not a function complaint from within my onPollinterval function
import React, { Component } from 'react';
import { connect } from 'react-redux';
import asyncPoll from 'react-async-poll';
import { fetchCaCities, } from '../actions';
import MyMap from './my-map';
class CaliforniaMap extends Component {
componentDidMount() {
this.props.fetchCaCities();
}
render() {
return (
<div>
<h1>California Map</h1>
<MyMap center={[37.5, -120]} zoom={6} layers={[this.props.caCities]} />
</div>
);
}
}
const onPollInterval = (props, dispatch) => {
console.log(dispatch); // undefined
return dispatch(fetchCaCities());
};
const mapStateToProps = state => ({
caCities: state.map.california.caCities,
});
export default asyncPoll(60 * 1000, onPollInterval)(connect(
mapStateToProps, { fetchCaCities }
)(CaliforniaMap)
Maybe react-async-poll doesn't work for connected components?
According to the docs:
The dispatch parameter is only passed to [onInterval] if it is
available in props, otherwise it will be undefined.
The example they give is confusing because it does not define dispatch anywhere, but they show onPollInterval using it.