Append/push in seamless immutable js - react js - reactjs

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.

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?

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

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.

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
}
}

React-Redux state not updating as expected—am I mutating reducer arguments?

thanks in advance for your attention with this (I believe) very basic question. I'm working on building my first "full-stack" application, and am running into something I can't quite wrap my head around with React-Redux. A brief explanation of the project: users can submit band idea names, and up or down vote others' submissions. Now, I believe that my problem is I'm not interacting with the state appropriately in my reducer dealing with MODIFY_BAND_SCORE actions. Here's the git repository, and I'll also copy and paste my store reducers here:
export const store = createStore(
combineReducers({
bands(bands = defaultState.bands, action) {
switch (action.type) {
case mutations.CREATE_BAND:
return [
...bands,
{
id: action.id,
owner: action.owner,
name: action.name,
score: 0,
flags: 0,
},
];
case mutations.MODIFY_BAND_SCORE:
let targetBandIndex = bands.findIndex(
(band) => band.id === action.bandID
);
let targetBand = bands.splice(targetBandIndex, 1)[0];
targetBand.score = targetBand.score + action.value;
bands.splice(targetBandIndex, 0, targetBand);
return bands;
}
return bands;
},
users(users = defaultState.users, action) {
return users;
},
}),
applyMiddleware(createLogger(), sagaMiddleware)
);
Hopefully that's enough context to make informed suggestions about what's going on here—my apologies for not having a truly minimal working example for this! The behavior I'm seeing from Redux-Logger when I dispatch an action of type MODIFY_BAND_SCORE is that I am (in a way) seeing the change reflected in that the correct band is having its score modified by the correct amount, but it is showing somehow in the previous and next states! Here's a screenshot:
I feel like I've maybe made this post longer than what it needs to be, am I correct in thinking that in my case for mutations.MODIFY_BAND_SCORE I'm actually modifying the state directly? This is probably occurring with my calling of .splice() on bands isn't it?
Like Siddharth mentioned,
let copyOfBands = [...bands]
will create a copy for you. It's important to remember that one of the key parts of Redux is that the store is read-only. It can be easy to forget that when dealing with non-primitive data (I've certainly done that a bunch), but you should always try to remember to make copies of the data, modify the copy, and then push the copy to store. This helps prevent you from getting really weird and hard to debug errors.
It is important to remember that the spread operator here will creates a shallow copy of the array, which means if you have other non-primitive objects inside the array (such as other arrays), you will have to copy those as well.

Add / Update Deep Array of Objects in React / Redux

In my react redux reducer, how do I add or update a deep nested array of objects with the spread operator? I’m having trouble getting the syntax right.
My state is roughly in this shape:
state: {
fields...
ups: {
fields...
ftr: {
fields...
arts: [
{
artId: 12,
name: ‘joe’,
phones: [
{
phoneId: 58,
number: ‘nnn’
}
]
}
]
}
}
}
I come in with artId and either with an empty phone object or an existing one to add/update the phones array. Likewise the same for the parent arts. Can't do byId and too late to switch to 'normilizr`.
To start, it's a little difficult comprehending the exact issue you are having.. It would be extremely useful if you could supply some of the code you are using to update your state inside of your reducer, not just the shape of your state.. In order to accurately assist, we need to see more code..
With that being said, if I am understanding this correctly, when you are updating state in a reducer, you need to make a deep copy first.. or are you trying to use the spread operator like this: fields...?...or is that just for brevity?
The spread operator offers a shallow copy, so you'll have to do something like this to get a deep[er] copy:
case types.SOME_CONSTANT: {
let _newstate = JSON.parse(JSON.stringify(state));
_newstate.some.deep.nested.property = action.payload;
return _newstate;
}

Resources