How to allow specific values for an icon dynamic import? - javascript

I have created a dynamic import to use icons from our library. It looks something like this:
// icons file
export { ArrowLeft } from './ArrowLeft';
export { ArrowRight } from './ArrowRight';
export { Checked } from './Checked';
export { CircleCar } from './CircleCar';
And this is the component:
import * as Icons from '../../components/Icons';
import * as z from 'zod';
const Schema = z.object({
content: z.object({
iconName: z.string()
})
})
type JustAReactComponentProps = z.infer<typeof Schema>
const JustAReactComponent = ({content}: JustAReactComponentProps) => {
const SecondaryIcon = Icons[content.iconName] as React.JSXElementConstructor<any>;//is this the correct type btw?
return <SecondaryIcon />
}
And lets say that we have 30 icons and someone tries to set iconName as an icon that is not in the list, how can I enforce with Typescript that only the ones in the icons file are enabled to be used?
This question below seems to have the same problem I do but the solutions do not work for me(And I'm not looking for an external package).
How to find all imported (in code) dependencies within a TypeScript project?

Related

Use Typescript declaration types inline

After installing types for a library such as "#types/openlayers" how can I then use these for inline type definitions for things like React props:
import Map from "ol/Map";
import { Map as MapTypes } from "#types/openlayers"; // Error: Cannot import type declaration files.
export type Props = {
mapInstance: MapTypes;
}
export const OlMap: FunctionComponent<Props> = (props): JSX.Element => {
const currentMap = props.mapInstance;
...
}
Use it from the actual package that is being typed, not the #types/* module. For example, with react-router, you add #types/react-router but to pull out the RouteProps interface, you use import {RouteProps} from "react-router";.
Additionally, from your edits, it seems that you may be using the wrong #types/* package. You mention using #types/openlayers but then mention that you are using the package ol, which should probably use the #types/ol package for types.
Note declaration file consumption:
For the most part, type declaration packages should always have the
same name as the package name on npm, but prefixed with #types/
After running yarn add ol #types/ol (or npm i ol #types/ol)
This seems to be using the right types:
import MapTypes from "ol/Map"; // Note that I don't need "{}" or "as" here
// import {Map as MapTypes} from "ol"; or pull it out of the root export
export type Props = {
mapInstance: MapTypes;
}
export const OlMap: FunctionComponent<Props> = (props): JSX.Element => {
const currentMap = props.mapInstance;
...
}

styled-system props typing with TypeScript

I'm using styled-system and one key of the library is to use the shorthand props to allow easy and fast theming.
I've simplified my component but here is the interesting part:
import React from 'react'
import styled from 'styled-components'
import { color, ColorProps } from 'styled-system'
const StyledDiv = styled('div')<ColorProps>`
${color}
`
const Text = ({ color }: ColorProps) => {
return <StyledDiv color={color} />
}
I have an error on the color prop which says:
Type 'string | (string | null)[] | undefined' is not assignable to
type 'string | (string & (string | null)[]) | undefined'.
I think that's because styled-system use the same naming as the native HTML attribute color and it conflicts.
How do I solve this?
color seems to be declared in react's declaration file under HTMLAttributes - it's not exported.
I had to work around this by creating a custom prop
Example is using #emotion/styled but also works with styled-components
// component.js
import styled from '#emotion/styled';
import { style, ResponsiveValue } from 'styled-system';
import CSS from 'csstype';
const textColor = style({
prop: 'textColor',
cssProperty: 'color',
key: 'colors'
});
type Props = {
textColor?: ResponsiveValue<CSS.ColorProperty>
}
const Box = styled.div<Props>`
${textColor};
`
export default Box;
// some-implementation.js
import Box from '.';
const Page = () => (
<Box textColor={['red', 'green']}>Content in a box</Box>
);
This seems to only happen when you pass the prop down from an ancestor/parent component to a custom component rather than directly to the "styled" component. I found a discussion about it in the styled-components GitHub issues. Following the thread from there there is discussion of utilising transient props and their ultimate inclusion in styled-components v5.1.
This however didn't seem to solve the problem completely in my case.
The problem appears to be due to the component in question returning an HTML div element and so it is extended correctly (by React.HTMLAttributes) to include color: string | undefined as a DOM attribute for that element. This is of course not compatible with ColorProps hence the error. Styled-components filters out a whitelist that includes color however this won't happen in your custom or HOC.
This can be resolved in a number of ways, but the cleanest seems to be adding as?: React.ElementType to your type definition.
In this case:
import React from 'react'
import styled from 'styled-components'
import { color, ColorProps } from 'styled-system'
interface Props extends ColorProps { as?: React.ElementType }
const StyledDiv = styled('div')<Props>`
${color}
`
const Text = ({ color }: Props) => {
return <StyledDiv color={color} />
}
This way the extension by React.HTMLAttributes is replaced by React.ElementType and so there is no longer a conflict with the color DOM attribute.
This also solves problems with passing SpaceProps.
NOTE:
It appears styled-system has been unceremoniously abandoned. There are a few open issues about what is being used to replace it. My recommendation after a little deliberation is system-ui/theme-ui. It seems to be the closest direct replacement and has a few contributors in common with styled-system.
Instead of using ColorProps, try using color: CSS.ColorProperty (`import * as CSS from 'csstype'); Here is a gist showing how I'm creating some a typed "Box" primitive with typescript/styled-system: https://gist.github.com/chiplay/d10435c0962ec62906319e12790104d1
Good luck!
What I did was to use Typescript cast capabilities and keep styled-system logic intact. e.g.:
const Heading: React.FC<ColorProps> = ({ color, children }) => {
return <HeadingContainer color={(color as any)} {...props}>{children}</HeadingContainer>;
};
Just to add to xuanlopez' answer - not sure what issue the 5.0.0 release specifically resolves - but using $color as the renamed prop rather than textColor designates it as a transient prop in styled components so as a prop it won't appear in the rendered DOM.
Building on Chris' answer, and using the latest docs on on custom props.
// core/constants/theme.ts
// Your globally configured theme file
export const theme = { colors: { primary: ['#0A43D2', '#04122B'] } }
// core/constants/styledSystem.ts
import {
color as ssColor,
ColorProps as SSColorProps,
TextColorProps,
compose,
system,
} from 'styled-system'
// Styled-system patch for the color prop fixing "Types of property 'color' are incompatible"
// when appling props to component that extend ColorProps.
export interface ColorProps extends Omit<SSColorProps, 'color'> {
textColor?: TextColorProps['color']
}
export const color = compose(
ssColor,
system({
// Alias color as textColor
textColor: {
property: 'color',
// This connects the property to your theme, so you can use the syntax shown below E.g "primary.0".
scale: 'colors'
}
})
)
// components/MyStyledComponent.ts
import { color, ColorProps } from 'core/constants/styledSystem.ts'
interface MyStyledComponentProps extends ColorProps {}
export const MyStyledComponent = styled.div<MyStyledComponentProps>`
${color}
`
// components/MyComponent.ts
export const MyComponent = () => <MyStyledComponent textColor="primary.0">...
EDIT: updating to styled-components ^5.0.0 fixes this
https://github.com/styled-components/styled-components/blob/master/CHANGELOG.md#v500---2020-01-13

react-native - *.js importing *.ts file

How to allow *.js files to import *.ts files in react-native but without rename of any of two files?
we want to import below src/lib/setGlobalStyle.ts file from the src/App.js
//MIT LICENSE from: https://github.com/Ajackster/react-native-global-props
import React from 'react'
import { StyleProp, ViewStyle } from 'react-native'
export function setGlobalStyle(obj, customProps) {
const oldRender = obj.prototype.render;
const initialDefaultProps = obj.prototype.constructor.defaultProps;
obj.prototype.constructor.defaultProps = {
...initialDefaultProps,
...customProps,
}
obj.prototype.render = function render() {
let oldProps = this.props;
this.props = { ...this.props, style: [customProps.style, this.props.style] };
try {
return oldRender.apply(this, arguments);
} finally {
this.props = oldProps;
}
};
}
but below import which is inside App.js only works when we rename the setGlobalStyle.ts file to setGlobalStyle.js:
import * as Utils from './lib/setGlobalStyle'
and of course the setGlobalStyle.ts currently does not contain any TypeScript types, that is because we had to remove all and rename it to .js so we can continue on the project until this gets an answer.
note: the reason why we need TypeScript is to allow IDE autocomplete of the parameters (i.e. the customProps argument).
JSDoc-support in Javascript allows you to import type definitions directly. I know that's not the same as importing the actual module (but I think that is madness if the type definitions exist):
/**
* #param {import("./mymodule").customProps } customProps
*/
export function setGlobalStyle(obj, customProps) {
customProps. // has full auto-completion

Multiple imported and registered components in vue.js

I am refactoring some code in my app and turns out,the below logic it is repeated in many many components.
import component1 from '...'
import component2 from '...'
import component3 from '...'
//...many others
export default {
//other data
components: {
component1,
component2,
component3
//...
}
}
Does exists a shorter approach in order to clean my code?
Thanks for your time
Below are 3 ways.I prefer method 3 by the way.
Method 1
Create a js file in my case dynamic_imports.js:
export default function (config) {
let registered_components = {}
for (let component of config.components) {
registered_components[component.name] = () => System.import(`../${config.path}/${component.file_name}.vue`)
}
return registered_components
}
In the component in which you have many component imports and registrations
import dynamic_import from '#/services/dynamic_imports' //importing the above file
let components = dynamic_import({
path: 'components/servers',
components: [
{ name: 'server-one', file_name: 'serverOne' },
{ name: 'server-two', file_name: 'serverTwo' },
]
})
export default {
//...other code
components: components
}
As a result you will import and register your components with "clean code".
But note that this worked for me,maybe it has to modified a lit bit to fit your needs,to understand:
The property path means that will look at this path for the names specified in file_name.The name property is the name you register the component
Method 2
If you don't like the above look below to another way:
function import_component(cmp_name){
return System.import(`#/components/${cmp_name}.vue`);
}
export default{
components: {
'component1': () => import_component('componentOne'),
'component2': () => import_component('componentTwo'),
'component3': () => import_component('componentThree')
}
}
Method 3
If again you are saying: This is not a cleaner way,take a look below but keep in mind that if you are working in team and skills differ,then some programmers will be a little bit confused.
dynamic_imports.js
export default function ({path, file_names, component_names}) {
let registered_components = {}
for (let [index, file_name] of file_names.entries()) {
registered_components[component_names[index]] = () => System.import(`../${path}/${file_name}.vue`)
}
return registered_components
}
In your component
import dynamic_import from '#/services/dynamic_imports'
let components = dynamic_import({
path: 'components/servers',
file_names: ['serverOne', 'serverTwo'],
component_names: ['server-one', 'server-two']
})
export default {
components: components
}
You can automatically register such repeated base components globally using the pattern described in the official docs
https://v2.vuejs.org/v2/guide/components-registration.html#Automatic-Global-Registration-of-Base-Components
Chris Fritz also talks about this pattern in his awesome video where he mentions 7 secret patterns for cleaner code and productivity boost while working with Vue.js
The disadvantage of this approach, however, is that the components that you autoregister this way always end up in the main bundle and therefore cannot be lazy loaded/code-splitted. So make sure you do this only for the base components that are very generic.

TypeScript sub modules?

Currently I do this:
import {validators} from 'marker';
validators.isEmail('foo#bar.com');
Instead, I would like:
isEmail('foo#bar.com');
// Without cheating by just: `const isEmail = validators.isEmail`
How do I import just the isEmail symbol?
Here's a little test-case, note that my actual module has many validators and other submodules (not just validators)
marker.d.ts
declare var marker: marker.marker;
declare module marker {
export interface marker {
validators: IValidators;
}
export interface IValidators {
isEmail(input: string): boolean;
}
}
export = marker;
index.ts
import * as vs from './validators'
export const validators = vs;
validators.ts
export function isEmail(input: string): boolean { return true }
You should cheat. But you can make that cheating prettier using further destructuring (more https://basarat.gitbooks.io/typescript/content/docs/destructuring.html). Instead of
const isEmail = validators.isEmail;
You can
const {isEmail} = validators;
You could try
const { validators: { isEmail } } = require('marker');
isEmail('aaa#bbb');
Note sure where exactly you don't want to include
// Without cheating by just: `const isEmail = validators.isEmail`
Below solution is based upon assumption that you do not want isEmail to be defined in the ts file where you are using validators.isEmail.
Assuming index.ts your main file for marker package, you may expose all validator methods directly.
index.ts
import * as vs from './validators'
export const isEmail = vs.isEmail;
and use it like below,
import { isEmail } from 'marker';
isEmail('foo#bar.com');
you also have to make necessary changes in definition files.

Categories