Typescript & Redux connect - reactjs

I'm trying to connect redux to a component using Typescript and keep running into the same error.
Argument of type 'typeof BaseLayoutUnconnected' is not assignable to
parameter of type 'Component < any, {}, any>'. Property 'setState' is
missing in type 'typeof BaseLayoutUnconnected'.
import * as React from 'react';
import { IBaseLayoutProps, IBaseLayoutState } from './base-layout.types';
import { ChatContainer } from '../../components';
import { connect, DispatchProp } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux';
import { RouteComponentProps } from 'react-router';
import { ChatActions } from 'app/actions';
import { RootState } from 'app/reducers';
import { omit } from 'app/utils';
export const mapStateToProps = (state: RootState, ownProps) => {
return {
chatItems: state.chatItems
};
};
export const mapDispatchToProps = (dispatch: Dispatch) => ({
actions: bindActionCreators(omit(ChatActions, 'Type'), dispatch)
});
export class BaseLayoutUnconnected extends React.Component<IBaseLayoutProps, IBaseLayoutState> {
constructor(props) {
super(props);
this.state = {};
}
render() {
const { actions, chatItems } = this.props;
return <ChatContainer actions={actions} chatItems={chatItems} />;
}
}
export const BaseLayout = connect(
mapStateToProps,
mapDispatchToProps
)(BaseLayoutUnconnected);
This is being called in my app.tsx via
<Route exact={true} path="/" component={BaseLayout} />
Here are the props and state
export interface IBaseLayoutProps {
chatItems: RootState.ChatState;
actions: ChatActions;
}
export interface IBaseLayoutState {}
ChatActions looks like
import { createAction } from 'redux-actions';
import { ChatItemModel } from 'app/models';
export namespace ChatActions {
export enum Type {
ADD_CHAT_ITEM = 'ADD_CHAT_ITEM'
}
export const addChatItem = createAction<PartialPick<ChatItemModel, 'text'>>(Type.ADD_CHAT_ITEM);
}
export type ChatActions = Omit<typeof ChatActions, 'Type'>;

That's a problem i had too when i first started with Redux and TypeScript. There is a tricky solution. The connect methode takes alot of generics. I try to explain it with your example.
First of all you have to split the properties of your BaseLayoutUnconnected.
export interface IBaseLayoutStateProps {
chatItems: RootState.ChatState;
}
export interface IBaseLayoutDispatchProps {
actions: ChatActions;
}
export interface IBaseLayoutOwnProps {
// put properties here you want to make available from the connected component
}
export type IBaseLayoutProps = IBaseLayoutOwnProps & IBaseLayoutDispatchProps & IBaseLayoutStateProps
export interface IBaseLayoutState {}
Then you have to fill the generics of the different redux functions.
const mapStateToProps: MapStateToProps<IBaseLayoutStateProps, {}, RootState> = (state: RootState): IBaseLayoutStateProps => ({
chatItems: state.chatItems
})
export const mapDispatchToProps: MapDispatchToPropsFunction<IBaseLayoutDispatchProps, IBaseLayoutOwnProps> = (dispatch: Dispatch, ownProps: IBaseLayoutDispatchProps): IBaseLayoutDispatchProps => ({
actions: bindActionCreators(omit(ChatActions, 'Type'), dispatch)
});
export default connect<IBaseLayoutStateProps , IBaseLayoutDispatchProps, IBaseLayoutOwnProps , RootState>(
mapStateToProps,
mapDispatchToProps
)(BaseLayoutUnconnected as any)
a good source, where you can find all this stuff i wrote and more is this repository

Related

react, redux with typescript error: Type '{}' is missing the following properties from type 'IProps': email, token

I am using HOC with redux, and I have been running into the error: Type '{}' is missing the following properties from type 'IProps': email, token. The HOC is supposed to inject the props(email and token) and state to the Lower component. But It is not passing them in this case. And the wrapper function (withLogin) does not appear in the react tree components in dev tools.
My HOC containing function (withLogin.tsx) looks like:
import * as React from "react";
import { connect, MapStateToProps } from "react-redux";
import { Dispatch } from 'redux';
import { propagateError } from "./actions";
import { Diff } from 'utility-types';
export interface IWithLoginProps {
email: string;
token: string;
}
type HocProps =
IDispatchProps & {
// here you can extend ConnectedHoc with new props
users: string
};
interface IDispatchProps {
cleanError(): void;
}
export default function WithLogin<T extends IWithLoginProps>(
BaseComponenet
): React.ComponentType<HocProps> {
class HOC extends React.Component<
HocProps,
{
hash: string;
}
> {
constructor(props) {
super(props);
this.state = {
hash: window.top.location.hash
};
}
render() {
return <BaseComponenet {...this.state} {...this.props} />;
}
}
const mapDispatchToProps = (dispatch: Dispatch): IDispatchProps => {
return {
cleanError: () => dispatch(propagateError(null))
};
};
// #ts-ignore
return connect<{}, IDispatchProps, Diff<HocProps, IWithHistoryProps>, {}>(
null,
mapDispatchToProps
)(HOC);
}
And my BaseComponent(App.tsx) looks like:
import React from "react";
import { connect } from "react-redux";
import { withLoginProps } from "./withLogin";
interface IStateProps {
usernames: string[];
}
interface IProps extends IWithLoginProps {}
const App: React.StatelessComponent <IProps & IStateProps> = (props) => {
return (
<div>
{props.users}
</div>
);
}
const mapStateToProps = (state: IRootState): IStateProps => {
return {
usernames: state.users
};
};
export default connect<IStateProps, null, null, IRootState>(
mapStateToProps, null)(withLogin(App));
My index.tsx:
import * as React from 'react';
import App from './App';
const Root: React.StatelessComponent<{}> = () => (
<div>
<Provider store={store}>
<App />
</Provider>
</div>
);
render(<Root />, document.getElementById(renderRoot));
};
Looks like an issue with how you are importing the default import, that is try changing from:
import { withLoginProps } from "./withLogin";
to:
import withLoginProps from "./withLogin";

React Redux | connect not mapping dispatch to props

I am new to react and I am making a little business directory as I'm learning.
I've made a very basic component such as:
BusinessDirectory.js
export class BusinessDirectory extends React.Component {
componentWillMount() {
console.log(this.props);
}
render() {
return (<div><h1>Business Directory</h1></div>)
}
}
const mapStateToProps = state => ({
businesses: state.businesses.items
});
export default connect(mapStateToProps, { fetchBusinesses })(BusinessDirectory);
App.js
import React from "react";
import { Provider } from "react-redux";
import { BusinessDirectory } from "./components/BusinessDirectory";
import store from "./store";
export class App extends React.Component {
render() {
return (
<Provider store={store}>
<BusinessDirectory />
</Provider>
);
}
}
export default App;
store.js
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import rootReducer from "./reducers";
const initialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
applyMiddleware(...middleware)
);
export default store;
reducers/index.js
import { combineReducers } from "redux";
import businessReducer from "./business.reducer";
export default combineReducers({
businesses: businessReducer
});
reducers/business.reducer.js
import { FETCH_BUSINESS, FETCH_BUSINESSES } from "../actions/types";
const initialState = {
items: []
};
export default function(state = initialState, action) {
switch (action.type) {
case FETCH_BUSINESSES:
return {
...state,
items: action.payload
};
break;
default:
return state;
}
}
business.action.js
import { FETCH_BUSINESSES } from "./types";
export const fetchBusinesses = () => dispatch => {
fetch("http://localhost:5000/businesses")
.then(res => res.json())
.then(data =>
dispatch({
type: FETCH_BUSINESSES,
payload: data.data
})
);
};
The issue I am having is that this.props inside of the BusinessDirectory is an empty object so I cannot call the methods that I am creating. I've tried to do it by doing the mapDispatchToProp but that also doesn't work.
The problem is that in App.js you import the wrong component:
import { BusinessDirectory } from "./components/BusinessDirectory";
will import the so called named export, i.e. what you defined as
export class BusinessDirectory extends React.Component {
This is a class that assumes that "someone" passes the props to it.
On the other hand,
import BusinessDirectory from "./components/BusinessDirectory";
would import the default export, which is the connected component
export default connect(mapStateToProps, { fetchBusinesses })(BusinessDirectory);
i.e. a component that actually takes care of passing the props from redux.

mapDispatchToProps isn't connecting function to props

I'm using React-Redux with typescript and am trying to access a function defined in mapDispatchToProps, however I am getting the following error:
Uncaught TypeError: this.props.getStoreList is not a function
at t.componentDidMount
The two files involved are container.tsx and mapper.ts. I've tried putting the contents of mapper.ts into container.tsx in case it was an issue with importing, however that doesn't fix the error.
The scenario is very similar to this previous stackoverflow question: mapDispatchToProps is not putting my function into props however the solution doesn't seem to apply to my situation.
container.tsx
import * as React from 'react';
import { connect } from "react-redux";
import { mapStateToProps, mapDispatchToProps } from "./mapper";
import { IStoreListComponentProps } from './component';
export interface IStoreListContainerProps extends IStoreListComponentProps {
fetchStoreList?: () => void;
}
export class StoreListContainer extends React.Component<IStoreListContainerProps> {
componentDidMount() {
this.props.fetchStoreList();
}
render() {
return <div>Example</div>;
}
}
export default connect(mapStateToProps, mapDispatchToProps)(StoreListContainer);
mapper.ts
import { fetchStoreList } from "./actions/fetch-store-list";
import { IState } from "../../features/store/model";
export const mapDispatchToProps = (dispatch: any) => {
return {
getStoreList: () => { dispatch(fetchStoreList()); },
};
};
export const mapStateToProps = (state: IState) => {
return {
storeList: state.storeList
};
};
Thank you for helping!
export const mapDispatchToProps = (dispatch: any) => {
return {
getStoreList: () => dispatch(fetchStoreList()),
};
};
try writing it this way

Importing dispatch actions to React Component assistance

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({
// ...

typeScript react -redux

I want to use React-Redux with typeScript to dipatch an action,But when I use mapDisPatchToProps(),I don't know how to define the type of dispatch,There is My code:
This is the component file:
import * as React from 'react';
import Content from '../component/content';
interface Props {
allCityInformation: {'cityName': string, 'img': string}[];
onGetAllCityInformation: Function;
}
class HomePage extends React.Component<Props> {
componentDidMount() {
this.props.onGetAllCityInformation();
}
render() {
return (
<div>
<Content/>
</div>
);
}
}
export default HomePage;
And this is my container file:
import { connect } from 'react-redux';
import HomePage from '../pages/homePage';
export type DispatchGetCityInformation = () => void;
const mapDispatchToProps = (dispatch: DispatchGetCityInformation) => {
return{
onGetAllCityInformation: dispatch(getCityInformation())
};
};
export default connect(() => {return {};
}, mapDispatchToProps)(HomePage);
Now, the error information is :
enter image description here
So,How to solve this problem?
like this
import { Dispatch } from 'redux';
/*other code*/
const mapDispatchToProps = (dispatch: Dispatch<object>) => ({
asyncRequest: (name: string) => dispatch(someAction(name)),
otherAction: () => dispatch(someAction())
});

Resources