Cover Photo by Ugur Akdemir on Unsplash
I have made it! This is my final project for the Flatiron School software engineering bootcamp! I was so excited to start this project, I flew through the last section of the course. Partially because I know from my experience building all of my previous projects that project mode is where I learn the most. And React is a handful - I did so much googling and reading and researching.
I landed on the idea of creating a book review app for this project after sifting through some other ideas that turned out to be a bit complicated for a first foray into building with React. The basic idea is to create a space where people can find or add a book in the database, then add their review to it. My MVP requires just the book and review models, but I built out my Rails API back end with the full app in mind -- I used the Rails scaffold generator to spin up four models: User, Book, Review, and Comment (no test framework, please). This was so quick that it felt like cheating. I added some books and a couple of reviews to the seed file, sorted out model associations, and set up my serializers.
A note on serializers here. I used
fast-jsonapi
for this project because I was familiar with it. I highly recommend that you use a different serializer like ActiveModel Serializer, because fast-jsonapi nests your serialized data inside a data object, which makes your data more complicated to work with on the front end. I spent a lot of time console-logging and sorting out how to access the pieces of data I wanted, and using a less deeply nested set of data would have made things a lot easier.
After playing around with creating and relating objects and seeing what I could access at different endpoints, I moved on to the front end.
I created a boilerplate react app using the create-react-app
command. I then spent a few minutes removing the bits I wasn't going to need. Then I tackled adding some dependencies. The project requirements state that I have to make use of Redux and Thunk, and I knew I wanted to tackle adding styling with bootstrap, as well as handling client-side routing with React Router, so I added those dependencies with npm.
npm install redux
npm install react-redux
npm install redux-thunk
npm install bootstrap
npm install react-bootstrap
npm install react-router-dom
The next step was to tackle setting up my App component, which I refactored from the boilerplate functional component to a class component. I then moved over to the Index component and set up the Redux store.
import React from 'react';
import ReactDOM from 'react-dom';
import {createStore, applyMiddleware, compose} from 'redux';
import thunk from 'redux-thunk';
import {Provider} from 'react-redux'
import './index.css';
import App from './App';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
// store is where data is stored globally, reducers tell us what to do with our store based on certain actions.
let store = createStore(booksReducer, composeEnhancers(applyMiddleware(thunk)));
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
And then create a reducer:
export default function booksReducer(state = {books: []}, action) {
return state;
}
This is where the app will process actions and modify the Redux store.
With the store and reducer set up, I moved on to creating some book components. I decided that the main component will be the BookContainer, which will render BooksList, BookCreateForm, and BookShow components. BooksList contains links to all of the books, BookShow renders a single book, and BookCreateForm contains a controlled form for creating a new book.
I ran into some trouble getting the correct book to render on the BookShow component. I ended up using the
useParams()
hook that comes built into React to access the id in the URL the user clicked in order to render the component, then used that id to filter out the correct book from state. Usinglet book = props.books[props.match.params.id - 1]
selected the book based on its position in the array, and I wanted the id of the book rendered to match the id in the URL, not a position in the props array. The final code I used to achieve this looks like this:
const {id} = useParams(); // This pulls the id value directly from the URL.
let book = props.books.filter(book => book.id === id) // This line filters out the book we're looking for based on the ID in the URL
if (book.length === 0) return null; // check if the book was found. If not, return null (allow time for the fetch to run).
let bookData = (book && book[0].attributes); // This is just to make it so I don't have to say 'book.attributes.property' every time I want to access the book data. Thanks, fast-jsonapi!
I followed a flow like this to complete the rest of the project: Create components -> import necessary files -> decide if the component should be a class or functional component -> build out the component -> connect to the store if necessary -> build a new action if necessary -> add a case to the reducer to handle the action if necessary -> connect to other components with React Router if needed -> test everything works.
With the Book components set up, I moved on to the Review components. I created a Review container that will be rendered by the BookShow component. The only place reviews will be visible currently is on a book's individual show page. Inside the ReviewContainer component, there are CreateReviewForm and Reviews components. CreateReviewForm contains a controlled form for adding a review to a Book, and the Reviews component is responsible for rendering all of the reviews.
With the main functionality in place, I moved on to UI design. The first thing I did with bootstrap is add the CDN script to the head of the index.html file. Aside from removing some standard create-react-app code, this was the only time I touched this file. I chose to use the CDN because users may have already come across it and their browsers will already be aware of Bootstrap's styles, which means that the styles for my app will load faster in their browser.
With that set up, I created a Navbar component and added links to the Routes that have been created throughout my app. I then styled it responsively using bootstrap class tags. I'll be honest: I pieced this together based on Bootstrap's documentation examples after a couple of hours of googling and figuring out how to set my own color scheme.
I found this article to be very helpful for setting up a responsive navbar.
The final component added is a Home component, which serves as a landing page for the app. My final steps were to move through all of my components and apply appropriate styles using bootstrap components and class tags. While I did spend a lot of time learning, implementing the Bootstrap library turned out to be much faster at making the app look appealing than trying to write out the CSS all on my own.
Overall, I am pleased with the outcome of this project. I am still a little shaky on some of the concepts, but I have a much better grasp on React and Redux (and Bootstrap!) than I did when I started. I am excited to come back to this project after graduation to sort out adding users, authentication, and comments.
If you'd like to check out the repos, you can find the front end here, and the back end here. (And if you do check them out, shoot me a message! I'd love to talk about it, especially if you have suggestions.)
Top comments (8)
Hi, I'm a Redux maintainer. Great to hear that you've picked up the core concepts of Redux!
FWIW, today we teach using our official Redux Toolkit package to write your Redux logic. It'll simplify a lot of the code you've got there, including the store setup and your reducers. I'd encourage you to check it out and give it a shot:
Also be sure to see our "Style Guide" docs page for the patterns and techniques we recommend for writing good Redux apps:
(unfortunately, most tutorials and course curriculums out there still haven't been updated to actually use RTK or many of the patterns we show in the Style Guide, so they're rather outdated.)
Thanks for this info! I know the curriculum I just went through is a bit out of date, and the good news (for new students) is that I hear that Flatiron is in the process of updating it.
I'm looking forward to updating my knowledge now that I get to choose what to study again!
So I have spent a couple of days going through the documentation for Redux Toolkit, and I just wanted to say that the documentation is amazing! Thank you! I'm going to be refactoring my project to use the Redux Toolkit shortly :)
Great, glad to hear it! Please let us know if there's anything we can do to improve the docs.
Before Redux Toolkit came out, I was afraid to use Redux. It seemed very complicated to me. I recently started using Redux Toolkit and I am very happy now. Thanks for this great development.
hi,
this looks awesome. y don't you put it online for public use..
Thank you! I do plan to host it eventually. I want to add the ability for users to sign up and log in to track their own reviews, and thus have reviews on books attributed to them. The current state of the project was my MVP. It still needs some work :)
I can collab with you for the api support if you like.
I really like the Idea and will be more than happy to be a part of it..