Flow doesn't complain with incompatible ReactClass usage and JSX - flowtype

I believe the following should be caught by flow:
type MyProps = {
foo: boolean,
};
const makeComponent = (C: ReactClass<MyProps>) => <C />;
From reading the source, I believe I am understanding ReactClass correctly.
What gives? This seems to also work with React.createElement(C, {})
On the other hand, the following breaks:
import MyComponent from '...'; // this component has props MyProps
const makeComponent = () => <MyComponent />;
// and likewise with React.createElement

According to this comment, ReactClass is buggy at best and should not be used. You can use the following instead:
type MyProps = {
foo: boolean,
};
const makeComponent = (C: Class<React.Component<void, MyProps, void>>) => <C />;
Note that the parameters for React.Component are defaultProps, Props, and State. In the above example it is assumed that the component does not have either defaultProps or State defined, hence the void values.

Related

An array of valid CSS property names in TypeScript, but hyphened

I have a property in my component that is supposed to get an array of valid CSS properties:
interface MyComponentProps {
cssProperties: (keyof CSSStyleDeclaration)[];
}
const MyComponent = ({ cssProperties }: MyComponentProps) => {
//
}
The problem is that CSSStyleDeclaration stores styles as an object with property names in camel case. I need real CSS property values, hyphened. So background-color instead of backgroundColor. I know there's also React.CSSProperties type, but it uses camel-cased properties too, while allowing for unitless numeric values.
Is there a TypeScript type to use original, hyphened CSS property names?
There is this library csstype which is used by MUI, emotion and some other popular component libraries.
Which allows you to use the hyphen props
import CSS from "csstype";
interface MyComponentProps {
cssProperties: (keyof CSS.PropertiesHyphen)[];
}
const MyComponent = ({ cssProperties }: MyComponentProps) => {
//
}
<MyComponent cssProperties={["color", "background-color"]} />;
Use Kebab type available here. Camelcased CSS properties will be transformed into kebabcase.
type Kebab<T extends string, A extends string = ""> =
T extends `${infer F}${infer R}` ?
Kebab<R, `${A}${F extends Lowercase<F> ? "" : "-"}${Lowercase<F>}`> :
A
type Props = {
cssProps: Kebab<keyof React.CSSProperties>[];
}
const Component = (props: Props) => null;
const App = () => (
<Component cssProps={['min-width']} />
);
Typescript Playground

How to reuse composable props in vue 3

Essentially, i want to build input fields components (textfeids, textarea, checkbox, ...).
Since they share some attributes ie (id, name,...), my approach was to create a composable and defined the common prop definition and use them in the individual components. However, it seem it does work. Is there a way I can achieve this in vue 3 composable approach. Below is the composable.
// prop definition
import { defineProps } from "vue";
export const useCoreFieldProps = () => {
const props = defineProps<{
id?: string;
name?: string;
disabled?: boolean;
required?: boolean;
}>();
return {
props,
};
};
// composable usage
<script setup lang="ts">
import { useCoreFieldProps } from "#/composables/useFields";
const { props: coreProps } = useCoreFieldProps();
</script>
Rather than calling defineProps in the composable, you can return just the object and you can call defineProps on it in your component:
// composable definition
export const useCoreFieldProps = () => {
const fieldProps = {
id?: string;
name?: string;
disabled?: boolean;
required?: boolean;
};
return {
fieldProps,
};
};
// composable usage
<script setup lang="ts">
import { useCoreFieldProps } from "#/composables/useFields";
const { fieldProps } = useCoreFieldProps();
defineProps(fieldProps);
// or add your own props or override:
defineProps({
...fieldProps,
maxLength?: string,
pattern?: string
});
</script>
You will unfortunately lose automated documentation of props if you’re using storybook or similar component library browsing tools, but hopefully they'll add support for that soon.
The other option is extends, but the docs don’t really explain how that works in Vue 3 with composition.

Dynamic Importing of an unknown component - NextJs

I want to load a component dynamically based on the route. I'm trying to make a single page which can load any individual component for testing purposes.
However whenever I try to do import(path) it shows the loader but never actually loads. If I hard code the exact same string that path contains then it works fine. What gives? How can I get nextjs to actually dynamically import the dynamic import?
// pages/test/[...component].js
const Test = () => {
const router = useRouter();
const { component } = router.query;
const path = `../../components/${component.join('/')}`;
console.log(path === '../../components/common/CircularLoader'); // prints true
// This fails to load, despite path being identical to hard coded import
const DynamicComponent = dynamic(() => import(path), {
ssr: false,
loading: () => <p>Loading...</p>,
});
// This seems to work
const DynamicExample = dynamic(() => import('../../components/Example'), {
ssr: false,
loading: () => <p>Loading...</p>,
});
return (
<Fragment>
<h1>Testing {path}</h1>
<div id="dynamic-component">
<DynamicComponent /> <!-- this always shows "Loading..." -->
<DynamicExample /> <!-- this loads fine. -->
</div>
</Fragment>
);
};
export default Test;
I put dynamic outside of the component, and it work fine.
const getDynamicComponent = (c) => dynamic(() => import(`../components/${c}`), {
ssr: false,
loading: () => <p>Loading...</p>,
});
const Test = () => {
const router = useRouter();
const { component } = router.query;
const DynamicComponent = getDynamicComponent(component);
return <DynamicComponent />
}
I had the same issue like the thread opener.
The Documentation describe, that it's not possible to use template strings in the import() inside dynamic:
In my case it was also impossible to add an general variable with the path there...
Solution
I've found an easy trick to solve this issue:
// getComponentPath is a method which resolve the path of the given Module-Name
const newPath = `./${getComponentPath(subComponent)}`;
const SubComponent = dynamic(() => import(''+newPath));
All the MAGIC seems to be the concatenation of an empty String with my generated Variable newPath: ''+newPath
Another Solution:
Another Solution (posted by bjn from the nextjs-Discord-Channel):
const dynamicComponents = {
About: dynamic(() => import("./path/to/about")),
Other: dynamic(() => import("./path/to/other")),
...
};
// ... in your page or whatever
const Component = dynamicComponents[subComponent];
return <Component />
This example might be useful, if you know all dynamically injectable Components.
So you can list them all and use it later on in your code only if needed)
The below code worked for me with dynamic inside the component function.
import dynamic from "next/dynamic";
export default function componentFinder(componentName, componentPath) {
const path = componentPath; // example : "news/lists"
const DynamicComponent = dynamic(() => import(`../components/${path}`),
{
ssr: false,
loading: () => <p>Loading Content...</p>,
});
return <DynamicComponent />;
}
It happens because router.query is not ready and router.query.component is undefined at the very first render of dynamic page.
This would print false at first render and true at the following one.
console.log(path === '../../components/common/CircularLoader');
You can wrap it with useEffect to make sure query is loaded.
const router = useRouter();
useEffect(() => {
if (router.asPath !== router.route) {
// router.query.component is defined
}
}, [router])
SO: useRouter receive undefined on query in first render
Github Issue: Add a ready: boolean to Router returned by useRouter
As it was said here before the dynamic imports need to be specifically written without template strings. So, if you know all the components you need beforehand you can dynamically import them all and use conditionals to render only those you want.
import React from 'react';
import dynamic from 'next/dynamic';
const Component1 = dynamic(() => import('./Component1').then((result) => result.default));
const Component2 = dynamic(() => import('./Component2').then((result) => result.default));
interface Props {
slug: string;
[prop: string]: unknown;
}
export default function DynamicComponent({ slug, ...rest }: Props) {
switch (slug) {
case 'component-1':
return <Component1 {...rest} />;
case 'component-2':
return <Component2 {...rest} />;
default:
return null;
}
}

bindActionCreators and mapDispatchToProps - Do I need them?

I'm looking at a React-Redux app and try to understand how everything is working.
Inside one of the components, I saw these lines of code:
import { bindActionCreators } from "redux";
...
function mapDispatchToProps(dispatch) {
return bindActionCreators({ fetchPhotos }, dispatch);
}
export default connect(
null,
mapDispatchToProps
)(SearchBar);
If I change the above code to the following, everything still works, without any errors:
function mapStateToProps(photos) {
return { photos };
}
export default connect(
mapStateToProps,
{ fetchPhotos }
)(SearchBar);
To me, it seems that my way of using connect is easier to understand and it also doesn't need to import an extra library.
Is there any reasons, to import bindActionCreators and use mapDispatchToProps?
I'm a Redux maintainer.
Yes, the second example you showed uses the "object shorthand" form of mapDispatch.
We recommend always using the “object shorthand” form of mapDispatch, unless you have a specific reason to customize the dispatching behavior.
I personally avoid using bindActionCreators explicitly. I prefer to directly dispatch the functions with mapDispatchToProps which internally uses bindActionCreators.
const mapStateToProps = state => ({
photos: state.photos.photos
});
const mapDispatchToProps = dispatch => ({
fetchPhotos: () => dispatch(fetchPhotos())
// ...Other actions from other files
});
export default connect(mapStateToProps, mapDispatchToProps)(SearchBar);
There are two cases in which you'll use bindActionCreators explicitly, both are not best practices:
If you have a child component to SearchBar that does not connect to redux, but you want to pass down action dispatches as props to it, you can use bindActionCreators.
Best practice would be doing same with example I. You can just pass this.props.fetchPhotos to childcomponent directly without using bindActionCreators.
class SearchBar extends React.Component {
render() {
return (
<React.Fragment>
<ChildComponentOfSearchBar fetchPhotos={this.props.fetchPhotos} />
</React.Fragment>
)
}
}
const mapStateToProps = state => ({
photos: state.photos.photos
});
const mapDispatchToProps = () => bindActionCreators({ fetchPhotos }, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(SearchBar);
There is another unlikely scenario where you can use bindActionCreators, defining actionCreator inside the component. This isn't maintainable & is not a good solution since action types are hard coded and not reusable.
class SearchBar extends React.Component {
constructor(props) {
super(props);
this.fetchPhotosAction = bindActionCreators({ fetchPhotos: this.searchFunction }, dispatch);
}
searchFunction = (text) => {
return {
type: ‘SEARCH_ACTION’,
text
}
}
render() {
return (
<React.Fragment>
// Importing selectively
<ChildComponentOfSearchBar fetchPhotos={this.fetchPhotosAction} />
</React.Fragment>
)
}
}
const mapStateToProps = state => ({
photos: state.photos.photos
});
export default connect(mapStateToProps, null)(SearchBar)

How to get some value from Reducer Ngrx

I have the following reducer
import { EntityState, createEntityAdapter } from '#ngrx/entity';
import { createFeatureSelector } from '#ngrx/store';
export const pageAdapter = createEntityAdapter<Page>({
//selectId: (collection: Collection) => collection.id,
});
export interface State extends EntityState<Page> {
}
const defaultPage = {
ids: ['kHnryus'],
entities: {
'kHnryus': {
id: '83nryus',
title: 'How to create a blog with Angular4 Ngrx',
description: 'How to create a blog with Angular4 Ngrx',
}
},
success_create: false
}
export const initialState: State = pageAdapter.getInitialState();
// Reducer
export function pageReducer(
state: State = initialState,
action: actions.PageActions) {
switch (action.type) {
case actions.ADD_ALL: {
return pageAdapter.addAll(action.pages, state);
};
case actions.SUCCESS: {
return {success_create: true}
};
default:
return state;
}
}
// Create the default selectors
export const getPageState = createFeatureSelector<State>('page');
export const {
selectIds,
selectEntities,
selectAll,
selectTotal,
} = pageAdapter.getSelectors(getPageState);
I want to get the boolean variable success_create of the state in my component.
Basically , I want that if there is SUCCESS, I should be able to get a success_create true in the component class. I have no idea as to how to do this and even if it's possible.
If it is, please how can I achieve this?
First make sure to remove defaultPage constant as you are not maintaining it within your reducers and you are already using #ngrx/entity for that. The success_create should be defined as follow:
export interface State extends EntityState<Page> {
success_create: boolean;
}
export const adapter: EntityAdapter<Item> = createEntityAdapter<Page>({
selectId: (page: Page) => page.id,
sortComparer: false
});
export const initialState: State = adapter.getInitialState({
success_create: false,
});
Then, after your default selectors add a third one that uses getPageState selector and gets one step deeper into your state:
export const getSuccessCreate = createSelector(
getPageState,
(state: State) => state.success_create
);
Then make your component/service listening to it directly like:
this.created$ = this.store.pipe(select(fromPages.getSuccessCreate));
note: the pipe is optional here and if used then select should be imported from #ngrx/store as it is done in the official ngrx demo app. pipe allow you to work with lettable rxjs operators. you can read more about it here.

Resources