Typescript with custom react hook - javascript

Trying to write a custom react hook in TypeScript that accepts an object with all optional React.CSSProperties as keys like so...
const something = useSomthing({
color: {
initial: 'red',
new: 'blue'
}
})
Can I write it in a way that i'll get all the css properties in the IDE autocomplete?

You can use keyof and read more here
type Config = {
[key in keyof React.CSSProperties]?: {
initial: React.CSSProperties[key];
new: React.CSSProperties[key];
}
};
const useSomething = (config:Config)=>{
...
}

Related

Map like object in Vue and typesafety

I try to create dynamic object with properties in Vue component with keys and values, but the the only way I managed to do that is to create const userProperties: any = {} then return it in data(). The problem appears when I try to build in two places I had to add //#ts-ignore because compiler complains about:
Type '{}' is missing the following properties from type 'Function': apply, call, bind, prototype, and 5 more.
Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'Function'.
Full code I have so far for component:
<script lang="ts">
import {usePropertiesDefinitionsStore} from "#/stores/properties-definitions";
import {storeToRefs} from "pinia";
import ax from "#/axios/axios"
import { toRaw } from 'vue';
export default {
props: ['userId'],
data() {
const userProperties: any = {}
const propertiesDefinitionsStore = usePropertiesDefinitionsStore()
propertiesDefinitionsStore.fetchPropertiesDefinitionsList('User')
const { propertiesDefinitionsList } = storeToRefs(propertiesDefinitionsStore)
ax.get(`/api/properties?id=` + this.userId).then(json => {
//#ts-ignore
this.userProperties = {}
if (json.data.results) {
json.data.results.forEach((result: any) => {
//#ts-ignore
this.userProperties[result.key] = result.value;
})
}
})
return { propertiesDefinitionsList, userProperties }
},
methods: {
saveProperties() {
const dataRaw = toRaw(this.userProperties)
const keys = Object.getOwnPropertyNames(dataRaw)
const data: any = []
keys.forEach(key => {
data.push({
'type': 'User',
'key': key,
'value': dataRaw[key]
})
})
ax.post(`/api/properties?id=` + this.userId, data).then(json => {
console.log(json)
})
},
onlyActive(propertiesDefinitionsList: any) {
return propertiesDefinitionsList.filter((i: any) => i.active)
}
}
}
</script>
Is there a better way to do that?
I tried to set dynamic structure on object without actually creating type, so my object is similar to Java's Map but created as Javascript object (keys and values).
Why not use Map? and since you're using typescript (and typing is the point), why not create a class or interface for your user properties? if you do use a class, don't forget to call the constructor (or just use an interface)

Creating string literal union from a property in an array of objects

Is it possible to get a literal string union type of a static array of object's specific property?
For example, I want to expose IconName as a type of literal strings from which is inferred from an array of objects.
I've seen it has been done to convert a read-only array to a string literal union, but not through an array of objects.
I don't think it's possible since we require some runtime interpretative functionality to access the array's values. But here's the example I'm trying to work through this svelte example https://codesandbox.io/s/svelte-typescript-forked-ef75y:
<script lang="ts">
interface Icon {
name: string
color: string
}
const icons: readonly Icon[] = <const>[
{
name: "close",
color: "red"
},
{
name: "open",
color: "blue"
}
];
const tuple = <K extends string[]>(...arr: K) => arr;
const iconNames = tuple(...icons.map(i => i.name));
type IconName = typeof iconNames[number];
export let name: IconName;
let displayIcon = icons.find((e) => e.name === name);
</script>
<h1>{displayIcon.color}</h1>
I guess the code below will do what you try to do. However, I suggest writing your type definitions in a separate file with .ts extension.
<script lang="ts">
interface Icon {
name: string;
color: string;
}
const icons = {
close: {
name: "close",
color: "red",
},
open: {
name: "open",
color: "blue",
},
} as const;
type IconName = keyof typeof icons;
export let name: IconName;
let displayIcon = icons[name];
</script>
<h1>{displayIcon.color}</h1>

How to have generic typescript props in react? Typescript generic props don't work

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[]>;
}>

Typescript reducer initial state and option argument for react component

I have a type for my reducer such as:
export type Style = {
color: string;
(rest...)
}
export const initialState: Style = {
color: 'blue';
(rest...)
}
I have a component that takes in a style object and the properties are optional and simply overwrites the current state of the style. The type looks like this:
export type InputStyle = {
color?: string;
(rest?...)
}
So I basically have to create two duplicate types, except one has all optional properties, and one doesn't. Is there a better pattern for this? My intuition says this is not the right way.
Try using the Partial utility type - might be exactly what you're looking for:
export type Style = {
color: string;
(rest...)
}
export type InputStyle = Partial<Style>;

how to access "this" in props validator

I'm working on a project using nuxt.js, I'm injecting a function in the context of the application as recommended in the official documentation
https://nuxtjs.org/guide/plugins/#inject-in-root-amp-context
but when I try to call the function inside a props validation I get an error
/plugins/check-props.js
import Vue from 'vue'
Vue.prototype.$checkProps = function(value, arr) {
return arr.indexOf(value) !== -1
}
in a component vue
export default {
props: {
color: {
type: String,
validator: function (value, context) {
this.$checkProps(value, ['success', 'danger'])
}
}
}
ERROR: Cannot read property '$checkProps' of undefined
Does anyone know how I can access "this" within validation?
thanks in advance!
Props validation is done before the component is initialized, so you won't have access to this as you are extending Vue.prototype.
Form their documentation:
Note that props are validated before a component instance is created, so instance properties (e.g. data, computed, etc) will not be available inside default or validator functions.
In general, if $checkProps is only used for checking the value of these props, I would just use a helper function.
// array.helpers.js
export function containsValue(arr, val) {
return arr.indexOf(value) !== -1
}
// component
import { containsValue } from 'path/to/helpers/array.helpers';
props: {
foo: {
//
validator(value) {
return containsValue(['foo', 'bar'], value);
}
}
}
Update
Based on your comments, if you don't want to import this specific function over and over again, you can just Array.prototype.includes see docs
// component
props: {
color: {
//
validator(value) {
return ['success', 'danger'].includes(value);
}
}
}
From the doc:
props are validated before a component instance is created, so
instance properties (e.g. data, computed, etc) will not be available
inside default or validator functions
If you want to access the nuxt plugins you can always use the window object.This is how I do it to access the i18n library
{
validator: (value: any): boolean => {
return window.$nuxt.$te(value);
}
}
In your case:
{
validator: (value: any): boolean => {
return window.$nuxt.$checkProps(value, ['success', 'danger']);
}
}
In any case you should never prototype in the window object. Let nuxt handle it with a plugin:
path: plugins/check-props.js or .ts
function checkProps(value: any, arr: string[]): boolean {
return arr.indexOf(value) !== -1
}
const $checkProps: Plugin = (_context: Context, inject: Inject) => {
inject('checkProps', checkProps);
};
export default $checkProps;
And then in nuxt config
{
plugins: [{ src: 'plugins/check-props.js', ssr: true }]
}

Categories