How to create an update form with Apollo client in Reactjs - reactjs

I like to edit a list of contacts I fetched from my GraphQL api with Apollo client. I see a lot of examples for add new items via mutations but I cannot find any example of updating existing items. Normally I would create a state object and make the input fields modify the properties of the state object but I'm not sure how I can modify the data I have fetched. How do I keep the view state updated?
import React, {Component} from 'react';
import gql from 'graphql-tag';
import { graphql } from 'react-apollo';
class EditContact extends Component {
_update() {
// Mutation call
}
render() {
if(this.props.contactQuery && this.props.contactQuery.loading) {
return <div>Loading</div>
}
if(this.props.contactQuery && this.props.contactQuery.error) {
return <div>Error</div>
}
const contact = this.props.contactQuery.contact;
return(
<div className="contact">
<input type="text" defaultValue={contact.firstName} onChange={??} />
<input type="submit" value="Save" onClick={this._update}>
</div>)
}
}
const CONTACT_QUERY = gql`
query contactQuery($id: Int!) {
contact(id: $id) {
id
firstName
lastName
}
}
`
export default graphql(CONTACT_QUERY, {
name: 'contactQuery',
options: ownProps => ({ variables: { id: ownProps.match.params.id } })
})(EditContact);
I know how to create and use the mutation itself but how do I keep track of the form changes and how do I set the initial data in the form fields?

You want to use a Controlled input, I learned about them here: https://goshakkk.name/controlled-vs-uncontrolled-inputs-react/

Related

React - Building a form to edit an existing product. Field contents are empty

I successfully built a form to create and save new products. Now I'm working on the page to modify previously created products. I can tell my API is returning data because the title shows up. However, it seems like the form is rendering before the state is populated and therefore the field is empty.
I think that the title is working because it's not reading from the state, it's reading directly from the product.name value which was returned from my API. However, for my form, I believe I need to point my field to the state which is empty at the moment the component rendered.
I'm just not sure how to only render the form once the API is done loading data and state is populated?
Any help greatly appreciated, have been trying to figure this out off and on for months.
I'm using redux toolkit: https://redux-toolkit.js.org/rtk-query/usage/queries#frequently-used-query-hook-return-values
import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import { useParams } from 'react-router';
import { useNavigate } from 'react-router-dom'
import AHeaderNav from '../../../../components/settings/AHeaderNav/AHeaderNav'
import './AdminProductEdit.scss';
import Wysiwyg from '../../../../components/global/Wysiwyg/Wysiwyg';
import SelectInput from '../../../../components/global/SelectInput/SelectInput'
import Breadcrumbs from '../../../../components/global/breadCrumbs/breadCrumbs';
import AdminBackButton from '../../../../components/settings/AdminBackButton/AdminBackButton'
import ImageC from '../../../../components/global/ImageC';
import { useGetProductAuthQuery, useUpdateProductMutation } from '../../../../api/apiSlice';
import { Spinner } from '../../../../components/global/Spinner/Spinner';
const AdminProductEdit = () => {
const { id: productId } = useParams()
const {
data: product = {},
isFetching,
isSuccess,
isLoadingCurrentPoduct } = useGetProductAuthQuery(productId, {
//pollingInterval: 3000,
refetchOnMountOrArgChange: true,
skip: false
})
const [updateProduct, { isLoading }] = useUpdateProductMutation()
const [productName, setProductName] = useState(product.productName)
const navigate = useNavigate()
const onProductNameChanged = e => setProductName(e.target.value)
if(!isSuccess) {
return <Spinner text="Loading..." />
} else {
const canSave = [productName].every(Boolean) && isSuccess && typeof productId === 'number'
const onSaveProductClicked = async () => {
if(canSave) {
try {
await updateProduct({productId,
productName,
productDescription,
productCategory,
shopNowUrl,
assetDescription})
navigate(`../settings/products`)
} catch (err) {
console.error(err)
}
}
}
return (
<div>
<AHeaderNav/>
<div className="topBannerImage">
<ImageC imageSrc={product.assetUrl}/>
</div>
<div className="contentWrapper">
<h3>Editting '{product.productName}'</h3>
<label>
Product Name
<input
type="text"
name="productName"
value={[productName]} // this is rendering an empty field.
// To me it seems like since this portion of the page is only
// supposed to be rendered once the API is successful, then
// this should be populated? What am I missing?
onChange={onProductNameChanged}
disabled={isLoading}
/>
</label>
<div className="button-group align-spaced">
<AdminBackButton/>
<button
className="submit button"
onClick={onSaveProductClicked}
disabled={!canSave}
>Save changes</button>
</div>
</div>
{spinner}
</div>
)
}
}
export default AdminProductEdit;

Show ApolloClient mutation result

This is my first time using Apollo and React so I'll try my best.
I have a GraphQl API from which I consume some data through ApolloClient mutations. The problem is that I don't know how to show the resulting information outside of the .result. I've tried to do so with a class that has a function to consume some data and a render to show it.
The mutation works and shows the data on the console but the page remains blank when the page is loaded, so the problem I've been stuck on is, how do I show this data?
Btw, if there's any advice on how to insert data from a form using this same mutation method I'd pretty much appreciate it.
Thanks in advance.
import React, { useEffect, useState, Component } from 'react';
import { graphql } from 'react-apollo';
import './modalSignUp.css';
import{header} from './Header.js';
import ReactDOM from "react-dom";
import { ApolloProvider, Query, mutation } from "react-apollo";
import { ApolloClient, InMemoryCache, gql, useMutation } from '#apollo/client';
export const client = new ApolloClient({
uri: 'http://localhost:4011/api',
cache: new InMemoryCache(),
});
client.mutate({
mutation: gql`
mutation signin{
login(data:{
username:"elasdfg",
password:"12345678"}){
id,roles,email,username}
}
`
}).then(result => console.log(result));
export class UserList extends Component {
displayUsers() {
console.log(this.result)
var data = this.props.data;
return data.login.map((user) => {
return (
<li>{user.email}</li>
);
})
}
render() {
return (
<div>
<li>
{this.displayUsers()}
</li>
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Header />);
Mutation result
I've tried to use a class to fetch the data given by the mutation and later render it in the component. I've also tried passing the result to a variable but I had no success with that.
I'm just expecting to see the data resulting from the mutation
You should request data inside the component and then save it to the state.
export class UserList extends Component {
constructor() {
this.state = {
newData: null,
};
this.mutateData = this.mutateData.bind(this);
}
mutateData() {
client
.mutate({
mutation: gql`
mutation signin {
login(data: { username: "elasdfg", password: "12345678" }) {
id
roles
email
username
}
}
`,
})
.then((result) => {
this.setState({ newData: result });
});
}
componentDidMount() {
this.mutateData();
}
render() {
// do something with new data
}
}

Call a GraphQL Mutation once after React component mounts

After a user creates a profile, they receive a link in their email that sends them back to the site with a verifyToken in the url. If the token matches the token that is stores in the database, their isVerified status is stored in the database with the value true.
new-profile.js
import VerifyEMail from '../components/VerifyEmail';
const NewProfilePage = props => (
<div>
<VerifyEMail verifyToken={props.query.verifyToken} />
</div>
);
export default NewProfilePage;
Currently, I have this implemented and working using a form with a "Verify" button that the user must click to call the graphQL mutation, verifyEmail. Since this sets the isVerified value to true in the database, I know that everything is working as it should.
../components/VerifyEmail.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Mutation } from 'react-apollo';
import gql from 'graphql-tag';
const VERIFY_EMAIL_MUTATION = gql`
mutation VERIFY_EMAIL_MUTATION($verifyToken: String!) {
verifyEmail(verifyToken: $verifyToken) {
isVerified
}
}
`;
class VerifyEmail extends Component {
render() {
const { verifyToken } = this.props;
return (
<Mutation mutation={VERIFY_EMAIL_MUTATION} variables={{ verifyToken }}>
{verifyEmail => (
<form
onSubmit={async () => {
await verifyEmail(verifyToken);
}}
>
<button type="submit">Verify</button>
</form>
)}
</Mutation>
);
}
}
VerifyEmail.propTypes = {
verifyToken: PropTypes.string.isRequired,
};
export default VerifyEmail;
However, I really don't want to force my users to have to click a button to fire the mutation. I would like it to be called once the component loads. I have been racking my brain for a day and a half on this, tried so many things, and just can't seem to find anything that works.
I've seen some solutions using React hooks, Apollo hooks, componentDidMount, etc. My mind is just having a difficult time seeing it any more. This link had some of the best solutions that I found so far, but I couldn't figure out how to implement them...
[Feature idea] Execute a mutation on mount #1939
Any help to point me in the right direction would be really appreciated. Thank you.
This is far simpler application when using React hooks:
import React, { useEffect } from "react";
function VerifyEmail({ verifyToken }) {
const [ verifyEmail, { loading, data, error }] = useMutation(VERIFY_EMAIL_MUTATION);
useEffect(() => {
verifyEmail({
variables: { verifyToken },
});
}, []);
return (
<>
{loading && <p>Loading...</p>}
{data && <p>Verified successfully!</p>}
{error && <p>Error!</p>}
</>
)
}
If you somehow want to keep using classes, the only solution is to create a component and utilise componentDidMount of the component for this purpose.
// Current component:
<Mutation mutation={VERIFY_EMAIL_MUTATION} variables={{ verifyToken }}>
{verifyEmail => (
<SendEmail token={verifyToken} verify={verifyEmail} />
)}
</Mutation>
// Send Email component
class SendEmail extends Component {
componentDidMount() {
const { token, verify } = this.props;
verify(token);
}
render() {
return (
//handle loading, data and error states
)
}
}

How to update GraphQL query on button click

I'm looking for a good way to update the "orderBy: createdAt_ASC" portion of the below graphql query when one of the two buttons below are clicked.
The default order is createAt_ASC and want the user to be able to switch between them.
const ALL_ITEMS_QUERY = gql`
query ALL_ITEMS_QUERY {
items(orderBy: createdAt_ASC) {
id
title
description
image
}
}
`;
Buttons:
<button onClick={sortNew}>Newest</button>
<button onClick={sortOld}>Oldest</button>
First of all, change your query
const ALL_ITEMS_QUERY = gql`
query ALL_ITEMS_QUERY($orderBy: String!) {
items(orderBy: $orderBy) {
id
title
description
image
}
}
`;
On react component use react-apollo
import { compose, graphql } from 'react-apollo'
class ReactComponentName extends Component {
......
//Inside render return this buttons
<button onClick={this.changeOrder.bind(this,"sortNew")}>Newest</button>
<button onClick={this.changeOrder.bind(this,"sortOld")}>Oldest</button>
.......
// And change your export statement and using HOC
export default compose(
graphql(ALL_ITEMS_QUERY, { name: 'allItemQuery' }),
// import query and bind this into props using compose
)(ReactComponentName)
onClick call a function
async changeOrder(order) {
const { allItemQuery } = this.props
const result = await allItemQuery({ variables: { 'orderBy': order } })
//Set state or store filter data
}
Change your state variable or render result data.
Or
Do this cool Query component in Apollo client

How to manage select box (representing a relation between entities) with Meteor React?

I'm using Meteor/React to build a social App. Users are related to others by a ManyToOne relation (a user have a representative).
So I made a Member Component which return a template with a form inside it to select the representative, and created an API to execute some actions on users.
Everything works quite well but the delete function do not update the client side (I opened another window with the application).
I feel like i'm missing the point with the Component notion, should that select box be a Component too ?
Here is Member.jsx
import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';
import { Meteor } from 'meteor/meteor';
import { Members } from '../api/members.js';
// Member component - represents a single member item
export default class Member extends Component {
componentDidMount() {
ReactDOM.findDOMNode(this.refs.select_representant).value = this.props.member.representant;
}
componentDidUpdate() {
ReactDOM.findDOMNode(this.refs.select_representant).value = this.props.member.representant;
}
setRepresentant() {
Meteor.call('members.setRepresentant', this.props.member._id, 'oo');
}
deleteThisMember() {
Meteor.call('members.remove', this.props.member._id);
}
renderRepresentants() {
let representants = Members.find().fetch();
return representants.map((representant) => (
<option key={representant._id} value={representant._id}>{representant.pseudo}</option>
));
}
handleSubmit(event) {
event.preventDefault();
// Find the text field via the React ref
const representantId = ReactDOM.findDOMNode(this.refs.select_representant).value.trim();
const representant = Members.findOne({ _id: representantId });
Meteor.call('members.setRepresentant', this.props.member._id, representantId);
}
render() {
return (
<div>
<h3 className="text">
{this.props.member.pseudo} <button className="delete" onClick={this.deleteThisMember.bind(this)}>×</button>
</h3>
<form className="form-horizontal">
<div className="form-group">
<label htmlFor="select_representant" className="col-sm-3 control-label">Représentant</label>
<div className="col-sm-7">
<select ref="select_representant" className="form-control custom-select" name="select_representant" onChange={this.handleSubmit.bind(this)}>
{this.renderRepresentants()}
</select>
</div>
</div>
</form>
</div>
);
}
}
and members.jsx
import { Mongo } from 'meteor/mongo';
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
export const Members = new Mongo.Collection('members');
Meteor.methods({
'members.insert'(pseudo) {
check(pseudo, String);
Members.insert({
pseudo
});
},
'members.remove'(memberId) {
check(memberId, String);
Members.remove(memberId);
represented = Members.find({ representant: memberId }).fetch();
for(representedItem in represented){
Members.update(representedItem, { $set: { representant: null } });
}
},
'members.setRepresentant'(memberId, representantId) {
check(memberId, String);
check(representantId, String);
Members.update(memberId, { $set: { representant: representantId } });
},
});
Try this:
deleteThisMember() {
Meteor.call('members.remove', this.props.member._id);
renderRepresentants();
}
Or, you tried to put your member list on thie.props?
Since you query for the Members within the React render function, it's not reactive with the changes in the database. You need to use createContainer from the react-meteor-data to see the changes in your front-end:
import { createContainer } from 'meteor/react-meteor-data';
...
export default class Member extends Component {
...
renderRepresentants() {
let representants = this.props.representants;
...
}
...
}
createContainer(() => {
return {
representants: Members.find().fetch()
};
}, Member);
Now the query should be properly reactively updated when there are changes in the Members collection.

Resources