Using lodash/set on a React.useState object - reactjs

when using React.useState, I can't mutate the object before using the setState method.
for example:
const [buffer, setBuffer] = React.useState(props.someObject);
function handleChange(field: string, data: someObject) {
const update = lodash.set(buffer, field, data);
setBuffer(update);
}
const update never gets the updated data at the path of field. If field is something like "path/path/value" just using setBuffer isn't simple without the help of lodash.set
I know I can do const update = lodash.set(lodash.cloneDeep(buffer), field, data); but that is expensive, especially when we're talking about handleChange being called on every user interaction with a form.
Any alternative approaches or insights into why updates on React.useState objects don't work is much appreciated!

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?

StencilJS unit testing keyboard event?

I want to write unit tests for my custom web-components in stencilJs but have no idea how to do it the right way. Here's what I did so far!
.tsx
...
valueFormat(event: Event): void {
const val = (event.target as HTMLInputElement).value;
const format = Number.parseInt(val, 10);
const newVal = format.toLocaleString(undefined, {
minimumFractionDigits: 2,
});
this.value = newVal;
}
.spec.tsx
it('should format value', async () => {
const comp = new MyComponent();
const spy = jest.spyOn(comp, 'valueFormat');
comp.myInputEvent.emit();
expect(spy).toHaveBeenCalled();
});
I want to test the case, when I type a number in the input field that it format it. So my valueFormat() method, I spying on should be called when a Keyboard event is firing. I hope you can help me out!
If you want to test it with Event in mind, I would strongly recommend you to use newSpecPage(https://stenciljs.com/docs/unit-testing) - as this will allow you to construct your component DOM in memory and allow you to test its logic (so you can easily trigger event like click, keyboard or trigger input value change which I assume where your valueFormat() method get called/binded?)
Another approach is to move formatting logic to separate function which takes just input value as an argument like:
formatInputValue(value: string) {
const format = Number.parseInt(value, 10);
const newVal = format.toLocaleString(undefined, {
minimumFractionDigits: 2,
});
return newVal;
}
then you could easily unit test this method by simply constructing component and then calling the method with whatever the value you want to test with (this is useful if you want to test edge cases like null, empty value, non numeric value etc.)
Personally I wouldn't bother creating function as conversion logic seem to be simple - also one advantage of doing testing via DOM (using newSpecPage()) is that if you ever want to change your formatting logic, amount of test code you need to update could be quite small, meaning your test code is bit more maintainable (again just my personal opinion, it's all depends on how complex the formatting logic or the expected input be)

Get current value of Animated.Value, React-native

I'm trying to animate View with interpolate. I'd like to get a current value of my Animated.Value, but don't know how. I didn't understand how to do it with React-native docs.
this.state = {
translateAnim: new Animated.Value(0)
}
DeviceEventEmitter.addListener('Accelerometer', function (data) {
console.log(this.state.translateAnim);
// returns an object, but I need a value in current moment
}
I find out, how to get a value:
this.state.translateAnim.addListener(({value}) => this._value = value);
EDIT
to log a value I do the following:
console.log(this.state.translateAnim._value)
This also works for me...
const headerHeight = new Animated.Value(0);
After some manipulation....
console.log(headerHeight.__getValue())
It feels hackish but it gets the job done...
For the people with typescript.
console.log((this.state.translateAnim as any)._value);
It worked for me to full tsc as any.
Number.parseInt(JSON.stringify(translateAnim))
It works on React Hook
edit: CAUTION - MAY CAUSE SEVERE PERFORMANCE ISSUES. I have not been able to figure out why, but if you use this for 30+ simultaneous animations your framerate will slow to a crawl. It seems like it must be a bug in react-native with Animated.Value addListener as I don't see anything wrong with my code, it only sets a listener which sets a ref which should be instantaneous.
Here's a hook I came up with to do it without resorting to accessing private internal values.
/**
* Since there's no (official) way to read an Animated.Value synchronously this is the best solution I could come up with
* to have access to an up-to-date copy of the latest value without sacrificing performance.
*
* #param animatedValue the Animated.Value to track
* #param initial Optional initial value if you know it to initialize the latest value ref before the animated value listener fires for the first time
*
* returns a ref with the latest value of the Animated.Value and a boolean ref indicating if a value has been received yet
*/
const useAnimatedLatestValueRef = (animatedValue: Animated.Value, initial?: number) => {
//If we're given an initial value then we can pretend we've received a value from the listener already
const latestValueRef = useRef(initial ?? 0)
const initialized = useRef(typeof initial == "number")
useEffect(() => {
const id = animatedValue.addListener((v) => {
//Store the latest animated value
latestValueRef.current = v.value
//Indicate that we've recieved a value
initialized.current = true
})
//Return a deregister function to clean up
return () => animatedValue.removeListener(id)
//Note that the behavior here isn't 100% correct if the animatedValue changes -- the returned ref
//may refer to the previous animatedValue's latest value until the new listener returns a value
}, [animatedValue])
return [latestValueRef, initialized] as const
}
It seems like private property. But works for me. Helpful for debugging, but wouldn't recommend using it in production.
translateAnim._value
I actually found another way to get the value (not sure if it is a recommended way, but it works).
Use JSON.stringify() on the animated value and use Number on the result to convert it into Number.
E.g,
const animatedVal = new Animated.Value(0);
const jsanimated = JSON.stringify(animatedVal);
const finalResult = Number(jsanimated)

reselect, Where do I put the calculate derive data logic?

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.

Immutable JS - How to replace a list with a new list

I have a React + Redux app which uses Immutability.js. In my reducer I have state defined as an Immutable List
const initialSuggestedResponseState = Immutable.List([]);
Every time a specific action happens, I would like to replace this list with a new list. I think I am missing something with Immutability.js as I have not been able to figure out a working solution. So far I have tried things like this:
state.clear();
action.messages.forEach(message => {
let newResponse = new suggestedResponseRecord({
body: message.body,
responseId: message.response_id,
type: message.type
});
state.push(newResponse);
});
return state;
This however does not push anything to my list. Why does this not work, and what is the appropriate methodology? I'm sure there is something MUCH simpler.
Thanks in advance :)
Doing state.push on any sort of immutable object returns a new object. The existing state is not modified, which is why returning the state after pushing to it doesn't work. A quick hack would be to change state.push(newResponse); to state = state.push(newResponse);. I suggest you read up more on how immutable structures work :)
As a follow-up to zackify's answer, here's one approach you could take to simplifying your code:
return new List(action.messages.map(message => {
return new suggestedResponseRecord({
body: message.body,
responseId: message.response_id,
type: message.type
});
});
This creates an array of records from your array of messages (using Array.prototype.map), then converts that new array into an immutable list. Not sure if this is the most efficient way of doing things, but it's quite readable.

Resources