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
}
}
Related
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?
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.
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>
Recently, I start to learn reselect, and try to use it to my project.
But, I'm doubtful about where should I put the code that calculates the derived data.
Below is my code snippet, I think I put formatDate calcDayLeftFromNow setDeriveData logic to my reducer will also be fine.
I do the derive data calculate in my reducer will also be fine.
If I do this, it seems there is no reason to use reselect.
function formatDate(millisecond) {
let d = new Date(millisecond);
let dateArr = [d.getFullYear(), d.getMonth() + 1, d.getDate()];
let date = dateArr.join('.');
return date;
}
function calcDayLeftFromNow(endTimeNum) {
const timeDiff = endTimeNum - new Date().getTime();
const daysDiff = Math.ceil(timeDiff / (1000 * 3600 * 24));
return daysDiff;
}
function setDeriveData(coupons) {
return Object.values(coupons).map((coupon, index) => {
coupon.startDate = formatDate(coupon.startTimeNum);
coupon.endDate = formatDate(coupon.endTimeNum);
coupon.dayLeft = calcDayLeftFromNow(coupon.endTimeNum);
return coupon;
});
}
const mapStateToProps = state => {
const { coupons, current_tab, result, page } = state.yao_coupon;
const newCoupons = setDeriveData(coupons);
return {
coupons: newCoupons,
current_tab,
result,
page
};
};
It's common to put your selector's code in your container component. Or if you don't want to split container from presentational, just put it in your component.
Selectors' role is to compute derived data from the state (store).
Whereas reducers specify how the application's state changes in response to an action.
So they serve a very different role in your app.
In the Reselect readme, they're putting everything in one file just to showcase its use in the simplest way.
Here is a common folder structure that might help you make sense of this:
| reducers #folder
date.js
| components #folder
| Clock #folder
ClockContainer.js #contains mapStateToProps (and your selectors) and mapDispatchToProps
Clock.js #the clock component
Some people choose to put the selectors in a separate file. But it's up to you to decide. For example, you can put your selector in your container component and only move it to a separate file if it gets big. Another reason to move it to a separate file is in the event you need that same selector throughout parts of the app. (credits: #kwelch)
Edit
when I fetch bookList data from server, I calculate the derivedPrice in my FETCH_SUCCESS reducer
Calculating the derived price in your reducer will make it highly coupled with the api call, and you won't be able to use the dispatched action elsewhere.
My suggestion is to move this calculation out of the reducer and calculate the derivedPrice before dispatching the action.
I've been working with react/flux for a few weeks now and while I feel like I've got a pretty good handle on everything from async loading to updating props/states/etc, one thing that is still bothering me is how to handle save states.
For example, when loading data, I just have an isLoading boolean parameter in my store that gets passed to my components. But when I try and post an updated object to the server, it's trivial to:
fire the update action
display a "save in progress" state
but figuring out the result of the update action seems to be way more difficult.
Probably the most applicable post I've seen on this is in Fluxxor's async data guide, but their solution (adding/modifying a status property on the object) feels error-prone to me.
onAddBuzz: function(payload) {
var word = {id: payload.id, word: payload.word, status: "ADDING"};
this.words[payload.id] = word;
this.emit("change");
},
onAddBuzzSuccess: function(payload) {
this.words[payload.id].status = "OK";
this.emit("change");
},
onAddBuzzFail: function(payload) {
this.words[payload.id].status = "ERROR";
this.words[payload.id].error = payload.error;
this.emit("change");
}
Is there a better way to manage save states or is adding a status property to the object the best way?
I recommend keeping your "model stores" and "ui stores" separate, or at least accessed via different cursor positions in the same store. So, in your case you'd have one store or branch for your "word model" and then another store or branch for "word status."
While this adds some complexity in the form of breaking up logic across stores and reacting twice to the AddBuzz action, it ends up reducing (more) complexity by confining model store updates to true changes in model data and managing ui states separately.
EDIT
This is what Relay will more-or-less be doing, keeping persisted data in a separate self-managed store, leaving custom stores for nothing but ui state. Some other libraries like https://github.com/Yomguithereal/baobab also recommend this approach. The idea is that these are fundamentally different kinds of state. One is domain-specific persisted data and the other is ui-specific ephemeral application state.
It might look something like this:
model_store.js:
onAddBuzz: function(payload) {
var word = {id: payload.id, word: payload.word};
this.words[payload.id] = word;
this.emit("change");
}
ui_store.js:
onAddBuzz: function(payload) {
this.wordStates[payload.id] = 'ADDING';
this.emit("change");
}
my_view_controller.js:
// listen to both stores and pass down both words and wordStates to your views
my_word_view.js:
...
render: function() {
var word = this.props.word,
wordState = this.props.wordState;
...
}
If you don't want to emit two change events, have one waitFor the other and emit the change only from the second one.