How to get useParams hook as string - reactjs

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" .

Related

TS: Common function with objects that have the same structure but different possible values on a property

So, we have an app with multiple resources, let's say we have Product, Cart, Whatever resources. For each of those resources you can create activities, the main idea here is that for each resource there is an endpoint to create/update those activities, which looks the same no matter the resource you are trying to update.
So in our app (React) we created a single form to create/update an activity, it looks the same no matter for which resource you want to create an activity for, same fields, same possible values. Therefore we have one single component instead of 3, and a common function that handles the api part.
Something like:
const { mutate } = useUniversalEditActivity(variant); // variant can be 'product', 'cart', 'whatever'
We call mutate when we want to submit the form.
Inside that hook, there is a simple map:
const variantMapper = {
product: {
serviceAction: updateProductActivity, // simple function that wraps fetch, does the network request
},
cart: {
serviceAction: updateCartActivity,
},
whatever: {
serviceAction: updateWhateverActivity,
},
};
// Using it like
const mutatingServiceAction = variantMapper[variant].serviceAction;
...
await mutatingServiceAction();
The body is typed as
type UniversalEditActivityBodyType =
| UpdateProductActivityRequestBody
| UpdateCartActivityRequestBody
| UpdateWhateverActivityRequestBody
Which works when all the properties are the same across the types, but the problem starts now when the BE changed the spec for the Whatever resource.
So, before the request body had a property which had 2 possible values, so it was typed like:
type UpdateProductActivityRequestBody = {
propertyWithIssues: 'a'| 'b';
}
All 3 looked the same, but the spec changed for the Whatever resource to:
type UpdateWhateverActivityRequestBody = {
propertyWithIssues: 'a'| 'b' | 'c' | 'd';
}
Adding 2 more possible values for the same property, now there is a difference on how they look and inside my generic function that handled all body types, now I get the Type '"a" | "b" | "c" | "d"' is not assignable to type '"a" | "b"'.
I kind of understand the error, but not sure how to fix it in order for my function to still work with all those 3 types when just the possible values on a single property is different between them.
I don't know if I explained as good as I should have, but it's a more complex question (I think), so I tried my best. Please also suggest a different title if you think it would better describe my problem. Thanks in advance.
UPDATE 1:
#chris-hamilton, the request is executed like this:
const resp = await mutatingServiceAction(id, activityId, payload);
This is where the issue happens, because payload is a union of all those 3 types, but now they have become incompatible.
Minimum reproducible example here: https://codesandbox.io/s/gallant-bessie-sxjc4x?file=/src/index.ts
I know in theory that the issue could be solved by doing something like:
if (variant === 'product') {
// directly use product function
}
...
But I have the feeling this can be something different, as they have the exact same structure, just one property can have different values.
This error is really just a big red flag for this design pattern. You've specified that these two parameters can be one of many types, meaning you can have any combination of variant and body. ie. this function would accept a variant of "product" and a body of UpdateCartActivityRequestBody. But clearly that is not how the function is meant to be used.
It's a question of what these updateActivity functions are doing, and how similar they are. It may be that the parameters of these functions can accept a common type:
type Body = {
propertyWithIssues: string;
}
function main(
variant: "product" | "cart" | "whatever",
body: Body
) {
const serviceAction = variantMapper[variant].serviceAction;
serviceAction(body); // no error
}
So you need to ask yourself, "do my functions need to know about all these specific properties?".
If the answer is no, then define a type with only the properties needed for those functions. If those properties are common for all functions then the design pattern is fine. The type may just be Object, it depends what the functions are doing.
If the answer is yes, then the design pattern is incorrect, and you shouldn't be coupling these service actions into a single function. You should just be calling updateWhateverActivity directly, with the correct parameter type. No need for this variantMapper object.
Maybe you have other reasons for implementing this pattern, but you'll need to give more details if that's the case.
I can see it maybe being the case that you have an object with variant and body and you don't know what they are until run time. In that case you will have to do type narrowing like you showed. But you should also be checking that the type of body actually matches variant or you're just asking for runtime errors. This is really what the error is trying to tell you.
if (variant === 'product') {
// somehow verify that body is of type UpdateProductActivityRequestBody
// directly use product function
}

de-duping items in a mongoose list of populate IDs

I have a mongoose object which contains an array of ObjectIds, being used for population from another table. I want to be able to dedupe these. eg I have
[ '61e34f3293d9361bbb5883c7' ,'61e34f3293d9361bbb5883c7', '61e34f3293d9361bbb5883c7' ]
When i print and iterate through these they look like strings.
But they also have an _id property, so I think they're somehow "populated" or at least contain references to the child table.
What's the best way to do this? I tried:
const uniqueTokens = _.uniqBy(tokens, '_id') which doesn't seem to work as _id is some kind of Object.
converting to a string will allow me to dedupe:
const tokens = this.tokens || []
let newTokens: string[] = []
for (let t of tokens) {
const text = t.toString()
// clog.info('t', t, t._id, typeof t._id)
if (!newTokens.includes(text)) {
newTokens.push(text)
}
}
but then these aren't real Objects I can assign back to the original parent object.
// this.tokens = newTokens
await this.save()
I could maybe go through and re-find the objects, but that seems to be digging deeper into the hole!
Seems there must be a better way to handle these type of types...
related searches
How to compare mongoDB ObjectIds & remove duplicates in an array of documents using node.js?
I also tried using lean() on the tokens array to try and convert it back to a simple list of references, in case somehow the 'population' could be undone to help.
I'm down to creating a unique signature field for the referenced items and de-duping based on that.

String interpolation in gql tag

I've already posted this as an issue in the graphql-tag repositoy but I'll post it here as I hope somebody on here has a workable solution.
What am I trying to do?
I'd like to dynamically define typedefs of local types based on props passed to my react component.
Why am I doing this?
I realise this is not the intended way of defining gql, however, I'm in the process of creating a React wrapper component around the Apollo Provider. The purpose of it is to make the process of mocking out data locally (as described here https://www.apollographql.com/docs/react/development-testing/client-schema-mocking/) more seamless with less boilerplate.
I'm going for a declarative approach where the user can simply define an array of fields (name, graphQL type and optional implementation that will default to a sensible faker implementation for the graphQL type) that will make local fields available directly on the Query type as well as an array of types (name, fields and an optional nested type) which should make it possible to define arbitrary local schemas declaratively with no boilerplate.
What's the outcome at the moment?
Just to establish a baseline, the following is working just fine and allows me to run codegeneration from Apollo CLI and query the new local test field (with a #client directive) in my application
const localTypeDefs = gql`
extend type Query {
test: String
}
`;
const client = new ApolloClient({
cache: new InMemoryCache(),
uri: "https://localhost:4000/graphql",
typeDefs: localTypeDefs,
...apolloClientOptions,
});
If I change the qgl definition to look like this instead
const name = "name";
const graphQLType = "String";
const localTypeDefs = gql`
extend type Query {
${name}: ${graphQLType}
}
`;
I get the following error when running codegen Syntax Error: Expected Name, found ":". If I change the gql definition to this
const testString = "test: String";
const localTypeDefs = gql`
extend type Query {
${testString}
}
`;
I get this error Syntax Error: Expected Name, found "}". on codegen. In general, it seems like everything after the interpolated string causes the compiler to emit this error on the next character. If I include the } in the testString, I get the error Syntax Error: Expected Name, found <EOF>..
Now, If I try to change to the function syntax instead so the localTypeDefs definition looks as follows:
const testString = "test: String";
const localTypeDefs = gql(`extend type Query { ${testString} } `);
The typedef is actually generated without any error, however, the codegeneration still fails with the error GraphQLError: Cannot query field "test" on type "Query" when I query for this field. The query hasn't change at all from the working baseline I posted at the top and if I change back to that without touching anything else, the codegen no longer complains about the query.
Curiously, if I change the above back to the baseline implementation but keep the explicit function call instead of the implicit `` string as the following:
const localTypeDefs = gql(`
extend type Query {
test: String
}
`);
I still get the error GraphQLError: Cannot query field "test" on type "Query" but as soon as I change back to the base case, everything is working just fine.
I was was able to get something similar to this working for a query by wrapping an input with double quotes:
const query = gql`
mutation RegenerateAgreement {
agreementsRegenerate(input: { userId: "${userId}" }) {
agreements {
body
prompt
}
}
}
`;

angular extract field of an array of object

I don't understand something and need explanations please !
I have a datatable and selection of lines generate in my .ts an array of Operation object. here is my object class :
export class Operation {
id: number;
name: string;
}
this is the declaration of array :
selectedOperations: Operation[];
when I log in console before extraction of ids, I have this :
this.selectedOperations = {"selected":[{"id":1,"name":"My name 1"},{"id":3,"name":"My name 3"}]}
and when I want to extract ids with this :
let ids = this.selectedOperations.map(o => o.id);
I have an exception =>
this.selectedOperations.map is not a function
It's not the first time I have this problem and I'd like to understand why. I search some reasons and found differences between Array and object[] ? I think it's not really an array because there is the {"selected": before the array...
Can someone tell me the thing and help me for extract ids ?
thanks a lot !
{"selected":[{"id":1,"name":"My name 1"},{"id":3,"name":"My name 3"}]} => this is of type object, whereas your array declaration looks like this selectedOperations: Operation[];
You either directly assign the array to your variable:
this.selectedOperations = [{"id":1,"name":"My name 1"},{"id":3,"name":"My name 3"}];
Or you can change your variable type to any or object:
selectedOperations: any;
this.selectedOperations = {"selected":[{"id":1,"name":"My name 1"},{"id":3,"name":"My name 3"}]}
const ids = this.selectedOperations.selected.map(o => o.id);
this.selectedOperations.map is not a function error is caused by the initialization, map function is reserved for arrays, therefore it throws an error when you try to use it on an object type variable.
I would recommend the first approach by the way, declaring a variable as any or object is contradicting with the purpose of Typescript.
You need to make some improvements to the code. In order to get the ids, you need to add selected to this.selectedOperations. See below.
let ids = this.selectedOperations.selected.map(o => o.id);

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