meeting with james
Use .not. to test for false positives
{% embed url="https: //codesandbox.io/s/github/kentcdodds/react-testing-library-examples/tree/main" %}
API
React Testing Library
re-exports everything from DOM Testing Library
as well as these methods:
[render
](https: //testing-library.com/docs/react-testing-library/api/#render)
[render
Options](https: //testing-library.com/docs/react-testing-library/api/#render-options)
[container
](https: //testing-library.com/docs/react-testing-library/api/#container)
[baseElement
](https: //testing-library.com/docs/react-testing-library/api/#baseelement)
[hydrate
](https: //testing-library.com/docs/react-testing-library/api/#hydrate)
[wrapper
](https: //testing-library.com/docs/react-testing-library/api/#wrapper)
[queries
](https: //testing-library.com/docs/react-testing-library/api/#queries)
[render
Result](https: //testing-library.com/docs/react-testing-library/api/#render-result)
[...queries
](https: //testing-library.com/docs/react-testing-library/api/#queries-1)
[container
](https: //testing-library.com/docs/react-testing-library/api/#container-1)
[baseElement
](https: //testing-library.com/docs/react-testing-library/api/#baseelement-1)
[debug
](https: //testing-library.com/docs/react-testing-library/api/#debug)
[rerender
](https: //testing-library.com/docs/react-testing-library/api/#rerender)
[unmount
](https: //testing-library.com/docs/react-testing-library/api/#unmount)
[asFragment
](https: //testing-library.com/docs/react-testing-library/api/#asfragment)
[cleanup
](https: //testing-library.com/docs/react-testing-library/api/#cleanup)
[act
](https: //testing-library.com/docs/react-testing-library/api/#act)
render
[](https:
//testing-library.com/docs/react-testing-library/api/#render)
function render( ui: React.ReactElement<any>, options?: { /* You won't often use this, expand below for docs on options */ },): RenderResult
Copy
Render into a container which is appended to document.body
.
import {render} from '@testing-library/react'render(<div />)
Copy
import {render} from '@testing-library/react'import '@testing-library/jest-dom'test('renders a message', () => {
const {container, getByText} = render(<Greeting />) expect(getByText('Hello, world!')).toBeInTheDocument() expect(container.firstChild).toMatchInlineSnapshot(` <h1>Hello, World!</h1> `)})
Copy
render
Options[](https:
//testing-library.com/docs/react-testing-library/api/#render-options)
You won't often need to specify options, but if you ever do, here are the available options which you could provide as a second argument to render
.
container
[](https:
//testing-library.com/docs/react-testing-library/api/#container)
By default, React Testing Library
will create a div
and append that div
to the document.body
and this is where your React component will be rendered. If you provide your own HTMLElement container
via this option, it will not be appended to the document.body
automatically.
For example: If you are unit testing a tablebody
element, it cannot be a child of a div
. In this case, you can specify a table
as the render container
.
const table = document.createElement("table");
const { container } = render(<TableBody {...props} />, {
container: document.body.appendChild(table),
});
Copy
baseElement
[](https:
//testing-library.com/docs/react-testing-library/api/#baseelement)
If the container
is specified, then this defaults to that, otherwise this defaults to document.body
. This is used as the base element for the queries as well as what is printed when you use debug()
.
hydrate
[](https:
//testing-library.com/docs/react-testing-library/api/#hydrate)
If hydrate is set to true, then it will render with [ReactDOM.hydrate](https: //reactjs.org/docs/react-dom.html#hydrate). This may be useful if you are using server-side rendering and use ReactDOM.hydrate to mount your components.
wrapper
[](https:
//testing-library.com/docs/react-testing-library/api/#wrapper)
Pass a React Component as the wrapper
option to have it rendered around the inner element. This is most useful for creating reusable custom render functions for common data providers. See [setup](https: //testing-library.com/docs/react-testing-library/setup#custom-render) for examples.
queries
[](https:
//testing-library.com/docs/react-testing-library/api/#queries)
Queries to bind. Overrides the default set from DOM Testing Library
unless merged.
// Example, a function to traverse table contentsimport * as tableQueries from 'my-table-query-library'import {queries} from '@testing-library/react'
const { getByRowColumn, getByText } = render(<MyTable />, {
queries: { ...queries, ...tableQueries },
});
Copy
See [helpers](https: //testing-library.com/docs/dom-testing-library/api-custom-queries) for guidance on using utility functions to create custom queries.
Custom queries can also be added globally by following the [custom render guide](https: //testing-library.com/docs/react-testing-library/setup#custom-render).
render
Result[](https:
//testing-library.com/docs/react-testing-library/api/#render-result)
The render
method returns an object that has a few properties:
...queries
[](https:
//testing-library.com/docs/react-testing-library/api/#queries-1)
The most important feature of render
is that the queries from [DOM Testing Library](https: //testing-library.com/docs/queries/about) are automatically returned with their first argument bound to the [baseElement](https: //testing-library.com/docs/react-testing-library/api/#baseelement), which defaults to document.body
.
See [Queries](https: //testing-library.com/docs/queries/about) for a complete list.
Example
const { getByLabelText, queryAllByTestId } = render(<Component />);
Copy
container
[](https:
//testing-library.com/docs/react-testing-library/api/#container-1)
The containing DOM node of your rendered React Element (rendered using ReactDOM.render
). It's a div
. This is a regular DOM node, so you can call container.querySelector
etc. to inspect the children.
Tip: To get the root element of your rendered element, use container.firstChild
.
NOTE: When that root element is a [React Fragment](https: //reactjs.org/docs/fragments.html), container.firstChild
will only get the first child of that Fragment, not the Fragment itself.
🚨 If you find yourself using container
to query for rendered elements then you should reconsider! The other queries are designed to be more resilient to changes that will be made to the component you're testing. Avoid using container
to query for elements!
baseElement
[](https:
//testing-library.com/docs/react-testing-library/api/#baseelement-1)
The containing DOM node where your React Element is rendered in the container. If you don't specify the baseElement
in the options of render
, it will default to document.body
.
This is useful when the component you want to test renders something outside the container div, e.g. when you want to snapshot test your portal component which renders its HTML directly in the body.
Note: the queries returned by the render
looks into baseElement, so you can use queries to test your portal component without the baseElement.
debug
[](https:
//testing-library.com/docs/react-testing-library/api/#debug)
NOTE: It's recommended to use [screen.debug
](https: //testing-library.com/docs/queries/about#screendebug) instead.
This method is a shortcut for console.log(prettyDOM(baseElement))
.
import React from 'react'import {render} from '@testing-library/react'
const HelloWorld = () => <h1>Hello World</h1>
const {debug} = render(<HelloWorld />)debug()
// <div>
// <h1>Hello World</h1>
// </div>
// you can also pass an element: debug(getByTestId('messages'))
// and you can pass all the same arguments to debug as you can
// to prettyDOM:
//
const maxLengthToPrint = 10000
// debug(getByTestId('messages'), maxLengthToPrint, {highlight: false})
Copy
This is a simple wrapper around prettyDOM
which is also exposed and comes from [DOM Testing Library
](https: //testing-library.com/docs/dom-testing-library/api-debugging#prettydom).
rerender
[](https:
//testing-library.com/docs/react-testing-library/api/#rerender)
It'd probably be better if you test the component that's doing the prop updating to ensure that the props are being updated correctly (see [the Guiding Principles section](https: //testing-library.com/docs/guiding-principles)). That said, if you'd prefer to update the props of a rendered component in your test, this function can be used to update props of the rendered component.
import { render } from "@testing-library/react";
const { rerender } = render(<NumberDisplay number={1} />);
// re-render the same component with different propsrerender(<NumberDisplay number={2} />)
Copy
[See the examples page](https: //testing-library.com/docs/example-update-props)
unmount
[](https:
//testing-library.com/docs/react-testing-library/api/#unmount)
This will cause the rendered component to be unmounted. This is useful for testing what happens when your component is removed from the page (like testing that you don't leave event handlers hanging around causing memory leaks).
This method is a pretty small abstraction over ReactDOM.unmountComponentAtNode
import {render} from '@testing-library/react'
const {container, unmount} = render(<Login />)unmount()
// your component has been unmounted and now: container.innerHTML === ''
Copy
asFragment
[](https:
//testing-library.com/docs/react-testing-library/api/#asfragment)
Returns a DocumentFragment
of your rendered component. This can be useful if you need to avoid live bindings and see how your component reacts to events.
import React, {useState} from 'react'import {render, fireEvent} from '@testing-library/react'
const TestComponent = () => {
const [count, setCounter] = useState(0) return ( <button onClick={() => setCounter(count => count + 1)}> Click to increase: {count} </button> )}
const {getByText, asFragment} = render(<TestComponent />)
const firstRender = asFragment()fireEvent.click(getByText(/Click to increase/))
// This will snapshot only the difference between the first render, and the
// state of the DOM after the click event.
// See https:
//github.com/jest-community/snapshot-diffexpect(firstRender).toMatchDiffSnapshot(asFragment())
Copy
cleanup
[](https:
//testing-library.com/docs/react-testing-library/api/#cleanup)
Unmounts React trees that were mounted with [render](https: //testing-library.com/docs/react-testing-library/api/#render).
Please note that this is done automatically if the testing framework you're using supports the afterEach
global and it is injected to your testing environment (like mocha, Jest, and Jasmine). If not, you will need to do manual cleanups after each test.
For example, if you're using the [ava](https: //github.com/avajs/ava) testing framework, then you would need to use the test.afterEach
hook like so:
import {cleanup, render} from '@testing-library/react'import test from 'ava'test.afterEach(cleanup)test('renders into document', () => { render(<div />)
// ...})
// ... more tests ...
Copy
Failing to call cleanup
when you've called render
could result in a memory leak and tests which are not "idempotent" (which can lead to difficult to debug errors in your tests).
act
[](https:
//testing-library.com/docs/react-testing-library/api/#act)
This is a light wrapper around the [react-dom/test-utils
act
function](https: //reactjs.org/docs/test-utils.html#act). All it does is forward all arguments to the act function if your version of react supports act
. It is recommended to use the import from @testing-library/react
over react-dom/test-utils
for consistency reasons.
jest.mock('src/lib/useIntersection');
it.skip('lets you skip the test for now')
If you are about to write an 'and' in the it block... create another it block
renderWithCTX(<Accordion {...props} />);
screen.logTestingPlaygroundURL();
playground needs to be under first render
screen.debug(numitems)
Cheatsheet
[Get the printable cheat sheet](https: //github.com/testing-library/react-testing-library/raw/main/other/cheat-sheet.pdf)
A short guide to all the exported functions in React Testing Library
render const {/* */} = render(Component)
returns:
unmount
function to unmount the component
container
reference to the DOM node where the component is mounted
all the queries from DOM Testing Library
, bound to the document so there is no need to pass a node as the first argument (usually, you can use the screen
import instead)
import {render, fireEvent, screen} from '@testing-library/react'test('loads items eventually', async () => { render(<Page />)
// Click button fireEvent.click(screen.getByText('Load'))
// Wait for page to update with query text
const items = await screen.findAllByText(/Item #[0-9]: /) expect(items).toHaveLength(10)})
import { screen, fireEvent } from "@testing-library/react";
import { renderWithCTX } from "src/lib/testWrappers";
import { compositionFunction } from "./composition";
import data from "./data";
import Accordion from "./index";
import "@testing-library/jest-dom";
jest.mock("src/lib/useIntersection");
describe("Accordion", () => {
const props = compositionFunction(data);
it("render the correct items", () => {
renderWithCTX(<Accordion {...props} />);
screen.logTestingPlaygroundURL();
const text = screen.getByText();
for (let i = 0; i < props.items.length - 1; i++)
//
const accordionItemsText = screen.getAllByRole("listitem");
//
// expect(numItems.length).toBe(data.fields.items.length);
// expect(accordionItemsText[0]).toHaveTextContent(props?.items[0]?.text?.value as string);
// typescript needs the value passed in to be a string
});
it("should render Accordion Component with image", () => {
// look up memory router...
renderWithCTX(<Accordion {...props} />);
const img = screen.getByRole("img", { name: /facebook/i });
expect(img).toBeInTheDocument();
});
it("fires the click handler on click", () => {
renderWithCTX(<Accordion {...props} />);
const buttons = screen.getAllByRole("button");
for (let i = 0; i < buttons.length - 1; i++) {
const curButton = buttons[i];
fireEvent.click(curButton);
}
});
it("changes the background color if theme prop is changed", () => {
renderWithCTX(<Accordion {...props} />);
const list = screen.getByRole("list");
expect(list).toHaveClass(themeMap.footer.button);
});
});
Memory router (from react router dom)
{% embed url="https: //v5.reactrouter.com/web/api/MemoryRouter" %}
useLocation (also from react router)
error cannot read property location of undefined
Portals - React
Excerpt
A JavaScript library for building user interfaces
Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.
Usage
Normally, when you return an element from a component's render method, it's mounted into the DOM as a child of the nearest parent node:
However, sometimes it's useful to insert a child into a different location in the DOM:
A typical use case for portals is when a parent component has an overflow: hidden
or z-index
style, but you need the child to visually "break out" of its container. For example, dialogs, hovercards, and tooltips.
Note:
Event Bubbling Through Portals
Even though a portal can be anywhere in the DOM tree, it behaves like a normal React child in every other way. Features like context work exactly the same regardless of whether the child is a portal, as the portal still exists in the React tree regardless of position in the DOM tree.
This includes event bubbling. An event fired from inside a portal will propagate to ancestors in the containing React tree, even if those elements are not ancestors in the DOM tree. Assuming the following HTML structure:
A Parent
component in #app-root
would be able to catch an uncaught, bubbling event from the sibling node #modal-root
.
Catching an event bubbling up from a portal in a parent component allows the development of more flexible abstractions that are not inherently reliant on portals. For example, if you render a <Modal />
component, the parent can capture its events regardless of whether it's implemented using portals.
{% embed url="https: //dukeenergy-my.sharepoint.com//g/personal/bryan_guner_duke-energy_com/EZ64SoMwF5xIq_Yufn9Fb9ABpgBdoRH8Sh3wiFOw8nFy_Q?email=Russell.Parkhouse%40duke-energy.com" %}
The first argument (child
) is any , such as an element, string, or fragment. The second argument (container
) is a DOM element.
When working with portals, remember that becomes very important.
For modal dialogs, ensure that everyone can interact with them by following the .
ReactDOM.createPortal(child, container)
render() {
// React mounts a new div and renders the children into it
return (
<div> {this.props.children}
</div> );
}
render() {
// React does *not* create a new div. It renders the children into `domNode`.
// `domNode` is any valid DOM node, regardless of its location in the DOM.
return ReactDOM.createPortal(
this.props.children,
domNode );
}
<html>
<body>
<div id="app-root"></div>
<div id="modal-root"></div>
</body>
</html>
// These two containers are siblings in the DOM
const appRoot = document.getElementById('app-root');
const modalRoot = document.getElementById('modal-root');
class Modal extends React.Component {
constructor(props) {
super(props);
this.el = document.createElement('div');
}
componentDidMount() {
// The portal element is inserted in the DOM tree after
// the Modal's children are mounted, meaning that children
// will be mounted on a detached DOM node. If a child
// component requires to be attached to the DOM tree
// immediately when mounted, for example to measure a
// DOM node, or uses 'autoFocus' in a descendant, add
// state to Modal and only render the children when Modal
// is inserted in the DOM tree.
modalRoot.appendChild(this.el);
}
componentWillUnmount() {
modalRoot.removeChild(this.el);
}
render() {
return ReactDOM.createPortal( this.props.children, this.el ); }
}
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {clicks: 0};
this.handleClick = this.handleClick.bind(this);
}
handleClick() { // This will fire when the button in Child is clicked, // updating Parent's state, even though button // is not direct descendant in the DOM. this.setState(state => ({ clicks: state.clicks + 1 })); }
render() {
return (
<div onClick={this.handleClick}> <p>Number of clicks: {this.state.clicks}</p>
<p>
Open up the browser DevTools
to observe that the button
is not a child of the div
with the onClick handler.
</p>
<Modal> <Child /> </Modal> </div>
);
}
}
function Child() {
// The click event on this button will bubble up to parent, // because there is no 'onClick' attribute defined return (
<div className="modal">
<button>Click</button> </div>
);
}
ReactDOM.render(<Parent />, appRoot);
import * as React from 'react';
import { createPortal } from 'react-dom';
const Portal = ({ children }: React.PropsWithChildren<{}>) => {
const portalNode = typeof window !== 'undefined' && document.querySelector('#root');
if (!portalNode) {
return null;
}
return createPortal(children, portalNode);
};
export { Portal };
TypeScript: Documentation - Migrating from JavaScript
Excerpt
How to migrate from JavaScript to TypeScript
TypeScript doesn't exist in a vacuum. It was built with the JavaScript ecosystem in mind, and a lot of JavaScript exists today. Converting a JavaScript codebase over to TypeScript is, while somewhat tedious, usually not challenging. In this tutorial, we're going to look at how you might start out. We assume you've read enough of the handbook to write new TypeScript code.
If you're looking to convert a React project, we recommend looking at the React Conversion Guide first.
Setting up your Directories
If you're writing in plain JavaScript, it's likely that you're running your JavaScript directly, where your .js
files are in a src
, lib
, or dist
directory, and then ran as desired.
If that's the case, the files that you've written are going to be used as inputs to TypeScript, and you'll run the outputs it produces. During our JS to TS migration, we'll need to separate our input files to prevent TypeScript from overwriting them. If your output files need to reside in a specific directory, then that will be your output directory.
You might also be running some intermediate steps on your JavaScript, such as bundling or using another transpiler like Babel. In this case, you might already have a folder structure like this set up.
From this point on, we're going to assume that your directory is set up something like this:
projectRoot
├── src
│ ├── file1.js
│ └── file2.js
├── built
└── tsconfig.json
If you have a tests
folder outside of your src
directory, you might have one tsconfig.json
in src
, and one in tests
as well.
Writing a Configuration File
TypeScript uses a file called tsconfig.json
for managing your project's options, such as which files you want to include, and what sorts of checking you want to perform. Let's create a bare-bones one for our project:
json{ "compilerOptions": { "outDir": "./built", "allowJs": true, "target": "es5" }, "include": ["./src/**/*"]}
Here we're specifying a few things to TypeScript:
Read in any files it understands in the src
directory (with include
).
Accept JavaScript files as inputs (with allowJs
).
Emit all of the output files in built
(with outDir
).
Translate newer JavaScript constructs down to an older version like ECMAScript 5 (using target
).
At this point, if you try running tsc
at the root of your project, you should see output files in the built
directory. The layout of files in built
should look identical to the layout of src
. You should now have TypeScript working with your project.
Early Benefits
Even at this point you can get some great benefits from TypeScript understanding your project. If you open up an editor like VS Code or Visual Studio, you'll see that you can often get some tooling support like completion. You can also catch certain bugs with options like:
noImplicitReturns
which prevents you from forgetting to return at the end of a function.
noFallthroughCasesInSwitch
which is helpful if you never want to forget a break
statement between case
s in a switch
block.
TypeScript will also warn about unreachable code and labels, which you can disable with allowUnreachableCode
and allowUnusedLabels
respectively.
You might have some more build steps in your pipeline. Perhaps you concatenate something to each of your files. Each build tool is different, but we'll do our best to cover the gist of things.
Gulp
If you're using Gulp in some fashion, we have a tutorial on using Gulp with TypeScript, and integrating with common build tools like Browserify, Babelify, and Uglify. You can read more there.
Webpack
Webpack integration is pretty simple. You can use ts-loader
, a TypeScript loader, combined with source-map-loader
for easier debugging. Simply run
shellnpm install ts-loader source-map-loader
and merge in options from the following into your webpack.config.js
file:
jsmodule.exports = { entry: "./src/index.ts", output: { filename: "./dist/bundle.js", }, // Enable sourcemaps for debugging webpack's output. devtool: "source-map", resolve: { // Add '.ts' and '.tsx' as resolvable extensions. extensions: ["", ".webpack.js", ".web.js", ".ts", ".tsx", ".js"], }, module: { rules: [ // All files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'. { test: /\.tsx?$/, loader: "ts-loader" }, // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'. { test: /\.js$/, loader: "source-map-loader" }, ], }, // Other options...};
It's important to note that ts-loader will need to run before any other loader that deals with .js
files.
The same goes for awesome-typescript-loader, another TypeScript loader for Webpack. You can read more about the differences between the two here.
You can see an example of using Webpack in our tutorial on React and Webpack.
Moving to TypeScript Files
At this point, you're probably ready to start using TypeScript files. The first step is to rename one of your .js
files to .ts
. If your file uses JSX, you'll need to rename it to .tsx
.
Finished with that step? Great! You've successfully migrated a file from JavaScript to TypeScript!
Of course, that might not feel right. If you open that file in an editor with TypeScript support (or if you run tsc --pretty
), you might see red squiggles on certain lines. You should think of these the same way you'd think of red squiggles in an editor like Microsoft Word. TypeScript will still translate your code, just like Word will still let you print your documents.
If that sounds too lax for you, you can tighten that behavior up. If, for instance, you don't want TypeScript to compile to JavaScript in the face of errors, you can use the noEmitOnError
option. In that sense, TypeScript has a dial on its strictness, and you can turn that knob up as high as you want.
If you plan on using the stricter settings that are available, it's best to turn them on now (see Getting Stricter Checks below). For instance, if you never want TypeScript to silently infer any
for a type without you explicitly saying so, you can use noImplicitAny
before you start modifying your files. While it might feel somewhat overwhelming, the long-term gains become apparent much more quickly.
Weeding out Errors
Like we mentioned, it's not unexpected to get error messages after conversion. The important thing is to actually go one by one through these and decide how to deal with the errors. Often these will be legitimate bugs, but sometimes you'll have to explain what you're trying to do a little better to TypeScript.
Importing from Modules
You might start out getting a bunch of errors like Cannot find name 'require'.
, and Cannot find name 'define'.
. In these cases, it's likely that you're using modules. While you can just convince TypeScript that these exist by writing out
ts// For Node/CommonJSdeclare function require(path: string): any;
or
ts// For RequireJS/AMDdeclare function define(...args: any[]): any;
it's better to get rid of those calls and use TypeScript syntax for imports.
First, you'll need to enable some module system by setting TypeScript's module
option. Valid options are commonjs
, amd
, system
, and umd
.
If you had the following Node/CommonJS code:
jsvar foo = require("foo");foo.doStuff();
or the following RequireJS/AMD code:
jsdefine(["foo"], function (foo) { foo.doStuff();});
then you would write the following TypeScript code:
tsimport foo = require("foo");foo.doStuff();
Getting Declaration Files
If you started converting over to TypeScript imports, you'll probably run into errors like Cannot find module 'foo'.
. The issue here is that you likely don't have declaration files to describe your library. Luckily this is pretty easy. If TypeScript complains about a package like lodash
, you can just write
shellnpm install -S @types/lodash
If you're using a module option other than commonjs
, you'll need to set your moduleResolution
option to node
.
After that, you'll be able to import lodash with no issues, and get accurate completions.
Exporting from Modules
Typically, exporting from a module involves adding properties to a value like exports
or module.exports
. TypeScript allows you to use top-level export statements. For instance, if you exported a function like so:
jsmodule.exports.feedPets = function (pets) { // ...};
you could write that out as the following:
tsexport function feedPets(pets) { // ...}
Sometimes you'll entirely overwrite the exports object. This is a common pattern people use to make their modules immediately callable like in this snippet:
jsvar express = require("express");var app = express();
You might have previously written that like so:
jsfunction foo() { // ...}module.exports = foo;
In TypeScript, you can model this with the export =
construct.
tsfunction foo() { // ...}export = foo;
Too many/too few arguments
You'll sometimes find yourself calling a function with too many/few arguments. Typically, this is a bug, but in some cases, you might have declared a function that uses the arguments
object instead of writing out any parameters:
jsfunction myCoolFunction() { if (arguments.length == 2 && !Array.isArray(arguments[1])) { var f = arguments[0]; var arr = arguments[1]; // ... } // ...}myCoolFunction( function (x) { console.log(x); }, [1, 2, 3, 4]);myCoolFunction( function (x) { console.log(x); }, 1, 2, 3, 4);
In this case, we need to use TypeScript to tell any of our callers about the ways myCoolFunction
can be called using function overloads.
tsfunction myCoolFunction(f: (x: number) => void, nums: number[]): void;function myCoolFunction(f: (x: number) => void, ...nums: number[]): void;function myCoolFunction() { if (arguments.length == 2 && !Array.isArray(arguments[1])) { var f = arguments[0]; var arr = arguments[1]; // ... } // ...}
We added two overload signatures to myCoolFunction
. The first checks states that myCoolFunction
takes a function (which takes a number
), and then a list of number
s. The second one says that it will take a function as well, and then uses a rest parameter (...nums
) to state that any number of arguments after that need to be number
s.
Sequentially Added Properties
Some people find it more aesthetically pleasing to create an object and add properties immediately after like so:
jsvar options = {};options.color = "red";options.volume = 11;
TypeScript will say that you can't assign to color
and volume
because it first figured out the type of options
as {}
which doesn't have any properties. If you instead moved the declarations into the object literal themselves, you'd get no errors:
tslet options = { color: "red", volume: 11,};
You could also define the type of options
and add a type assertion on the object literal.
tsinterface Options { color: string; volume: number;}let options = {} as Options;options.color = "red";options.volume = 11;
Alternatively, you can just say options
has the type any
which is the easiest thing to do, but which will benefit you the least.
any
, Object
, and {}
You might be tempted to use Object
or {}
to say that a value can have any property on it because Object
is, for most purposes, the most general type. However any
is actually the type you want to use in those situations, since it's the most flexible type.
For instance, if you have something that's typed as Object
you won't be able to call methods like toLowerCase()
on it. Being more general usually means you can do less with a type, but any
is special in that it is the most general type while still allowing you to do anything with it. That means you can call it, construct it, access properties on it, etc. Keep in mind though, whenever you use any
, you lose out on most of the error checking and editor support that TypeScript gives you.
If a decision ever comes down to Object
and {}
, you should prefer {}
. While they are mostly the same, technically {}
is a more general type than Object
in certain esoteric cases.
Getting Stricter Checks
TypeScript comes with certain checks to give you more safety and analysis of your program. Once you've converted your codebase to TypeScript, you can start enabling these checks for greater safety.
No Implicit any
There are certain cases where TypeScript can't figure out what certain types should be. To be as lenient as possible, it will decide to use the type any
in its place. While this is great for migration, using any
means that you're not getting any type safety, and you won't get the same tooling support you'd get elsewhere. You can tell TypeScript to flag these locations down and give an error with the noImplicitAny
option.
Strict null
& undefined
Checks
By default, TypeScript assumes that null
and undefined
are in the domain of every type. That means anything declared with the type number
could be null
or undefined
. Since null
and undefined
are such a frequent source of bugs in JavaScript and TypeScript, TypeScript has the strictNullChecks
option to spare you the stress of worrying about these issues.
When strictNullChecks
is enabled, null
and undefined
get their own types called null
and undefined
respectively. Whenever anything is possibly null
, you can use a union type with the original type. So for instance, if something could be a number
or null
, you'd write the type out as number | null
.
If you ever have a value that TypeScript thinks is possibly null
/undefined
, but you know better, you can use the postfix !
operator to tell it otherwise.
tsdeclare var foo: string[] | null;foo.length; // error - 'foo' is possibly 'null'foo!.length; // okay - 'foo!' just has type 'string[]'
As a heads up, when using strictNullChecks
, your dependencies may need to be updated to use strictNullChecks
as well.
No Implicit any
for this
When you use the this
keyword outside of classes, it has the type any
by default. For instance, imagine a Point
class, and imagine a function that we wish to add as a method:
tsclass Point { constructor(public x, public y) {} getDistance(p: Point) { let dx = p.x - this.x; let dy = p.y - this.y; return Math.sqrt(dx ** 2 + dy ** 2); }}// ...// Reopen the interface.interface Point { distanceFromOrigin(): number;}Point.prototype.distanceFromOrigin = function () { return this.getDistance({ x: 0, y: 0 });};
This has the same problems we mentioned above - we could easily have misspelled getDistance
and not gotten an error. For this reason, TypeScript has the noImplicitThis
option. When that option is set, TypeScript will issue an error when this
is used without an explicit (or inferred) type. The fix is to use a this
-parameter to give an explicit type in the interface or in the function itself:
tsPoint.prototype.distanceFromOrigin = function (this: Point) { return this.getDistance({ x: 0, y: 0 });};
{% embed url="https: //react-spectrum.adobe.com/react-aria" %}
Description
Notes:
Invalid form controls are visually indicated as invalid, but this state is not communicated programmatically. Assistive technology users may have difficulty determining which controls are invalid.
Recommendation
Set aria-invalid="true"
on form fields that contain invalid data. This will ensure that the field is exposed to assistive technologies as an invalid field, and screen readers will announce this, for example, by announcing "invalid entry" as part of the field description. Once the field is valid, remove the aria-invalid
attribute or set it to false
.
Setting aria-invalid
<label for="address">Address</label>
<input id="address" type="text" aria-invalid="true">
When an inline error message communicates specific information (e.g., beyond the fact that the field was left blank), use aria-describedby
to associate the error message with the form field. Once the field is valid, remove the error message.
Setting aria-describedby
to point to an error message
Email <input id="email" type="email" aria-invalid="true" aria-describedby="email_error"> <p id="email_error">Please enter a valid email address in the format name@example.com
[11:25 AM] Guest, James
[https: //accessible360.com/accessible360-blog/use-aria-label-screen-reader-text/](https: //accessible360.com/accessible360-blog/use-aria-label-screen-reader-text/)
Should I use an aria-label or screen-reader only text?
Every now and then a developer needs to include special content that should be seen by screen reader users, but not by anyone else. But what is the best way to convey this information? In some case...
\
[11:28 AM]
11:28 AM Meeting ended: 1h 1m 17s
\
[11:29 AM] Greufe, Chris
Guest, James [https: //v2.tailwindcss.com/docs/screen-readers#class-reference](https: //v2.tailwindcss.com/docs/screen-readers#class-reference)
Screen Readers - Tailwind CSS
Utilities for improving accessibility with screen readers.
\
[11:30 AM] Greufe, Chris
It has all the goodies in the sr-only
class, minus the prefix stuff from the article you posted. The prefixes are not needed in 2022 ... thank goodness.
\
Checkbox, Text Area, Select, RadioGroup