16

I'm trying to use JSDoc to document the destructured parts of my react state hooks for example:

const [referenceState, setReferenceState] = useState(null);

Here, referenceState is of type Object, and setReferenceState expects an Object.

Based on some information online, I'm trying to do something along the lines of:

/**
* @param {Object} stateToSet
* @returns {GenericArray} current state and function to change value
*/
const [referenceState, setReferenceState] = useState(null);

But that doesn't generate anything..

Can someone please help me document referenceState and setReferenceState?

Brett Zamir
  • 14,034
  • 6
  • 54
  • 77
theabhinavdas
  • 454
  • 4
  • 16
  • What type is `referenceState`? An object? and what params does `setReferenceState` expect and return? – Brett Zamir Oct 19 '20 at 12:49
  • @BrettZamir sorry I didn't add those details. I've edited the question. And you're right, `referenceState` is an object, while `setReferenceState` expects an object. – theabhinavdas Oct 19 '20 at 14:03

6 Answers6

6

In webstorm, You can write like this (I haven't tested it in other editors):

const [state, setState] = useState(/** @type {{name: string, age: number?}} */null)

or

/**
 * @typedef People
 * @property {string} name
 * @property {number} [age]
 */

//........

const [state, setState] = useState(/** @type {People} */null)

zhang0ZGC
  • 69
  • 1
  • 1
4

I think you can try this approach:

/**
 * @typedef {Object} ReferenceState
 */

/**
 * @callback ReferenceStateSetter
 * @param {ReferenceState} state
 * @returns {void}
 */

/**
 * @namespace {Object}
 * @property {ReferenceState} 0
 * @property {ReferenceStateSetter} 1 
 */
const [referenceState, setReferenceState] = useState(null);

Or, to avoid having to document the immediately destructured array, but benefiting from adding some indent changes at the end:

/**
 * @typedef {Object} ReferenceState
 */

/**
 * @callback ReferenceStateSetter
 * @param {ReferenceState} state
 * @returns {void}
 */

const [
    /**
     * @type {ReferenceState}
     */
    referenceState,

    /**
     * @type {ReferenceStateSetter}
     */
    setReferenceState
] = useState(null);

If you don't want to have documents for ReferenceState, you can get rid of its @typedef and replace references to it with Object, but I think it is clearer to have docs.

void above is a simpler way to say nothing special (i.e., undefined) is returned--if that's what the setter returns. Some projects would just drop the @returns if it only returns undefined, but I like to add it to show the return value is known to be undefined and not merely undocumented.

Brett Zamir
  • 14,034
  • 6
  • 54
  • 77
4

The shortest way is this: by adding a template

/**
 * @type {[MyType, React.Dispatch<MyType>]} state
 */
const [value, setValue] = useState(null);

I also use *.d.ts files in the js projects. VS code sees them and uses them so you can describe types and interfaces in a modern way.

Just put a line

type useState<T> = [T, React.Dispatch<T>];

to a useState.d.ts file and then in a JS file use

/** @type {useState<MyType>} */
const [value, setValue] = useState(null);
zamuka
  • 796
  • 2
  • 9
  • 21
  • For some reason the typecast I wrote above works fine only with useState(null). If you provide some decent initial state value, then this value type overloads the casted type.. and even more interesting... in the useState line VS code still shows casted type, but below the value has type of the value inside useState. The solution with providing type inside useState() is more robust. Despite it looks much worse I use it sometimes. – zamuka Feb 17 '23 at 07:44
2

I would create a generic type that equals the same return type as React's useState function.

/**
 * Add this type in top of your file, or if commonly used in some types file.
 * @template T
 * @typedef {[T, import('react').Dispatch<import('react').SetStateAction<T>>]} useState
 */

Then you could use it in you React component like this:

/** @type {useState<string>} */
const [someString, setSomeString] = useState('');

/** @type {useState<number>} */
const [someNumber, setSomeNumber] = useState(2);

Or if you want to use a custom object:

/**
 * @typedef SomeOtherType
 * @property {string} property1
 * @property {number} property2
 */

/** @type {useState<SomeOtherType>} */
const [someOtherValue, setSomeOtherValue] = useState(null);
Herman Jansson
  • 164
  • 1
  • 8
0

As an alternative, the variables can be declared prior to destructuring and annotated with JSDoc as normal.

/**
* Current state.
* @type {Object}
*/
let referenceState;
/**
* Current state setter.
* @type {Function}
* @param {any} state updated state value.
* @returns void
*/
let setReferenceState;
[referenceState, setReferenceState] = useState(null);
qslabs
  • 406
  • 4
  • 8
0

Give the initial value in useState(value) a type.

The TypeScript types included with @types/react will be used to infer the type of your destructured array values. If you are using Create React App (react-scripts) then you should already have @types/react installed. Otherwise it can be installed as a devDependency.

Note the brackets around null, which will help if using VSCode, which checks JSDoc types with TypeScript. See How to cast TypeScript type in javascript using JSDoc.

string example

const [name, setName] = useState(/** @type {string?} */ (null));

object example

/**
 * @typedef {{ name: string, age: number}} Person
 */

const [people, setPeople] = useState(/** @type {Person[]?} */ (null));
markdon
  • 784
  • 8
  • 19