Learning React - Building a simple list of blog posts with React and Bootstrap


The best way to learn React is to dive straight in and build something. To keep things simple I decided to copy our current www.coursedate.com website (PHP with Twig templates) in React. That way I wouldn’t have to spend any time thinking about the usual stuff that comes with a new web project (requirements, UI design etc), instead I could just focus on understanding how React works.

I’m using Express.js with React. I created the React project in a folder called client inside the Express project. I think that’s just for convenience - I can open both projects at once in VS Code.

There isn’t much to the Express project. There’s an API called GetPosts that returns a list of blog posts from the database as JSON. One tip - when you’re trying to get your head around React, it can be quicker to work with dummy data - manually create some JSON that you can use in the React component and leave the backend stuff till later.

The blog component was the first place where I had to do a bit more than just recreate static pages. It’s a simple component, but it introduces a bunch of different React concepts. It replaces www.coursedate.com/blog

1. How does the Blog.jsx component get data from the database?

There’s a method in Blog.jsx called getPosts that calls the API in the Express part of the project. It takes 2 parameters - an offset (because we only want the posts for the page we are looking at) and an optional category.

                     
                        getPosts = (offset, cat='') => {    
                             fetch('http://localhost:3001/blog/posts?offset=' + offset + '&category=' + cat)
                             .then(res => res.json())
                             .then((data) => {
                               this.setState({ posts: data.rows, totalPosts: parseInt(data.rows[0].full_count) });
                             })
                             .catch(console.log)
                         }
                     
                  

The data that comes back from the API is saved in the component’s state - a row of data for each blog post and the total number of blog posts in the data.

2. Where do we call getPosts?

There are two places in Blog.jsx where we call getPosts to get fresh data.

The first time is when the component is loaded and the componentDidMount lifecycle method is called.

                     
                        componentDidMount(){
                           //get category if it exists
                           let category = this.props.match.params.blogCategory;
                           if(!category){
                             category = '';
                           }
                           //get the initial page of results
                           let page = this.getUrlPageParam();
                           let offset = (page - 1) * 6;
                           this.getPosts(offset, category);
                         }
                     
                  

Then there’s the componentDidUpdate lifecycle method. That gets called everytime the component’s props or state changes. componentDidUpdate actually gets called after Render - the component is re-rendered and then componentDidUpdate is called. We then call getPosts if we need to revisit the database from fresh data.

                     
                        componentDidUpdate(prevProps, prevState, snapshot){    
                           window.scrollTo(0, 0);
                           //we should refresh data if the page or category has changed
                           if (this.props.location.search !== prevProps.location.search || this.props.location.pathname !== prevProps.location.pathname) {
                             //get category if it exists
                             let category = this.props.match.params.blogCategory;
                             if(!category){
                               category = '';
                             }
                             //get the initial page of results
                             this.initialPage = this.getUrlPageParam();
                             let offset = (this.initialPage - 1) * 6;
                             this.getPosts(offset, category);
                           }
                         }
                     
                  

The slightly confusing thing about componentDidUpdate is that the call to getPosts changes the component state again - so another call to the Render function. There’s a check in componentDidUpdate so we don’t end up in a kind of infinite loop.

3. Rendering the rows - how does the component build up rows of Bootstrap cards, one for each blog post?

Render() returns the JSX that’s used to create the html we see in the browser. The Blog page on the original site uses a grid of Bootstrap cards to display blog post summaries. Each card has a title, small image, a category and the few words of the post body.

The grid is 3 cards per row and there are 2 rows. There’s another function in Blog.jsx called renderRows() that builds the grid. It’s clever, but simple.

                     
                        renderRows(){
                           let results = this.state.posts;
                           let finalArr = [], cols = [];
                           console.log(results);
                           results.forEach ((result, i, results) => {
                             // prepare the array
                             cols.push( 
                               <div key ={result.b_id} className="col-md-4"> 
                                 <div className="card card-ani card-course box-shadow">
                                     {result["image"] === 1 				
                                       ? <img src={'/images/blog/' + result.b_id + '.jpg'} alt={result.title} className="card-img-top mb-1" />
                                       : <img src="/images/blog/default.jpg" alt={result.b_title} className="card-img-top mb-1" />
                                     }		
                                     <div className="card-body">  
                                       <div className="blog-category mb-3">
                                         <a href={'/blog/' + result.cat_url}>{result.cat_name}</a>
                                       </div>
                                       <div className="blog-post-date mb-3">
                                         <Moment format="Do MMMM YYYY">{result.b_createddate}</Moment>
                                       </div> 						
                                       <h2 className="blog-title">
                                         {result.b_url.length === 0
                                           ? <a style=&lcub{color:'#555'}} href={'/blogpost/' + result.cat_url + '/' + result.b_id + '/' + result.title }>{result.b_title}</a>
                                           : <a style=&lcub{color:'#555'}} href={'/blogpost/' + result.cat_url + '/' + result.b_id + '/' + encodeURI(result.b_url)}>{result.b_title}</a>
                                         }
                                       </h2>
                                       <div className="blog-summary mb-3">
                                         <div dangerouslySetInnerHTML=&lcub{ __html: this.textEllipsis(result.b_summary, 150) }} />
                                       </div> 		
                       
                                       <div className="blog-summary mb-3">
                                         {result.b_url.length === 0
                                           ? <a style=&lcub{color:'#555'}} href={'/blogpost/' + result.cat_url + '/' + result.b_id + '/' + result.b_title }>Read more</a>
                                           : <a style=&lcub{color:'#555'}} href={'/blogpost/' + result.cat_url + '/' + result.b_id + '/' + encodeURI(result.b_url)}>Read more</a>
                                         }
                                       </div> 								
                                   </div>
                                 </div>
                               </div>
                             );
                             // after three items add a new row 
                             if((i+1) % 3 === 0) {
                               finalArr.push(
                                 <div key ={i} className="row cards">
                                   <div className="col-md-12 mb-1 mt-1 pl-0">
                                     <div className="card-deck mb-5">
                                       {cols}
                                     </div>
                                   </div>
                                 </div>
                               );
                               cols = [];
                             }else{
                               if((i+1) === results.length){
                                 finalArr.push(
                                   <div key ={i} className="row cards">
                                     <div className="col-md-12 mb-1 mt-1 pl-0">
                                       <div className="card-deck mb-5">
                                         {cols}
                                       </div>
                                     </div>
                                   </div>
                                 );
                                 cols = [];
                               }
                             }
                           });
                           return finalArr;
                         }
                        
                  

3 cards at a time are pushed into an array called cols. Then, when we’ve got the 3 cards in cols, we push that array into an array called rows. Each element in the rows array is an array of 3 cards.

The neat thing is that in React it takes very little code to output the elements inside the rows array. All we need to do is put this inside our JSX in Render().

                     
                        <div className="App">
                           
                           <div className ="container">
                             
                             {this.renderRows()}
                         
                             <div className="d-flex flex-row py-4 align-items-center">
                               <Pagination initialPage={this.initialPage} key={totalPosts} totalPosts={totalPosts} pageLimit={6} pageNeighbours={1} onPageChanged={this.onPageChanged} />
                             </div>
           
                           </div>
                         </div>      
                     
                  

Conclusion

One thing I haven’t covered in this post is how the pagination or filtering by category works. That was a bit more complicated to implement and I’ll make that the subject of a future post.

The bigger goal for me is to get to the point where I can make an informed choice between PHP & Twig vs. React & Express.js. I’m interested in how you can develop big web applications that will grow overtime, but don’t become harder and harder to maintain. A big bespoke web application (or software product) might get a lot of development from different developers over several years. Is React a better choice if you want to keep creeping complexity at bay?



Related Posts