I'm getting a flow (incompatible-call) error when setting someProp to a setTimeout. The error states that I cannot call setTimeout because function [1] requires another argument. I'm not sure what this means, and I need help! The code worked before I tried to "flow" it. TIA!
// #flow
import React, { Component } from 'react';
type Props = {
someProp: TimeoutID,
};
export default class MyClass extends Component<Props> {
someProp: ?TimeoutID;
resetState = (i: number) => {
this.setState({ ... });
delete this.someProp;
};
onMouseLeave = () => {
this.someProp = setTimeout(this.resetState, 300); // <- error
};
render() {}
}
Related
Sorry for pseudo-code. Think that the problem can be understood like so. Working in a reproducible example.
Consider the following pseudo code:
Class foo:
let fooInstance = null
class Foo() {
constructor(){
fooInstance = this;
this.fooCallback = null // property to assign callback
}
static getInstance() {
return fooInstance
}
callbackExecutor() {
if (this.fooCallback) // callback always null here even the assignment is ok in the component
this.fooCallback();
}
}
React-redux component:
import { argumentFunc } from 'src/argumentFunc'
class MyComponent extends Component {
constructor(props) {
...
}
componentDidUpdate(prevProps, prevState) {
const foo = Foo.getInstance()
if (whateverCond) {
// callback assignment to instance. this.props.argumentFunc() works if called here
foo.fooCallback = this.props.argumentFunc();
}
}
}
...
const mapDispatchToProps = (dispatch) => ({
argumentFunc: () => dispatch(argumentFunc()),
})
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
I assign the callback to the instance, but then in the callbackExecutor, this.fooCallback is null.
I tried:
foo = this.props.argumentFunc();
foo = this.props.argumentFunc;
foo = () => this.props.argumentFunc();
foo = () => this.props.argumentFunc;
How can I pass a callback to the instance (fooInstance) to be called later on within the class instance methods(callbackExecutor())?
Have you tried:
foo.getInstance().fooCallback = this.props.argumentFunc.bind(this)
In following example i received this error
TypeError: _this is undefined
I tried many ways but couldn't find a way to fix the error, how can i solve it?
component_for_home_page.js
import { dispatch } from 'dispatch'
class Home extends PureComponent {
componentDidMount() {
dispatch('ADD_TEXT', 'Beep, Boop')
// instead of following
// this.props.dispatch({
// type: 'ADD_TEXT'
// text: 'Beep, Boop'
// })
//
}
render() {
return (<p>{this.props.text}</p>)
}
}
function mapStateToProps(state) {
return {
...state
}
}
const connectedHomeComponent = connect(mapStateToProps)(Home)
export {
connectedHomeComponent
}
simplify_dispatch.js
const dispatch = (type, text) => {
return this.props.dispatch({
type,
text
})
}
In simplify_dispatch.js, you're trying to access this.props but that is not in the correct context, since you want this to be from your component_for_home_page. I'm not sure why you don't want to just use this.props.dispatch(...) but if you insist on following this approach, I would suggest passing a third argument to your function in simplify_dispatch.js, either this.props or this.props.dispatch
simplify_dispatch.js
const dispatch = (type, text, passedDispatch) => {
return passedDispatch({
type,
text
})
}
component_for_home_page.js
...
componentDidMount() {
dispatch('ADD_TEXT', 'Beep, Boop', this.props.dispatch)
}
...
here in this tutorial https://redux.js.org/advanced/exampleredditapi in the section containing containers/AsyncApp.js they have code that looks like
componentDidMount() {
const { dispatch, selectedSubreddit } = this.props
dispatch(fetchPostsIfNeeded(selectedSubreddit))
}
but I don't know what they are doing here. I am trying to follow their example except in a project with typescript. Currently i get this runtime error from the chrome console
Uncaught TypeError: dispatch is not a functon
at ProxyComponent.componentDidMount (MyComponent.tsx?23ad:27)
My component code looks like this
export interface MyProps {
prop1: any[]
}
type MyComponentProps = MyProps & DispatchProp;
class MyComponent extends React.Component<MyComponentProps, MyState> {
componentDidMount() {
const { dispatch } = this.props;
dispatch(fetchAsyncData());
}
render() {
return (
<div>
</div>
);
}
}
export { MyComponent };
The dispatch function is meant to call an async action in my redux code. I tried including something called DispatchProps to obtain the dispatch function in my props, but it clearly hasn't worked. Where does this dispatch function come from?
Where are you defining your dispatch function? Are you doing it using connect?
Regardless, you need to define your DispatchProp interface like you did with MyProps
export interface MyProps {
prop1: any[]
}
export interface DispatchProp {
dispatch: () => void // unsure what the actual type of your dispatch function is
}
type MyComponentProps = MyProps & DispatchProp;
...
If you're trying to use connect to define your props, it would look something like this
function mapDispatchToProps(dispatch: Dispatch<any>): DispatchProp {
return {
dispatch: () =>
dispatch({ type: "some_action_type", payload: somePayload})
};
}
I am using redux and typescript for my current webapp.
What is the best practice to define the props of a component which receives redux-actions via #connect, but also props from parent?
Example:
// mychild.tsx
export namespace MyChildComponent {
export interface IProps {
propertyFromParent: string;
propertyFromRedux: string; // !!!! -> This is the problem
actionsPropertyFromRedux: typeof MyReduxActions; // !!!! -> And this
}
}
#connect(mapStateToProps, mapDispatchToProps)
export class MyChildComponent extends React.Component <MyChildComponent.IProps, any> {
... react stuff
}
function mapStateToProps(state: RootState) {
return {
propertyFromRedux: state.propertyFromRedux
};
}
function mapDispatchToProps(dispatch) {
return {
actionsPropertyFromRedux: bindActionCreators(MyReduxActions as any, dispatch)
};
}
// myparent.tsx
export class MyParentComponent extends React.Component <MyParentComponent.IProps, any> {
... react stuff
render(){
// typescript complains, because I am not passing `propertyFromRedux`!
return <div><MyChildComponent propertyFromParent="yay" /></div>;
}
}
As I see it I got 2 solutions.
Just pass the actions & state down through my whole app. But that would mean that my whole app gets re-rendered even when just some small child component would have to change. Or is it the redux way to listen in my top level component on all store changes? Then I would have to write a lot of logic inside shouldComponentUpdate for props which are no flat objects.
Set the param in IProps of MyChildComponent optional like this:
-
// mychild.tsx
export namespace MyChildComponent {
export interface IProps {
propertyFromParent: string;
propertyFromRedux?: typeof MyAction; // This is the problem
}
}
Is there another way? Both of the above ways seem too messy in my eyes.
You need to split up your props - you'll need a DispatchProps, StateProps, and an OwnProps type. You'll also have to use TypeScript's generics with connect
DispatchProps are your action creators.
StateProps are your state props (duh) - these come from mapStateToProps - the return type of that function should match this type.
OwnProps are props which are accepted (and perhaps expected) by your component. Optional props should be marked as optional in the interface.
The way I do it (without decorators, but i'm sure it applies here) is
interface ComponentDispatchProps {
doSomeAction: typeof someAction;
}
interface ComponentStateProps {
somethingFromState: any;
}
interface ComponentOwnProps {
somethingWhichIsRequiredInProps: any;
somethingWhichIsNotRequiredInProps?: any;
}
// not necessary to combine them into another type, but it cleans up the next line
type ComponentProps = ComponentStateProps & ComponentDispatchProps & ComponentOwnProps;
class Component extends React.Component<ComponentProps, {}> {...}
function mapStateToProps(state, props) {
return { somethingFromState };
}
export default connect<ComponentStateProps, ComponentDispatchProps, ComponentOwnProps>(
mapStateToProps,
mapDispatchToProps
)(Component);
I think you have to use #connect<StateProps, DispatchProps, OwnProps> which will decorate and return a class which accepts OwnProps.
If you look at connects implementation in TS
export declare function connect<TStateProps, TDispatchProps, TOwnProps>(...): ComponentDecorator<TStateProps & TDispatchProps, TOwnProps>
interface ComponentDecorator<TOriginalProps, TOwnProps> {
(component: ComponentClass<TOriginalProps> | StatelessComponent<TOriginalProps>): ComponentClass<TOwnProps>;
}
connect<...> returns a ComponentDecorator, which, when passed the component (in your case this is done transparently when transpiling the decorator out), regardless of StateProps, and DispatchProps returns a component which expects OwnProps.
connect (non-generic) returns InferableComponentDecorator
export interface InferableComponentDecorator {
<P, TComponentConstruct extends (ComponentClass<P> | StatelessComponent<P>)>(component: TComponentConstruct): TComponentConstruct;
}
which attempts to infer the props based on the props supplied to the component, which in your case is the combination of all props (OwnProps becomes ComponentProps from above).
Of course you can setup types manually. But much comfortable to use generated, that you actually get from connect. It helps to avoid annoying duplicates.
Example 1:
type Props = ReturnType<typeof mapStateToProps> & ReturnType<typeof mapDispatchToProps>
class MyComponent extends React.PureComponent<Props> {
...
}
const mapStateToProps = (state: ReduxState) => ({
me: state.me,
})
const mapDispatchToProps = (dispatch: ReduxDispatch) => ({
doSomething: () => dispatch(Dispatcher.doSomething()),
})
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
Now we get types directly from redux state and action/dispatch functions.
After some time we simplified this example to:
Example 2:
//// other file
import { InferableComponentEnhancerWithProps } from 'react-redux'
type ExtractConnectType<T> = T extends InferableComponentEnhancerWithProps<infer K, any> ? K : T
//// <= Save it somewhere and import
type Props = ExtractConnectType<typeof connectStore>
class MyComponent extends React.PureComponent<Props> {
...
}
const connectStore = connect(
(state: ReduxState) => ({
me: state.me,
}),
(dispatch) => ({
doSomething: () => dispatch(Dispatcher.doSomething()),
})
)
export default connectStore(MyComponent)
To put it simply,
a component should be clear on what props should be coming from a parent and connect (redux).
Now, connect() can issue redux state (your app state) or action as a prop to the component and the rest props of the component should be coming from the parent.
As suggested, it is better to break the component props into 3 parts (ComponentStateProps, ComponentDispatchProps & ComponentOwnProps) and then use them in connect(). And, join these 3 props to form ComponentProps.
I think the below code will give a better understanding.
// Your redux State
type SampleAppState = {
someState: any;
};
// State props received from redux
type ChildStateProps = {
propFromState: any;
};
// dispatch action received from redux (connect)
type ChildDispatchProps = {
actionAsProp: () => void;
};
// Props received from parent
type ChildOwnProps = {
propFromParent: any;
};
// All Props
type ChildProps = ChildStateProps & ChildDispatchProps & ChildOwnProps;
const ChildComponent = (props: ChildProps) => {
return <>{/*....*/}</>;
};
let ConnectedChildComponent = connect<
ChildStateProps,
ChildDispatchProps,
ChildOwnProps,
SampleAppState
>(
(appState: SampleAppState, ownProps: ChildOwnProps) => {
// Shape that matches ChildStateProps
return {
propFromState: appState.someState,
};
},
(dispatch, ownProps: ChildOwnProps) => {
return bindActionCreators(
// Shape that matches ChildDispatchProps
{
actionAsProp: () => {},
},
dispatch,
);
},
)(ChildComponent);
const ParentComponent = () => {
return (
<>
<ConnectedChildComponent propFromParent={'Prop Value'} />
</>
);
};
I thought of creating a factory function called createScreen for reducing the boilerplate required by react-redux.
It looks like this:
ParentScreenFactory.js
export default function createScreen(stateActions = []) {
class ParentScreen extends React.Component {
}
function mapStateToProps(state) {
return {
...state,
};
}
function mapDispatchToProps(dispatch) {
const creators = Map()
.merge(...stateActions)
.filter(value => typeof value === 'function')
.toObject();
return {
actions: bindActionCreators(creators, dispatch),
dispatch,
};
}
return connect(mapStateToProps, mapDispatchToProps)(ParentScreen);
}
Child.js
const ParentScreen = createScreen([
routingActions,
authActions,
]);
class Child extends ParentScreen {
constructor(props) { // <-- error on this line
super(props);
}
render() {
return (
<View/>
);
}
}
export default Child;
for some reason though, I get undefined is not an object (evaluating 'context.store').
Stacktrace:
Connect(ParentScreen)
connect.js:129
which is this line of code _this.store = props.store || context.store;.
Any obvious mistakes you see here?
Other than that do you have any better idea about how to reduce all that boilerplate code?
Thank you.
Everything will be simpler if you work with the actual component class, rather than trying to extend the empty connected one (this is the class you're actually extending).
If you want your component to work predictably, then you need to connect your component directly. Try returning a function from your factory instead.
export default function createScreen(stateActions = []) {
return (Component) => {
// ...
return connect(mapStateToProps, mapDispatchToProps)(Component);
};
}
Then your instantiation starts to look something like this.
class Child extends React.Component {
// ...
}
const ParentScreen = createScreen([
routingActions,
authActions,
]);
export default ParentScreen(Child);
If you want to share some behaviour between all components, then you're better off using a higher-order-component.
function withCommonBehaviour(Component) {
return (props) => {
let newProps = doSomething(props);
return <Component {...newProps} />;
};
}
Then just hook that up inside your createScreen function.
// ...
let CommonComponent = withCommonBehaviour(Component);
return connect(mapStateToProps, mapDispatchToProps)(CommonComponent);