Welcome back to our ongoing series on crafting a fully functional forum using React and Appwrite! If you haven’t checked out Part 2 yet, make sure to explore it here.
This chapter focuses on enabling users to post new discussions and engage in comments. Prepare for an in-depth guide, so have your tea and snacks ready!
Database
As with any new part of this series, we need to get a few things ironed out in the database.
Firstly head over to your Appwrite Console and click ‘Database’. We’re going to need a new collection to hold our comments for the articles. Click add collection and fill out the prompt like below:
Attributes
Navigate to your Appwrite Console and select ‘Database’. We require a new collection specifically for storing comments related to articles. Create a new collection by following the instructions provided:
For managing attributes, visit the newly created collection’s attributes section and add the following:
Attribute ID | Type | Size | Required | Array | Default Value |
---|---|---|---|---|---|
postId | String | 255 | Yes | ||
userId | String | 255 | Yes | ||
content | String | 255 | No | ||
author | String | 255 | No |
Additionally, set up the necessary indexes for optimizing queries:
Index Key | Type | Attributes |
---|---|---|
userId | key | userId (ASC) |
postId | key | categoryId (ASC) |
Don’t forget to configure the collection permissions appropriately to enhance security and manageability, adjusting them as necessary for admin-level modifications in future steps.
Collection Permissions
One thing I’ve forgotten to mention throughout the series is you’ll need to setup your collection permissions. By default it’s set to collection wide. We dont want this.
Later on in the series we may need to adjust some permissions to allow things to be edited by an Administrator. But for now, go through each of your collection settings and double check they’re set to the following:
Profiles, Posts and Comments collections:
Categories collection:
🛠️ On The Tools
With the pleasantries out the way, let’s get cracking! Head over to your .env file and add the following to the bottom of the file:
REACT_APP_COMMENTS_COLLECTION=6263216f884ae458a235
Make sure you replace 6263216f884ae458a235
with the comments collection id found in your appwrite console.
Create Documents
We need to add some code into src/Services/api.js
to provide an interface for our UI to be able to create new doucmnets into our database. Add the following somewhere into the file:
createDocument: (collectionId, data, read, write) => {
return api.provider().database.createDocument(collectionId, 'unique()', data, read, write);
},
Essentially what we’re doing here is telling AppWrite’s SDK to call the REST endpoint that handles document creation with a unique ID along with the permission and data information for the document.
New Post
Open src/Components/Forum/Posts/NewPostButton/NewPostButton.js
and update it to look like the following:
const style = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'background.paper',
boxShadow: 24,
p: 4,
};
export function NewPostButton(props) {
const {REACT_APP_POSTS_COLLECTION} = process.env;
const user = useSelector((state) => state.user);
const [isLoggedIn, setIsLoggedIn] = useState(user.isLoggedIn);
const [open, setOpen] = React.useState(false);
const [title, setTitle] = React.useState('');
const [content, setContent] = React.useState('');
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
useEffect(() => {
setIsLoggedIn(user.isLoggedIn);
});
function submitPost(){
let {fetchPosts, id} = props;
api.createDocument(REACT_APP_POSTS_COLLECTION, {
'categoryId': id,
'userId': user.account.$id,
'title': title,
'content': content,
'author': user.account.name,
}, ['role:all']).then(() => {
setTitle('');
setContent('');
handleClose();
fetchPosts();
})
}
return isLoggedIn ? (
<>
<Button style={{marginTop: '1rem'}} variant="contained" color="primary" onClick={handleOpen} disableElevation>New Post</Button>
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={style}>
<Typography id="modal-modal-title" variant="h6" component="h2">
New Post
</Typography>
<TextField
fullWidth
label="Tile"
id="title"
sx={{mt: 1}}
value={title}
onChange={(e) => {setTitle(e.target.value)}}
/>
<TextField
sx={{mt: 1}}
id="content"
label="Content"
fullWidth
multiline
rows={4}
onChange={(e) => {setContent(e.target.value)}}
/>
<Button sx={{mt: 1}} variant="contained" onClick={() => submitPost()}>Submit</Button>
</Box>
</Modal>
</>
) : null;
}
Your also going to need to update src/Components/Forum/Posts/Posts.js
to pass through the category id through the props to the child component:
return (
<>
<Grid container>
<Grid item xs={6}>
<NewPostButton id={searchParams.get("id")} fetchPosts={fetchPosts}/>
</Grid>
<Grid item xs={6} style={{textAlign: 'right'}}>
<BackButton/>
</Grid>
</Grid>
{posts.map((post) => (
<PostItem title={post.title} description={post.description} author={post.author} key={post.$id} id={post.$id} />
))}
</>
);
Add Comment
We’re going to need a new button to click to create a new comment.
It’s very similar to the new post button. We could refactor it to leverage it for both scenarios; but I’m lazy. We will revisit this but for now, create a new file src/Components/Post/Components/NewCommentButton/NewCommentButton.js
with the following:
export function NewCommentButton(props) {
const user = useSelector((state) => state.user);
const [isLoggedIn, setIsLoggedIn] = useState(user.isLoggedIn);
useEffect(() => {
setIsLoggedIn(user.isLoggedIn);
});
return isLoggedIn ? <Button style={{marginTop: '1rem'}} variant="contained" color="primary" disableElevation>New
Comment</Button> : null;
}
View Post & Comments
Lets render the post and comments! Create a new file src/Components/Post/Post.js
with the following content:
export function Post(props) {
const {REACT_APP_COMMENTS_COLLECTION, REACT_APP_POSTS_COLLECTION} = process.env;
let [comments, setComments] = useState([]);
let [post, setPost] = useState({});
const [searchParams, setSearchParams] = useSearchParams();
const navigate = useNavigate();
function fetchComments() {
api.listDocuments(REACT_APP_COMMENTS_COLLECTION, [Query.equal('postId', searchParams.get("id"))]).then((result) => {
setComments(result.documents);
});
}
function fetchPost(){
api.getDocument(REACT_APP_POSTS_COLLECTION, searchParams.get("id")).then((post) => {
setPost(post)
});
}
useEffect(() => {
if (searchParams.get("id")) {
fetchComments();
fetchPost();
} else {
navigate('/');
}
}, []);
return (
<>
<Grid container>
<Grid item xs={6}>
<NewCommentButton id={searchParams.get("id")} fetchComments={fetchComments}/>
</Grid>
<Grid item xs={6} style={{textAlign: 'right'}}>
<BackButton/>
</Grid>
</Grid>
<Card style={{marginTop: '1rem'}}>
<CardContent>
<Typography gutterBottom variant="h5" component="div">
{post?.title}
</Typography>
<Typography variant="body2" color="text.secondary">
{post?.content}
</Typography>
<Typography variant="body2" color="text.secondary">
by {post?.author}
</Typography>
</CardContent>
</Card>
{comments.map((comment) => (
<Card style={{marginTop: '1rem'}}>
<CardContent>
<Typography variant="body2" color="text.secondary">
{comment?.content}
</Typography>
<Typography variant="body2" color="text.secondary">
by {comment?.author}
</Typography>
</CardContent>
</Card>
))}
</>
);
}
Final Adjustments
Now we’ve got the leg work out the way, let’s make some adjustments so what you’ve developed is useable. Head over to your App.js
file to add a new route. Under your ‘posts’ route, add the following:
<Route path="/post" element={<Post />}/>
Finally, let’s make posts clickable! Open src/Components/Forum/Posts/PostItem/PostItem.js
and update <CardActionArea>
to:
<CardActionArea onClick={() => {
navigate(`/post?id=${id}`);
}}>
You may also need to add this in the same function (under export function PostItem(props) {
):
const navigate = useNavigate();
You should now be able to add new posts, for example:
Also, if you login as another user you can see other comments and posts:
Conclusion
By following these steps, you’ll establish a basic yet operational forum platform, capable of listing categories, topics, and displaying comments.
Whats next?
Moving forward, our articles will focus on integrating smaller, incremental features to enhance our forum’s functionality.
Stay tuned for our upcoming sub-series, which will transition our project to utilize AWS’ Amplify, incorporating Lambda functions, API Gateway, and Cognito for a more robust infrastructure.
I’m eager to hear your thoughts and suggestions for future features! Feel free to reach out via Twitter or leave a comment below with your ideas.