How to document useCallback with jsdoc? - reactjs

If I have my arrow function, wrapped in a React useCallback, how am I supposed to document it with JSDoc?
const floopPig = useCallback((pigKey, floopCost) => {
const pigAbility = pigs[pigKey];
if (pigAbility.floopCost < magicPoints) {
return pigAbility.activate()
} else {
throw new Error('Not enough magic points');
}
}, [pigs, magicPoints])
I have been trying this in VSCode, but I haven't gotten anything to show up on hover over:
/**
* Floops a pig
* #callback
* #param {number} pigKey unique key for each pig
* #param {number} floopCost how much the ability costs to activate
*/
I think I've followed the documentation for #callback correctly. But based on this answer perhaps I'm using #callback for the wrong purpose?
How am I supposed to document this?

I've had success documenting the callback function itself.
const getNewToken = useCallback(
/**
* Requests a token, either by the normal flow or with a refresh token
* #param {URLSearchParams} axiosBody The body of the axios request
*/
async (axiosBody) => {
//Function
}, [myVar]);
Lower in the code, I can see intellisense working its magic when I call my function. I'm not 100% sure this is the best answer, but this is the best answer I've found

Related

How to properly deal with code that need to wait for previous constants to be loaded?

Here is a change to a long code before a render that wasted me time to correct many lines in the code:
Before:
const my_model:MyModel = JSON.parse(localStorage.getItem('my_model')!)
const my_positions:MyModel = JSON.parse(localStorage.getItem('my_positions')!)
After:
const waitformyIcm:Boolean = useDetailMyModelQuery(paramid.id).isSuccess;
const myIcm:undefined | MyModel| any = useDetailMyModelQuery(paramid.id).data
const waitforpositions:Boolean = usePatternPositionsPerModelQuery(paramid.icm).isSuccess;
const positions:undefined | PatternPositions[] = usePatternPositionsPerModelQuery(paramid.icm).data
The consequence is that for almost all the next constants I needed to let the program wait until those first lines completed loading. My question is how to cope with such situations because I feel my approach was not as it should:
For example this previous line would already cause crashes:
const harvestAfter:number[] = [myIcm.length_period1, myIcm.length_period2, myIcm.length_period3]
This was solved by:
const harvestAfter:number[] = waitformyIcm && [myIcm.length_period1, myIcm.length_period2, myIcm.length_period3]
Another challenge was to set a default state with data that had to be loaded first. I solved that with:
const [myFilters, setMyFilters] = useState(myfilters);
useEffect(() => setMyFilters(myfilters), [myfilters && waitforpositions]);
How to properly deal with code that need to wait for previous constants to be loaded in REACT?
Using myconst && ......?
Using useEffect?
Using useRef?
Using async await (how to do that for declaring constants)
Other?
Please don't call the same hook multiple times, that is just a waste of user memory and CPU time. (These hooks are doing work internally and you invoke them twice for no good reason.)
Just don't annotate the types here and assign them to a variable. They are already 100% typesafe from the hook.
Then, use skip:
const icmResult = useDetailMyModelQuery(paramid.id);
const patternsResult = usePatternPositionsPerModelQuery(paramid.icm, { skip: !icmResult.isSuccess });
As for the variable assignment, you could also use destructuring although I don't really see the point:
const { isSuccess: waitformyIcm, data: myIcm } = useDetailMyModelQuery(paramid.id);
As for a default state, there are many approaches. If it is an object you define inline, it would be a new reference on every render, so you best useMemo to get around that:
const filtersWithDefaults = useMemo(() => filters ?? defaultFilters, [filters])

How to get d3 shape constructors to return the type string properly in typescript?

I am really stuck on how to work with #types/d3 properly. All the return types are really confusing and even console.logging is not helping.
Essentially when I call the arc() construct I get an error that: An argument for 'd' was not provided. But when I console.log it only returns the proper path string and not an error or invalid value.
I can't seem to find what to put as the argument as the d value in the constructor. Also, a lot of js code I've seen doesn't have any input, what specific reason does typescript need to have an additional value?
const Test = () => {
// ...........
// causes error if mArc is not mArc: any
const mArc = arc() // how to not force any as a type here
.innerRadius(radius)
.outerRadius(width + radius)
.startAngle(Math.PI / 2)
.endAngle((Math.PI * 3) / 2)
console.log(mArc()); // returns a string...
// but typescript throws an error, An argument for 'd' was not provided.
return <path d={mArc()} />;
}
Sorry, I may not be understanding this software engineering correctly. When I look in the types.d file for d3-shape, it says no definition found for 'Datum', which is the type 'd' is supposed to be
I was also struggling with getting the type safety working with d3. There are two methods to use the arc method, via chaining (which seems to be the most popular) or via an object. It doesn't seem like the d3 type definitions work with (fully) chaining. Calling your mArc() function with no arguments is the issue (although it works).
One caviate is that cornerRadius doesn't seem to be in the type definition for the object (padAngle does work), however it can be set on your mArc function via chaining.
The following snippet will work with no type errors.
import * as d3 from "d3";
const Test = () => {
const radius = 100;
const width = 5;
const mArc = d3.arc();
return (
<path
d={
mArc({
innerRadius: radius,
outerRadius: width + radius,
startAngle: Math.PI / 2,
endAngle: (Math.PI * 3) / 2,
}) || ""
}
/>
);
};
A proper answer:
const Test = () => {
const mArc = arc<void, void>()
.innerRadius(radius)
.outerRadius(width + radius)
.startAngle(Math.PI / 2)
.endAngle((Math.PI * 3) / 2)
console.log(mArc());
return <path d={mArc() || ""} />;
}

Redux dispatch on requestAnimationFrame taking 250-300ms

I'm having a hard time figuring out why this is the case, but one of the simplest actions in my React app is being dispatched on each animation frame like so:
step = () => {
if (this.player.howler.playing()) {
this.props.playProgress(this.player.seek())
window.requestAnimationFrame(this.step)
}
}
playProgress is an absurdly simple action, but is taking a ludicrous amount of time to execute:
/**
* Dispatched when currentlyPlaying's position changes
*
* #param {integer} pos Position to progress to (in seconds)
*
* #return {object} An action object with a type of PLAY_PROGRESS
*/
export function playProgress(pos) {
return {
type: PLAY_PROGRESS,
pos,
}
}
And it's reduced like so:
case PLAY_PROGRESS:
return state
.setIn(['currentlyPlaying', 'pos'], action.pos)
Why on earth would that take so long to execute?? window.requestAnimationFrame only gives you 16ms before it executes again, so 250ms isn't cutting it... Any help is appreciated!
This is an old question, but I believe it was related to an infinite scrolling issue we encountered in Chrome. Here's a rundown: github.com/CassetteRocks/react-infinite-scroller/pull/125

Chrome extension won't enter for..of loop despite array length equal to 1

Background
Developing a Chrome extension (latest Chrome running on Mac OS Sierra) and I can't work out how to loop over an array which is also dynamically built at runtime.
Forgive me if I am missing something really obvious, but I cannot for the life of me work out why this for..of loop is not being entered.
I've also tried a for..in and the good old classic for loop structure i.e. for (let i = 0; i < array.length; i++) - no matter what style of loop this block is never entered, despite my array at runtime reportedly having a single item in it.
Problem Code and Statement
This code gets all files inside a directory and slices off the last 3 chars (to remove .js):
const getDirectoryContents = (path) => {
let fileNames = []
chrome.runtime.getPackageDirectoryEntry( (directoryEntry) => {
directoryEntry.getDirectory(path, {}, (subDirectoryEntry) => {
const subDirectoryReader = subDirectoryEntry.createReader()
subDirectoryReader.readEntries( (entries) => {
for (const entry of entries) {
fileNames.push(entry.name.slice(0, -3))
}
})
})
})
return fileNames
}
From inside the chrome.runtime.onStartup() callback function we want to add some context menus, which we do like so:
const addContextMenus = () => {
console.log(getDirectoryContents('commands'))
for (const command of getDirectoryContents('commands')) {
const properties = {
id: command,
title: command,
contexts: ['editable']
}
chrome.contextMenus.create(properties)
console.log(`Created context menu ${properties.title}`)
}
console.log('End addContextMenus')
}
Now, during runtime, the above code will output this inside the background page console:
However as we can see (due to the lack of the console logging "Created context menu ..." - the loop is never entered, despite the array having a length of 1.
I've found nothing online inside the Chrome developer docs that indicated that getDirectoryContents is asynchronous -- which would be one possible explanation -- but just to be sure I even tried adding a callback param to the getDirectoryContents function to ensure we are looping after the array has been populated.
EDIT: after closer inspection of the original function, it's clear that the array is in fact being returned before is has a chance to be populated by the directory reader. Answer below.
Same result!
Any help would be much appreciated. Thanks for your time.
How embarrassing! Passing in a callback function and executing it at the right time solved it. Comments were all correct - typical async issue - thanks for the support.
The problem was on line 15 of the original function: I was returning the array before it had a chance to be populated.
Working function:
const getDirectoryContents = (path, callback) => {
chrome.runtime.getPackageDirectoryEntry( (directoryEntry) => {
directoryEntry.getDirectory(path, {}, (subDirectoryEntry) => {
const subDirectoryReader = subDirectoryEntry.createReader()
let fileNames = []
subDirectoryReader.readEntries( (entries) => {
for (const entry of entries) {
fileNames.push(entry.name.slice(0, -3))
}
callback(fileNames)
})
})
})
}

symfony2 routing, optional prefix from database

Basically what i want to achieve is this:
I have this paths:
http://SITENAME/dashboard
http://SITENAME/users
And they are mapped to the right controllers and actions:
/**
* #Route("/dashboard")
*/
public function dashboardAction()
{
// handle the request
}
/**
* #Route("/users")
*/
public function usersAction()
{
// handle the request
}
Now I want to make these other paths to map to the same controllers and actions:
http://SITENAME/{companyName}/dashboard
http://SITENAME/{companyName}/users
companyName is a name that I check on db if exists and if not throw a NotFound Exception.
These paths will just add some filter to the queries made, basically showing the same data structure.
I know I can create other actions and put those after the previous ones in order to be catched but I'm asking if there's something clever... something like this:
/**
* #Route("/({companyName}/)dashboard")
*/
public function dashboardAction($companyName = null)
{
// handle the request
}
where companyName is optional.
Thanks...
UPDATE
As Manolo suggested I already tried something like this and it works:
/**
* #Route("/dashboard")
* #Route("/{companyName}/dashboard")
*/
public function dashboardAction($companyName = null)
{
// handle the request
}
But I think there's a clever way of handling it...
Look at this example: http://symfony.com/doc/current/book/routing.html#routing-with-placeholders
// src/AppBundle/Controller/BlogController.php
// ...
/**
* #Route("/blog/{page}", defaults={"page" = 1})
*/
public function indexAction($page)
{
// ...
}
When page is not defined in the route, it is equal to 1.
But this won't work in your case, because you defined two routes for the same action:
/dasboard
/{companyName}/dashboard
So you have two options:
1. Define two routes for the same action (weird).
2. Change your defined route to: /dashboard/{companyName} (better)
Then, you can get /dashboard and /dashboard/{companyName} with the same action.
Try like this:
/**
* #Route("/{companyName}/dashboard")
*/
public function dashboardAction($companyName = null)
{
// handle the request
}
I suggest you to use the #ParamConverter annotation calls converters to convert request parameters to objects.
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
/**
* #Route("/dashboard")
* #Route("/({companyName}/)dashboard")
* #ParamConverter("company", class="AcmeDemoBundle:Company", options={"name" = "companyName"})
*/
public function showAction(Company $company = null)
{
}
The double route definition permit the default null company value
Hope this help

Resources