I'm working on my first project using React + Redux and am encountering few issues when I try to dispatch a function in the componentDidMount part. I tried to follow the Reddit API example project in the docs but as they use JavaScript and I'm using TypeScript not quite everything is the same.
This is the React component where I'm trying to achieve this:
export class EducationComponent extends Component {
constructor(props: any) {
super(props);
}
componentDidMount() {
const { dispatch, getUser } = this.props;
dispatch(getUser());
}
public render() {
return (
<Content className="component">
<Demo/>
</Content>
);
}
}
function mapStateToProps(state: State) {
const { isLoaded, isFetching, user } = state.userProfile;
return {
user,
isFetching,
isLoaded
}
}
export default connect(mapStateToProps)(EducationComponent)
export const Education = (EducationComponent);
I'm receiving following error in the const { dispatch, getUser } = this.props; line:
Error:(16, 17) TS2339: Property 'dispatch' does not exist on type 'Readonly<{}> & Readonly<{ children?: ReactNode; }>'.
Error:(16, 27) TS2339: Property 'getUser' does not exist on type 'Readonly<{}> & Readonly<{ children?: ReactNode; }>'.
The project can be found here if there are any uncertainties: https://github.com/jLemmings/GoCVFrontend
Is this the right way to get this done or are there better options?
Thanks
EDIT with current state:
const {Content} = Layout;
export class EducationComponent extends Component {
constructor(props: any) {
super(props);
}
componentDidMount() {
this.props.userAction();
}
public render() {
return (
<Content className="component">
<Demo/>
</Content>
);
}
}
const mapDispatchToProps = (dispatch: Dispatch) => bindActionCreators({
userAction: action.getUser,
}, dispatch);
function mapStateToProps(state: State) {
const { isLoaded, isFetching, user } = state.userProfile;
return {
user,
isFetching,
isLoaded
}
}
export default connect(mapStateToProps, mapDispatchToProps)(EducationComponent)
FINAL EDIT (got it working):
interface MyProps {
getUser: () => void
}
interface MyState {
userAction: ThunkAction<Promise<void>, {}, {}, AnyAction>
}
export class EducationComponent extends Component<MyProps, MyState> {
static defaultProps = {getUser: undefined};
constructor(props: any) {
super(props);
}
componentDidMount() {
this.props.getUser()
}
public render() {
return (
<Content className="component">
<Demo/>
</Content>
);
}
}
const mapDispatchToProps = (dispatch: ThunkDispatch<{}, {}, any>, ownProps: MyProps) => {
return {
getUser: () => {
dispatch(getUser());
}
}
}
function mapStateToProps(state: State) {
const {isLoaded, isFetching, user} = state.userProfile;
return {
user,
isFetching,
isLoaded
}
}
export default connect(mapStateToProps, mapDispatchToProps)(EducationComponent)
After looking at your project, you are importing the component like this :
import {Education} from "../pages/Public/Education";
This will import the non connected component that's why you don't have access to dispatch.
You should import the default connected component :
import Education from "../pages/Public/Education";
You can do it like this
componentDidMount() {
this.props.getUser();
}
// mapStateToProps
mapDispatchToProps = (state) => {
getUser: () => dispatch(getUser()); //Don't forget to import getUser from your action creator
}
export default connect(mapStateToProps, mapDispatchToProps)(EducationComponent)
export const Education = (EducationComponent); // Delete this line no need for this because you're already exporting it as default
Reference: https://react-redux.js.org/using-react-redux/connect-mapdispatch#arguments
Do you bind the dispatch method using mapDispatchToProps?
Related
I'm recently learn about react-redux. Now i'm bit stuck about how can i execute the api call.
Here is my action.ts
export const fetchEvent = () => async (
dispatch: Dispatch
) => {
dispatch({ type: ActionTypes.FETCH_EVENT_REQUEST });
try {
const response = await axios.get<EvenData[]>(
`https://jsonplaceholder.typicode.com/todos/`
);
dispatch<FetchEventAction>({
type: ActionTypes.FETCH_EVENT_SUCCESS,
payload: response.data
});
} catch (error) {
dispatch({ type: ActionTypes.FETCH_EVENT_FAILURE, error });
}
};
and here is my event list and this is where i want call the api call
const mapStateToProps = (state: ActionTypes) => ({});
const dispatchProps = {};
type Props = ReturnType<typeof mapStateToProps> & typeof dispatchProps;
type State = {};
class EventList extends React.Component<Props, State> {
componentDidMount() {
// dispatch(fetchEvent());
}
render() {
return (
<section>
<p>{ }</p>
</section>
);
}
}
export default connect(
mapStateToProps,
dispatchProps
)(EventList);
So how can i make the api call ? inside my componentDidMount
you are missing the mapDispatchToProps , the one your are using is empty {}.
import {fetchEvent} from 'action.ts' // change to correct path
const mapStateToProps = (state: ActionTypes) => ({});
const mapDispatchToProps = (dispatch: Dispatch) => {
return {
fetchEvent: () => {
dispatch(fetchEvent())
}
};
};
type Props = ReturnType<typeof mapStateToProps> & ReturnType<typeof mapDispatchToProps>;
type State = {};
class EventList extends React.Component<Props, State> {
componentDidMount() {
this.props.fetchEvent()
}
render() {
return (
<section>
<p>{ }</p>
</section>
);
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(EventList);
I am working on an existing project and this function already works in the other components except in this component 'ComingSoon' which I have just created.
And I get this error in my code with trying to run it.
Uncaught TypeError: this.props.activateAction is not a function
My Component.tsx
import React from 'react';
import { connect } from 'react-redux';
import { RouteComponentProps } from 'react-router-dom';
import { IRootState } from 'app/shared/reducers';
import { activateAction } from 'app/modules/account/activate/activate.reducer';
export interface IComingProps extends StateProps, DispatchProps, RouteComponentProps<{ key: any }> {}
export class ComingSoon extends React.Component<IComingProps, {}> {
componentDidMount() {
const { key } = this.props.match.params;
this.props.activateAction(key);
}
render() {
return (
...
);
}
}
const mapStateToProps = ({ authentication }: IRootState) => ({
account: authentication.account,
isAuthenticated: authentication.isAuthenticated
});
const mapDispatchToProps = { activateAction };
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = typeof mapDispatchToProps;
export default connect(mapStateToProps,mapDispatchToProps)(ComingSoon);
activate.reducer.ts :
import axios from 'axios';
import { REQUEST, SUCCESS, FAILURE } from 'app/shared/reducers/action-type.util';
export const ACTION_TYPES = {
ACTIVATE_ACCOUNT: 'activate/ACTIVATE_ACCOUNT',
RESET: 'activate/RESET'
};
const initialState = {
activationSuccess: false,
activationFailure: false
};
export type ActivateState = Readonly<typeof initialState>;
// Reducer
export default (state: ActivateState = initialState, action): ActivateState => {
switch (action.type) {
case REQUEST(ACTION_TYPES.ACTIVATE_ACCOUNT):
return {
...state
};
case FAILURE(ACTION_TYPES.ACTIVATE_ACCOUNT):
return {
...state,
activationFailure: true
};
case SUCCESS(ACTION_TYPES.ACTIVATE_ACCOUNT):
return {
...state,
activationSuccess: true
};
case ACTION_TYPES.RESET:
return {
...initialState
};
default:
return state;
}
};
// Actions
export const activateAction = key => ({
type: ACTION_TYPES.ACTIVATE_ACCOUNT,
payload: axios.get('api/activate?key=' + key)
});
export const reset = () => ({
type: ACTION_TYPES.RESET
});
I forgot a declaration or an import?
Is the approach taken correct?
will i need to configure something in the store?
I found solution by referring to this subject React Redux TypeError: this.props is not a function.
I just removed braces from component import in routes component.
import ComingSoon from './shared/layout/coming-soon/comingSoon';
instead of
import {ComingSoon} from './shared/layout/coming-soon/comingSoon';
I think I found your solution, you need to dispatch your action before connect it
export const activateAction = key => dispatch => {
dispatch({
type: ACTION_TYPES.ACTIVATE_ACCOUNT,
payload: axios.get('api/activate?key=' + key)
});
}
check 1
constructor(props) {
super(props);
}
I'm trying to write a function that takes Higher Order Components as a argument and returns component connected with the redux store. But this code have a type error in the last line. How to I define type correctly?
import React from 'react'
import { connect } from 'react-redux'
import { Dispatch } from 'redux'
import { AppState } from '~/store'
import { decrement, increment } from '~/store/home'
export type CountableProps = StateType & DispatchType
type StateType = ReturnType<typeof mapStateToProps>
const mapStateToProps = (state: AppState) => ({
count: state.home.count
})
type DispatchType = ReturnType<typeof mapDispatchToProps>
const mapDispatchToProps = (dispatch: Dispatch) => ({
increment: () => dispatch(increment()),
decrement: () => dispatch(decrement())
})
const createCountableComponent =
<P extends CountableProps>(Component: React.ComponentType<P>) =>
class extends React.Component<P> {
componentDidMount() {
// do something...
}
render() {
return <Component {...this.props} />
}
}
export const countable = <P extends CountableProps>(Component: React.ComponentType<P>) =>
connect(
mapStateToProps,
mapDispatchToProps
)(createCountableComponent(Component))
Type error
TS2345: Argument of type 'typeof ConfigurableComponent' is not assignable to parameter of type 'ComponentType<Matching<CountableProps, P>>'
Explicitly typing the connect function seems to work:
export const countable = <P extends CountableProps>(Component: React.ComponentType<P>) =>
connect<StateType, DispatchType, Omit<P, keyof (StateType & DispatchType)>>(
mapStateToProps,
mapDispatchToProps
)(createCountableComponent(Component) as any);
We can cast createCountableComponent(Component) as any, since we typed connect with correct typings (state, dispatch and ownProps types). This means when you use this component you will only see props that are not Countable (wich is the desired solution).
So:
class Test extends React.Component<{test: string}> {
render() {
return <div />;
}
};
countable(Test)
will give an error, while:
class Test extends React.Component<CountableProps & {test: string}> {
render() {
return <div />;
}
};
const Test2 = countable(Test)
<Test2 /> -> intelisense will only give you test now :)
Hope this helps.
I am having trouble importing a dispatch action. The compiler is complaning that :
Type 'Readonly<{ children?: ReactNode; }> & Readonly<{}>' has no property 'onShowError' and no string index signature.
const { onShowError, error, errorMessage } = this.props this is the code that is causing the problem.
I understand there is something wrong with my imports and how React extends the Component, etc but I just can't find the solution. I'm new to TypeScript let alone JavaScript. I just cannot figure out where something is going wrong.
I have tried creating my own interface CustomProps and declaring onShowError is a function but does not work. Not Assignable to {}
import * as React from "react"
import { Dispatch, Action } from "redux"
import { connect } from "react-redux"
import { AppState } from "reducers"
import { showError } from "data/error_handler"
import Snackbar from "material-ui/Snackbar"
import RaisedButton from "material-ui/RaisedButton"
class ErrorHandler extends React.Component {
hideErrorPopup = () => {
this.setState({
error: false,
})
}
public render() {
const { onShowError, error, errorMessage } = this.props
return (
<div>
<RaisedButton
onClick={onShowError}
label="Toggle ErrorHandler"
/>
<Snackbar
bodyStyle={{ backgroundColor: "#ffa000", marginBottom: "5px" }}
open={error}
message={errorMessage}
autoHideDuration={5000}
onRequestClose={this.hideErrorPopup}
/>
</div>
)
}
}
const mapStateToProps = (state: AppState) => ({
errorMsg: state.errorRedux.errorMessage,
error: state.errorRedux.error,
})
const mapDispatchToProps = (dispatch: Dispatch<Action>) => {
return {
onShowError: () => dispatch(showError()),
}
}
export default connect<any>(
mapStateToProps,
mapDispatchToProps
)(ErrorHandler)
Reducer.ts
import { ErrorHandlerProps, ActionTypes } from "./"
const initialState: ErrorHandlerProps = {
error: false,
errorMessage: "",
}
export default (
state: ErrorHandlerProps = initialState,
action: ActionTypes
) => {
switch (action.type) {
case "SHOW_ERROR":
return {
...state,
}
}
}
Interface.ts & index.ts
export interface ErrorHandlerProps {
error: boolean
errorMessage: string
}
import reducer from "./reducer"
export { reducer }
export * from "./actions"
export * from "./interfaces"
actions.ts
export type ActionTypes = {
type: "SHOW_ERROR"
error: boolean
errorMessage: string
}
export const showError = (): ActionTypes => ({
type: "SHOW_ERROR",
error: true,
errorMessage: "[ACTIONS]",
})
You probably want to explicitly specify the shape of your component:
class myClass extends React.Component<PropShape, StateShape>
To get the props working, provide the types of your props (which includes your component's actual props, and the props injected by connect: mapStateToProps and mapDispatchToProps). In this case, you only need the injected props:
class ErrorHandler extends React.Component<
ReturnType<typeof mapStateToProps>
& ReturnType<typeof mapDispatchToProps>
> {
...
}
const mapStateToProps = (state: AppState) => ({
errorMsg: state.errorRedux.errorMessage,
error: state.errorRedux.error,
})
const mapDispatchToProps = (dispatch: Dispatch<Action>) => {
return {
onShowError: () => dispatch(showError()),
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ErrorHandler)
You'll probably also want to include the shape of your state, if you truly intend to keep a separate local component state, although I'm not sure what your final intention there is:
class ErrorHandler extends React.Component<
ReturnType<typeof mapStateToProps>
& ReturnType<typeof mapDispatchToProps>,
IState> {
...
}
interface IState {
error: boolean;
}
See https://github.com/sw-yx/react-typescript-cheatsheet for some common use cases for using React with TypeScript.
If you just want to quickly get around ts' complaint, the as any trick will do:
const { onShowError, error, errorMessage } = this.props as any
To address this properly, you need to pass the CustomProps to your Component:
interface CustomProps {
onShowError: Function;
error: boolean;
errorMessage: string;
}
class ErrorHandler extends React.Component<CustomProps> {
hideErrorPopup = () => {
this.setState({
// ...
I am trying to call connect on a decorator that returns a react class
const SetLanguageFromPage = () => {
return WrappedComponent =>
class setLang extends React.Component {
static propTypes = {
pathContext: PropTypes.shape({
language: PropTypes.string.isRequired
})
};
componentDidMount() {
const currentLanguage = i18n.language;
const pageLanguage = this.props.pathContext.language;
// First request
if (!currentLanguage) {
i18n.language = pageLanguage;
}
// Only update on language change
if (currentLanguage !== pageLanguage) {
i18n.changeLanguage(pageLanguage);
}
}
render() {
return <WrappedComponent {...this.props} />;
}
};
};
const mapStateToProps = (state) => { return{...} }
const mapDispatchToProps = (dis) => { return{...} }
export default connect(...)(SetLanguageFromPage);
but when I then use the decorator on another react class I get this error...
Uncaught TypeError: Cannot call a class as a function
which I suppose is from connect changing my function to a react class. Is there any way to accomplish what I am trying to do? I would really like to be able to call actions to set the state from within this decorator, but I can't see how I can get at the store to call dispatch or map the dispatch to the props...
I am using https://www.gatsbyjs.org/ for this, so the general method has the store instantiated in a way where I cannot access is directly
You get an error, because you are trying to pass and HOC to connect, whereas it expects a React component. You can instead connect the returned component inside the HOC, which is what you essentially want to do
const SetLanguageFromPage = () => {
return WrappedComponent => {
class SetLang extends React.Component {
static propTypes = {
pathContext: PropTypes.shape({
language: PropTypes.string.isRequired
})
};
componentDidMount() {
const currentLanguage = i18n.language;
const pageLanguage = this.props.pathContext.language;
// First request
if (!currentLanguage) {
i18n.language = pageLanguage;
}
// Only update on language change
if (currentLanguage !== pageLanguage) {
i18n.changeLanguage(pageLanguage);
}
}
render() {
return <WrappedComponent {...this.props} />;
}
};
return connect(mapStateToProps, mapDispatchToProps)(SetLang);
}
};
const mapStateToProps = (state) => { return{...} }
const mapDispatchToProps = (dis) => { return{...} }
export default SetLanguageFromPage;