how to avoid nested document's property assignment that results in undefined - reactjs

This is how my incoming object from server looks like:
{
"name":"product1",
"categories": {
"cat1": {
"supported": false
},
"cat2": {
"supported": true
}
}
When the page loads, I will have 100s of categories and I populate categories object based on which category user selects. Those categories that were not selected by the user, don't exist in the product object.
When user is trying to edit the product, I have to show all the 100 categories in the checkboxes and show those categories checked which as supported set to true.
This is how my checkbox looks like:
data.props.categories.map((category, index) =>
<Form.Checkbox defaultChecked={productData.categories[category._id].supported} label={category.displayname}></Form.Checkbox>
);
This throws me an error saying when a category does not exist in product object because I am trying to access supported property of an undefined object. I am able to achieve what I need by writing a function that checks if a particular category exists in the incoming products object or not.
const isCategorySupported = (category_id) => {
debugger
if (productData.categories.hasOwnProperty(category_id)) {
return productData.categories[category_id].supported
}
return false
};
<Form.Checkbox defaultChecked={isCategorySupported(category._id)} label={category.displayname}></Form.Checkbox>
I was wondering if there is a better way or react way of doing this without writing a function?

Your solution looks fine. You may use optional chaining for this if you want a more elegant way:
<Form.Checkbox
defaultChecked={productData?.categories?.[category_id]?.supported}
label={category.displayname}>
</Form.Checkbox>
You have to keep in mind that this is not natively supported in the browsers just yet so a babel setup will be needed for this.

You are trying to map through categories object. So, you should be able to do like:
Object.keys(data.props.categories).map((category, index) =>
<Form.Checkbox defaultChecked={data.props.categories[category].supported} label={data.props.categories[category].displayname}></Form.Checkbox>
);

Related

Using keys with graphql fragment masking

As far as I know, fragment masking is considered a best practice when developing graphql clients, but I'm having some difficulties wrapping my head around how to write even some simple react necessities with that level of obscurity. One common necessity is providing key properties in iterations:
One example I'm working on is pulling repository data from Github to print cards with each of them. The query is:
fragment RepositoryCard on Repository {
resourcePath
description
}
Then, I'd use this query in a bigger one that request a user profile and gets some of their repositories:
query GetUserData($login: String!) {
user(login: $login) {
bio
name
repositories(first: 10) {
edges {
node {
...RepositoryCard
}
}
}
}
}
So far, so good. Then I'd map the responses to cards:
{
data?.user?.repositories?.edges?.map((repository) => (
<RepositoryCard
className="my-2"
repositoryNode={repository?.node}
/>
))
}
But then, I need a key prop for this iteration. The best approach would be to use the resourcePath since that's unique. However, since fragment masking is used, graphql-codegen doesn't allow me to see the contents of the type of repository.node, so I can't access resourcePath from outside of the component to get it.
What's the common approach to solve this?
It seems like useFragment is not a real hook, so it isn't necessary to follow the rules of hooks. In an answer to another question I see that the solution is to simply rename that function to something that won't trigger warnings (and better show the fact that it isn't a hook), so in your codegen.ts:
generates: {
"./src/__generated__/": {
preset: "client",
plugins: [],
presetConfig: {
gqlTagName: "gql",
fragmentMasking: {
unmaskFunctionName: "getFragmentData",
}
}
}
Then you can just call getFragmentData (previously useFragment) inside the map without getting any warnings:
data?.repositories?.edges?.filter((r) => !!(r?.node))
.map((r) => r?.node)
.map((repository) => {
const key = getFragmentData(REPOSITORY_CARD_FRAGMENT, repository)!.resourcePath;
return (
<RepositoryCard
key={key}
className="my-2"
query={repository!}
/>
);
})

When I using redux selector there is an type error came

enter image description hereMaking e commerce website on react and there is an error came
The collection prop is undefined. Make sure you are passing the collection as prop like this.
<CollectionPage collection = {collection} />
Also make sure your collection object is structured like this.
collection = { title: ".." , items : [] } // Or whichever data type you are using.
In the case the collection is undefined on first render and it is fetched asynchronously. then you can try this syntax to make sure you are not attempting to destructure undefined but instead an empty object.
const { title, items } = collection ? collection : {}

querying stripe api & display image

I have been following a tutorial to query Stripe API and display data. One thing that is not mentioned is how to query the images and display it. I can see from the structure that the images property is there but I need some help to display it. I assume it should be the same logic how it is displaying the product name but I just need to understand to follow the same logic for the images.
Here is my query, I have added the image option in my query:
and can see the result in GrapiQL:
Here is example of how I am mapping over my products to display correctly. From what I understand I need to do the same for the image. I have followed the same logic by just replacing product with image but just can't seem to get it working. Here is the snippet:
const Products = () => {
return (<StaticQuery query={PRODUCTS_QUERY}
render={
({ allStripeSku, allStripeProduct }) => {
return allStripeProduct.edges.map(product => {
const skus = allStripeSku.edges.filter(
sku => sku.node.product.id === product.node.id
)
return (
<Product
key={product.node.id}
skus={skus}
product={product.node} />
)
})
return
}
}
/>)
}
Can anyone please point my in the right direction so I can get this working?
You need to add 2 lines of code:
in GraphQL query to return images alongside id and name as you've already done
in ProductCard component to return an <img> element using the query field added above
For me this meant adding the + lines below:
in src/components/Products/Products.js
in src/components/Products/ProductCard.js
I assume it's the Gatsby E-commerce Tutorial you were following; if so please be aware that, since your OP, they've updated the docs so it no longer uses the deprecated Orders API.

reactjs -- handle checkbox (multiple) click events

I know there are some similar topics but none seems to be in the same direction of what I'm trying to do, thus a new thread.
I have a component that displays a list of keys, each with a checkbox attached to the string. In addition, I have a button that supposedly calls an API with all keys selected and delete these keys.
Several things I'm trying to achieve:
checking a check box enables the delete button
click the delete button should send a POST to API, the list should then reload
Since the list is reloaded, all checkbox should be unselected, thus the delete button is once again disabled
there's another button outside of this function that checks for the length of the list as well, which I don't know how to associate with this list if I fetch the list in the component.
I'm facing the problem which I don't know how to make the button and the checkboxes associate to each other. I tried using state with a checked state, which is a boolean, but that's only one boolean and cannot record several keys. I think using an array would work? Then again I'm not sure how to properly append or remove the key checked.
my code looks like
class AppList extends Component {
constructor() {
super();
this.state = {
checked: [],
apps: []
};
this.handleChecked = this.handleChecked.bind(this);
}
componentDidMount() {
fetch("some_url", {
method: 'post',
body: JSON.stringify({"user": "some_email"}),
headers: {'Content-Type': ' application/json'}
})
.then(res => res.json())
.then(
(result) => {
this.setState({apps: JSON.parse(result)});
},
(error) => {
console.log("error", error);
}
);
}
handleDeleteKey = (event) => {
// fetch(I'll worry about this later)
this.setState({checked: false});
console.log("delete!!!!");
}
handleChecked () {
this.setState({checked: !this.state.checked});
}
render () {
const apps = this.state.apps.map((app) =>
<div>
<input type="checkbox" onChange={this.handleChecked} />
{` ${app}`}
</div>
);
return (
<div>
<h4>Client Key List:</h4>
{this.state.apps.length > 0 ? <ul>{apps}</ul> : <p>No Key</p>}
{this.state.apps.length > 0 ? <button className="api-instruction-button" onClick={this.handleDeleteKey}>Delete Selected Key(s)</button> : null}
</div>
);
}
}
export default AppList;
I feel like my design is completely wrong but I don't know how to fix it. It seems like there are so many states to be passed around and nothing is the outermost, almost a cyclic dependency.
Anyone had any experience dealing with this problem? It seems like it's a common user action but I can't figure it out.
EDIT: after digging it a bit more, it seems like I need to call componentDidMount outside of the AppList. It should be in the component that uses AppList, let's call it MainApp.
MainApp calls componentDidMount which is the same as the one in AppList. The one in AppList gets removed, and the keys are passed to AppList as props.
I have trouble handling the clicking event. It seems like the component is always updating, so if I want to append the clicked key to the array, it wouldn't work. The same call will be made again and again.
Since there's another button in MainApp that requires the list of keys, I can't just pass the call into AppList. However, updating in AppList should update the MainApp as well. How does it work? I'm so confused
EDIT2:
https://codesandbox.io/s/7w2w11477j
This recreation should contain all functions I have so far, but I can't get them to work together.
Again my task is simply:
I have a list of strings, each with a checkbox
checking the checkbox selects the specific string
There's a button that I can click to delete these entries in my db by calling an API
Is refreshing the MainApp needed in this case? Otherwise I need to delete the strings in frontend so they don't display after the delete button is pressed
Here's what I believe you were going for: https://codesandbox.io/s/w23wv002yw
The only problem that made yours not work properly was you were just getting a little jumbled with where to put everything.
Contents:
The MainApp.js will only contain the apps and a method for deleting them in the backend. Other than those two methods, nothing else really concerns the MainApp.js file.
The AppList.js will contain all the methods that update its own checked state, the delete button itself, and a method to clear the checked state on delete.
Processes:
First, MainApp.js will load and remount with a backend api pull and populate its apps state. Once it's finished that, it will pass it on to AppList.js. From there, AppList.js will render that list as a multi-select field onscreen. The user can then select or deselect any of the options. As an option is selected, its index is pushed to the checked state and organized in ascending order.
(ordering the array isn't that necessary, but I figured it would help if you wanted to retool it sometime down the road)
When one or more option is selected, a delete button will appear. When the user clicks the delete button, AppList.js will call the delete function passed to it from MainApp.js, then it will clear the current checked state.

How to prevent User from creating duplicate values using React-Select Creatable?

I am using react-select' Creatable to make a dropdown and allow user to create new item to the list.
This is what I have:
<Creatable
name="form-field-name"
value={this.props.selectWorker}
options={this.props.selectWorkers}
onChange={this.props.handleSelectWorker}
/>
Right now user can create new name even though it already exist, creating duplicates like shown below.
I saw that there is an option called isOptionUnique on react-select site.
Searches for any matching option within the set of options. This
function prevents duplicate options from being created. By default
this is a basic, case-sensitive comparison of label and value.
Expected signature: ({ option: Object, options: Array, labelKey:
string, valueKey: string }): boolean
I have not been able to use it. I have tried isOptionUnique=true, isOptionUnique={options:this.props.workers}, but I got Creatable.js:173 Uncaught TypeError: isOptionUnique is not a function error.
I can't find an example for isOptionUnique, what is the best way to filter react-select to prevent duplicates using Creatable?
It's expecting a function
isOptionUnique(prop) {
const { option, options, valueKey, labelKey } = prop;
return !options.find(opt => option[valueKey] === opt[valueKey])
}
Don't forget to add it to your component instance
isOptionUnique={this.isOptionUnique}
This can also be achieved using the isValidNewOption prop.
isValidNewOption = (inputValue, selectValue, selectOptions) => {
if (
inputValue.trim().length === 0 ||
selectOptions.find(option => option.email === inputValue)
) {
return false;
}
return true;
}
you define a function taking three parameter of the inputValue you typed in, the selectValue if a value is selected and the existing options as selectOptions.
The function should return true or false dependent on what conditions you want a new option to be valid. this will prevent addition of duplicates.
In the case above we prevent adding of new options if there is no text or if the email already exists in the available options

Resources