replacing enum - better solution needed - javascript

I have an app that uses React Native' Native Module.
Currently the logic is that the Data type will be obtained from my enum as shown below
export declare enum HKQuantityTypeIdentifier {
HeartRate = "HKQuantityTypeIdentifierHeartRate"
}
const requestAuthorization = (
read: (HKQuantityTypeIdentifier)[],
write: HKQuantityTypeIdentifier[] = []
): Promise<boolean> => {
const readAuth = read.reduce((obj, cur) => {
return { ...obj, [cur]: true };
}, {});
const writeAuth = write.reduce((obj, cur) => {
return { ...obj, [cur]: true };
}, {});
return NativeModule.requestAuthorization(writeAuth, readAuth);
};
const MyHealthLibrary: ReactNativeHealthkit = {
requestAuthorization
}
export default MyHealthLibrary;
Front-end call:
await MyHealthLibrary.requestAuthorization([HKQuantityTypeIdentifier.HeartRate]);
This will give my expected result
Now i do not want to call the function from my front-end with the entire type "HKQuantityTypeIdentifier.HeartRate" instead i just want to call it like "HeartRate". Something like below:
await MyHealthLibrary.requestAuthorization(["HeartRate"]);
How do i achieve this??? Any help would be great!

I don't understand your issue with enums, but you can easily do that this way then:
export type HKQuantityTypeIdentifier = 'HKQuantityTypeIdentifierHeartRate' | 'HKQuantityTypeIdentifierOtherData';
const requestAuthorization = (
read: (HKQuantityTypeIdentifier)[],
write: HKQuantityTypeIdentifier[] = []
): Promise<boolean> => {

Assuming that you want to be able to extend your set of values, you could replace the enum by a ts-file that exports constants. It is not state of the art but an alternative.
TS-File
my-constants.ts
export const HEART_RATE = "HeartRate";
export const CO2 = "CO2";
// and so forth
Your component
import { HEART_RATE, CO2 } from 'my-constants.ts';
// ...
await MyHealthLibrary.requestAuthorization([HEART_RATE]);

Related

React. Rerendering nested array

I am building quiz app and there is a problem that i have to shuffle question options but they are located in the nested question array. Here is the interface:
export interface IConnectionsQuestionData extends IQuestionData {
type: 'connections';
questionsTitle: string;
answersTitle: string;
pairs: {
questionOption: IConnectionsOption;
answerOption: Maybe<IConnectionsOption>;
}[];
}
export interface IConnectionsOption {
key: IDType;
value: string;
type: 'answer' | 'question'
}
So in order to shuffle options i have created custom useShuffle hook:
export const useShuffle = <T>(array: T[]): T[] => {
return useMemo(() => {
return shuffleArray(array);
}, [array])
}
In Question component I get question from props. I use this hook like this:
const shuffledLeftSideOptions = useShuffle(question?.pairs.map(pair => pair.questionOption) ?? [])
const shuffledRightSideOptions = useShuffle(question?.pairs.map(pair => pair.answerOption) ?? [])
But every time component need to be rerendered when i choose the option, options array shuffles again on every render. I tried to test it and it works fine with pairs array but reshuffles with question or answer options.
You've only memoized the shuffling, not the map operation. So you get a new array every time, because map produces a new array every time, and so useShuffle has to do its work every time.
If you need to do the map, you'll need to memoize that as well, e.g.:
const mappedLeftOptions = useMemo(
() => question?.pairs.map((pair) => pair.questionOption) ?? [],
[question]
);
const mappedRightOptions = useMemo(
() => question?.pairs.map((pair) => pair.answerOption) ?? [],
[question]
);
const shuffledLeftSideOptions = useShuffle(mappedLeftOptions);
const shuffledRightSideOptions = useShuffle(mappedRightOptions);
But, beware that the memoization provided by useMemo provides only a performance optimization, not a semantic guarantee. (More in the documentation.) But your shuffling code is relying on it as though it were a semantic guarantee.
Instead, when you need a semantic guarantee, use a ref:
export const useShuffle = <T>(array: T[]): T[] => {
const arrayRef = useRef<T[] | null>(null);
const shuffleRef = useRef<T[] | null>(null);
if (!Object.is(array, arrayRef.current)) {
shuffleRef.current = null;
}
if (!shuffleRef.current) {
arrayRef.current = array;
shuffleRef.current = shuffleArray(array);
}
return shuffleRef.current;
};
Or it's probably cleaner with a single ref:
export const useShuffle = <T>(array: T[]): T[] => {
const ref = useRef<{ array: T[]; shuffled: T[] } | null>(null);
if (!ref.current || !Object.is(ref.current.array, array)) {
ref.current = {
array,
shuffled: shuffleArray(array),
};
}
return ref.current.shuffled;
};

How to abstract multiple similar function components (providers) in React?

I have several providers / contexts in a React app that do the same, that is, CRUD operations calling a Nestjs app:
export const CompaniesProvider = ({children}: {children: any}) => {
const [companies, setCompanies] = useState([])
const fetchCompany = async () => {
// etc.
try {
// etc.
setCompanies(responseData)
} catch (error) {}
}
const updateCompany = async () => {
// etc.
try {
// etc.
} catch (error) {}
}
// same for delete, findOne etc..
return (
<CompaniesContext.Provider value={{
companies,
saveSCompany,
}}>
{children}
</CompaniesContext.Provider>
)
}
export const useCompanies = () => useContext(CompaniesContext)
Another provider, for instance, the Technology model would look exactly the same, it just changes the api url:
export const TechnologiesProvider = ({children}: {children: any}) => {
const [technologies, setTechnologies] = useState([])
const fetchTechnology = async () => {
// etc.
try {
// etc.
setTechnologies(responseData)
} catch (error) {}
}
const updateTechnology = async () => {
// etc.
try {
// etc.
} catch (error) {}
}
// same for delete, findOne etc..
return (
<TechnologiesContext.Provider value={{
technologies,
savesTechnology,
}}>
{children}
</TechnologiesContext.Provider>
)
}
export const useTechnologies = () => useContext(TechnologiesContext)
What is the best way to refactor? I would like to have an abstract class that implements all the methods and the different model providers inherit the methods, and the abstract class just needs the api url in the constructor..
But React prefers function components so that we can use hooks like useState.
Should I change function components to class components to be able to refactor? But then I lose the hooks capabilities and it's not the react way nowadays.
Another idea would be to inject the abstract class into the function components, and the providers only call for the methods.
Any ideas?
One way to achieve it is to create a factory function that gets a url (and other parameters if needed) and returns a provider & consumer
This is an example for such function:
export const contextFactory = (url: string) => {
const Context = React.createContext([]); // you can also get the default value from the fn parameters
const Provider = ({ children }: { children: any }) => {
const [data, setData] = useState([]);
const fetch = async () => {
// here you can use the url to fetch the data
try {
// etc.
setData(responseData);
} catch (error) {}
};
const update = async () => {
// etc.
try {
// etc.
} catch (error) {}
};
// same for delete, findOne etc..
return (
<Context.Provider
value={{
data,
save
}}
>
{children}
</Context.Provider>
);
};
const hook = () => useContext(Context)
return [Provider, hook]
};
And this is how you can create new providers & consumers
const [CompaniesProvider, useCompanies] = contextFactory('http://...')
const [TechnologiesProvider, useTechnologies] = contextFactory('http://...')
I ended up creating a class representing the CRUD operations:
export class CrudModel {
private api = externalUrls.api;
constructor(private modelUrl: string) {}
async fetchRecords() {
const url = `${this.api}/${this.modelUrl}`
try {
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-type': 'application/json'
},
})
return await response.json()
} catch (error) {}
}
// removeRecord
// updateRecord
// saveRecord
}
Then for every provider I reduced the code, since I just call an instance of the CrudModel, which has the method implementations.
type Technology = {
id: number;
name: string;
}
type Context = {
technologies: Technology[];
saveTechnology: any;
removeTechnology: any;
updateTechnology: any;
}
const TechnologiesContext = createContext<Context>({
technologies: [],
saveTechnology: null,
removeTechnology: null,
updateTechnology: null,
})
export const TechnologiesProvider = ({children}: {children: any}) => {
const [technologies, setTechnologies] = useState([])
const router = useRouter()
const crudModel = useMemo(() => {
return new CrudModel('technologies')
}, [])
const saveTechnology = async (createForm: any): Promise<void> => {
await crudModel.saveRecord(createForm)
router.reload()
}
// fetchTechnologies
// removeTechnology
// updateTechnology
useEffect(() => {
async function fetchData() {
const fetchedTechnologies = await crudModel.fetchRecords()
setTechnologies(fetchedTechnologies)
}
fetchData()
}, [crudModel])
return (
<TechnologiesContext.Provider value={{
technologies,
saveTechnology,
removeTechnology ,
updateTechnology,
}}>
{children}
</TechnologiesContext.Provider>
)
}
This way I can have types for every file, and it's easy to debug / maintain. Having just one factory function like previous answer it feels cumbersome to follow data flow. The downside is that there is some repetition of code among the provider files. Not sure if I can refactor further my answer

getStaticPaths not creating paths

I have data folder that contains events.ts:
export const EventsData: Event[] = [
{
name: 'School-Uniform-Distribution',
images: ['/community/conferences/react-foo.png', "/community/conferences/react-foo.png"],
},
{
name: 'College-Uniform',
images: ['/community/conferences/react-foo.png', "/community/conferences/react-foo.png"],
},
];
type Event is:
export type Event = {
name: string;
images: string[];
};
I have the getStaticPath and getStaticProps methods in pages/our-contribution/[pid].tsx :
export const getStaticProps: GetStaticProps<Props> = async (context) => {
const event = EventsData;
console.log(event, "event")
return {
props: { event: event },
};
};
export async function getStaticPaths() {
// Get the paths we want to pre-render based on posts
const paths = EventsData.map(event => ({
params: {pid: event.name},
}));
console.log(paths, "paths")
// We'll pre-render only these paths at build time.
return {paths, fallback: false}
}
I get this error:
Can you help me, please ?
Update:
This is the error trace for one route:
pages/our-contribution/[pid].tsx:
import { useRouter } from 'next/router';
import { GetStaticProps } from 'next';
import { Event } from 'types/event';
import {EventsData} from 'data/events';
type Props = {
event: Event[];
};
const Event = ({event} : Props) => {
const router = useRouter()
const { pid } = router.query
return <p>Event: {event}</p>
}
export const getStaticProps: GetStaticProps<Props> = async (context) => {
const event = EventsData;
console.log(event, "event")
return {
props: { event: event },
};
};
export async function getStaticPaths() {
// Get the paths we want to pre-render based on posts
const paths = EventsData.map(event => ({
params: {pid: event.name},
}));
console.log(paths, "paths")
// We'll pre-render only these paths at build time.
return {paths, fallback: false}
}
export default Event
I think there are a couple of errors in the code.
One error that block your build is
const Event = ({event} : Props) => {
const router = useRouter()
const { pid } = router.query
return <p>Event: {event}</p>
}
You can't directly print an object or array in tsx, you should convert the object first into string if you are trying to debug it. Something like:
return <p>Event: {event.toString()}</p>
Then a i notice something strange in your variables name event props looks like a single event but instead you give an array of events i don't know if it is correct but maybe it should be like this:
type Props = {
event: Event;
};
or it should be named:
type Props = {
events: Event[];
};

React useState hook affecting default variable used in state regardless spread operators, Object.assign and etc

I am experienced js/React developer but came across case that I can't solve and I don't have idea how to fix it.
I have one context provider with many different state, but one state looks like following:
const defaultParams = {
ordering: 'price_asc',
page: 1,
perPage: 15,
attrs: {},
}
const InnerPageContext = createContext()
export const InnerPageContextProvider = ({ children }) => {
const [params, setParams] = useState({ ...defaultParams })
const clearParams = () => {
setParams({...defaultParams})
}
console.log(defaultParams)
return (
<InnerPageContext.Provider
value={{
params: params,
setParam: setParam,
clearParams:clearParams
}}
>
{children}
</InnerPageContext.Provider>
)
}
I have one button on page, which calls clearParams function and it should reset params to default value.
But it does not works
Even when i console.log(defaultParams) on every provider rerendering, it seems that defaultParams variable is also changing when state changes
I don't think it's normal because I have used {...defaultParams} and it should create new variable and then pass it to useState hook.
I have tried:
const [params, setParams] = useState(Object.assign({}, defaultParams))
const clearParams = () => {
setParams(Object.assign({}, defaultParams))
}
const [params, setParams] = useState(defaultParams)
const clearParams = () => {
setParams(defaultParams)
}
const [params, setParams] = useState(defaultParams)
const clearParams = () => {
setParams({
ordering: 'price_asc',
page: 1,
perPage: 15,
attrs: {},
})
}
None of above method works but 3-rd where I hard-coded same object as defaultParams.
The idea is to save dafult params somewhere and when user clears params restore to it.
Do you guys have some idea hot to make that?
Edit:
This is how I update my params:
const setParam = (key, value, type = null) => {
setParams(old => {
if (type) {
old[type][key] = value
} else old[key] = value
console.log('Params', old)
return { ...old }
})
}
please show how you update the "params".
if there is something like this in the code "params.attrs.test = true" then defaultParams will be changed
if old[type] is not a simple type, it stores a reference to the same object in defaultParams. defaultParams.attrs === params.attrs. Since during initialization you destructuring an object but not its nested objects.
the problem is here: old[type][key] = value
solution:
const setParam = (key, value, type = null) => {
setParams(old => {
if (type) {
old[type] = {
...old[type],
key: value,
}
} else old[key] = value
return { ...old }
})
}

How to check for subscription.ready() in a react component?

This is a very basic example how I get the data from the mongoDB to my meteor/react application.
Now I would like to show a loading icon while the data is getting loaded. Therefore I need to use something like subscription.ready() but where should I put this?
import { Meteor } from 'meteor/meteor'
import { createContainer } from 'meteor/react-meteor-data'
import Example from '../components/example.jsx'
import ExampleCollection from '/imports/api/collection.js'
export default createContainer((props) => {
const id = props.params.id,
subscription = Meteor.subscribe('anything', id)
data = ExampleCollection.find({ parent: id }).fetch()
return { data: data }
}, Example)
i do it like this:
render() {
if (this.props.isLoading) {
return null; // or show loading icon
}
return (
// stuff
);
}
export default createContainer((props) => {
const id = props.params.id;
let subscription = Meteor.subscribe('anything', id);
let data = ExampleCollection.find({ parent: id }).fetch();
let isLoading = !subscription.ready();
return {data, isLoading};
}, Example);
For those who might still be looking for this type of functionality, I think this might be a little bit better way to do this. Also note that currently createContainer is deprecated and changed to withTracker.
export default withTracker((props) => {
let isLoading = true;
let data = [];
const id = props.params.id;
const subscription = Meteor.subscribe('anything', id);
if (subscription.ready()) {
isLoading = false;
data = ExampleCollection.find({ parent: id }).fetch();
}
return {data, isLoading};
}, Example);
This is Zim's answer refactored a little.

Categories

Resources