ReactCrashCourse
Setting up a React App
Creating a React app is not a walk in the park, especially if it’s your first time. As I worked on my first React app there were many things I encountered along the way that I felt was important to highlight and also what I felt I could’ve done different, so let us begin.
So starting out like most front-end applications, we need a source or an api to fetch our data so we have something to work with. I used Ruby on Rails which I am very fond with the simplicity of its syntax.
For my first React app I decided to go with a simple news style app. So you can checkout my schema below:
create_table "articles", force: :cascade do |t|
t.integer "category_id", null: false
t.string "title"
t.string "body"
t.string "subtitle"
t.string "author"
t.datetime "publish_date"
t.string "image"
t.index ["category_id"], name: "index_articles_on_category_id"
endcreate_table "categories", force: :cascade do |t|
t.string "name"
end
So as you can see we are working with just two models at the moment, so the next part is creating the React app in the terminal.
create-react-app
This is the way I created my app. After doing this I make sure that my api is properly working so I will fetch inside src/App.js using componentDidMount() which is a built-in function that is invoked immediately when component is loaded into the DOM.
src/app.js
componentDidMount(){
fetch('http://localhost:3000/api/v1/categories', {
method: 'GET'
})
.then(response => response.json())
.then(data => console.log(data ))
}
So in the console if the api you’re fetching from is seeded with data then it should log an array of objects depending on what you were aiming to fetch. I also want to notate that I changed my App.js into a class rather than a functional component.
The next step in making sure you have all of your packages that you will be using. The ones I used were react-redux, redux-thunk, react-router-dom. After using npm install for each of these you can double check it in package.json.
package.json
{"name": "the-break-app-frontend",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.16.3",
"@testing-library/react": "^12.1.4",
"@testing-library/user-event": "^13.5.0",
"bootstrap": "^5.1.3",
"navigation-bar": "^0.0.7",
"react": "^17.0.2",
"react-bootstrap": "^2.2.3",
"react-dom": "^17.0.2",
"react-redux": "^7.2.6",
"react-router-dom": "^5.2.0",
"react-scripts": "^2.1.3",
"redux": "^4.1.2",
"redux-thunk": "^2.4.1",
"startbootstrap-clean-blog": "^6.0.8",
"web-vitals": "^2.1.4"
},
One thing I would’ve done differently was adding Bootstrap into the packages now and finding a template I wanted to use to sync with my app before actually building anything. I got a little confused trying to incorporate everything after building the basic functionality of my app.
The next step I did was very helpful to me it was setting up the redux store now. So to start off making a store theres a few things that needs to be done.
src/index.js
import {createStore, applyMiddleware, compose} from 'redux'
import thunk from 'redux-thunk'
import { Provider } from 'react-redux'
A quick breakdown what these do, thunk is a middleware we are using , while compose combines middleware into one so we can pass one argument.
src/index.js
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
We then create our store passing in the arguments it will take in. The store is where we are storing our data globally. It takes a reducer argument, which the reducer is responsible for an action object which will update our store.
let store = createStore(reducer, composeEnhancers(applyMiddleware(thunk)))
So Provider on the other hand allows any component wrapped in it to have access to our store. We must pass in store into Provider like we do a prop.
<Provider store={store}>
On my next project I do want to experiment with making the store in a separate file rather then having it in the index.js, I then moved onto creating my respective directories: containers, actions, components, and a reducer.
Breaking down a reducer, in simple terms this is a conditional which defines different action types it is a function that will take in two arguments a state/object and an action object. Since I used a serializer and with the help of es6 naming convention is key when passing in the state.
src/reducers/categoryReducer.js
export default function categoryReducer(state = {categories: []}, action){
return state
}
Don’t forget that your reducers need to be imported into index.js or wherever holds your store.
Moving on now that the store and reducer is set up we must move on to creating an action that will be used to update the backend and DOM. With this blog I will be just covering one action to start you off. So to start off we want to fetchCategories or whatever data you plan on trying to get.
src/actions/fetchCategories.js
export function fetchCategories() {
return (dispatch) => {
fetch('http://localhost:3000/api/v1/categories')
.then(response => response.json())
.then(categories => dispatch({
type: 'FETCH_CATEGORIES',
payload: categories
}))
}
}
So the above code is just like vanilla javascript we are fetching from our source, then chaining along some then. The difference here is we have access to dispatch which comes from thunk. We can call dispatch and pass it in the two arguments needed, the first being the type which is the name that will match the conditional in the reducer. We then pass in the payload which is the data returning from the backend which will update our state.
import {connect} from 'react-redux'export default connect(mapStateToProps, mapDispatchToProps or directly pass action creator(fetchCategories))
Using connect was a little confusing but we import it from redux which then we use it at the bottom of our code when exporting, Connect takes in two arguments, it’s important to notate that if not passing in one we replace it with null. This is to connect a component and to either receive the current state in the store or to dispatch new actions to our store directly.
So jus to notate on thunk we use this to make async requests because when using a backend these fetch request may take a little more time so connect is expecting an immediate return from the action creator so think allows us to call dispatch from inside action creator and lets it finish before dispatching.
To get this working we can create a component that will render the fetch data.
import React from 'react'
import { Link, } from 'react-router-dom'
import categoryReducer from '../reducers/categoryReducer'
import Category from './Category'
import { connect } from 'react-redux'
import { deleteCategory } from '../actions/deleteCategory'const Categories = (props) => {const handleDelete = (category) => {props.deleteCategory(category.id)
}return (
<div>
<h1>Categories</h1>
<Link to='/categories/new'><button id='create-category-button'>create new category</button></Link>{props.categories.map(category =>
<div key={category.id}>
<Link to={`categories/${category.id}`}><h4>{category.name}</h4></Link>
<Link to={`/categories/${category.id}/edit`}><button>edit</button></Link>
<button onClick={() => handleDelete(category, props.categories)}>Delete</button>
</div>
)}
</div>
)
}
const mapStateToProps = state => {
return {
categories: state.categories
}
}
export default connect(mapStateToProps, {deleteCategory}) (Categories)
Stay tuned for part 2 of this react crash course I will be breaking down the code above and how I got it working. Thank you for reading and hope you got some insightful tips and knowledge.