I'm making multiple selectable checkboxes using React, GraphQL, Apollo.
I would like to make a query if it is selected, insert query and unselected, delete query(I want to generate a query only for selected/removed items)
Right now I'm making it using mutation but I have a problem where everything is deleted and then inserted.
I would like to generate a query only for selected/removed items. How can I fix it?
My code is as follows.
const [updateNoteMutation] = useUpdateNoteMutation();
const updateLogic = (key: string, value: string | number | Condition[]) => {
const noteVariables = {
variables: {
id: noteData.id,
noteAttributes: {
[key]: value,
},
},
};
updateNoteMutation(noteVariables).then(({ errors }) => {});
});
const handleCollection = (name: string, arr: Array) => {
//arr: List of selected checkboxes.
const noteArr = [];
diff.map((val) => {
noteArr.push({ name: val });
});
updateLogic(name, noteArr);
};
updateNoteMutation
mutation UpdateNote($id: ID!, $noteAttributes: FemappNoteInput!) {
femappUpdateNote(input: {id: $id, noteAttributes: $noteAttributes}) {
note {
id
checkboxList {
name
}
}
}
}
Please let me know if there is a source code that I can refer to
Thanks for reading my question.
You can try react-select for that. Really easy to implement and fully customizable.
Here is the repo;
https://github.com/JedWatson/react-select
Related
I'm trying to display a react-select with options equal to Firestore data but I get only one option with all the array. How can I show the options individually? Thanks.
Well, this is my options from react-select:
const options = [{ value: read01, label: read01 }];
and this is where I retrieve the date:
const retrieveNetwork13 = async () => {
try {
//const querySnapshot = await getDocs(collection(db, "cities"));
const q = query(collection(db, "schedule"));
const qq = query(q, where("uid2", "==", uid3));
const querySnapshot = await getDocs(qq);
setRead01(querySnapshot.docs.map((doc) => doc.data().schedule2));
} catch (e) {
alert(e);
}
};
Let's begin by isolating the key parts of your code. Here, you are using a react setter function to set read01 to some transformation of your query snapshot (I'm assuming that elsewhere there's code like const [read01, setRead01] = useState([])):
setRead01(querySnapshot.docs.map((doc) => doc.data().schedule2));
querySnapshot.docs appears to be an array of objects, which you are querying for its data() and then extracting the property schedule2. I can't say what the shape of schedule2 is from the information you've provided, but let's assume that it's just a string. Given all that, you are setting read01 to an array of strings (the result of querySnapshot.docs.map).
Now, let's look at the options you provide to react-select:
const options = [{ value: read01, label: read01 }];
You have specified one and only one option, for which both the value and label will be read01, which we've established is an array of strings. However, value and label in a react-select options array are intended to be strings, not arrays of strings. See e.g. the first example on the react-select homepage. The relevant options array is:
export const colourOptions: readonly ColourOption[] = [
{ value: 'ocean', label: 'Ocean', color: '#00B8D9', isFixed: true },
{ value: 'blue', label: 'Blue', color: '#0052CC', isDisabled: true },
...
];
See how value and label are just plain strings?
I think what you probably intended to do is map read01 into an array of options. Imagine that this is the contents of read01:
const read01 = ["option1", "option2", "option3"];
Then, we could define options as:
const options = read01.map((val) => ({ value: val, label: val }));
Here's a working sandbox for you to play with.
I am somewhat new to React and I am running into an issue and I was hoping someone will be willing to help me understand why my method is not working.
I have this state:
const [beers, setBeers] = useState([
{
id: 8759,
uid: "8c5f86a9-87bf-41fa-bc7f-044a9faf10be",
brand: "Budweiser",
name: "Westmalle Trappist Tripel",
style: "Fruit Beer",
hop: "Liberty",
yeast: "1056 - American Ale",
malts: "Special roast",
ibu: "22 IBU",
alcohol: "7.5%",
blg: "7.7°Blg",
bought: false
},
{
id: 3459,
uid: "7fa04e27-0b6b-4053-a26b-c0b1782d31c3",
brand: "Kirin",
name: "Hercules Double IPA",
style: "Amber Hybrid Beer",
hop: "Nugget",
yeast: "2000 - Budvar Lager",
malts: "Vienna",
ibu: "18 IBU",
alcohol: "9.4%",
blg: "7.5°Blg",
bought: true
}]
I am rendering the beers with a map function and I have some jsx that calls a handleClick function
<button onClick={() => handleClick(beer.id)}>
{beer.bought ? "restock" : "buy"}
</button>
this is the function being called:
const handleClick = (id) => {
setBeers((currentBeers) =>
currentBeers.map((beer) => {
if (beer.id === id) {
beer.bought = !beer.bought;
console.log(beer);
}
return beer;
})
);
};
I wanted to use an updater function to update the state, I am directly mapping inside the setter function and since map returns a new array, I thought everything would work correctly but in fact, it doesn't. It works only on the first button click and after that it stops updating the value.
I noticed that if I use this method:
const handleClick = (id) => {
const newbeers = beers.map((beer) => {
if (beer.id === id) {
beer.bought = !beer.bought;
}
return beer;
});
setBeers(newbeers);
};
Then everything works as expected.
Can someone help me understand why my first method isn't working?
OK, I think I have figured it out. The difference between my sandbox and your sandbox is the inclusion of <StrictMode> in the Index file. Removing this fixes the issue, but is not the correct solution. So I dug a little deeper.
What we all missed was that in your code you were modifying the previous state object that is passed in. You should instead be creating a new beer object and then modifying that. So this code works (I hope):
setBeers((currentBeers) =>
currentBeers.map((currentBeer) => { // changed beer to currentBeer
const beer = {...currentBeer};
if (beer.id === id) {
beer.bought = !beer.bought;
}
return beer;
)
});
I hope that this helps.
react does not deeply compares the object in the state. Since you map over beers and just change a property, they are the same for react and no rerender will happen.
You need to set the state with a cloned object.
e.g.:
import {cloneDeep} from 'lodash';
...
setBeers(
cloneDeep(currentBeers.map((beer) => {
if (beer.id === id) {
beer.bought = !beer.bought;
console.log(beer);
}
return beer;
})
)
);
I have this example using multi select box from material-ui v4.
The example contains two components the first with a default value list of object that is not working properly and a second component with a list of string that works fine.
The problem: (first component) when I open the Select component the default values is not selected and when I click on the default value it added again to the select box I have the same value multiple time.
Use the same object, eg change.
const [personName, setPersonName] = React.useState([
{ _id: '1', name: 'Oliver Hansen' },
{ _id: '2', name: 'Van Henry' },
]);
to:
const [personName, setPersonName] = React.useState(names.slice(0,2));
I don't remember this being necessary previously with materialUI, but it fixes the problem in your demo, so maybe I just haven't run into it before.
This is what i tried Array.reduce (Shallow copy):
const namesCopy = names.reduce((newArray, element) => {
props.defaultList.map((item) => {
if (item._id === element._id) {
newArray.push(element);
}
});
return newArray;
}, []);
const [personName, setPersonName] = React.useState(namesCopy);
Each of my posts on Contentful has some associated 'categories' with it. For example, one post might contain:
major: "HCD"
year: "1st Year"
tools: ["R", "Python", "Wordpress"]
These are just fields called major, year etc. with these values but they are treated as individual categories.
On the website, they are displayed as such:
I am trying to create a page for each of these categories. For example, if a user clicks on Photoshop, they should be taken to a page tags/photoshop and all posts containing that tag should be listed out.
Fortunately, I was able to find this guide to help me do this. However, the guide is not for Contentful data so I'm having a bit of trouble on how to do this. I have created the tagsTemplate.jsx and but I'm stuck at creating the actual pages.
For example, this is what I did to try and create pages for tools:
My gatsby-node.js file looks like this:
const path = require(`path`)
const _ = require('lodash');
exports.createSchemaCustomization = ({ actions }) => {
const { createTypes } = actions
const typeDefs = `
type contentfulPortfolioDescriptionTextNode implements Node {
description: String
major: String
author: String
tools: [String]
files: [ContentfulAsset]
contact: String
}
type ContentfulPortfolio implements Node {
description: contentfulPortfolioDescriptionTextNode
gallery: [ContentfulAsset]
id: ID!
name: String!
related: [ContentfulPortfolio]
slug: String!
major: String!
files: [ContentfulAsset]
author: String!
tools: [String]!
year: String!
thumbnail: ContentfulAsset
url: String
contact: String
}
`
createTypes(typeDefs)
}
exports.createPages = ({ graphql, actions }) => {
const { createPage } = actions
return new Promise((resolve, reject) => {
graphql(`
{
portfolio: allContentfulPortfolio {
nodes {
slug
tools
}
}
}
`).then(({ errors, data }) => {
if (errors) {
reject(errors)
}
if (data && data.portfolio) {
const component = path.resolve("./src/templates/portfolio-item.jsx")
data.portfolio.nodes.map(({ slug }) => {
createPage({
path: `/${slug}`,
component,
context: { slug },
})
})
}
const tools = data.portfolio.nodes.tools;
const tagTemplate = path.resolve(`src/templates/tagsTemplate.js`);
let tags = [];
// Iterate through each post, putting all found tags into `tags`
tags = tags.concat(tools);
// Eliminate duplicate tags
tags = _.uniq(tags);
// Make tag pages
tags.forEach(tag => {
createPage({
path: `/tags/${_.kebabCase(tag)}/`,
component: tagTemplate,
context: {
tag
},
});
});
console.log("Created Pages For" + tags)
resolve()
})
})
}
My tagsTemplate is minimal right now, since I don't know how to query the data:
import React from 'react';
import Layout from "../layouts/Layout"
const Tags = ({ data }) => {
return (
<Layout>
<div>Tags</div>
</Layout>
);
};
export default Tags;
The problem: When I visit the page for one of the tags I know exists (like photoshop), I get a 404. Why are these pages not being created?
What am I doing wrong and how can I fix it? How can this be generalized for three of my 'categories'?
According to what you said in the comments:
I tried console.log(tags) but is shows it as undefined
I did that and it is just a blank space. Does that mean there is nothing in tags at all?
Your contact function looks good, the approach is good since you are adding the tools (list of tags) into a new array to clean it up and leaving unique values (uniq). Once done, you loop through the unique tags and create pages based on that array.
That said, there are a few weak points where your house of cards can fall apart. Your issue start in this line:
const tools = data.portfolio.nodes.tools;
And propagates through the code.
nodes is an array so, to get any value you should do:
const tools = data.portfolio.nodes[0].tools;
To get the first position and so on...
Since tools is never populated, the rest of the code doesn't work.
You can easily fix it looping through the nodes and populating your tags array with something similar to:
const toolNodes = data.portfolio.nodes;
const tagTemplate = path.resolve(`src/templates/tagsTemplate.js`);
let tags = [];
// Iterate through each post, putting all found tags into `tags`
toolNodes.map(toolNode => tags.push(toolNode.tools);
// if the fetched data is still an array you can do toolNodes.map(toolNode => tags.push(...toolNode.tools);
// Eliminate duplicate tags
tags = _.uniq(tags);
// Make tag pages
tags.forEach(tag => {
createPage({
path: `/tags/${_.kebabCase(tag)}/`,
component: tagTemplate,
context: {
tag
},
});
});
console.log("Created Pages For" + tags)
I'm using Relay Modern for my app and am trying to update the cache after a mutation using the updater and optimisticUpdater but it doesn't quite work.
Basically, I have a Link type with a votes connection - here's the relevant part of my schema:
type Link implements Node {
createdAt: DateTime!
description: String!
id: ID!
postedBy(filter: UserFilter): User
url: String!
votes(filter: VoteFilter, orderBy: VoteOrderBy, skip: Int, after: String, before: String, first: Int, last: Int): VoteConnection
}
type Vote implements Node {
createdAt: DateTime!
id: ID!
link(filter: LinkFilter): Link!
updatedAt: DateTime!
user(filter: UserFilter): User!
}
# A connection to a list of items.
type VoteConnection {
# Information to aid in pagination.
pageInfo: PageInfo
# A list of edges.
edges: [VoteEdge]
# Count of filtered result set without considering pagination arguments
count: Int!
}
# An edge in a connection.
type VoteEdge {
# The item at the end of the edge.
node: Vote
# A cursor for use in pagination.
cursor: String
}
Here's the code for my Link component request the votes in a fragment:
class Link extends Component {
render() {
const userId = localStorage.getItem(GC_USER_ID)
return (
<div>
{userId && <div onClick={() => this._voteForLink()}>▲</div>}
<div>{this.props.link.description} ({this.props.link.url})</div>
<div>{this.props.link.votes.edges.length} votes | by {this.props.link.postedBy ? this.props.link.postedBy.name : 'Unknown'} {this.props.link.createdAt}</div>
</div>
)
}
_voteForLink = () => {
const userId = localStorage.getItem(GC_USER_ID)
const linkId = this.props.link.id
CreateVoteMutation(userId, linkId, this.props.viewer.id)
}
}
export default createFragmentContainer(Link, graphql`
fragment Link_viewer on Viewer {
id
}
fragment Link_link on Link {
id
description
url
createdAt
postedBy {
id
name
}
votes(last: 1000, orderBy: createdAt_DESC) #connection(key: "Link_votes", filters: []) {
edges {
node {
id
user {
id
}
}
}
}
}
`)
Finally, this is the CreateVoteMutation with the updater:
const mutation = graphql`
mutation CreateVoteMutation($input: CreateVoteInput!) {
createVote(input: $input) {
vote {
id
link {
id
}
user {
id
}
}
}
}
`
export default (userId, linkId, viewerId) => {
const variables = {
input: {
userId,
linkId,
clientMutationId: ""
},
}
commitMutation(
environment,
{
mutation,
variables,
updater: (proxyStore) => {
const createVoteField = proxyStore.getRootField('createVote')
const newVote = createVoteField.getLinkedRecord('vote')
const viewerProxy = proxyStore.get(viewerId)
const connection = ConnectionHandler.getConnection(viewerProxy, 'Link_votes')
// `connection` is undefined, so the `newVote` doesn't get inserted
if (connection) {
ConnectionHandler.insertEdgeAfter(connection, newVote)
}
},
onError: err => console.error(err),
},
)
}
The call to ConnectionHandler.getConnection(viewerProxy, 'Link_votes') only returns undefined, so the newVote doesn't actually get inserted.
Does anyone see what I'm doing wrong?
Problem:
When you're getting your connection:
const connection = ConnectionHandler.getConnection(viewerProxy, 'Link_votes')
you're trying to get the connection 'Link_votes' on the ViewerProxy. However what you want to be doing is getting the connection on the link.
Solution:
First you would need to get the id of the link that your adding the vote to.
const linkId = newVote.getLinkedRecord('link').getValue('id');
Then you want to get the Link Proxy so that you can then get the correct connection.
const linkProxy = proxyStore.get(LinkId)
Now that you have the Link Proxy that represents the link that you wanted the connection for, you can now get that connection.
const connection = ConnectionHandler.getConnection(linkProxy, 'Link_votes')
Sweet so now you've got the connection. Which solves the issue you're having.
However there is another problem, the way you go on to add the vote is wrong first you need to create an Edge out of it, and then add the edge.
First we need to create an edge
const voteEdge = createEdge(proxyStore, connection, newVote, 'VoteEdge');
Now that we have the voteEdge we can append it to the connection.
ConnectionHandler.insertEdgeAfter(connection, voteEdge).
Now it should all work. However you probably shouldn't be using the updater function for this kind of action. You should be using the RANGE_ADD configuration https://facebook.github.io/relay/docs/mutations.html#range-add and change how your server responds to that mutation.