I am having a problem trying to figure out how assign an array of users after an async fetch. I have a simple interface User, which has an id and a name:
export interface User {
id: number
}
I then have a UserState that includes an array of users:
export interface UserState {
loading: boolean
users: User[]
status: userStatus
error: string | null | undefined
}
In my slice, I have an asychronous function:
export const getUsers = createAsyncThunk(
'user/fetchUsers',
async (users: []) => {
const response = await fetchUsers();
return response.data;
}
);
I don't have reducers but I do have extraReducers:
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {},
extraReducers: builder => {
builder.addCase(getUsers.fulfilled, (state, {payload: users}) => {
state.loading = false
state.status = 'idle'
state.users = users //??? Or something like this...???
state.error = ''
})
}
})
For clarity, I've just included this one addCase where I am trying to assign the expected array of users in JSON format into the users array. I've tried a number of different syntax variations and I'm getting errors like:
Type 'User[]' is not assignable to type '(() => Element)[]'.
Type 'User' is not assignable to type '() => Element'.
Type 'User' provides no match for the signature '(): Element'.ts(2322)
I'm not entirely sure how to address this one. I've looked at a number of tutorials and examples but there are many different ways of doing things. I'm not sure if I'm close to a solution -- there is a simple and direct fix from where I am -- or if I'm a world away from fixing this. I feel as if I've gone down too many rabbit holes and ended up creating a monster of mixed parts that don't quite fit together.
Thanks for any help!
From the error message, it seems as if you are trying to assign a component type to the User interface here - it seems you have at some point been importing a User component instead of your User interface. Going through your imports and finding the wrong import will fix your problem.
Related
For some reason I can not run my React app, because following error:
TypeScript: Property 'name' does not exist on type 'any[]'.
I am exporting my TS type based on my API data:
export type CartItemType = {
id: number;
name: string;
image: string;
amount: number;
chair: {
name: string;
colors: {
white: {
hex: string;
};
};
};
};
Fetching the data:
const getProducts = async (): Promise<CartItemType[]> =>
await (await fetch("apiEndpoint")).json();
const [cartItems, setCartItems] = useState([] as CartItemType[]);
const { data, isLoading, error } = useQuery<CartItemType[]>(
"products",
getProducts
);
I would like to select the fetched data and I am doing it in such a way:
const [selectedData, setSelectedData] = React.useState([]);
{data?.map((item) => (
<button onClick={() => setSelectedData(item)}>SHOW</button>
{selectedData === item ? (
<p>{selectedData.chair.description}</p>
) : null}
Unfortunately I am having this error on selectedData:
This condition will always return 'false' since the types 'any[]' and 'CartItemType' have no overlap.
I don't really know what that means and how to fix it.
It would work only when I would specify the number in object array like this:
selectedData[0].name
But I have to make it work for selectedData.name
You must aware of the value that the state is going to hold. It is important because the state update cause the re-render of the application.
Change your initial value of selectedData state variable to {}.
const [selectedData, setSelectedData] = React.useState({} as CartItemType);
Reason: Initially you are holding array value in a state and then later you are setting the state with objects and performed operation assuming it's an object. So, it's obvious that you must hold object value in the selectedData state.
This is the reason that you are getting this error message.
This condition will always return 'false' since the types 'any[]' and 'CartItemType' have no overlap.
The good part: I have made that change in the sandbox and the error goes away.
The accepted answer will fix the error but ideally you want to avoid Type casting
In my opinion, it is safer to set selectedData to null first to make sure you only use it if it has a correct value:
const [selectedData, setSelectedData] = React.useState<CartItemType | null>(null);
Then in your code you can do
if(selectedData) {
...
}
or
selectedData ? ... :
I'm currently in the process of migrating a nuxt application with Vuex from JS to TS. I'm starting with one of ou Vuex modules. The thing is that we use a library we wrote at our company and that is used in other projects (can't touch it or migrate it to TS...) to generate some base state, getters, setters, actions and mutations for each module given a certain entity. This library is written is JS with no types available so I'm working on some shims to get some typings in my project. Let me walk you through the whole workflow before I explain my problem.
The library exports a createEntity function that takes a String and returns an object that looks like this:
{
path, //The name of the module
mutationPrefix, //path.uppercase()
actionPrefix, //path
singleSchema, //some normalizr stuff
multipleSchema, //some normalizr stuff
storeKey: 'entities', //the default name for the key in the store
}
We then use the Entity that has been returned to generate the baseState/baseMutations/baseGetters/baseActions of this store module (these are basic CRUD operations on a given entity).
For the module I'm working on (the first I'm migrating to TS), the state.ts file looks like this
import { createState } from '#kissmylabs/vuex-entitystore'
import { Provider } from '~/types/models/Provider'
import { provider } from './entities' // The entity created by our createEntity() library function
export const baseCreateForm = (): Provider => ({
username: '',
email: '',
password: '',
// ... some other form fields
})
export const getBaseState = () => ({
...createState(provider), // The state created by our createState() library function
pagination: {
total: 0,
},
createForm: baseCreateForm(),
isImpersonating: false,
})
export default getBaseState
So I'm trying to implement types for my state, using some TS Interfaces. My interface BaseState (the return type of my createState() library function) looks like this
interface BaseState {
storeKey: {
byId: any,
allIds: Array<number>,
active: Array<any>,
deleted: Array<any>,
isFetching: boolean,
errors: Array<any>,
}
}
This is working mostly fine, my types are working, except for the main storeKey part. This is because the createState function takes the entity.storeKey as the key for the returned state, like so:
export const createState = (entity) => {
return {
[`${entity.storeKey}`]: {
byId: {},
allIds: [],
active: [],
deleted: [],
isFetching: false,
errors: [],
},
}
}
So the whole state generation thing works fine, types are ok and everything, the problem comes when I'm trying to reference my state in a mutation for example. In my mutations.ts I want to be able to do something like
export const mutations = {
...createMutations(provider), // My base mutations, typed fine
ADD_PROVIDER_FILE(state: State, { id, file }: { id: number, file: any }) {
state.entities.byId[id].files.push(file) // The problem is here "Property entity doesn't exist"
},
// More mutations...
}
export default mutations
TS does not recognize the fact that my entities key on the state object can be pretty much anything and is generated dynamically by my createState function, depending on a given entity's storeKey attribute.
So my question is: Is there a way to declare an interface for my BaseState that declares storeKey as an object with any name but still containing the right attributes and types inside ?
What I've tried so far :
[key: string] instead of storeKey as my key in BaseState
Extracted the content of the storeKey object in a new interface and doing something like :
interface BaseStateKeys {
byId: any,
allIds: Array<number>,
active: Array<any>,
deleted: Array<any>,
isFetching: boolean,
errors: Array<any>,
}
interface BaseState {
[key: string]: BaseStateKeys
}
None of this worked.
Anything clever I'm missing ? Thanks for help !
Say I have an application for keeping track of university courses. Each course has some attributes like name time notes id. In the application, the user can add/remove courses and edit their attributes. How would I structure a redux store for this?
My first thought is to make the state a mapped object like so:
interface Course {
id: string;
name: string;
time: string;
notes: string;
}
interface StoreState {
[id: string]: Course;
}
where each entry in the state corresponds to a course instance. Then, when the user adds a new course, I add a new entry to the state
const slice = createSlice({
name: 'courses',
initialState: {},
reducers: {
addCourse: (state, action) => {
state[action.payload.id] = {
id: action.payload.id,
name: action.payload.name,
...
...
}
},
updateNotes: (state, action) => {
state[action.payload.id].notes = action.payload.notes
}
...
...
}
})
This is not ideal because I have to redundantly use the course id in every case reducer to update the correct course. I'd like to have a state object in the store for each course and write my case reducers like this:
updateNotes: (state, action) => {
state.notes = action.payload.notes
}
I shouldn't have to worry about which course I'm updating because they all function the same way. Is there a way to dynamically create states for each course that share the same reducer logic?
You need to implement a dynamic version of redux's combineReducers().
There seems to be a package that does this: https://www.npmjs.com/package/redux-injector
Looking at this I think what you concerned about is ending up with a data structure where ID is both a key and a property in your stored data and this breaks the idea that the data store in Redux should be the minimum dataset without any repetition.
The solution to this is to only store the ID as the key and the use a library called Reselect to merge the key back into the data when you take it from the store.
https://github.com/reduxjs/reselect
I have a small problem, typing my Redux Reducer. I know this question, has been generally answered, but I find the solutions lacking, and I would call them more workarounds than anything else.
The Problem:
I have a Redux Reducer. Inside it, 3 functions managing state for Loading, Success and Failure, supplying the necessary data or errors in state along the way. And now TS comes into the game. Basically I get this error:
TS2418: Type of computed property's value is
'(state: ImmutableObjectMixin<IState>, { error }: ActionFailure<Type.LIST_ITEMS_FAILURE, HttpError>) => ImmutableObjectMixin<...>',
which is not assignable to type '(state: ImmutableObjectMixin<IState>, action: ActionWithPayload<Type.LIST_ITEMS_SUCCESS, IItems[]>, { error }: ActionFailure<Type.LIST_ITEMS_FAILURE, HttpError>) => ImmutableObjectMixin<...>'.
Types of parameters '__1' and 'action' are incompatible.
Property 'error' is missing in type 'ActionWithPayload<Type.ITEMS_SUCCESS, IItems[]>' but required in type 'ActionFailure<Type.LIST_ITEMS_FAILURE, HttpError>'.
The error appears in the below block:
const ACTION_HANDLERS: {
[key: string]: (
state: ImmutableObjectMixin<IState>,
action: ActionWithPayload<Type.LIST_ITEMS_SUCCESS, IItems[]>,
{ error }: ActionFailure<Type.LIST_ITEMS_FAILURE, HttpError>
) => ImmutableObjectMixin<IState>;
} = {
[Type.LIST_ITEMS_ATTEMPT]: onListFetching,
[Type.LIST_ITEMS_SUCCESS]: onListFetchingSuccess,
[Type.LIST_ITEMS_FAILURE]: onListFetchingFailure // This line throws the TS Error
};
Now, I read about providing the error, to all possible properties, or making them all optional, in relevant answers, but they are not really solving my problem.
Here are the functions:
const onListFetching = (state: ImmutableObjectMixin<IState>): ImmutableObjectMixin<IState> =>
state.merge({
listFetching: true,
list: [],
errorListing: null
});
const onListFetchingSuccess = (
state: ImmutableObjectMixin<IState>,
action: ActionWithPayload<Type.LIST_ITEMS_SUCCESS, IItems[]>
): ImmutableObjectMixin<IState> => {
const { payload = [] } = action;
return state.merge({
listFetching: false,
list: payload,
errorListing: null
});
};
const onListFetchingFailure = (
state: ImmutableObjectMixin<IState>,
{ error }: ActionFailure<Type.LIST_ITEMS_FAILURE, HttpError>
): ImmutableObjectMixin<IState> =>
state.merge({
listFetching: false,
errorListing: error,
list: []
});
Any help typing this would be appreciated. I think the problem, might like on the key property. Since we are restructuring error but I could be wrong. Thanks you..
Instead of typing it on my own, I used, the Handlers Generic interface, from redux-sauce.
Here is the interface itself:
export interface Handlers<S> {
[type: string]: (state: S, action: Action) => S;
}
It basically does the exact same thing as my typing but it overlaps the TS Error. Not sure why, though. That would be useful to know, if you have the answer.
Here is the new Code:
const ACTION_HANDLERS:Handlers<ImmutableObjectMixin<IState>> = {
[Type.LIST_ITEMS_ATTEMPT]: onListFetching,
[Type.LIST_ITEMS_SUCCESS]: onListFetchingSuccess,
[Type.LIST_ITEMS_FAILURE]: onListFetchingFailure
};
Everything works like a char,. But if it can be improved even more, please let me know..
I'm working through methods of typing Redux actions with Flow. Using the following definitions it is partially working in both the action creator and the reducer. Autocomplete picks up all 4 actions as possible values for action.type in both locations.
// #flow
type ModuleActionTypes1 = 'moduleOne/ACTION_ONE' | 'moduleOne/ACTION_TWO';
type ModuleActionTypes2 = 'moduleTwo/ACTION_ONE' | 'moduleTwo/ACTION_TWO';
type ActionTypes = ModuleActionTypes1 | ModuleActionTypes2;
type Action<T> = {
type: ActionTypes,
payload: T
};
type Payload = {
url: string
};
const actionCreator = (url: string): Action<Payload> => {
return {
type: 'moduleOne/ACTION_ONE',
payload: {
url: url
}
};
};
const reducer = (state: string, action: Action<Payload>): string => {
if(action.type === 'wrong') {
// action.type cannot be this
}
switch (action.type) {
case 'moduleOne/ACTION_ONE':
case 'wrong': // action type cannot be this
const { url } = action.payload;
return url;
default:
return state;
}
};
It almost does what I want. There are two main issues:
The switch cases don't have to be one of action.type, so typos could slip through. This suggests I should stick with string constants.
When I import type { ModuleActionTypes1 } from '../moduleOne'; (instead of defining it in the same file for testing) autocomplete stops working but flow reports no errors on the command line. All the files contain the #flow comment.
Is there a better way to do this?
So, it's a little different from your question, but let me suggest doing this a little differently. Right now, your action type says that any payload can belong to any action. Flow can be a lot more helpful if you use your types to define only allowable states.
I don't know what your actions do, so here is a random example:
type CreatePerson = { type: 'create-person', person: Person };
type DestroyPerson = { type: 'destroy-person', personId: number };
type ChangePersonName = { type: 'change-name', personId: number, name: string }
type PersonActions = CreatePerson | DestroyPerson | ChangePersonName
Setting it up this way helps ensure that each Redux action has the right data in the right types. In your reducer, you can still do a switch/if on action.type to pick which case to use. But now, by picking out the type by string, Flow also knows exactly what type of data exists on the action. This can let you avoid casting or unsafe destructuring that Flow can't type check.