In App I have a button that saves a message and another that creates a new component
import React from "react";
import { connect } from "react-redux";
import { AppState } from "./redux/store";
import { ChatState } from "./redux/chat/types";
import { sendMessage } from "./redux/chat/actions";
import Test from './components/test'
interface AppProps { sendMessage: typeof sendMessage; chat: ChatState }
const App: React.FC<AppProps> = (props: AppProps) => {
const { sendMessage } = props;
const AddChat = () => {
sendMessage({ user: "John", message: "Message one", timestamp: new Date().getTime() });
};
const AddNode = () => {
const newNode = new Test();
// ^ Error
};
return(
<React.Fragment>
<button onClick={AddChat}>Add Chat</button>
<button onClick={AddNode}>Add Node</button>
</React.Fragment>
);
}
const addState = (state: AppState) => ({ chat: state.chat });
const ReduxConnect = connect(addState, { sendMessage }) (App)
export { ReduxConnect as App} ;
The error here is
(alias) const Test: ConnectedComponent<typeof Test, Pick<{}, never>>
import Test
----------------
Expected 1 arguments, but got 0.ts(2554)
index.d.ts(324, 10): An argument for 'props' was not provided.
So what is it expecting? How can I find out? The constructor doesn't have props, does it want me to declare some, pass some into the new component or what? I'm just not that familiar with class-based components when it comes to redux
The Test component looks like
import { Component } from 'react'
import { connect } from 'react-redux'
import { AppState } from "../redux/store";
import { ChatState } from "../redux/chat/types";
import { sendMessage } from "../redux/chat/actions";
class Test extends Component {
constructor() {
super();
}
render() {
return null
}
}
const mapDispatchToProps = {
sendMessage
}
export default connect(null, mapDispatchToProps)(Test);
I want to be able to sendMessage and get state from this new component
UPDATE
Changing the class component to this fixes the previous but gives a new error
import { Component } from 'react'
import { connect } from 'react-redux'
import { AppState } from "../redux/store";
import { ChatState } from "../redux/chat/types";
import { sendMessage } from "../redux/chat/actions";
interface AppProps { sendMessage: typeof sendMessage; chat: ChatState }
class Test extends Component {
constructor(props: AppProps) {
super();
}
render() {
return null
}
}
const addState = (state: AppState) => ({ chat: state.chat });
const ReduxConnect = connect(addState, { sendMessage }) (Test)
// ^ Error
export { ReduxConnect as Test} ;
and the error is
Argument of type 'typeof Test' is not assignable to parameter of type 'ComponentType<never>'.
Type 'typeof Test' is not assignable to type 'ComponentClass<never, any>'.
Type 'Test' is not assignable to type 'Component<never, any, any>'.
Types of property 'props' are incompatible.
Type 'Readonly<{}> & Readonly<{ children?: ReactNode; }>' is not assignable to type 'never'.ts(2345)
When I hover over new Test I get
You're connecting your Test component to Redux -- which wants to pass in props -- yet you're not accepting any constructor params, so they're not getting passed in. If you just omit your constructor override I think this will work.
EDIT:
class Test extends Component<{}> {
constructor(props = {}) {
super(props);
}
render() {
return null
}
}
I think this default prop param will now mean you don't have to send in a {} when instantiating Test.
Related
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";
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'm new to Redux and new to Typescript.
I've found a fairly good basic cut-down version of what I'm trying to do in the react-redux docs.
The code is like this :
import * as actionCreators from '../actions/index'
import { bindActionCreators } from 'redux'
import React, { Component } from 'react'
import { connect } from 'react-redux'
class TodoApp extends Component {
render() {
return (<div>test!</div>)
}
}
function mapStateToProps(state) {
return { todos: state.todos }
}
function mapDispatchToProps(dispatch) {
return { actions: bindActionCreators(actionCreators, dispatch) }
}
export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)
Both my code editor (VS Code with the TSLint extension) and tsc highlight that final (TodoApp) as an error, and this is the message I get :
src/components/test.tsx(20,61): error TS2345: Argument of type 'typeof
TodoApp' is not assignable to parameter of type 'ComponentType<{
todos: any; } & { actions: typeof "(filepath)...'. Type 'typeof
TodoApp' is not assignable to type 'StatelessComponent<{ todos: any; }
& { actions: typeof "(filepath)...'.
Type 'typeof TodoApp' provides no match for the signature '(props: { todos: any; } & { actions: typeof "(filepath)/actions/index"; } & {
children?: ReactNode; }, context?: any): ReactElement | null'.
20 export default connect(mapStateToProps,
mapDispatchToProps)(TodoApp)
My problem is that I don't entirely understand exactly what mapStateToProps and connect are doing, but prior to getting that understanding,
I'd like to know if there is a code change I can make here to fix this Typescript "error".
Your react component expects no props, so your connect has an error as it infers that mapStateToProps and mapDispatchToProps should both return empty objects
You can get around this by adding type defs for the react props, but there is also a lot of unsafe use of implicit any. If you were to fully type this application for safety's sake it would look something like this ....
interface ITodo {
description: string
}
interface ITodosState {
todos: ITodo[]
}
interface ITodoProps {
todos: ITodo[]
}
interface ITodoActionProps {
someAction: () => void
}
class TodoApp extends React.Component<ITodoProps & ITodoActionProps> {
render() {
return (<div>test!</div>)
}
}
function mapStateToProps(state: ITodosState): ITodoProps {
return { todos: state.todos }
}
function mapDispatchToProps(dispatch: Dispatch<ITodosState>): ITodoActionProps {
return bindActionCreators({ someAction: actionCreators.someAction }, dispatch)
}
export default connect<ITodoProps, ITodoActionProps, {}>(mapStateToProps, mapDispatchToProps)(TodoApp)
You haven't typed TodoApp's props.
type Props = {
todos: any[] // What ever the type of state.todos is
actions: {
addTodo: Dispatch<any>
}
}
class TodoApp extends React.Component<Props> {
render() {
return <div>test!</div>
}
}
For some reason, the onSubmit() function I cannot set the state of isLoading even though I'm pretty sure I have done the same thing in the past.
import * as React from 'react';
import * as Redux from 'redux';
const { connect } = require('react-redux');
import { push } from "react-router-redux";
import { Col, Jumbotron, Row, Well, Label, Button, FormGroup, FormControl } from 'react-bootstrap';
import { ISession, IApplicationState } from "store";
import './styles.scss';
import { FeedComponent, Feed, StorageApiContext } from "api"
import { Content, ContentLoadingWrapper } from "components";
interface IProfileUserPageProps {
session: ISession;
feed: Feed;
feedComponent: FeedComponent;
}
interface IProfileUserPageState {
isLoading: boolean;
}
#connect(
(state: IApplicationState) => ({
session: state.session.data,
}),
(dispatch: Redux.Dispatch<any>) => ({
navigateToLogin: () => dispatch(push("/")),
})
)
export class ProfileUserPage extends React.Component<IProfileUserPageProps, IProfileUserPageState> {
constructor() {
super();
this.state = { isLoading: false };
}
componentWillMount() {
const {
session,
} = this.props;
}
onSubmit() {
this.setState = ({ isLoading: true });
var inputValue = (document.getElementById("competitors-area") as HTMLInputElement).value;
const addTo: string[] = [];
inputValue.split("\n").forEach(company => {
addTo.push(company)
});
const context = new StorageApiContext();
this.props.session.user.Competitors = addTo;
context.Users.modify(this.props.session.user);
}
The error I receive is:
ERROR in [at-loader] ./src/app/pages/profileUser/ProfileUserPage.tsx:38:28
TS2322: Type '{ isLoaded: boolean; }' is not assignable to type '{ <K extends "isLoaded">(f: (prevState: IProfileUserPageState, props: IProfileUserPageProps) => P...'.
Object literal may only specify known properties, and 'isLoaded' does not exist in type '{ <K extends "isLoaded">(f: (prevState: IProfileUserPageState, props: IProfileUserPageProps) => P...'.
setState() is not an object. It is function to be called like that:
this.setState({ isLoading: true });