How do I define component type? - reactjs

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.

Related

linter swears on the lack of properties

When calling a component in which there is a 'connect' from 'redux' and properties that need to be transferred when calling, the linter swears at the absence of properties
Component1
interface Component1Props {
id: numbers;
users: any[];
}
const Component1: React.FC<Component1Props> = ({
id,
users
}) => {
...
};
const mapStateToProps = ({ users }: any) => ({
users
});
export default connect(mapStateToProps, null)(Component1);
Component2
import Component1 from ...
const Component2 = () => {
return <Component1 id={id} />; <-- linter error: property 'users' is missing
}
How fix this?
You may want to use this approach from their official document
typing-the-connect-higher-order-component
ConnectedProps
Use the ConnectedProps<T> type exported by #types/react-redux^7.1.2 to infer the types of the props from connect automatically.
import { connect, ConnectedProps } from 'react-redux'
interface RootState {
}
const mapState = (state: RootState) => ({
})
const mapDispatch = {
}
const connector = connect(mapState, mapDispatch)
type PropsFromRedux = ConnectedProps<typeof connector>
type Props = PropsFromRedux & {
backgroundColor: string
}
const MyComponent = (props: Props) => (
<div style={{ backgroundColor: props.backgroundColor }}>
<button onClick={props.toggleOn}>
Toggle is {props.isOn ? 'ON' : 'OFF'}
</button>
</div>
)
export default connector(MyComponent)

How to namespace parent props using mapState

I am told that props from the parent component are automatically passed to child, you cannot intercept them, even if you use:
function mapStateToProps(state: RootState, ownProps: OwnProps) {
return {
s: {...state},
o: {...ownProps},
};
}
I want to make sure that props from state do not clash with props from the parent component. Is there
we are using Redux connect like:
export default connect<StateProps, DispatchProps, AllProps, RootState>(
mapState,
mapDispatch
)(OurComponent);
so for example if I have a component
'use strict';
import React from 'react';
import { AppDispatch } from '../../cp';
import { RootState } from '../../reducers/root';
import { connect } from 'react-redux';
import moment from "moment";
interface OwnProps {
//The type for the props provided by the parent component
s?: any;
z1?: any,
z2?: any,
z3?: any,
o?:any
}
function mapDispatch(dispatch: AppDispatch<any>) {
return { dispatch };
}
function mapState(state: RootState, ownProps: OwnProps) {
return {
o: {
...ownProps
},
s: {
...state
}
};
}
type StateProps = ReturnType<typeof mapState>;
type DispatchProps = ReturnType<typeof mapDispatch>;
type AllProps = StateProps & DispatchProps & OwnProps;
class Home extends React.Component<AllProps, RootState>{
constructor(p: AllProps) {
super(p);
console.log('home props:', p);
}
render(){
return <div>Home Works</div>;
}
}
export default connect<StateProps, DispatchProps, AllProps, RootState>(mapState, mapDispatch)(Home);
and then I render it with:
<Home dispatch={null as any} s={null as any} o={null as any} z1={1} z2={2} z3={true}/>
I get this logged:
I think the only good solution is to use mergeProps like so:
interface OwnProps {
//The type for the props provided by the parent component
}
function mergeProps(state: RootState, dispatch: Dispatch<any>, ownProps: OwnProps) {
return {
dispatch,
o: ownProps, // ownProps are namespaced onto o.
s: { // stateProps are namespaced onto s.
apiInReach: state.dev.apiInReach,
}
};
}
//// ...
export default connect(
null,
null,
mergeProps
)(Dev);
the documentation for mergeProps is here:
https://github.com/reduxjs/react-redux/blob/master/docs/api/connect.md#mergeprops-stateprops-dispatchprops-ownprops--object

React Redux dispatch on componentDidMount -> property does not exist

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?

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 & Redux connect

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

Resources