Tryign to implement TypeScript with React/Redux and I'm running into some issues with my Types.
I have these types:
export interface IAuthState {
isSignedIn: Boolean | null;
userId: string | null;
}
export interface IStream {
id: string;
title: string;
description: string;
userId: string;
}
export interface IStreamState {
streams: { [index: string]: IStream };
}
Then I have two components:
interface IStreamsProps {
fetchStreams: () => void;
streams: IStreamState;
currentUserId: String | null;
isSignedIn: Boolean | null;
auth: IAuthState;
}
class StreamsList extends Component<IStreamsProps, IAppState> {
// Error: The Property Map does not exist on type IStreamState
renderList() {
return this.props.streams.map((stream: IStream) => (// CODE)
}
}
const mapStateToProps = (state: IAppState) => {
return {
streams: Object.values(state.streams),
currentUserId: state.auth.userId,
isSignedIn: state.auth.isSignedIn
};
};
export default connect(
mapStateToProps,
{ fetchStreams }
)(StreamsList);
Then I have another similar component:
const mapStateToProps = (state: IAppState, ownProps: HomeProps) => {
return {
//Error: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'IStreamState'
stream: state.streams[ownProps.match.params.id]
};
};
export default connect(
mapStateToProps,
null
)(StreamEdit);
How do I solve these two errors:
Error 1: The Property Map does not exist on type IStreamState
Error 2: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'IStreamState'
You incorrectly defined IStreamState. Look closely. You've defined it as an object
export interface IStreamState {
streams: { [index: string]: IStream }[];
}
// equivalent declaration using type:
export type IStreamState = {
streams: { [index: string]: IStream }[]
}
I'm sure you mean to type it as just an array, as so:
export type IStreamState = { [index: string]: IStream }[]
EDIT:
While not directly related to your question, you need to be careful with your types. I noticed you used IAppState in two different places. The class declarations are for props and local state. IAppState appears to be for your redux store.
// the second generic argument is for `this.state`
class StreamsList extends Component<IStreamsProps, IAppState> {
EDIT2:
Defining both arguments in a class is optional. If you leave the second one out, it defaults to {} for you.
It's impossible to be 100% sure as to why you're having that issue in your mapStateToProps, because I don't know what IAppState looks like. Just go double back and confirm your typing for state.streams is exactly what you expect.
Related
I want to get a type for redux state, which will be used to mock said state.
So far I figured out I have this Reducer:
(alias) const reducer: {
selectedElement: Reducer<StructureElement, AnyAction>;
fetchedState: Reducer<FetchedState, AnyAction>;
... 10 more ...;
mesApi: Reducer<...>;
}
So what I want to do, is somehow get the StructureElement, FetchedState and so on from inside of the Reducer</here/, AnyAction>; part.
How can I achieve that?
Or is there a better way to get combined state type? It has to be done automatically though. I don't want to change types in 2 places when it should be auto-generated.
If I try this:
type State = typeof reducer;
I get
Type '{ selectedElement: Reducer<StructureElement, AnyAction>; fetchedState: Reducer<FetchedState, AnyAction>; ... 10 more ...; mesApi: Reducer<...>; }' is not assignable to type '{ selectedElement?: { stateId: string; parentId: string; hasChild: boolean; pathList: string[]; name: string; color: string; isActive: boolean; level: number; children?: ...[]; key?: string; type: StateType; }; ... 11 more ...; mesApi?: { ...; }; }'.
Types of property 'selectedElement' are incompatible.
which makes sense.
I'd recommend to simply model it explicitly and then import those types where you need them:
export type UserState = {
readonly isLoggedIn: boolean;
// other user state here
};
// Type for entire global redux state
export type AppState = {
users: UserState;
// other sub-states here
};
I personally create a slice for each feature that I want in my state, then I have a file in which I'll first declare and export my RootState Type, and then I'll combine all of my reducers.
export interface RootState {
pokemon: pokemonState;
pokemonTrainer: pokemonTrainerState;
}
const reducer = combineReducers<RootState>({
pokemon: pokemonSLice,
pokemonTrainer: pokemonTrainerSlice,
});
Where pokemonState and pokemonTrainerState are the types of each feature.
And then I'll use RootState wherever I need.
Ok, I was being stupid. I had something like this back from the basic setup:
export type RootState = ReturnType<typeof store.getState>;
And it seems to do the trick.
I have a type of User that contains some user data. There's also a type of UsersResponse which basically is users: User[]. So far everything is easy. The problem is I have to pass UsersResponse as props and then I can't access .users. Why is that?
Link to Codesandbox.
Part of the code:
export type User = {
login: string;
password: string;
}
export type UsersResponse = {
users: User[];
}
export interface MyProps {
props: UsersResponse;
}
const App = (props: MyProps): any => {
console.log('props: ', props.users);
return (
<>foo</>
);
}
Console.log fails because Property 'users' does not exist on type 'MyProps'.
But this makes no sense to me, because MyProps returns UsersResponse and it has users. What am I missing here? How do I fix that? I know it's something really obvious or I made a typo, but really, I can't find it. Note I have to use props: type as this format is forced by the framework I'm using.
You had set the wrong key in MyProps interface.
And you also don't need to declare UsersResponse.
Try this:
export type User = {
login: string;
password: string;
}
export interface MyProps {
users: User[];
}
const App = (props: MyProps): any => {
console.log('props: ', props.users);
return (
<>foo</>
);
}
Basically I'm using a React function based component.
*** But the question has nothing to do with React specificly.
const Component = <Condition extends boolean>(props: React.PropsWithChildren<Props<Condition>>) => {
Props:
interface Props<Condition extends boolean> {
condition: Condition;
}
In this function, I create a variable to store some data.
const initialValues: Fields<Condition> = (() => {
const base = {
unit: '',
};
if (props.condition) {
return {
...base,
from2: '',
};
}
return base;
})();
The Fields type is configured as following:
interface Base {
unit: string;
}
interface Extended extends Base {
from2: string;
}
export type Fields<Condition extends boolean> = Condition extends true ? Extended : Base;
The entire code organized together:
interface Base {
unit: string;
}
interface Extended extends Base {
from2: string;
}
export type Fields<Condition extends boolean> = Condition extends true ? Extended : Base;
interface Props<Condition extends boolean> extends PropsFromState {
condition: Condition;
}
const Component = <Condition extends boolean>(props: React.PropsWithChildren<Props<Condition>>) => {
const initialValues: IJobFormFields<Condition> = (() => {
const base = {
unit: '',
};
if (props.condition) { // Check if condition (also Condition type) is true
return {
...base,
from2: '',
};
}
return base;
})();
};
The issue is that I receive the following error:
Type '{ unit: string; } | { unit: string; from2: string; }' is not assignable to type 'Fields<Condition>'.
Type '{ unit: string; }' is not assignable to type 'Fields<Condition>'.ts(2322)
That's a current design limitation of Typescript. It cannot narrow the type of conditional type depending on unspecified generic type parameter. And while the type parameter is not explicitly specified the type of Fields<Condition> is opaque to the compiler.
Usually similar cases when function returns a conditional type depending on a generic type parameter are good candidates for rewriting with function overloads. But since you're not returning the value of initialValues I believe you're better off splitting prop generation into separate branches:
const BaseComponent = (props: Base) => null
const ExtendedComponent = (props: Extended) => null
const Component = <T extends boolean>(props: Props<T>) => {
const base = { unit: '' }
if (props.condition) {
return <ExtendedComponent {...base} from2="" />
}
return <BaseComponent {...base} />
};
playground link
You can simply add the property with a question mark making it as an optional property.
unit?: string
Here's what I'm trying to do in react,
I've got a functional component where I pass down 1 prop
<TableComponent tableStateProp={tableState} />
tableState is a state hook in the parent component
const [tableState, setTableState] = useState<TableState<string[]>>();
the table state type is defined inside of my table component
export type TableState<T> = {
pagination: {
limit: number,
skip: number,
}
data: T[];
columns: string[],
}
But here is where my problem starts, ideally I would be able to do this
const TableComponent: React.FC<{
tableState: TableState<T>;
}> = ({tableState}) => {
But I get an error saying TS2304: Cannot find name 'T'.
I know for a generic prop function the syntax is something like function<T>(): type<T>
but what is it for a generic prop/object?
Edit: I am using this component elsewhere where data is not a string[], hence why I'm trying to make it generic
Thank you
You don't need to use React.FC<>. Declare your component as a named function and you can add the generic <T>.
export type TableState<T> = {
pagination: {
limit: number;
skip: number;
};
data: T[];
columns: string[];
};
function TableComponent<T>({
tableState,
}: React.PropsWithChildren<{
tableState: TableState<T>;
}>) {
// ...
}
If you don't need the children prop to work, you don't need to use React.PropsWithChildren either, just:
function TableComponent<T>({ tableState }: { tableState: TableState<T> }) {
If you want to be explicit about the return typing right at the TableComponent level (and not when you use it in your app later on), you can peek at what React.FC is doing and type explicitly accordingly:
function TableComponent<T>({
tableState,
}: {
tableState: TableState<T>;
}): ReactElement<any, any> | null {
return null;
}
You need update like this:
const TableComponent: React.FC<{
tableState: TableState<string[]>;
}>
I'm trying to create a Union type with the structure of optional fields. I have created the following types:
export type StartEndType = {
start_date: string;
end_date: string;
};
export type PayrollContract = StartEndType & {
type: 'ON_PAYROLL';
yearly_holidays: number;
};
export type FreelanceContract = StartEndType & {
type: 'FREELANCE';
hourly_rate: number;
};
export type Contract = PayrollContract | FreelanceContract;
In my component it looks like:
{contractType === 'ON_PAYROLL' ? (
<Number name="yearly_holidays" />
) : contractType === 'FREELANCE' && (
<Number name="hourly_rate" />
)}
When I hover contract, it knows that it's one of ON_PAYROLL or FREELANCE. Although unfortunately I get a DeepMap error within my component.
Isn't thist supported by TypeScript out of the box?
Property 'yearly_holidays' does not exist on type 'DeepMap<PayrollContract, FieldError> | DeepMap<FreelanceContract, FieldError>'.
Property 'yearly_holidays' does not exist on type 'DeepMap<FreelanceContract, FieldError>'.
How can I solve this?
Thanks in advance.
Could you provide the code for Number component? Looks like an issue with how you are rendering the number by name, I was able to compile and use the TS code above fine in TS playground.
const renderPayField = (contract: Contract) => {
if (contract.type === 'ON_PAYROLL') {
return <Number name="yearly_holidays" />;
}
if (contract.type === 'FREELANCE') {
return <Number name="hourly_rate" />
}
return null;
};
Additionally, updated some of the TS to use interfaces + readonly to improve readability
export interface StartEndDate {
start_date: string;
end_date: string;
};
export interface PayrollContract extends StartEndDate {
readonly type: 'ON_PAYROLL';
yearly_holidays: number;
};
export interface FreelanceContract extends StartEndDate {
readonly type: 'FREELANCE';
hourly_rate: number;
};
export type Contract = PayrollContract | FreelanceContract;