React routing with names instead of ids - reactjs

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?

Related

How to get useParams hook as string

I have a search page that is linked to the product detail page using the productId.
In the product detail page, I'm able to use:
const productId = useParams();
Then I have to cross it with a product list, to get the correct Product. For that, I use:
const productSelected = listOfProducts.find(e => e.productId === productId);
The problem is that the productId that I get from useParams(), comes as an object. And even though this objects holds the correct productId, it fails when I'm searching in the list, as e.productId is a string.
And I'm not able to use double ==, as the JSLint won't allow me.
I saw some posts saying to use JSON.Stringfy, but it converts the whole object in string, and not only the value for productId.
this objects holds the correct productId
It sounds like you just need to destructure it?:
const { productId } = useParams();
This would declare the productId variable to have the value from the productId property on the object, not the whole object itself.
It's also worth noting that, by default, URL parameters are strings. If you expect it to be a number then you can convert it as such. Perhaps something like this:
const { productIdParam } = useParams();
const productId = parseInt(productIdParam);
Or, if you're using TypeScript, you can indicate the type when calling useParams. For example:
const { productId } = useParams<{ productId: Number }>();
In this case productId will be a Number.
Based on comments below, it's also important to note something about types when using TypeScript. It only enforces them at design-time, not a run-time. So for example you can declare an interface to have a property that is a Number, and then use that interface when fetching data from somewhere. But at runtime if the actual property value is a string then === won't work, because the types are wrong. Even though TypeScript said they should be correct, at runtime it's all still JavaScript which doesn't enforce that.
I think you don't need to destruct the object !
const productId = useParams(); //return an object and inside this object there is a property called id contains the exact value of userParams() so you need just to access to this property "productId.id" .

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-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]

Find element by id in react-testing-library

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');

Redux selector on many to many

React + Redux recommend saving data normalized and using selectors to get derived data. So in our store we save Users and Tags which have a many to many relationship.
type Store = {
entities: {
users: User[];
tags: Tag[];
userTagMapping: { userId: string, tagId: string }[]
}
}
And in our view we want to show some derived data for this many to many relation-ship. For example we want to calculate the total users with some tag or the online-users with some tag. Right now we solved this using rselect. The only problem is that calculating these things becomes quite tedious. For example we have a selector which returns a map from a tag-id to a list of users to show which users belong to this tag (and vice versa a selector from user-id to tag list).
const selectUsersPerTag = createSelector(
selectUsers, selectTags, selectUserTagMapping,
(users, tags, mapping) => {
let result = {};
for (const tag on tags) {
const isUserMappedToTag = user => ({userId, tagId}) => userId == user.id && tagId === tag.id
result[tag.id] = users.filter(user => mapping.some(isUserMappedToTag(user)))
}
return result
}
)
and in my opinion this looks quite ugly and is a bit too hard to read for my taste.
My questions are:
Are we understanding the recommendations correctly (to use normalization and selectors)?
Is using a map the correct way to process our data and show it in the view or is there a better way? I am asking because this basically copies our data (slightly modified) many times into the props of our React components
Is there a nicer way to do this mapping (which is basically a SQL like join)? Because I really don't like this imperative approach and would find a functional one much nicer.

Resources