Find element by id in react-testing-library - reactjs

I'm using react-testing-libarary to test my react application. For some reason, I need to be able to find the element by id and not data-testid. There is no way to achieve this in the documentation.
Is there a way to achieve this?
I have the rendered output as follows:
const dom = render(<App />);
I'm looking for something along the lines of:
const input = dom.getElementById('firstinput');
//or
const input = dom.getById('firstinput');

I feel like none of the answers really gave a complete solution, so here it is:
const result = render(<SomeComponent />);
const someElement = result.container.querySelector('#some-id');

I found a way to do this.
import App from './App';
import { render, queryByAttribute } from 'react-testing-library';
const getById = queryByAttribute.bind(null, 'id');
const dom = render(<App />);
const table = getById(dom.container, 'directory-table');
I hope this helps.

It looks you have DOM node itself as a container. Therefore, you should be able to call .querySelector('#firstinput') with that.

There are two ways to do so
Simply use container.getElementById('id'). In the end, all the helpers are doing is making queries like this one under the hood
If you want to have your custom query you can write a custom render. Check the documentation for more info https://github.com/kentcdodds/react-testing-library#getbytestidtext-textmatch-htmlelement
As a final note, if you can avoid looking for elements by id it's better.

You can set up with testIdAttribute in the configuration.
configure({ testIdAttribute: 'id' })
https://testing-library.com/docs/dom-testing-library/api-configuration
The setting has pros and cons. The benefit of it is that you can set an id for multiple uses. (Test id, marketing analytics, tag manager, ...etc) You don't have to add both id and test-id. It's good for the conciseness of the code.
But be careful, you might accidentally set the same id at two different components on the same page. Remember to add index or identification to a component id for list items.

My advice: stop adding and searching by ids, this always takes to much time and effort because you have to add the ids (sometimes test-ids) and then find out the best way to query the element. But even if you really need an id, this tool will save you a lot of time by showing the best way to query any DOM element on your screen: Testing Playground

If you use TypeScript, and want to get a non-null result, here's a convenience function:
function getById<T extends Element>(container: HTMLElement, id: string): T {
const element = container.querySelector<T>(`#${id}`);
assert(element !== null, `Unable to find an element with ID #${id}.`)
return element;
}
You can then use it like this:
import { render } from '#testing-library/react';
const { container } = render(<App />);
const myInputElement = getById<HTMLInputElement>(container, 'myInputElement');

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?

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 routing with names instead of ids

In my React application, I'd like to be able to navigate to sub-pages of my domain using the name property of my entity, rather than its id.
For example:
Now: https://.../companies/1
Desired: https://.../companies/company-name
The problem that I am trying to deal with is that with the first approach, I can easily fetch the company object from my backend, since I can simply use the passed id for that. However, for the second case, that is not anymore possible. Of course, I could create a method that fetches the company by name instead of id but that is neither elegant nor completely correct, considering that for some company names (e.g. those with spaces), the url parameter will be encoded, thus will not match with the original one.
Is there a smart way to achieve the desired outcome without storing some kind of mapping for ALL the potential company-urls in my Redux store? (e.g. company1 -> 1, company2 -> 2)
May be you need this:
Use Link to dynamically generate a list of routes.
Use : to indicate url params, :id in this case
Use the match object passed as props to the rendered route component to access the url params. this.props.match.params.id
<BrowserRouter>
/* Links */
{heroes.map(hero => (<Link to={'heroes/' + hero.id} />)}
/* Component */
<Route path="heroes/:id" component={Hero} />
</BrowserRouter>
class Hero extends Component {
render() {
return (
<div>
{this.props.match.params.id}
</div>
);
} }
What you're asking for is most definitely possible, but there is more work on the backend than the client (React).
If your route is defined as /companies/:id the :id can be anything. It can be 1234, it can be h3h3h3 and it can be amazon-company.
What :id marks is the name of the param which will hold your value.
For example:
https://.../companies/amazon
const { useParams } from 'react-router-dom';
const { id } = useParams(); // id will be 'amazon'
or, alternatively:
https://.../companies/1
const { useParams } from 'react-router-dom';
const { id } = useParams(); // id will be '1'
Now you have a specific problem with your idea, what about duplicate names? There are definitely more than 1 "Amazon" in the world, maybe not LLC, maybe D.o.o, maybe GMBH, but the point is, all of those would use the same name in your system.
What you want to do is incorporate some form of a slug in your backend.
When you're creating a Company Model in your backend, add something like this in the controller:
// CreateCompany.js
const Schema = {
slug: req.body.title + Math.random(...)
}
...
This will create a unique slug for the company name, such as amazon-235h35. You can learn more about proper slug usages on google, this is just an example ;)
And then instead of routing by companies/${company.id} you would do companies/${company.slug}
Does this make anything clearer?

React-Redux mapStateToProps with dynamic (uid based) path

I am new to React and React-Redux. I'm trying to pass "mapStateToProps" using a dynamic pathway depending on the users id. The basic code being used follows:
const mapStateToProps = (state) => {
console.log(state);
console.log(state.firebase.auth.uid); <===== THIS DISPLAYS THE USERS ID, PROVING THE PATH EXISTS
return {
courses: `state.firestore.ordered.${state.firebase.auth.uid}`, <======= THIS LINE IS AN ERROR EVEN THOUGH THE PATH EXISTS
}
}
The code works fine if I manually type in the users ID. Lets say the users ID is "12345", then replacing that line with
courses: state.firestore.ordered.12345, <======= THIS LINE WOULD WORK
Any clarification as to why this doesn't work and an alternative method of making this work would be greatly appreciated!
If you want to access dynamic property in JS object, you need to use square braces
courses: state.firestore.ordered[state.firebase.auth.uid]

How to check whether a property supplied to React component is an instance of Immutable.js Record with Flow?

Trying to setup Flow in my project, but don't really understand how to work with Immutable records. I want to statically check component props, here is how I'm doing it:
// #flow
import React from "react";
import {render} from "react-dom";
import * as I from "immutable";
const Person = I.Record({
name: null,
});
type Props = {
data: Person,
};
const PersonInfo = (props: Props) => {
const {data} = props;
return (
<span>
Name: {data.name}
</span>
);
};
render(
<PersonInfo data={1} />, // I would expect to get some compile error here
document.getElementById("app")
);
I also added immutable.js.flow in the project and .flowconfig.
This is actually an issue with the Immutable.js type definition. It always returns an any type. Basically meaning Records aren't typechecked. I went into the reason why records are so loosly defined here. The gist of it is, Flow doesn't support intersect-types with objects yet (which the type of a record would have to be). But you can override the Record type with the more restrictive type definition describedd in this answer. I copied it over:
declare class Record<T: Object> {
static <T: Object>(spec: T, name?: string): Record<T>;
get: <A>(key: $Keys<T>) => A;
set<A>(key: $Keys<T>, value: A): Record<T>;
remove(key: $Keys<T>): Record<T>;
}
If you add this decleration as a local decleration, you won't be able to access the properties directly anymore (like you did with data.name), but will have to use the get function like this data.get('name'). IMO the downside of this definition is pretty minor, compared to the added type savety. Now sadly, due to other restrictions in the language, the types of the values aren't typechecked, as illustrated in this example.
Sadly there is no good solution for stronly typed immutable data structures in Flow yet. The features, required to make this perfect are pretty much on the roadmap for Flow though.
TL;DR
Records aren't typechecked, due to restrictions in the language. But you can improve typechecking by using the declaration provided above.

Resources