AWS Appsync graphqlMutation helper not updating query - javascript

I'm following this tutorial: https://egghead.io/lessons/react-execute-mutations-to-an-aws-appsync-graphql-api-from-a-react-application
I have a simple todo react app hooked up to AppSync via amplify. The queries and mutations were autogenerated by Amplify.
Using the graphqlMutation helper, my query is supposed to be automatically updated after running my mutations, but it's not working. Upon refresh I do see the mutations are updating the AppSync backend, but I also expect it to update immediately with an optimistic response.
Here is the code:
import React, { Component } from "react";
import gql from "graphql-tag";
import { compose, graphql } from "react-apollo";
import { graphqlMutation } from "aws-appsync-react";
import { listTodos } from "./graphql/queries";
import { createTodo, deleteTodo } from "./graphql/mutations";
class App extends Component {
state = { todo: "" };
addTodo = async () => {
if (this.state.todo === "") {
return;
}
const response = await this.props.createTodo({
input: {
name: this.state.todo,
completed: false
}
});
this.setState({ todo: "" });
console.log("response", response);
};
deleteTodo = async id => {
const response = await this.props.deleteTodo({ input: { id } });
console.log("response", response);
};
render() {
return (
<div>
<div>
<input
onChange={e => this.setState({ todo: e.target.value })}
value={this.state.todo}
placeholder="Enter a name..."
/>
<button onClick={this.addTodo}>Add</button>
</div>
{this.props.todos.map(item => (
<div key={item.id}>
{item.name}{" "}
<button onClick={this.deleteTodo.bind(this, item.id)}>
remove
</button>
</div>
))}
</div>
);
}
}
export default compose(
graphqlMutation(gql(createTodo), gql(listTodos), "Todo"),
graphqlMutation(gql(deleteTodo), gql(listTodos), "Todo"),
graphql(gql(listTodos), {
options: {
fetchPolicy: "cache-and-network"
},
props: props => ({
todos: props.data.listTodos ? props.data.listTodos.items : []
})
})
)(App);
A repo containing the codebase is here: https://github.com/jbrown/appsync-todo
What am I doing wrong here that my query isn't updated?

Your input contains only properties name and completed. Tool graphqlMutation will add id automatically.
Code doesn't contains list query, I can guess than query requested for more data than name, completed and id.
So item will not be added to list because of missing required informations.
Solution is add all listed properties to createTodo.

Related

Getting component to re-render after form submit. Using useContext and custom hooks to fetch data

I'm having some issues getting a component to re-render after submitting a form. I created separate files to store these custom hooks to make them as reusable as possible. Everything is functioning correctly, except I haven't figured out a way to re render a list component after posting a new submit to that list. I am using axios for fetch requests and react-final-form for my actual form. Am I not able to re-render the component because I am using useContext to "share" my data across child components? My comments are set up as nested attributes to each post, which is being handled through Rails. My comment list is rendered in it's own component, where I call on the usePost() function in the PostContext.js file. I can provide more info/context if needed.
**
Also, on a slightly different note. I am having difficulty clearing the form inputs after a successful submit. I'm using react-final-form and most the suggestions I've seen online are for class components. Is there a solution for functional components?
react/contexts/PostContext.js
import React, { useContext, useState, useEffect } from "react";
import { useParams } from "react-router-dom";
import { useAsync } from "../hooks/useAsync";
import { getPost } from "../services/post";
const Context = React.createContext();
export const usePost = () => {
return useContext(Context);
};
export const PostProvider = ({ children }) => {
const id = useParams();
const { loading, error, value: post } = useAsync(() => getPost(id.id), [
id.id,
]);
const [comments, setComments] = useState([]);
useEffect(() => {
if (post?.comments == null) return;
setComments(post.comments);
}, [post?.comments]);
return (
<Context.Provider
value={{
post: { id, ...post },
comments: comments,
}}
>
{loading ? <h1>Loading</h1> : error ? <h1>{error}</h1> : children}
</Context.Provider>
);
};
react/services/comment.js
import { makeRequest } from "./makeRequest";
export const createComment = ({ message, postId }) => {
message["post_id"] = postId;
return makeRequest("/comments", {
method: "POST",
data: message,
}).then((res) => {
if (res.error) return alert(res.error);
});
};
react/services/makeRequest.js
import axios from "axios";
const api = axios.create({
baseURL: "/api/v1",
withCredentials: true,
});
export const makeRequest = (url, options) => {
return api(url, options)
.then((res) => res.data)
.catch((err) => Promise.reject(err?.response?.data?.message ?? "Error"));
};
react/components/Comment/CommentForm.js
import React from "react";
import { Form, Field } from "react-final-form";
import { usePost } from "../../contexts/PostContext";
import { createComment } from "../../services/comment";
import { useAsyncFn } from "../../hooks/useAsync";
const CommentForm = () => {
const { post, createLocalComment } = usePost();
const { loading, error, execute: createCommentFn } = useAsyncFn(
createComment
);
const onCommentCreate = (message) => {
return createCommentFn({ message, postId: post.id });
};
const handleSubmit = (values) => {
onCommentCreate(values);
};
return (
<Form onSubmit={handleSubmit}>
{({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<div className="comment-form-row">
<Field name="body">
{({ input }) => (
<textarea
className="comment-input"
placeholder="Your comment..."
type="text"
{...input}
/>
)}
</Field>
<button className="comment-submit-btn" type="submit">
Submit
</button>
</div>
</form>
)}
</Form>
);
};
export default CommentForm;

How to implement Finnhub.io stock API into a React App

I am new to web development and I am having trouble in a project I am creating. The end goal of this app is to make a stock reference app where the user can look up information about different stocks. It would end up being similar to the "Stocks" app on iOS devices. However, at this point I am still struggling to make this app work because I can't seem to figure out the API call. I managed to get the data that I wanted to get, however it doesn't work exactly how I wanted to.
The first issue with my code is that it takes two clicks of the submit button to actually get the data and then set the state to match the data. The second issue is that the once the data is received and is displayed by the StockItem component, the stockPrice state will go back to zero after a couple of seconds.
Here is my App.js
import './App.css';
import StockItem from "./StockItem"
class App extends React.Component {
constructor (props) {
super(props)
this.state = {
symbol: "",
stockPrice: 0,
value: "",
stockName: "",
}
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange (event) {
this.setState({
value: event.target.value
})
console.log(this.state.value)
}
handleSubmit(event) {
this.setState({
symbol: this.state.value
})
const finnhub = require('finnhub');
const api_key = finnhub.ApiClient.instance.authentications['api_key'];
api_key.apiKey = "API Key"
const finnhubClient = new finnhub.DefaultApi()
finnhubClient.quote(this.state.symbol, (error, data, response) => {
this.setState({stockPrice: (data.c)})
});
finnhubClient.companyProfile2({'symbol': this.state.symbol}, (error, data, response) => {
this.setState({stockName: (data.name)})
console.log(data.name)
});
console.log("update was called")
console.log(this.state.stockPrice)
event.preventDefault();
}
render () {
return (
<div>
<form onSubmit={this.handleSubmit}>
<label>
Stock Symbol:
<input type = "text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
<div>
<StockItem symbol = {this.state.symbol} price = {this.state.stockPrice} name = {this.state.stockName}/>
</div>
</div>
)
}
}
export default App;
Here is my StockItem.js component
function StockItem (props) {
return (
<div className = "stockItem">
<h2>Stock Symbol: {props.symbol} Stock Name: {props.name}</h2>
<h3>Stock Price: {props.price}</h3>
</div>
)
}
export default StockItem
Here is my Index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();
Also I am not sure if it is ok to share my API key publicly so I purposely left it out. If I can safely share it and it would help with solving the problem then please let me know. Again, I am new to this and there are a lot of things in this that I just don't know.
The problem here is the use of the state inside the handleSubmit function. this.setState will save the state at some point in the future. Change your handleChange and handleSubmit like the following (You'll also want to force the stock symbol to uppercase.):
handleChange(event) {
const value = `${event.target.value}`.toUpperCase();
this.setState({ value });
console.log(value);
}
handleSubmit(event) {
const symbol = this.state.value; // Use this as your symbol
this.setState({ symbol });
console.log(symbol);
const finnhub = require('finnhub');
const api_key = finnhub.ApiClient.instance.authentications['api_key'];
api_key.apiKey = "<API Key>";
const finnhubClient = new finnhub.DefaultApi()
finnhubClient.quote(symbol, (error, data, response) => {
console.log(data);
this.setState({stockPrice: (data.c)})
});
finnhubClient.companyProfile2({'symbol': symbol}, (error, data, response) => {
this.setState({stockName: (data.name)})
console.log(data.name)
});
console.log("update was called")
console.log(this.state.stockPrice)
event.preventDefault();
}

Save and Edit post in reactjs and redux

I've been trying to create a blog like website where user can add new post, save them and edit them later. I'm making this website in reactjs and redux. I've few confusions as to how to edit the post, like how will my website know that the user has clicked on this certain post to edit. To do I've used link from react router with the id at the end of the url but I'm not sure if it's the right way to do. Also, when I open the editor page of an existing post, it should load as it was last saved, i.e both the title input and the textarea should already be loaded with text when a user clicks on an already existing posts from the homepage.
I've created a codesandbox of the website. I'm not sure why all the lines in the switch statements in the reducer file is underlined with red.
this is my home.js file where the snippets will load
import React from "react";
import axios from "axios";
import { Link } from "react-router-dom";
import { onLoad, setEdit } from "./actions/posts";
import { connect } from "react-redux";
class Home extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
//Load all the snippets
onLoad();
}
render() {
const { snippets } = this.props;
return (
<div className="container">
<div className="row pt-5">
<div className="col-12 col-lg-6 offset-lg-3">
<h1 className="text-center">Snippets</h1>
</div>
</div>
<div className="row pt-5">
<div className="col-12 col-lg-6 offset-lg-3">
{snippets.map(snippet => {
return (
<div className="card my-3" key={snippet.snippetData.snippetId}>
<div className="card-header">{snippet.title}</div>
<div className="card-body">{snippet.snippetDescription}</div>
<div className="card-footer">
<div className="row">
<button
// onClick={() => this.handleEdit(snippet)}
className="btn btn-primary mx-3"
>
<Link to={`/editor/:${snippet.snippetData.snippetId}`}>
Edit
</Link>
</button>
</div>
</div>
</div>
);
})}
</div>
</div>
</div>
);
}
}
const mapStateToProps = state => ({
snippets: state.snippets,
snippetData: state.snippetData
});
export default connect(
mapStateToProps,
{ onLoad, setEdit }
)(Home);
editor.js page
import React, { Component } from "react";
import { connect } from "react-redux";
import { savePost, retrievePost } from "./actions/posts";
class Editor extends Component {
state = {
title: "",
enteredText: ""
};
componentDidMount() {
//Load the snippet
retrievePost(); // will it load the snippetId too?
}
handleChange = event => {
const { value } = event.target;
this.setState({
enteredText: value
});
};
// Save Snippet
performSave = snippetData => {
const { enteredText, title } = this.state;
savePost(snippetData.snippetId, enteredText, title); //is it the right way to send the parameters to save the post??
};
render() {
return (
<>
<input
type="text"
id="titletext"
placeholder="Enter title here"
limit-to="64"
className="inptxt"
onChange={title => this.setState({ title })}
/>
<button
className="btn savebtn"
onClick={() => this.handlePost({ ...this.state })}
>
Save Snippet
<i className="fas fa-save" />
</button>
<div
contentEditable={true}
spellCheck="false"
name="enteredText"
placeholder="Enter your text here"
onChange={this.handleChange}
/>
</>
);
}
}
const mapStateToProps = state => ({
snippetData: state.snippetData
});
export default connect(
mapStateToProps,
{ savePost, retrievePost }
)(Editor);
actions.js file
import { SAVE_POST, UPDATE_POST, RETRIEVE_POST, HOME_LOADED } from "./types";
export const savePost = ({
snippetId,
snippetDescription,
snippetTitle
}) => async dispatch => {
const config = {
headers: {
"Content-Type": "application/json"
}
};
let snippetData = { snippetId, snippetDescription, snippetTitle };
try {
if (snippetId == null) {
const res = await axios.post(
"/api/save",
JSON.stringify(snippetData),
config
);
snippetData.snippetId = res.data;
dispatch({
type: SAVE_POST,
payload: snippetData
});
} else {
const res = await axios.post(
"/api/update",
JSON.stringify(snippetData),
config
);
dispatch({
type: UPDATE_POST,
payload: snippetData
});
}
} catch (err) {
console.log(err);
}
};
// Retrieve post
export const retrievePost = snippetId => async dispatch => {
try {
const res = await axios.post(`/api/snippetdata/${id}`);
dispatch({
type: RETRIEVE_POST,
payload: res.data
});
} catch (err) {
console.error(err);
}
};
//Retrieve all the post
export const onLoad = () => async dispatch => {
try {
const res = await axios.post(`/api/mysnippets/`);
dispatch({
type: HOME_LOADED,
payload: res.data
});
} catch (err) {
console.error(err);
}
};
// edit a post
First, I have fixed some problems for you:
https://codesandbox.io/s/amazing-bird-dd2mb
I did not fix the editor page, cuz I give up, it is meaningless to give you a working code while learning nothing.
I suggest you stop playing react now, you do not have enough experience to use a complex framework.
What problem your code has:
Wrongly import a commonJS module
Misuse combineReducers
Misuse html form element
Misuse js switch
Do not understand redux state correctly
Do not understand reducer fully
Do not have basic debuging skills
...
STOP WRITING CODE THAT YOU DO NOT UNDERSTAND
This project is too complex for a beginner.
I suggest you:
Implement a counter in vanilla js
Implement a todo list in vanilla js
RE-implement the counter with pure react, no redux, no react-router
RE-implement the counter with react + redux
RE-implement the counter with react + redux + thunk
RE-implement the counter with react + redux + saga
Repeat 3-6 but a todo list.
Then try to code a blog.

Update a list after an item triggers its own deletion

I am rendering a card in a parent component for every post that a user has. In the card, all of the data is passed down via props. I have a delete axios call that works, however I have to manually refresh the page for the page to show updates.
Any way I can have it manually update the UI?
// DASHBOARD.JS
if (this.state.posts.length > 0 && this.state.loaded === true) {
const posts = this.state.posts;
content = posts.map(post => (
<Card
key={post._id}
author={post.author}
title={post.title}
date={post.date}
body={post.body}
id={post._id}
/>
));
// CARD.JS
deleteOnClick = e => {
axios
.delete('http://localhost:5000/auth/deletePost', {
params: {
id: this.props.id
}
})
.then(res => {
console.log(res);
})
.catch(err => console.log(err));
};
I think you have two problems to fix in order to make this pattern work.
First thing first: avoid defining business logic in components used only for presentational purposes (have a read here).
So in Card component there should be no explicit definition of the deleteOnClick method, while it should receive it from above in a dedicated prop of type func.
Second thing: the list component should handle the logic of deleting items from the list through the axios call and in the then statement you should think of a way to update the list items you are using to render Cards.
Examples in pseudocode:
List Component
import React from 'react';
import Card from './Card';
export default class List extends PureComponent {
state = {
items: [],
error: null,
}
componentDidMount() {
// add axios call to retrieve items data
}
deleteItemHandler = () => {
axios
.delete('http://localhost:5000/auth/deletePost', {
params: {
id: this.props.id
}
})
.then(res => {
this.setState({
items: res.json(),
})
})
.catch(err => {
this.setState({
error: err,
})
});
};
}
render() {
const { items } = this.state;
return (
<div>
{items.map(item => (
<Card
{...item}
onClick={this.deleteItemHandler}
/>
))}
</div>
)
}
}
Card component:
import React from 'react';
import PropTypes from 'prop-types';
export default class Card extends PureComponent {
static propTypes = {
title: PropTypes.string,
// other props
onClick: PropTypes.func.isRequired,
}
// other things in this class
render() {
const { onClick, title } = this.props;
return (
<div onClick={onClick}>
<h1>{title}</h1>
</div>
)
}
}
Once you get familiar with concept of separating logic and presentation you can start introducing redux and do things at another level :)
Hope this helps!

Assigning a value inside a function and using it outside

I'm writing a reaction web application with firebase backend. Based on field query of one document, I wanted to access another document.
I'm only able to return the query and directly use it for accessing the document. It's making a call to the function and query over and over again.
import React, { Component } from 'react';
import { compose } from 'recompose';
import { withAuthorization } from '../Session';
import { withStyles } from '#material-ui/core/styles';
import Paper from '#material-ui/core/Paper';
import './Image/1.jpeg'
import Album from './Album.js';
const styles = theme => ({
root: {
...theme.mixins.gutters(),
paddingTop: theme.spacing.unit * 2,
paddingBottom: theme.spacing.unit * 2,
},
});
class ChildPage extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
items: [],
};
this.classes = props;
}
componentDidMount() {
this.setState({ loading: true });
let query = [];
this.unsub = this.props.firebase.users().doc(this.props.firebase.userId()).get().then(doc => {
query.push(doc.data().LinkedUsername)
const lU = this.props.firebase.users().where("email", "==", query[0])
lU.get().then(snapshot => {
console.log(snapshot.docs[0].id)
})
})
this.unsubscribe = this.props.firebase
.users().doc(this.props.firebase.userId()).collection('tasks')
.onSnapshot(snapshot => {
let items = [];
snapshot.forEach(doc =>
(doc.data().status === false) ?
items.push({ ...doc.data(), uid: doc.id })
:
null
);
this.setState({
items,
loading: false,
});
});
}
componentWillUnmount() {
this.unsubscribe();
this.unsub();
}
removeItem(itemId) {
const itemRef = this.props.firebase.users().doc(`${itemId}`);
itemRef.remove();
}
render() {
return (
<div className='background'>
<div className='topBar'>
</div>
<Paper className={ this.classes.root } elevation={ 1 }>
<Album cards={ this.state.items } />
</Paper>
</div>
);
}
}
const condition = authUser => !!authUser;
export default compose(
withAuthorization(condition),
withStyles(styles),
)(ChildPage)
I want the query to run one time and assign the return value to a variable. Then be able to use that value to access them and load the documents.
Currently, this gives me a document id in the console. And if I want to use that id, I take the next two "})" brackets and put them before
"
}
componentWillUnmount() {"
and take everything inside the console i.e. "snapshot.docs[0].id" and put it inplace of "this.props.firebase.userId()" in the doc of this.unsubscribe.
But it's what calls the function over and over and gives the error,
"Warning: Encountered two children with the same key, [object Object]. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version."

Categories

Resources