The GraphQL updatePost mutation

A mutation is always located at two points in our code. One part is written inside of our GraphQL API in the back end, and the other one is written in our front end code.

We should start with the implementation on the back end side, as follows:

  1. There is a new mutation that we need to insert into our schema, as follows:
updatePost (
post: PostInput!
postId: Int!
): Post

  1. Once it is inside of our schema, the implementation to execute the mutation will follow. Copy the following code over to the resolvers.js file, in the RootMutation field:
updatePost(root, { post, postId }, context) {
return Post.update({
...post,
},
{
where: {
id: postId
}
}).then((rows) => {
if(rows[0] === 1) {
logger.log({
level: 'info',
message: 'Post ' + postId + ' was updated',
});

return Post.findById(postId);
}
});
},

The only special thing here is that we need to specify which posts we want to update. This is done by having the where property inside of the function call. The first parameter of the update function receives the post that should be updated. Because we currently do not have authentication implemented yet, we cannot verify the user updating the post, but for our example, this is no problem.

When updating a post, we are required to fetch the post from our database again, in order to return the row. This is a limitation of Sequelize when working with MySQL server. If you are running Postgres, for example, you can remove this part and directly return the post, without a special, separate query.

We can now focus on the front end again.

Recall how we implemented the previous mutations; we always created reusable React components for them. We should do the same for the update mutation.

Create a new file, called updatePost.js, inside of the mutations folder:

  1. As always, you have to import all of the dependencies. They should be the same as in the other mutations. This includes the GET_POSTS query, because we are going to read and update the cached data stored behind this query.
  2. Add the new updatePost mutation to the new file, as follows:
const UPDATE_POST = gql`
mutation updatePost($post : PostInput!, $postId : Int!) {
updatePost(post : $post, postId : $postId) {
id
text
}
}
`;
  1. Create an UpdatePostMutation class, as follows:
export default class UpdatePostMutation extends Component {
state = {
postContent: this.props.post.text
}
changePostContent = (value) => {
this.setState({postContent: value})
}
}

As you can see, the postContent is not just an empty string, but is taken from the properties, because updating a post requires that the post already exists and so does the text of it.

  1. A React component always needs a render method. This one is going to be a bit bigger:
render() {
const self = this;
const { children } = this.props;
const { postContent } = this.state;

const postId = this.props.post.id;
const variables = { page: 0, limit: 10};

return (
<Mutation
update = {(store, { data: { updatePost } }) => {
var query = {
query: GET_POSTS,
};
if(typeof variables !== typeof undefined) {
query.variables = variables;
}
const data = store.readQuery(query);
for(var i = 0; i < data.postsFeed.posts.length; i++) {
if(data.postsFeed.posts[i].id === postId) {
data.postsFeed.posts[i].text = updatePost.text;
break;
}
}
store.writeQuery({ ...query, data });
}}
optimisticResponse= {{
__typename: "mutation",
updatePost: {
__typename: "Post",
text: postContent,
}
}}
mutation={UPDATE_POST}>
{updatePost =>
React.Children.map(children, function(child){
return React.cloneElement(child, { updatePost,
postContent, postId, changePostContent:
self.changePostContent });
})
}
</Mutation>
)
}

There are some differences from the other mutations that we have implemented before. The changes are as follows:

  • We read the children and post id from the component's properties. Furthermore, we extract the postContent state variable.
  • We have hardcoded the variables. This is not a good approach, however. It would be better to receive this from the parent component, too, but for this example, it is fine.
  • The update method now searches through the cache and reads and updates the post's text when a post with a matching id is found.
  • All underlying children accept the updatePost method and the postId.

This chapter is all about reusable React components. To make use of our mutation, we need to have a form allowing us to edit a post. We will handle this within the Post component itself, because we want to edit the post in place, and do not want to open a modal or a specific Edit page. Go to your post's index.js file and exchange it with the new one, as follows:

import React, { Component } from 'react';
import PostHeader from './header';
import PostContent from './content';
import PostForm from './form';
import UpdatePostMutation from '../mutations/updatePost';

export default class Post extends Component {
state = {
editing: false
}
changeState = () => {
const { editing } = this.state;
this.setState({ editing: !editing });
}
render() {
const { post } = this.props;
const { editing } = this.state;
return (
<div className={"post " + (post.id < 0 ? "optimistic": "")}>
<PostHeader post={post} changeState={this.changeState}/>
{!editing && <PostContent post={post}/>}
{editing &&
<UpdatePostMutation post={post}>
<PostForm changeState={this.changeState}/>
</UpdatePostMutation>
}
</div>
)
}
}

We should quickly go over the changes, one by one, as follows:

  • We are importing the update mutation that we just wrote at the top.
  • We added an editing state variable. Based on this variable, we decide whether we show the normal PostContent component or our PostForm.
  • We are using conditional rendering based on the editing variable, in order to switch between the standard and update form.
  • The changeState function lets us switch between both states.
  • Our PostHeader and the PostForm receive the new function, allowing them to control its parent state.
  • Our PostForm is wrapped inside of our mutation. The form then receives the mutation's updatePost function.

We already have a post form that we can reuse with some adjustments, as you can see in the following code snippet. To use our standard post submission form as an update form, we must make some small adjustments. Open and edit the form.js file, as follows:

import React, { Component } from 'react';

export default class PostForm extends Component {
handlePostContentChange = (event) => {
this.props.changePostContent(event.target.value);
}
render() {
const self = this;
const { addPost, updatePost, postContent, postId } = this.props;

return (
<div className="postForm">
<form onSubmit={e => {
e.preventDefault();

if(typeof updatePost !== typeof undefined) {
updatePost({ variables: { post: { text: postContent },
postId } }).then(() => {
self.props.changeState();
});
} else {
addPost({ variables: { post: { text: postContent } }
}).then(() => {
self.props.changePostContent('');
});
}
}}>
<textarea value={postContent} onChange=
{self.handlePostContentChange} placeholder="Write your custom
post!"/>
<input type="submit" value="Submit" />
</form>
</div>
)
}
}

We are reading the updatePost mutation from the component properties. If it is defined, we can assume that the parent component is our UpdatePostMutation component, so we can run the updatePost mutation with the postContent and postId variables. If not, we will just run the addPost mutation, like before.

The critical thing to note is that, upon finishing the request, we are running the changeState function, which switches our Post component back to the normal text mode, and also hides the form.

Where did it all begin? We wanted to have a context menu that allowed us to update the post.

Go to your post header file. The header is a great place to insert the drop-down component, as follows:

import React from 'react';
import Dropdown from '../helpers/dropdown';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

export default ({post, changeState}) =>
<div className="header">
<img src={post.user.avatar} />
<div>
<h2>{post.user.username}</h2>
</div>
<Dropdown trigger={<FontAwesomeIcon icon="angle-down" />}>
<button onClick={changeState}>Edit</button>
</Dropdown>
</div>

FontAwesome is useful now. The drop-down trigger is displayed in the same row as the username.

Our drop-down component receives a trigger component, which is just a FontAwesome icon. Furthermore, the only child that our drop-down component has, for now, is a simple button. When it is clicked, it changes the parent Post component's editing state and makes the update post form visible, instead of the normal post content.

Nothing works without the magic of CSS. All of the CSS takes up a lot of space, so you should look it up in the official Git repository of this book. If you have added the new CSS, you should be able to see a small icon on the right-hand border of each post. Clicking on it makes a small drop-down menu visible, including the 'Edit' button, as shown at the beginning of this section. The user is now able to make in-place edits of posts with the post update form.

Something that we have not spoken about is user rights. At the moment, the user can edit everybody's posts, even if the user is not the author of the post. That is a problem that we will look into in the next chapter, when we have implemented authentication.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.239.195