I am attempting to assign FireStore data(), forwarded by props, to a reactive() proxy object, but am receiving the following error:
Object is possibly 'undefined'.
(property) fireStoreData: Record<string, any> | undefined
I wish to use a forEach loop instead of direct assignments i.e. (pageForm.name = props.fireStoreData?.name) to minimize code.
props: {
fireStoreData: Object,
}
...
setup(props){
...
const pageForm: { [key: string]: any } = reactive({
name: '',
address: '',
...
})
onMounted(() => {
Object.keys(pageForm).forEach(key => {
if(key in props.fireStoreData) // <-- error found here
pageForm[key] = props.fireStoreData[key] // <-- and here
})
})
})
...
}
The issie is that the fireStoreData prop is not required yet in your code you assume it is.
try:
props: {
fireStoreData: {
required: true,
type: Object
}
}
If you dont want the prop to be required, its always possible to check for it being defined and add a watcher to look for changes.
Also, dont forget props can change in value.
// on mobile, will format later on pc
Related
Background
I'm not sure how I should approach sanitizing data I get from a Java backend for usage in a React form. And also the other way around: sanitizing data I get from a form when making a backend request. For frontend/backend communication we use OpenApi that generates Typescript interfaces and API for us from DTOs defined in Java.
Scenario
Example of the Schema in Java:
public enum Pet {
CAT,
DOG
}
#Schema(description = "Read, create or update an account")
public class AccountDto {
#NotNull
private Boolean active;
#NotBlank
private String userName;
#NotNull
private Pet preferedPet;
#Nullable
private String bioDescription;
// Constructor and getter/setters skipped
}
Current implementation
Example of the generated Typescript interface:
enum Pet {
CAT,
DOG
}
interface AccountDto {
active: boolean,
userName: string,
preferedPet: Pet,
bioDescription?: string // Translates to: string | undefined
}
Example React.js:
import {getAccount, updateAccount, Pet, AccountDto} from "./api"
export default function UpdateAccount() {
const [formData, setFormData] = useState<AccountDto>({
active: true,
userName: "",
preferedPet: Pet.CAT,
bioDescription: ""
})
useEffect(() => {
async function fetchAccount() {
const response = await getAccount();
// Omitted error handling
setFormData(response.data);
// response.data could look like this:
// {
// active: true,
// userName: "John",
// preferedPet: Pet.DOG,
// bioDescription: null
// }
}
}, [])
async function updateAccountHandler() {
const response = await updateAccount(formData);
// Omitted error handling
// Example formData object:
// {
// active: true,
// userName: "John",
// preferedPet: Pet.CAT,
// bioDescription: ""
// }
}
return (
// All input fields
)
}
Problems
When fetching the account, bioDescription is null. React will throw a warning that a component (bioDescription input) is changing from uncontrolled to controlled.
If by any chance there is a situation where null is set for preferedPet we will get a warning that the select value is not valid.
When updating the account all empty strings should be null. Required for the database and generally cleaner in my opinion.
Questions
1.) I'm wondering how other React users prepare/sanitize their data for usage and requests. Is there a go to or good practice I'm not aware of?
2.) Currently I'm using the following function to sanitize my data. It seems to work and Typescript does not notify me about any type mismatches but I think it should since bioDescription can only be string | undefined and not null.
function sanitizeData<T>(data: T, type: "use" | "request"): T {
const sanitizedData = Object.create({});
for (const [key, value] of Object.entries(data)) {
if (!value && type === "use") {
sanitizedData[key] = "";
} else if (!value && type === "request") {
sanitizedData[key] = null;
} else {
sanitizedData[key] = value;
}
}
return sanitizedData;
}
I have a situation where I'm trying to manually change a prop without using the React setState.
formData.description = null;
At this point Typescript is telling me that null is not possible. That's how I detected that my sanitizer function might not be correct.
Demo
Sandbox - https://codesandbox.io/s/async-cdn-7nd2m?file=/src/App.tsx
I'm trying to understand how Typescript infers the return values from a function call. Say I have a function called sendRequest that can return two specifically formatted objects. Why isn't Typescript able to know that if loading is true, that the only possible value for data is null, and that if loading is false, the only possible value for data is a Data type? It is still saying that data could be null even though that's impossible based on the possible return types listed.
type Data = { firstName: string; lastName: string };
type ReturnType =
| { loading: true; data: null }
| { loading: false; data: Data };
const sendRequest = (fetch: boolean): ReturnType => {
if (fetch) {
return { loading: true, data: null };
}
return { loading: false, data: { firstName: 'Bart', lastName: 'Simpson' } };
};
const { loading, data } = sendRequest(true);
if (loading) {
data; // <-- Typescript says that `data` can be either `Data` or `null`, while based on the return type, and since `loading` if `true`, `data` can't be `null`.
}
This works as a discriminated union until you destructure the variables. TypeScript can understand that the data type of the union is conditional on the value of loading, but it doesn't keep this information once you break it apart into two variables.
const ret = sendRequest(true);
if (!ret.loading) {
ret.data; // <-- This is now of type Data, not null.
}
Playground Link
As jcalz points out in the comments, the behavior you're requesting is tracked as microsoft/TypeScript#30581. (Thanks jcalz!)
The first thing worth noting is that you don't want to use ReturnType as a custom type name since that actually has a reserved purpose as of TypeScript 2.8.
But to answer your question: The reason that TypeScript complains is because that's how you've specified the return type. When loading is true, data is null:
type Response =
| { loading: true; data: null }
| { loading: false; data: Data };
In fact, I'm surprised that TypeScript even types it as Data | null rather than null alone. That is indeed the case in TS Playground:
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 !
I'm looking to create an object from a typescript interface that will store initial empty values:
// Interface I'm looking to mock
interface IMyInterface {
name: string;
posts: string[];
}
From this interface, I would need to initialize an object with initial values based on the interface keys and type, something like:
const initObjFromInterface = (interface: any) => {
// do something
return initObject;
}
const initializedObj = initObjFromInterface(IMyInterface);
Note that I don't know the keys of the interface passed as an argument the initObjFromInterface function!
The returned value of the initObjFromInterface function should be:
console.log(initializedObj);
/* should output:
{
name: "", // empty string
posts: [] // empty array
}
*/
You can't do that as stated in comments.
You have to store somewhere your instance of empty object.
const emptyObject: IMyInterface = {
name: '',
posts: []
}
And pass some unique value to function so it can determine which empty object of interface to get.
const initObjFromInterface = <T>(interfaceName: string) => {
if(interfaceName === "IMyInterface") {
return emptyObject as any as T;
}
return {} as T;
}
I sure the code above can be designed better ( perhaps type guards and type inferring )