How to load component dynamically using component's name in angular15? - angular13

How can i load dynamically a component using a string of his name or his selector
i tried this:
but not working with angular 15
import { Type } from '#angular/core';
#Input() comp: string;
...
const factories = Array.from(this.resolver['_factories'].keys());
const factoryClass = <Type<any>>factories.find((x: any) => x.name === this.comp);
const factory = this.resolver.resolveComponentFactory(factoryClass);
const compRef = this.vcRef.createComponent(factory);

Related

Can't get Reflect.metadata to work in React

I have this Typescript code:
import 'reflect-metadata';
export const formatMetadataKey = Symbol("format");
export function format(formatString: string)
{
return Reflect.metadata(formatMetadataKey, formatString);
}
export function getFormat(target: any, propertyKey: string)
{
return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}
class MyData
{
#format("dd.MM.yyyy")
dateString: string = "";
}
Later in the code, in another file, I have a ReactJs component that takes an instance of MyData
const MyComponent = (props) =>
{
const { data } = props;
// trying to see if a property has a certain decorator here
const meta = getFormat(data, "dateString");
...
}
Tried a gazillion different things, but getFormat always outputs undefined instead of the metadata.

function imported from react context does not exist on type

I'm currently attempting to do a fairly low-level Typescript migration for my React app. The app itself uses context, in which it defines various functions and exports them to be used elsewhere when needed.
Here's my 'DataContext' file:
export const DataContext = React.createContext({})
export const DataProvider = ({ children }: PropsWithChildren) => {
const fetchAll = async (userId: string) => {
// Function here
}
return (
<DataContext.Provider value={{ fetchAll }}>
{ children }
</DataContext.Provider>
)
}
However, when I try to import this file into another Typescript file, I get the following error:
Property 'fetchAll' does not exist on type '{}'
I'm assuming this is because on my 'createContext' I'm using an empty object. But is there another way for me to do it, and is this creating my error?
Here's my file importing the 'fetchAll' function in this case:
import React, { useEffect, useContext } from 'react'
import { DataContext } from '../../context/DataContext'
type Props = {
text: string,
subcontent: string,
id: string
}
export const Pending = ({ text, subcontent, id }: Props) => {
const { fetchAll } = useContext(DataContext)
// I use fetchAll in a useEffect, but removed it for simplicity
return (
// Component here...
)
}
Any advice / pointers would be appreciated!
You will have to set the type of your context in order to be able to retrieve fetchAll
export const DataContext = React.createContext<{
fetchAll: (userId: string) => Promise<WhateverIsTheReturnType>
}>(null)
Then I would set up an hook to retrieve the context value like this
const useDataContext = () => {
const value = useContext(DataContext)
if (value === null) {
throw new Error(
'`DataContext` should always have `DataProvider` as a parent component',
)
}
return value
}
So then you can just use it like this const {fetchAll} = useDataContext()

React useContext problem with Typescript -- any alternative to <Partial>?

I have a React app that uses useContext, and I'm having trouble getting the typing right with my context. Here's what I have:
import React, { useState, createContext } from 'react';
import endpoints from '../components/endpoints/endpoints';
interface contextTypes {
endpointQuery: string,
setEndpointQuery: React.Dispatch<React.SetStateAction<string>>,
searchInput: string,
setSearchInput: React.Dispatch<React.SetStateAction<string>>,
filmSearch: string | undefined,
setFilmSearch: React.Dispatch<React.SetStateAction<string>>
pageIndex: number,
setPageIndex: React.Dispatch<React.SetStateAction<number>>,
resetState: () => void;
}
export const DisplayContext = createContext<Partial<contextTypes>>({});
interface Props {
children: React.ReactNode;
}
const DisplayContextProvider = (props: Props) => {
const { nowShowing } = endpoints;
const [ endpointQuery, setEndpointQuery ] = useState(nowShowing);
const [ searchInput, setSearchInput ] = useState('');
const [ filmSearch, setFilmSearch ] = useState('');
const [ pageIndex, setPageIndex ] = useState(1);
const resetState = () => {
setSearchInput('');
setFilmSearch('');
setPageIndex(1);
};
const values = {
endpointQuery,
setEndpointQuery,
pageIndex,
setPageIndex,
filmSearch,
setFilmSearch,
searchInput,
setSearchInput,
resetState
};
return (
<DisplayContext.Provider value={values}>
{props.children}
</DisplayContext.Provider>
);
};
export default DisplayContextProvider;
The problem is, when I use <Partial<contextTypes>>, I get this error all over my app:
Cannot invoke an object which is possibly 'undefined'
Is there a way to fix this so I don't have to go around adding ! marks to everything where I get the undefined error? (I'm also pretty new to Typescript, so it's totally possible that I'm going about typing my context in completely the wrong way)
I think the issue is that you can't initialize the context with a useful default value, but you expect that the context provider will always be higher in the component tree.
When I'm in this situation, I want the following behavior:
If a component tries to use consume the context but the provider wasn't used above it, throw an error
Components consuming the context should assume the context has been set.
So, I usually create a hook that wraps useContext and does the null check for me.
import React, { useContext, createContext } from 'react';
interface contextTypes {
// ...
}
// private to this file
const DisplayContext = createContext<contextTypes | null>(null);
// Used by any component that needs the value, it returns a non-nullable contextTypes
export function useDisplay() {
const display = useContext(DisplayContext);
if (display == null) {
throw Error("useDisplay requires DisplayProvider to be used higher in the component tree");
}
return display;
}
// Used to set the value. The cast is so the caller cannot set it to null,
// because I don't expect them ever to do that.
export const DisplayProvider: React.Provider<contextTypes> = DisplayContext.Provider as any;
If useDisplay is used in a component without a DisplayProvider higher in the component tree, it will throw and the component won't mount.

Re-render the component when the store state is changed

I'm stuck on this problem, I am using redux to solve this problem and divided question into 4 parts. What I am trying to achieve is dynamically map component props with the UI inside another component (also known as PropEditor Form). What I'm talking about, First see this it is not implemented yet it just a prototype that I want to implement.
I will also appreciate it if you provide me a better solution to solve this problem.
My approach:
I have a component named Heading.js which contains 2 props hasFruit a boolean type and a fruitName string type. It can be a component from any library but let's start with simple.
src/components/Heading.js
import React from 'react';
export const Heading = (props) => {
const { hasFruit, fruitName } = props;
return <h1>Fruit name will show { hasFruit ? fruitName : 'Oh no!'}</h1>
};
Part A: InputTypes
I want to show this component props as a UI on the PropEditor component. So, I have to define the different UI components for the props. So, I have created 2 input type components.
src/editor/components/types/Boolean.js
import React from 'react';
import PropTypes from 'prop-types';
const propTypes = {
/** object for the boolean input type. */
prop: PropTypes.shape({
/** It will be the name of the prop. */
name: PropTypes.string,
/** It will be the value of the prop. */
value: PropTypes.bool,
}),
/** onChange handler for the input */
onChange: PropTypes.func
};
const defaultProps = {
prop: {},
onChange: (value) => value,
};
const Boolean = (props) => {
const { prop, onChange } = props;
return (
<input
id={prop.name}
name={prop.name}
type="checkbox"
onChange={(event) => onChange(event.target.checked)}
checked={prop.value}
/>
);
};
Boolean.propTypes = propTypes;
Boolean.defaultProps = defaultProps;
export default Boolean;
src/editor/components/types/Text.js
import React from 'react';
import PropTypes from 'prop-types';
const propTypes = {
/** object for the text input type. */
prop: PropTypes.shape({
/** It will be the name of the prop. */
name: PropTypes.string,
/** It will be the value of the prop. */
value: PropTypes.string
}),
/** onChange handler for the input */
onChange: PropTypes.func
};
const defaultProps = {
prop: {},
onChange: (value) => value,
};
const Text = (props) => {
const { prop, onChange } = props;
const handleChange = (event) => {
const { value } = event.target;
onChange(value);
};
return (
<input
id={prop.name}
type="text"
onChange={handleChange}
value={prop.value}
/>
);
};
Text.propTypes = propTypes;
Text.defaultProps = defaultProps;
export default Text;
Later we will import these components inside PropForm component which is the child of the PropEditor component. So we can map these types.
src/editor/components/types/index.js
import BooleanType from './Boolean';
import TextType from './Text';
export default {
boolean: BooleanType,
text: TextType,
};
Part B: Redux
The whole scenario, 2 actions will dispatch SET_PROP to set prop data on the store and SET_PROP_VALUE i.e. dispatch through PropEditor component when the input is changed and its update the value of the input.
src/editor/actionTypes:
// PropEditor Actions
// One single prop
export const SET_PROP = 'SET_PROP';
// One single prop value
export const SET_PROP_VALUE = 'SET_PROP_VALUE';
I have defined 2 action creators.
src/editor/PropActions.js:
import * as actionTypes from './actionTypes';
// Prop related action creators
/**
* #param prop {Object} - The prop object
* #return {{type: {string}, data: {Object}}}
*/
export const setProp = (prop) => {
return {
type: actionTypes.SET_PROP,
data: prop
};
};
// Prop value related actions
/**
* #param prop {Object} - The prop object
* #return {{type: {string}, data: {Object}}}
*/
export const setPropValue = (prop) => {
return {
type: actionTypes.SET_PROP_VALUE,
data: prop
};
};
src/editor/PropReducer.js:
import * as actionTypes from './actionTypes';
const INITIAL_STATE = {};
export const propReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
// Prop Actions
case (actionTypes.SET_PROP):
const { data } = action;
return { ...state, [data.name]: {...data} };
// Prop Value Actions
case (actionTypes.SET_PROP_VALUE):
return { ...state, [action.data.name]: { ...state[action.data.name], value: action.data.value } };
default:
return state;
}
};
src/editor/PropStore.js:
import { createStore } from 'redux';
import { propReducer } from './PropReducer';
const REDUX_DEV_TOOL = window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__();
export const store = createStore(propReducer, REDUX_DEV_TOOL);
Bootstrap our whole App with the react-redux provider on the DOM.
src/index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { store } from './editor/PropStore';
import App from './App';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
Part C: Main part
How to map component Heading.js props with a UI on the PropEditor component?
For this user has to wrap its component with a higher-order component and inside that HOC user has to call some functions which will behind the scenes help us to dynamically populate the store. I have created some functions like boolean and text which will dispatch an action named SET_PROP to populate the store state.
src/editor/index.js
import { store } from './PropStore';
import { setProp } from './PropActions';
/**
* #param name {string} - The name of the prop
* #param options {Object} - The prop with some additional properties
* #return {*} - Returns the associated value of the prop
*/
const prop = (name, options) => {
const defaultValue = options.value;
// Create an object and merge with additional properties like `defaultValue`
const prop = {
...options,
name,
defaultValue,
};
store.dispatch(setProp(prop));
return defaultValue;
};
/**
* #param name {string} - The name of the prop
* #param value {boolean} - The value of the prop
* #return {boolean} - Returns the value of the prop
*/
export const boolean = (name, value) => {
// Returns the value of the prop
return prop(name, { type: 'boolean', value });
};
/**
* #param name {string} - The name of the prop
* #param value {string} - The value of the prop
* #return {text} - Returns the value of the prop
*/
export const text = (name, value) => {
// Returns the value of the prop
return prop(name, { type: 'text', value });
};
Render the HOC component and PropEditor on the DOM:
src/blocks.js:
import React from 'react';
import { boolean, text } from './editor';
import { Heading } from './components/Heading';
// WithHeading Block
export const WithHeading = () => {
const boolVal = boolean('hasFruit', true);
const textVal = text('fruitName', 'Apple');
return (<Heading hasFruit={boolVal} fruitName={textVal}/>);
};
This is our main App component.
src/App.js:
import React from 'react';
import { PropEditor } from './editor/components/PropEditor';
import { WithHeading } from './blocks';
const App = () => {
return (
<div className="App">
{/* PropEditor */}
<PropEditor />
{/* Blocks */}
<WithHeading/>
</div>
);
};
export default App;
Part D: Final Part PropEditor component
PropEditor will dispatch an action when any input is changed but remember all our props are converted into an array of objects for rendering the UI which will be passed inside the PropForm component.
src/editor/components/PropEditor.js:
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { PropForm } from './PropForm';
import { setPropValue } from '../PropActions';
export const PropEditor = () => {
// Alternative to connect’s mapStateToProps
const props = useSelector(state => {
return state;
});
// Alternative to connect’s mapDispatchToProps
// By default, the return value of `useDispatch` is the standard Dispatch type defined by the
// Redux core types, so no declarations are needed.
const dispatch = useDispatch();
const handleChange = (dataFromChild) => {
dispatch(setPropValue(dataFromChild));
};
// Convert objects into array of objects
const propsArray = Object.keys(props).map(key => {
return props[key];
});
return (
<div>
{/* Editor */}
<div style={styles.editor}>
<div style={styles.container}>
{ propsArray.length === 0
? <h1 style={styles.noProps}>No Props</h1>
: <PropForm props={propsArray} onFieldChange={handleChange} />
}
</div>
</div>
</div>
);
};
src/editor/components/PropForm.js:
import React from 'react';
import PropTypes from 'prop-types';
import TypeMap from './types';
const propTypes = {
props: PropTypes.arrayOf(PropTypes.object).isRequired,
onFieldChange: PropTypes.func.isRequired
};
// InvalidType component
const InvalidType = () => (<span>Invalid Type</span>);
export const PropForm = (properties) => {
/**
* #param name {string} - Name of the prop
* #param type {string} - InputType of the prop
* #return {Function} - Returns a function
*/
const makeChangeHandler = (name, type) => {
const { onFieldChange } = properties;
return (value = '') => {
// `change` will be an object and value will be from the onChange
const change = {name, type, value};
onFieldChange(change);
};
};
// Take props from the component properties
const { props } = properties;
return (
<form>
{
props.map(prop => {
const changeHandler = makeChangeHandler(prop.name, prop.type);
// Returns a component based on the `type`
// if the `type` is boolean then
// return Boolean() component
let InputType = TypeMap[prop.type] || InvalidType;
return (
<div style={{marginBottom: '16px'}} key={prop.name}>
<label htmlFor={prop.name}>{`${prop.name}`}</label>
<InputType prop={prop} onChange={changeHandler}/>
</div>
);
})
}
</form>
);
};
PropForm.propTypes = propTypes;
After all this explanation my code is working perfectly.
The problem is re-rendering of the Heading component is not happening when SET_PROP_VALUE action is dispatched on the input change inside the PropEditor component.
The store is changed perfectly as you can see with the Redux DevTools extension but the re-render of the component Heading is not happening.
I think because inside my HOC text() and boolean() functions are not returning an updated value.
Is there a way to solve this problem?
Please don't mention this I have to connect my WithHeading component with the react-redux. I know this but Is there a way the functions like boolean('hasFruit', true) and text('fruitName', 'Apple') returns the latest value when the store state is updated?
Codesandbox: Sandbox
Repository: Repository
Here I've created 4 demos, each demo is an extended version of the previous one :
1) Connect the sore and update component via mapStateToProps
2) By Using the useSelector
const boolVal = useSelector(state => state.hasFruit ? state.hasFruit.value : false );
3) Paasing the dynamic name to useSelector
const booleanVal = useSelector(state => booleanSelector(state, "hasFruit"));
4) Created a custom hook, so that you can get the updated value bu just passing the name
const booleanVal = useGetValueFromStore("hasFruit");
The problem is re-rendering of the Heading component is not happening
Reason :
Yes because it's not connected to the store, how does it know that there are some changes going on the store, you need to call connect to make a connection with the store and be up to date with changes.
Here is the updated code of the blocks.js :
// WithHeading Block
const WithHeading = props => {
useEffect(() => {
boolean("hasFruit", true); // <--- Setting initial value
text("fruitName", "Apple"); // <--- Setting initial value
}, []); // <----- get called only on mount
return <Heading hasFruit={props.boolVal} fruitName={props.textVal} />;
};
// to get updated state values inside the component as props
const mapStateToProps = state => {
return {
boolVal: state.hasFruit ? state.hasFruit.value : false,
textVal: state.fruitName ? state.fruitName.value : ""
};
};
// to make connection with store
export default connect(mapStateToProps)(WithHeading);
1) WORKING DEMO :
Another approach is you can use useSelector :
// WithHeading Block
const WithHeading = props => {
// console.log(props);
const boolVal = useSelector(state =>
state.hasFruit ? state.hasFruit.value : false
);
const textVal = useSelector(state =>
state.fruitName ? state.fruitName.value : ""
);
useEffect(() => {
boolean("hasFruit", true);
text("fruitName", "Apple");
}, []);
return <Heading hasFruit={boolVal} fruitName={textVal} />;
};
export default WithHeading;
2) WORKING DEMO :
You can also put the selector in separate file,so that you can use it whenever you want
const WithHeading = props => {
// you can pass the input names here, and get value of it
const booleanVal = useSelector(state => booleanSelector(state, "hasFruit"));
const textVal = useSelector(state => textValSelector(state, "fruitName"));
useEffect(() => {
boolean("hasFruit", true);
text("fruitName", "Apple");
}, []);
return <Heading hasFruit={booleanVal} fruitName={textVal} />;
};
3) WORKING DEMO :
Custom Hook with use of useSelector :
// a function that will return updated value of given name
const useGetValueFromStore = name => {
const value = useSelector(state => (state[name] ? state[name].value : ""));
return value;
};
// WithHeading Block
const WithHeading = props => {
//------- all you need is just to pass the name --------
const booleanVal = useGetValueFromStore("hasFruit");
const textVal = useGetValueFromStore("fruitName");
useEffect(() => {
boolean("hasFruit", true);
text("fruitName", "Apple");
}, []);
return <Heading hasFruit={booleanVal} fruitName={textVal} />;
};
export default WithHeading;
4) WORKING DEMO :
There are several ways to handle state in React, and many of those choices are based on complexity and requirements. As mentioned in the comments, Redux is a powerful option. Mobx is a remarkable piece of technology, to name two.
React itself does have capacity to disseminate and respond to these changes without external libs. You might consider using the Context API -
./src/contexts/Store
import React, {
useContext,
useState,
useMemo,
createContext,
useEffect,
} from 'react';
const StoreContext = createContext(null);
const StoreProvider = (props) => {
const [state, setLocalState] = useState({});
function set(objToMerge) {
setLocalState({ ...state, ...objToMerge });
}
function get(k) {
return state[k];
}
function getAll(){
return state;
}
const api = useMemo(() => {get, set, getAll}, []);
return <StoreContext.Provider value={api} {...props}></StoreContext.Provider>;
};
function useStoreContext(): StoreProviderApi {
const api = useContext(StoreContext);
if (api === null) {
throw new Error(
'Component must be wrapped in Provider in order to access API',
);
}
return api;
}
export { StoreProvider, useStoreContext };
to use, you do need a Parent level component -
import {StoreProvider} from './contexts/Store';
...
<StoreProvider>
<PropEditor/>
<WithHeading/>
</StoreProvider>
...
Then, within the component itself, you can access the latest state -
import {useStoreContext} from './contexts/Store';
export const Heading = (props) => {
const store = useStoreContext();
const { hasFruit, fruitName } = store.getAll();
return <h1>Fruit name will show { hasFruit ? fruitName : 'Oh no!'}</h1>
};
This has the benefit of not needing to pass tons of props around, and it will auto-render on change.
The downside, however, is that it will re-render on change. That is, there are no mechanisms for selectively re-rendering only the components with changed props. Many projects have multiple contexts to alleviate this.
If your store props need to be used throughout the app, then Redux (with the toolkit) is a good option, because it is a store outside of React, and it handles broadcasting only the prop changes to the subscribing components for those props, rather than re-rendering all of the subscribers (which is what the Context API does).
At that point, it becomes a question of architecture and what is needed for your application requirements.

How To Declare TypeScript Static Method From React Functional Component

I'm needing to reference static methods from my functional component in React. I've made a small example here of what I want to do (it works in JavaScript). The error I'm getting is on line 10 const x = ...
TS2339: Property 'GetMyArray' does not exist on type
'FunctionComponent'.
import React, {FunctionComponent} from 'react';
interface Props {
isLoading?: boolean,
}
const Speakers : FunctionComponent<Props> = ({isLoading}) => {
if (isLoading) {
const x = Speakers.GetMyArray();
return (
<div>{JSON.stringify({x})}</div>
);
} else {
return <div></div>;
}
};
Speakers.GetMyArray = () => {
return [1,2,3,4]
};
export default Speakers
This would be easier to do by using a class component since the static functions and property types can be inferred in class definitions.
class Speakers extends React.Component<Props> {
static GetMyArray = () => [1, 2, 3, 4]
render() {
const { isLoading } = this.props
if (isLoading) {
const x = Speakers.GetMyArray(); // works great
return (
<div>{JSON.stringify({x})}</div>
);
} else {
return <div></div>;
}
}
}
That said, you could do it extending React.SFC or using an intersection type:
const Speakers: React.SFC<Props> & { GetMyArray?: () => number[]; } = (props) => {
const { isLoading } = props
if (isLoading) {
const x = Speakers.GetMyArray!(); // works, sorta
return (
<div>{JSON.stringify({x})}</div>
);
} else {
return <div></div>;
}
}
You'll have to mark GetMyArray as optional because you cannot define it at the same time as you define the function, so you'll have to use a ! operator (or check that the function exists) when calling the static function. The ! in Speakers.GetMyArray!(); tells the type checker that you know what you're doing and that GetMyArray is not indeed undefined. See here to read about the non-null assertion operator
Update
I did not see that using React.FC is now the preferred way to use function components since function components can no longer be considered stateless.
If you can get away with only using const Speakers = (props: Props) => ... then upgrading TypeScript to 3.1 might be your best bet!
My answer is not exact to your question. But I think will be helpful for someone who wants to add a static component to the function component.
import React from 'react';
import { Text } from 'react-native';
import Group, { RadioButtonGroupProps } from './Group';
type RadioButtonType = {
text: string,
};
interface StaticComponents {
Group?: React.FC<RadioButtonGroupProps>
}
const RadioButton: React.FC<RadioButtonType> & StaticComponents =
({ text }) => (<Text>{text}</Text>);
RadioButton.Group = Group;
export default RadioButton;
Remember config like this if you want to import React from 'react' directly instead of import * React from 'react'
Achievement:
You can write an interface that extends React.FC with your custom static method, then assign that interface to your functional component.
// Extends FC with custom static methods
interface ComponentWithStaticMethod<TProps> extends React.FC<TProps> {
staticMethod: (value: string) => void;
}
// Your component with the interface
const Component: ComponentWithStaticMethod<{}> = () => {
// Your component goes here...
return null;
}
// Declare your static method for your functional component
Component.staticMethod = (value: string): void => {
console.log(value);
}
You should have it working since TS version 3.1 (see section "Properties declarations on functions")
Main Answer
For future googlers out there, now it works as intended:
interface Props {
isLoading?: boolean,
}
interface StaticFields {
staticField: number,
staticFunctionField: () => void,
}
export const Component: FC<Props> & StaticFields = (props) => {}
Component.staticField = 42;
Component.staticFunctionField = () => {}
Special Case: memo and forwardRef
In such cases, you won't be able to define typings like in above. Using Object.assign can solve the problem:
import { memo } from 'react'
interface Props {
isLoading?: boolean,
}
const Component = memo((props: Props) => {})
export const ComponentWithStaticFields = Object.assign({}, Component, {
staticField: 42,
staticFunctionField: () => {},
})

Resources