Unable to use inject without observer - reactjs

I'm currently using typed React (TSX) and mobx for state management.
I am able to build a component which uses both observer and inject decorators. But I am not able to build a component which uses inject without observer.
This passes the typescript compiler
export const DealershipBreadCrumb = inject("appStore")(observer((props: Props) => {
const {appStore} = props;
const dealership = appStore.getSelectedDealership();
return (
<div className="filter__container filter__group">
<a className="filter__link" href={`cars?q=${appStore.searchResults.searchQuery}`}>
<span className="filter__text">{dealership.name}</span>
</a>
</div>
)
}))
However this fails
export const DealershipBreadCrumb = inject("appStore")((props: Props) => {
With the following error message
[ts] Argument of type '(props: Props) => Element' is not assignable to parameter of type 'ComponentClass<{}>'.
Type '(props: Props) => Element' provides no match for the signature 'new (props?: {}, context?: any): Component<{}, ComponentState>'
Please assist me in making heads and tails of this error message. My bet is that the typings are out of date or something. Or otherwise the use of inject without observer is actually an invalid combination.

I think that your error is coming from this:((props: Props)...
when using inject that way I believe it is expecting a function, try use it like this:
export const DealershipBreadCrumb = inject('SystemDataStore')((props => {
btw don't forget that if you want to use inject without observer it will inject the last values of that store but it wont be aware of any change of values.

Related

Wrapper function for JSX elements, typed via generics

Currently this function works fine
function wrapElement(elem: JSX.Element) {
return ({ ...props }) => React.cloneElement(elem, { ...props })
}
I use it like this, because this way I can get intelliSense for tailwind classes
const Btn = wrapElement(<button className="[A LOT OF TAILWIND UTILITY CLASSES]" />)
But I'm trying to get it to return same type as it receives, so I could get intelliSense for attributes on intrinsic HTML elements.
Right now inferred type is
function wrapElement(elem: JSX.Element): ({ ...props }: {
[x: string]: any;
}) => React.FunctionComponentElement<any>.FunctionComponentElement<any>
I tried some stuff and it all failed with all kinds of errors, at this point it feels like this could be to hacky, but maybe I don't understand something?
It is basically impossible to get the correct props from a JSX.Element. You can achieve the design that you want, but you should pass in the element name and the props as separate arguments rather than passing in a JSX.Element.
This code can accept an element name like 'button' or any React component. It returns a function component with the same props. I am not dropping any props from the returned component because it seems like you are using this for setting defaults rather than dropping requirements.
import React, { ComponentType, ComponentProps } from "react";
const wrapElement = <
C extends keyof JSX.IntrinsicElements | ComponentType<any>
>(
Component: C,
presetProps: Partial<ComponentProps<C>>
) => (props: ComponentProps<C>) => {
const merged: ComponentProps<C> = { ...presetProps, ...props };
return <Component {...merged} />;
};
const Btn = wrapElement("button", {
className: "[A LOT OF TAILWIND UTILITY CLASSES]"
});
const Dbl = wrapElement(Btn, { onClick: () => alert("clicked") });
const Test = () => {
return <Dbl>Click</Dbl>;
};
Typescript Playground Link
You might want to customize your merge behavior to combine className or style properties rather than overriding them.
Note: when I tried to merge the props inline like <Component {...presetProps} {...props} /> I got a weird error "Type Partial<ComponentProps<C>> & ComponentProps<C> is not assignable to type IntrinsicAttributes & LibraryManagedAttributes<C, any>." So that is why I am merging the props on a separate line and annotating the type as ComponentProps<C> instead of the inferred type Partial<ComponentProps<C>> & ComponentProps<C>.

React useCallback in typescript

Type error: Argument of type 'Function' is not assignable to parameter of type '(...args: any[]) any
Type 'Function' provides no match for the signature '(...args: any[]): any'.
const handleImageSearch = useCallback(
debounce((value) => searchUnsplashImage(value), 400),
^
[],
);
I have tried adding the
interface IFunction {
(...argArray: any[]): any;
}
const handleImageSearch = useCallback<IFunction>(
debounce((value) => searchUnsplashImage(value), 400),
^
[],
);
Befor adding props
But it does not work. Please help me with this.
oneI have been facing this issue on .jsx files, and I assume this happens only for tsc compilations for non tsx? files (not sure, though). Also, the weird point is, this is not getting detected by VSCode as an error.
Anyways, the issue is quite clear, it happens because lodash/debounce returns a DebouncedFunc, as can be seen in the below signature:
debounce<T extends (...args: any) => any>(func: T, wait?: number, options?: DebounceSettings): DebouncedFunc<T>;
To get around this you can try one of the followings:
Disable ts check for that debounce call, i.e. add the following comment above debounce() function call
useCallback(
// #ts-ignore
debounce(),
[]
);
Better way, instead of ignoring ts-checks completely is to, separate the function into a different instance:
const debounceFn = debounce(...);
const callback = useCallback((...args) => debounceFn(...args), []);
Or even more concise:
const callback = useCallback((...args) =>
debounce((param1, paramN) => {
/** Your Function Body */
})(...args)
, []);
Obviously, this is a workaround, and adds more code. For me // #ts-ignore seems like a better option.
A much better solution would be to override React Types itself for useCallback, details: How to overwrite incorrect TypeScript type definition installed via #types/package, but that's all a personal preference.
I hope this fixes your issue.

Type of inputRef - TypeScript

I have a React component that accepts as props an inputRef
My Interface
interface Props extends WithStyles<typeof styles> {
body: text
inputRef?: any // I am not sure which TypeScript type I should use
}
My component
const MyComponent = ({
classes,
body,
inputRef
}: Props) => {
<TextField
body={body}
inputRef={inputRef}
/>
}
I am not sure which TypeScript type should I use for inputRef. I currently declare it as any but I want the right type.
I tried to use React.createRef<TextInput>() but I get the error: Namespace 'React' has no exported member 'createRef'
I have as types version: "#types/react": "^16.9.21"
Here is how I figure that out. When I create a reference to an element:
const wrapperRef = useRef<HTMLDivElement>(null);
I hover over the variable wrapperRef in VSCode to see what type it is. In this case I see:
const wrapperRef: RefObject<HTMLDivElement>
So, what you want to pass is RefObject<HTMLDivElement>.
Now if you do want an ElementRef, go ahead and use that (I'm not sure what kind of ref you are trying to pass). When you look at documentation or the actual type file you will see <T>. This is a Typescript Generic. It allows you to pass a type to the type declaration. Here is some documentation on it with many examples.

How to create stateless functional components that accept generics?

Typescript generics can used to extend interfaces.
interface Sample1<P> {
prop1: P;
}
interface Sample2<P> extends Sample1<P> {
prop2: string;
}
But when I try to create a functional component that uses the generic interface, typescript throws error.
const SampleSFC: React.SFC<Sample2<P>> = () => <div />;
error TS2304: Cannot find name 'P'.
If I replace P with a known type like string the error goes away.
const SampleSFC: React.SFC<Sample2<string>> = () => <div />;
Instead of hard coding type of P, that totally kills the purpose of generics, I want to enable users of SampleSFC to set type of P.
How can I do that? If it's not possible then what alternate design I should follow that would let me have SFC with generic props.
Generic type variables can be used in function and type declarations like your interfaces Sample1 and Sample2.
But when you actually use/call/invoke generic types, you have to specify a concrete type argument instead of P.
React.SFC - the stateless function component type - is an interface declaration, so using it in an const assignment requires a concrete type for P.
The only solution that comes to my mind is to make the SFC look like a function type for the ts-compiler, since function declarations/expressions can
be assigned generic type parameters. See Typescript docs for some examples.
Generic SFC declaration
Leave out the React.SFC type annotation to your assignment const SampleSFC:
const SampleSFC = <P extends {}>(props: Sample2<P>) => <div>{props.prop1} </div>;
Note: The <P extends {}> part is needed due to JSX/TSX compatibility
issues with generics, see the end of this post.
This way, your component remains a plain function. If you need the additional members from React.SFC, add them to your props. E.g. if children needed:
const SampleSFC = <P extends {}>(props: Sample2<P> & { children?: ReactNode })
=> <div >{props.prop1} </div>;
Generic SFC usage
Since Typescript 2.9, the type parameter can be directly set in JSX.
const MyApp: React.SFC<{}> = (props) => {
return <SampleSFC<number> prop2="myString" prop1={1} />
}
Alternatives
1.) A switch to class components would be easy, if you don't like the quirks.
class SampleSFCClass<P> extends React.Component<Sample2<P>> {
render() {
return <div>{this.props.prop1}</div>
}
}
const MyApp2: React.SFC<{}> = (props) => {
return <SampleSFCClass<number> prop2="myString" prop1={1} />
}
2.) You could even wrap the SFC in another function.
const withGeneric: <P>() => React.SFC<Sample2<P>> = <P extends {}>() => {
return (props) => <div> {props.prop1} {props.prop2} </div>;
}
const SampleSFCCalled: React.SFC<Sample2<string>> = withGeneric<string>();
const MyApp: React.SFC<{}> = (props) => {
return <SampleSFCCalled prop2="myString" prop1="aString" />
}
It will work, but disadvantage might be slight performance decrease, because the SFC funtion is always recreated in each render cycle of the parent comp.
JSX/TSX compatibility issues with Generics:
In some constellations Typescript seems to have problems parsing generics in combination with JSX/TSX (up to most recent 3.0.1) due to ambiguous syntax. Compile error will then be:
"JSX element has no corresponding closing tag."
One of the contributors recommended to use the function syntax in this case (see issue).
When you stick to arrow function, workaround is to let the type parameter extend from object (shown here or here) to clarify its meant to be a generic lambda and not a JSX tag:
<P extends {}> or <P extends object>
Hope, that helps.
Generics only work within generic types. So when you are declaring a type interface Sample1<P>, then you can use P as a type within that declaration. However, the concrete type Sample1<P> does not actually exist. Instead P is a placeholder for a type when the Sample1 type is being used.
So using Sample1<P> outside of a generic type definition that also has a generic type argument P does not really work. You can only use actual non-generic types P then.
That’s why Sample1<string> works, because string is a valid value for the type argument P in the Sample1<P> definition.
If you want so have a SampleSFC that works for any type P, then you could create it as a React.SFC<Sample1<any>>:
const SampleSFC: React.SFC<Sample1<any>> = () => <div />;
interface MyComponentProps<T> {
value?: T;
}
export const MyComponent = <T,>(
props: React.PropsWithChildren<MyComponentProps<T>>,
) => {
return (
<View>
{props.children}
<Text>{(props.value as unknown) as string}</Text>
</View>
);
};
then in the render method..
return (
<MyComponent<string> />
)

Typescript: type inference for children on generic react component

Given a react component that is generic on two properties, a load function returning type T, and the children (which is a single function taking a parameter of type T)...
class Test<T> extends React.Component<{
load: () => T;
children: (r: T) => JSX.Element;
}> {
render() {
return this.props.children(this.props.load());
}
}
typescript is not able to infer the type of res from the return value of load, it defaults to type {}.
<Test load={() => ({ test: 'x' })}>
{res =>
<span>
{res.test}
</span>}
</Test>;
Is this a bug, or is typescript not able to infer based on return values -- as it looks like typing of JSX children is supported.
EDIT:
for background, the usecase is creating a "lazy" component which accepts load function which return a promise resolving to some further JSX.
Update: In the meantime TypeScript has been improved, such that the code given in the question automatically infers the type for res. I haven't checked which change was responsible for that, but I guess it happened somewhere around 3.0-3.3.
I think the problem is that your code gets transpiled to something similar to:
React.createElement(
Test,
{load: () => ({ test: 'x' })},
res =>
<span>
{res.test}
</span>
)
From that situation it is more clear that it would be very hard for the compiler/definitions to correctly infer the generic parameter. In your situation it would be enough to tip the compiler:
<Test load={() => ({ test: 'x' })}>
{(res: { test: string }) =>
<span>
{res.test}
</span>}
</Test>

Resources