Can't get Reflect.metadata to work in React - reactjs

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.

Related

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.

In React, how do I instantiate a class without a constructor in my unit test?

I'm using React 16.13.0. I have the below class (Missions.ts) ...
class Missions {
repo() : MissionRepository {
return getRepository(Mission) as MissionRepository;
}
async getAll() : Promise<Mission[]> {
return this.repo().find();
}
...
async removeVolunteerFromMission(missionId: string) {
const missions = this.repo();
const mission = await missions.findById(missionId);
mission.volunteerId = '';
mission.status = MissionStatus.unassigned;
return missions.update(mission);
}
...
}
export default new Missions();
I'm trying to write some unit tests for the methods but I'm having trouble figuring out how to instantiate my class. I have tried the below ...
import React from "react";
import { CustomRepository, getRepository } from 'fireorm';
import { BaseRepository } from './BaseRepository'
import { Mission } from './schema';
import { Missions } from './Missions';
describe('Missions', () => {
describe('#removeVolunteerFromMission', () => {
const missionId = "1234";
const mission = new Mission();
beforeEach(() => {
const mockFindById = jest.fn();
BaseRepository.prototype.findById = mockFindById;
mockFindById.mockReturnValue(Promise.resolve(mission));
const mockUpdate = jest.fn();
BaseRepository.prototype.update = mockUpdate;
mockUpdate.mockReturnValue(Promise.resolve(mission));
});
it('unassigns volunteer', () => {
const obj = new Missions();
obj.removeVolunteerFromMission(missionId)
expect(mockFindById).toHaveBeenCalledTimes(1);
expect(mockUpdate).toHaveBeenCalledTimes(1);
expect(mission.volunteer).toBe(null);
});
});
});
However upon running my test using "hpm test", I'm getting this error
TypeError: _Missions.Missions is not a constructor
on the line
const obj = new Missions()
How do I instantiate my class within the test?
The problem is you're exporting an instance of Missions class and not the class itself, and you cannot instantiate an instance again. So export only the class and then instantiate it where you want to use it.
export default Missions;
Instantiate it where you want to use it:
const missions = new Missions();
Also default export are imported without the curly brackets, so do this:
import Missions from './Missions';

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: () => {},
})

React-intl for non components

Currently I have the following code to expose react-intl to non-components, but it throws an error for intl as undefined.
I have created a separate component as 'CurrentLocale' and inject-intl to it. The exporting function t will use intl formatMessage from CurrentLocale context.
import React from 'react';
import {injectIntl} from 'react-intl';
import PropTypes from 'prop-types';
import { flow } from 'lodash';
class CurrentLocale extends React.Component {
constructor(props,context){
super();
console.log(context,props);
console.log(this.formatMessage);
const { intl } = this.context.intl;//this.props;
this.formatMessage = intl.formatMessage;
}
render() {
return false;
}
}
CurrentLocale.contextTypes={
intl:PropTypes.object,
};
injectIntl(CurrentLocale);
function intl() {
return new CurrentLocale();
}
function formatMessage(...args) {
return intl().formatMessage(...args);
}
const t = opts => {
const id = opts.id;
const type = opts.type;
const values = opts.values;
let t;
switch (type){
case 'message':
default:
t = formatMessage(id, values);
}
return t;
}
export default t;
t is called as in another plain javascript file as,
import t from './locale/t';
t( { type: 'message', id:'button.Next'});
Following is the error message.
Thanks in advance.
There's also another approach very simple I used for solving a similar problem: Provide access to the intl object for a non-component:
import { IntlProvider, addLocaleData } from 'react-intl';
import localeDataDE from 'react-intl/locale-data/de';
import localeDataEN from 'react-intl/locale-data/en';
import { formMessages } from '../../../store/i18n'; // I defined some messages here
import { Locale } from '../../../../utils'; //I set the locale fom here
addLocaleData([...localeDataEN, ...localeDataDE]);
const locale = Locale.setLocale(); //my own methods to retrieve locale
const messages = Locale.setMessages(); //getting messages from the json file.
const intlProvider = new IntlProvider({ locale, messages });
const { intl } = intlProvider.getChildContext();
export const SCHEMA = {
salutation: {
label: intl.formatMessage(formMessages.salutationLabel),
errormessages: {
required: intl.formatMessage(formMessages.salutationError),
},
},
academic_title_code: {
label: intl.formatMessage(formMessages.academicTitleLabel),
},
};
It's working like a charm!
UPDATE for v3.x
After migration to react-intl 3.x
import { createIntl, createIntlCache } from 'react-intl'
import { formMessages } from '../../../store/i18n'; // I defined some messages here
import { Locale } from '../../../../utils'; //I set the locale fom here
const locale = Locale.setLocale(); //my own methods to retrieve locale
const messages = Locale.setMessages(); //getting messages from the json file.
// This is optional but highly recommended
// since it prevents memory leak
const cache = createIntlCache();
const intl = createIntl({ locale, messages }, cache)
export const SCHEMA = {
salutation: {
label: intl.formatMessage(formMessages.salutationLabel),
errormessages: {
required: intl.formatMessage(formMessages.salutationError),
},
},
academic_title_code: {
label: intl.formatMessage(formMessages.academicTitleLabel),
},
};
There's a new way to do it pretty easily with createIntl, it returns an object that you can use outside React components. Here's an example from the documentation.
import {createIntl, createIntlCache, RawIntlProvider} from 'react-intl'
// This is optional but highly recommended
// since it prevents memory leak
const cache = createIntlCache()
const intl = createIntl({
locale: 'fr-FR',
messages: {}
}, cache)
// Call imperatively
intl.formatNumber(20)
// Pass it to IntlProvider
<RawIntlProvider value={intl}>{foo}</RawIntlProvider>
I personally store the intl object in Redux store so I can access it everywhere in my app.
This line: const { intl } = this.context.intl; should be const { intl } = this.context;
Here is a reference post of someone doing almost the exact same thing as you are: https://github.com/yahoo/react-intl/issues/983#issuecomment-342314143
In the above the author is creating essentially a singleton that is exported instead of creating a new instance each time like you have above. This might be something you want to consider as well.
There's also another way solving a similar problem to used react-intl formatMessage for non-components.
Create a LocaleStore.js store file.
import _formatMessage from "format-message";
export default class LocaleStore {
formatMessage = (id, values) => {
if (!(id in this.messages)) {
console.warn("Id not found in intl list: " + id);
return id;
}
return _formatMessage(this.messages[id], values);
};
}
import LocaleStore your CombinedStores.js
import LocaleStore from "./stores/LocaleStore";
import en from "./translations/en";
import de from "./translations/de";
import Global from "./stores/global"
const locale = new LocaleStore("en", {
en,
de
});
export default {
global:new Global(locale)
}
now you can use this in your GlobalStore.js
class GlobalStore {
constructor(locale) {
this.locale = locale;
}
formatMessage=(message_is,formatLanguage="en")=> {
return this.locale.formatMessage(message_id, formatLanguage);
}
}
react-intl decorates your React.Component with wrapped component which is injected internationalized message dynamically so that the locale data is able to be loaded dynamically.
import { injectIntl } from 'react-intl';
class MyComponent extends Component {
render() {
const intl = this.props;
const title = intl.formatMessage({ id: 'title' });
return (<div>{title}</div>);
}
};
export default injectIntl(MyComponent);
It can be applied only in view layer such as React.Component.
react-intl can't be used in Vanilla JS. For example,
export default const rules = {
noSpace(value) {
if (value.includes(' ')) {
return 'Space is not allowed.';
}
}
};
One of alternative is react-intl-universal. It can be used not only in Component but also in Vanilla JS.
For example:
import intl from 'react-intl-universal';
export default const rules = {
noSpace(value) {
if (value.includes(' ')) {
return intl.get('no_space');
}
}
};
See react-intl-universal online example
If you can accept to use a function component I prefer to use the useIntl hook
https://reactjs.org/docs/components-and-props.html#function-and-class-components
I can then get values like this:
import { useIntl } from "react-intl";
const intl = useIntl()
intl.formatMessage({ id: 'myId' }),
https://formatjs.io/docs/react-intl/api/#useintl-hook

Resources