Add functions to an enum in React 17+ (with functional components) - reactjs

I want to add functions to an Enum type in my React Functional Components (i.e. classless) TypeScript project.
As already asked and answered here, this can be done in one of two ways:
class ModeUtil {
public static toString(mode: Mode) {
return Mode[mode];
}
or
enum Mode {
X,
Y
}
namespace Mode {
export function toString(mode: Mode): string {
return Mode[mode];
}
export function parse(mode: string): Mode {
return Mode[mode];
}
}
Since I have been able to avoid classes in my project so far, I prefer keeping it that way and thus I'm in favor of the namespace approach.
However, the namespace approach is in violation with the no-namespace ESLint rule.
Hence, is use of classes a valid approach after all? I mean, in React, functional components were introduced in favor of class based components to avoid problems with mutation. In this context, the class itself would only contain static methods...

I don't think the React choice to move away from classes is something that means "In general, classes are bad and should be avoided".
It just means "In React, a component can be better expressed and easily defined with a function component".
So answering to your question: classes ARE a valid approach in general.
But it is up to you to understand if it fits your scenario.
In your specific case, I think it can work smoothly. Or, if you think otherwise, you could just disable that eslint rule.
But, again, the fact that React dismissed class based components, doesn't mean that classes are bad. Just use React functional components and feel free to use classes whenever you think they could help.

So I ended up with a module (i.e. non-class specific) approach without namespaces or module names. This ensures that I am compliant with the no-namespace ESLint rule, and I can keep my codebase clean without introducing classes.
Given that you have a Drink enum like this:
export enum Drink {
GinTonic,
LongIslandIceTea
}
You can then define your utils/helper functions like this:
import { Drink } from "../models/drink";
const toHumanString = (drink: Drink): string => {
switch (drink) {
case Drink.GinTonic:
return "Gin Tonic";
case Drink.LongIslandIceTea:
return "Long Island Ice Tea";
default:
throw new Error(`Unknown drink ${drink}.`);
}
};
const toMachineString = (drink: Drink) => {
switch (drink) {
case Drink.GinTonic:
return "GinTonic";
case Drink.LongIslandIceTea:
return "LongIslandIceTea";
default:
throw new Error(`Unknown drink ${drink}.`);
}
};
const parse = (drinkAsString: string): Drink => {
switch (drinkAsString) {
case "GinTonic":
return Drink.GinTonic;
case "LongIslandIceTea":
return Drink.LongIslandIceTea;
default:
throw new Error(`Failed to parse drink ${drinkAsString}.`);
}
};
export const DrinkUtils = {
parse: parse,
toHumanString: toHumanString,
toMachineString: toMachineString
};
I added a demo here.
This works like a class with static methods, as the DrinkUtil object is available in the IDE at compile time.

Related

Should I create a context for something that is mostly useCallback or simply create a hook?

I'm just doing a bit of refactoring and I was wondering if I have a bunch of useCallback calls that I want to group together, is it better do it as a simple hook that I would reuse in a few places?
The result would be
interface IUtils {
something(req: Something) : Result;
somethingElse(req: SomethingElse) : Result;
// etc...
}
So a plain hooks example would be:
export function useUtils() : IUtils {
// there's more but basically for this example I am just using one.
// to narrow the focus down, the `use` methods on this
// block are mostly getting data from existing contexts
// and they themselves do not have any `useEffect`
const authenticatedClient = useAuthenticatedClient();
// this is a method that takes some of the common context stuff like client
// or userProfile etc from above and provides a simpler API for
// the hook users so they don't have to manually create those calls anymore
const something = useCallback((req:SomethingRequest)=> doSomething(authenticatedClient), [authenticatedClient]
// there are a few of the above too.
return {
something
}
}
The other option was to create a context similar to the above
const UtilsContext = createContext<IUtils>({ something: noop });
export UtilsProvider({children}:PropsWithChildren<{}>) : JSX.Element {
const authenticatedClient = useAuthenticatedClient();
const something = useCallback((req:SomethingRequest)=> doSomething(authenticatedClient), [authenticatedClient]
const contextValue = useMemo({something}, [something]);
return <UtilsContext.Provider value={contextValue}>{children}</UtilsContext.Provider>
}
The performance difference between the two approaches are not really visible (since I can only test it in the device) even on the debugger and I am not sure how to even set it up on set up on jsben.ch.
Having it as just a simple hook is easier I find because I don't have to deal with adding yet another component to the tree, but even if I use it in a number of places I don't see any visible improvement but the devices could be so fast that it's moot. But what's the best practice in this situation?

RTK Query: Accessing cached data outside of a react component

We are in the process of integrating RTK query in our app.
Our current "architecture" is as follow:
All the business logic actions are written inside services which are plain JS classes.
Those services are passed using react context in order for the component tree to be able to call services functions.
As of now those services were accessing the redux store directly to perform the appropriate logic.
Now that we are moving to RTK, accessing the RTK cache from a service is less trivial:
As far as I can see, the only way to access it is via the select function of the relevant endpoint.
The point is that this method is a "selector factory" and using it outside of a react component doesn't seems to be the right way to go.
Here is an exemple:
class TodoService {
getTodoTitle( todoId: string ) {
// This doesn't looks the right way to do it
const todoSelector = api.endpoints.getTodo.select( {id: todoId } );
const todo = todoSelector( state )
return todo.data.title
}
}
Is there any way to implement safely the following code
class TodoService {
getTodoTitle( todoId: string ) {
// Is there any way to do that kind of call ?
const todoEntry = api.endpoints.getTodo.getCacheEntry( {id: todoId } );
return todoEntry.data.title
}
}
I guess that the answer is "no" and I have to refactor our whole architecture, but before doing so I'd like to be sure that there is no alternate approach.
Note that I could build the cache entry key by myself, but that also doesn't sound like a robust approach...
The thing is that you don't want to just get the cache entry - the selector does a little more for you than just that.
So let's just stay with "please use the selector" and "we won't add another way of doing that" because, selectors is how your code should interact with Redux all the time - React or not.
If you are not calling this code from React where you would need a stable object reference, it is good as it is. The selector factory will create a new selector and thus you get an un-memoized result. This is not perfect, but if you are not relying on referential equality, it also does not hurt at all.
If you want to have the referential equality, you'll have to store the selector in some way, probably as a class property.
Something like this would be possible:
class TodoService {
getSelectorForTodo(todoId: string) {
if (this._lastId !== todoId)
this._lastSelector = api.endpoints.getTodo.select( {id: todoId } )
return this._lastSelector
}
getTodoTitle( todoId: string ) {
const todo = this.getSelectorForTodo(todoId)(state)
return todo.data.title
}
}

Using String objects in react-native

I am working on moving all my core business logic to a model layer, which would be used across all my projects.
My project set mainly comprises of a web client-facing application built on Reactjs, few internal tools also built on Reactjs, and a mobile application built on react-native.
In my model layer, I have created custom data types to handle null/empty scenarios from backend and also to add custom format functions.
Following is the code for a string model which I have built.
/**
Author - Harkirat Saluja
Git - https://bitbucket.org/salujaharkirat/
**/
"use strict";
class CustomString {
static init (value = "") {
if (value === null) {
value = "";
}
const s = new String(value);
s.__proto__.upperCaseFormat = function () {
return this.toUpperCase();
};
return s;
}
}
export default WealthyString;
The way I invoke this is as follows:-
const firstName = WealthyString.init(firstName);
const lastName = WealthyString.init(lastName);
Now, if we see this returns a string object.
In my web project, I use this as follows in the react component, and it works nice and fine.
<span>{firstName}{" "} {lastName}</span>
But, in my react-native project, if I use it in the same way, it throws this error. Also, this error only comes when remote debugging if off and not when I am connected to chrome debugger.
<Text>{firstName}{" "} {lastName}</Text>
So, in order to resolve this for now, where strings are appended as the way shown above I have used toString(). But I was wondering is there something wrong the library or am I missing something?
Update
So looks like String objects are not working with Text in react-native at all. So to fix this I do the following:-
const SecondaryText = ({style, children}) => {
const styleCopy = addLineHeightToStyle(style, 14);
let dupChildren = children;
if (dupChildren instanceof String) {
dupChildren = dupChildren.toString();
}
return (
<ReactNative.Text
allowFontScaling={false}
style={[styles.secondaryText, styleCopy]}
>
{dupChildren}
</ReactNative.Text>
);
};
Built a wrapper over Text which react-native provides and convert the object to a string inside this.
Concatenate string using template literals in case of string combination.
Consider use template literals for complicated string. Eg:
<Text>{`${firstname} ${lastname}`}</Text>
Do you want to change this as follows?
<Text>{firstName +" "+lastName}</Text>
There are multiple ways you can do this. Write a function and return the concatenated string.
const test = (firstName ,lastName) => {
return firstName+lastName
}
If you have a class, you can do like this inside your render function.
<Text>{this.test()}</Text>

Append/push in seamless immutable js - react js

In Reducer for initial state I am using a seamless-immutable.
export const INITIAL_STATE = Immutable({
foo:[],
})
function orderReducer(state = initialState, action) {
console.log('reducer is runnig');
switch (action.type) {
case GET_MENU: {
return state.foo.push("newData") // how to achive this <----
}
default:
return state;
}
}
How do I to push new data into the foo?
If you want to stick with ImmutableJS you can use new List() that has a push method. Here are the docs. There are a bunch of data sets with different APIs that always return immutable objects. You will need to start learning those.
However if you are looking for a familiar way to deal with immutability I'd recommend you to switch to Immer. It let's you use plain javascript to return immutable objects, so you don't need to learn any new APIs. Take a look here:
https://github.com/mweststrate/immer
I personally always use plain javascript with the spread operator. I find it comfortable, fast, without any extra libraries. Very verbose when you get use to the syntax:
const pushedArr = [...oldArray, newElement]
const mergedObj = {...objA, ...objB}
Now that you know some alternatives, find the way that fits you and your team the most.

How to check whether a property supplied to React component is an instance of Immutable.js Record with Flow?

Trying to setup Flow in my project, but don't really understand how to work with Immutable records. I want to statically check component props, here is how I'm doing it:
// #flow
import React from "react";
import {render} from "react-dom";
import * as I from "immutable";
const Person = I.Record({
name: null,
});
type Props = {
data: Person,
};
const PersonInfo = (props: Props) => {
const {data} = props;
return (
<span>
Name: {data.name}
</span>
);
};
render(
<PersonInfo data={1} />, // I would expect to get some compile error here
document.getElementById("app")
);
I also added immutable.js.flow in the project and .flowconfig.
This is actually an issue with the Immutable.js type definition. It always returns an any type. Basically meaning Records aren't typechecked. I went into the reason why records are so loosly defined here. The gist of it is, Flow doesn't support intersect-types with objects yet (which the type of a record would have to be). But you can override the Record type with the more restrictive type definition describedd in this answer. I copied it over:
declare class Record<T: Object> {
static <T: Object>(spec: T, name?: string): Record<T>;
get: <A>(key: $Keys<T>) => A;
set<A>(key: $Keys<T>, value: A): Record<T>;
remove(key: $Keys<T>): Record<T>;
}
If you add this decleration as a local decleration, you won't be able to access the properties directly anymore (like you did with data.name), but will have to use the get function like this data.get('name'). IMO the downside of this definition is pretty minor, compared to the added type savety. Now sadly, due to other restrictions in the language, the types of the values aren't typechecked, as illustrated in this example.
Sadly there is no good solution for stronly typed immutable data structures in Flow yet. The features, required to make this perfect are pretty much on the roadmap for Flow though.
TL;DR
Records aren't typechecked, due to restrictions in the language. But you can improve typechecking by using the declaration provided above.

Resources