gauravbytes.com is now codefoundry.dev.

Netlify provides global scale platfom for web projects without any server, devops and infrastructure setup. It has a good support deploy static and dynamic site and you can start your project for free.

Deploying Static Site generated by Next.js

Add following to the scripts in package.json.

"start": "next start",
"build": "next build",
"export": "next export"

Create netlify.toml file under root folder and add following commands.

[build]
  command = "npm run build && npm run export"
  publish = "out"

That's it. Go to netlify.com and follow instruction on setting up project with github repository.

Depoying server-rendered React Apps generated by Next.js

Netlify follows serverless architecture. It has the concept of Functions. You need to add little extra steps. Add next-on-netlify library to your project with npm i next-on-netlify command.

Add following to the scripts in package.json.

"start": "next start",
"build": "next build",
"export": "next export",
"postbuild": "next-on-netlify"

Create/ Update next.config.js file under root directory and add following code.

module.exports = {
  target: 'serverless'
}

With above code, we are changing the target from default server to serverless.

Create netlify.toml file under root folder and add following commands.

[build]
  command = "npm run build"
  functions = "out_functions"
  publish = "out_publish"

next-on-netlify library will change every server-side rendering code to individual function on Netlify.

That's it. Go to netlify.com and follow instruction on setting up project with github repository.

Recap

We explained how to deploy SSG and SSR React apps generatd by Next.js on Netlify platform.

What's next?

In the next post, we will dig deep on React hooks. So, stay tuned!

In the previous post, We created a BlogPost application rendering content on server-side with Next.js. We will be changing the same application to use SSG (Static Generation) capability of Next.js.

What will we create and which Next.js functions will we use?

We will create an application which will have index page to list all the available blog links and statically generated blog posts. We will use getStaticProps and getStaticPaths functions from Next.js.

getStaticProps function is called by Next.js at build time and pre-renders the page.

getStaticPaths function is also called by Next.js at build time. If you are using getStaticProps and there are dynamic paths then you define those in getStaticPaths

Update index.tsx

import React from 'react';
import { GetStaticProps, GetStaticPropsContext } from 'next';
import BloggerService from '../service/BloggerService';
import styles from '../App.module.css'

/* Line 1*/ interface IndexPageProps {
  blogLinks: Array<{ id: string, title: string }>
}


export default function IndexPage(props: IndexPageProps) {
  /* Line 2 */ return (
    <div className={ styles['blog-container'] }>
      <ul className={ styles['blog-posts'] }>
      {props.blogLinks.map(blogLink => <li key={blogLink.id}><a href={`/posts/${encodeURIComponent(blogLink.id)}`}>{blogLink.title}</a></li>)}
      </ul>
    </div>
  );
}

/* Line 3 */ export const getStaticProps: GetStaticProps = async (context: GetStaticPropsContext<any>) => {
  const bloggerPosts = await BloggerService.getAllPosts();
  const blogLinks = bloggerPosts.posts.map(post => {
    const splittedId = post.id.split('-')
      return {
        id: splittedId[splittedId.length - 1],
        title: post.title
      }
  });

  return {
    props: {
      blogLinks
    }
  }
}

At line 1, we defines the props type expected by IndexPage function. At Line 2, we are returning React.Element. We are iterating on the blogLinks passed as props to this component and returns a list. This property is passed from getStaticProps function at build time. At Line 3, we are defining getStaticProps function. We are calling BloggerService and retrieving blog feeds from https://codefoundry.dev. Then, we are extracting two properties id and title and are returning those from this method.

At the build time, Next.js will call this function on server-side and will pass these properties to IndexPage function and will generate static HTML on server. That static HTML will be served everytime.

Creating [slug].tsx to catch all the routes

Next step is to create [slug].tsx under pages/posts folder. This component will catch all the dymaically generated URL and will open the statically generated HTML if found otherwise endup showing 404 page.

import React from 'react';
import { GetStaticPaths, GetStaticProps, GetStaticPropsContext } from 'next';
import IBlogPost from '../../models/IBlogPost';
import BloggerService from '../../service/BloggerService';
import BlogPost from '../../components/BlogPost';
import styles from '../../App.module.css';

/* Line 1 */ interface IServerProps {
  post: IBlogPost
}

export default (props: IServerProps) => {
  /* Line 2 */ return (
    <>
      <div className={styles['App-Container']}>
        <BlogPost post={props.post} />
      </div>
    </>
  );
}

/* Line 3 */ export const getStaticPaths: GetStaticPaths = async() => {
  const bloggerPosts = await BloggerService.getAllPosts();
  const paths = bloggerPosts.posts.map(post => {
    const splittedId = post.id.split('-')
    const slug = splittedId[splittedId.length - 1]
    return {
      params: {
        slug
      }
    }
  });

  return {
    paths,
    fallback: false
  }
}

/* Line 4 */ export const getStaticProps: GetStaticProps = async(context: GetStaticPropsContext<any>) => {
  const slug = context?.params?.slug ?? 'na'
  const bloggerPosts = await BloggerService.getAllPosts();

  const post = bloggerPosts.posts.find( post => post.id.endsWith(slug))
  return {
    props: {
      post
    }
  }
} 

At Line 1, we are creating type for props this component takes. At Line 2, we are returning React.Element. We are passing post property from props to BlogPost component. This property is passed by getStaticProps function at build time.

At Line 3, we are defining getStaticPaths function. This function returns the list of dynamic paths this page will handle.

At Line 4, we are defining getStaticProps function. This function reads the slug property from context's params property. This proeprty is generated by getStaticPaths function(Line3) and then get the post from BloggerService and finally this post property is passed to this page (Line 2).

I have modified existing code for this example. Let's cleanup code which is not required.

First, remove unwanted libraries with npm remove @reduxjs/toolkit react-redux next-on-netlify command. Remove BlogSearch.tsx, BlogListing.tsx, BlogPosts.tsx and BlogPosts.module.css from src/components folder. Remove index.css and index.tsx from src folder. Update _app.tsx under src/pages and remove redux store and provider. That's it.

Let's run the application.

Click on any link and you will see statically generated blog page.

That's it :). You can download the full code from github.

Recap

In this post, we introduced getStaticPaths and getStaticProps methods of Next.js for Static site generation. We used both methods in [slug].tsx for dynamic path generation. At last, we removed unwanted files from last code example.

What's next?

In the next post, we will deploy Next.js project (Static Generation and SSR) on Netlify.

In the previous post, we created a BlogPost application with React and Redux and managed global state with Redux. We will extend the same application and will introduce Next.js for server-side rendering. The bigger benefit of using Next.js is pre-rendering of the page along with automatic code-splitting, static site export, CSS-in-JS.

Next.js functions

Next.js exposes three functions for data fetching and these are getStaticProps, getStaticPaths and getServerSideProps. First two functions are used for Static generation and the last function getServerSideProps is used for Server-side rendering. Static generation means the HTML is generated at the build(project build) time whereas in Server-side rendering HTML is generated at each request.

Adding required libraries

Run npm i --save next @types/next from root of the project to add the required libraries for this example.

Update following commands under scripts in package.json.

"dev": "next dev",
"start": "next start",
"build": "next build"

Next.js is built around the concept of pages. A page is nothing but a React component exported in pages directory. For example; pages/welcome.tsx will be mapped to /welcome. You also have an option to have Dynamic routes.

Basic setup

Let's start with creating _document.tsx and _app.tsx under src/pages directory. In _document.tsx, you defines your skeleton structure of your html page generated by Next.js. You usually do this to add meta tags or loading scripts or styles from CDN. Consider _app.tsx as the root component of your React application. In our example, we provide redux store to the Provider in this component.

// _document.tsx


import React from 'react';
import Document, { Html, Head, Main, NextScript } from 'next/document'

export default class MyDocument extends Document {
  render() {
    return (
    <Html lang="en">
      <Head>
        <meta content='https://www.codefoundry.dev/' property='og:url'/>
        <meta content='Tutorials for Java, Java 8, Spring, Spring Cloud, Spring Boot, React JS, Redux, Next.JS' property='og:description'/>
        <meta content='Gaurav Rai Mazra' name='Author'/>
        <meta content='https://www.codefoundry.dev/' property='og:url'/>
        <meta content='https://www.codefoundry.dev/favicon.ico' property='og:image'/>
      </Head>
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
    )
  }
}
// _app.tsx


import React from 'react';
import { AppProps } from 'next/app';
import { Provider } from 'react-redux';
import { store } from '../redux/store';

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <React.StrictMode>
      <Provider store={store}>
        <Component {...pageProps} />
      </Provider>
    </React.StrictMode>
  )
}

export default MyApp;

Creating first page

Let's create our first page index.tsx under src/pages directory.

/* Line 1 */
interface IServerProps {
  bloggerPosts: {
    allTags: string[]
    posts: IBlogPost[]
  }
}

export default (props: IServerProps) => {
  /* Line 2 */ const dispatch = useDispatch();
  useEffect(() => {
    /* Line 3 */ dispatch(setPostsAsync(props.bloggerPosts));
  }, [dispatch, props.bloggerPosts])
  return (<App />)
}

/* Line 4 */ export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext<any>) => {
  /* Line 5 */const bloggerPosts = await BloggerService.getAllPosts();
  return {
    props: {
      bloggerPosts
    }
  }
} 

At Line 1, we have defined type of prop field of this functional component. At line 2, We are using useDispatch hook from redux to get reference of dispatch function. Inside useEffect hook, at line 3, we are dispatching the bloggerPosts that were computed on server-side by Next.js(Line 4).

At Line 4, we are defining getServerSideProps function which gets executed on every request by Next.js on the server-side and the result is passed onto this functional component.

At Line 5, we are calling BloggerService's getAllPosts function which is retrieving the posts from blogger(http://codefoundry.dev)'s feed. Let's create this service(BloggerService.ts) as well under src/service.

/* Line 1 */ declare type BloggerEntry = {
  id: {
    $t: string
  },
  updated: {
    $t: string
  },
  published: {
    $t: string
  },
  category: Array<{scheme: string, term: string}>,
  title: {
    $t: string
  },
  summary: {
    $t: string
  },
  author: Array<{name: { $t: string }}>,
  link: Array<{ rel: string, href: string }>
}

const getAllPosts = async() => {
  /* Line 2 */ const response = await fetch('https://www.blogger.com/feeds/5554118637855932326/posts/summary?alt=json&start-index=1&max-results=100')
  const result = await response.json();
  const categories = result?.feed?.category ?? [];
  const allTags = (categories as Array<{term: string}>).map(category => category.term)
  const entries = result?.feed?.entry ?? [];
  const posts = (entries as Array<BloggerEntry>).map(entry => {
    const id = entry.id.$t;
    const datePublishedOrUpdated = entry.updated.$t || entry.published.$t;
    const tags = entry.category.map(cat => cat.term);
    const title = entry.title.$t;
    const content = entry.summary.$t;
    const author = entry.author.map(a => a.name.$t).join(', ')
    const postLink = entry.link.find(l => l.rel === 'alternate');
    const postUrl = !!postLink ? postLink.href : '';

    /* Line 3 */ return {
      id,
      tags,
      title,
      content,
      author,
      postUrl,
      postedOn: datePublishedOrUpdated
    }
  })
  return { allTags, posts };
}

export default { getAllPosts }

At Line 1, we declared a type BlogEntry which refers to entry sent by the blogger's feed. At Line 2, we are using fetch api to retrieve summary feed from blogger(http://codefoundry.dev) and we are transforming and returning it to the type that our reducer store understands (At Line 3).

Cleanup App.tsx and BlogPosts.tsx

Earlier, we hard-coded posts(POSTS array) in App.tsx and were passing to BlogPosts component. Let's clean it up.

// App.tsx
function App() {
  return (
    <>
      <div className={styles['App-Container']}>
        <BlogPosts />
      </div>
    </>
  );
}

export default App;
// BlogPosts.tsx
function BlogPosts() {
  return (
    <div className={styles["blog-container"]}>
      <BlogPost/>
      <BlogListing/>
    </div>
  );
}

Let's run the application with command npm run dev.

That's it :). You can download the full code from github.

Recap

In this post, we first added required set of libraries (next and @types/next). Then, we added scripts to build, run and start project with next.js. Then, we did basic setup for next.js application e.g. Setting up _document.tsx and _app.tsx. Then, we created our first page index.tsx and created getServerSideProps method for server-side rendering. At last, we cleaned up App.tsx and BlogPosts.tsx file and ran the application.

What's next?

In the next post, we will use Next.js to generate static site along with Dynamic routing in static site. So, stay tuned!

In the previous post, we created a blog post application with React and managed local state with useState hook. We will extend the same application and will introduce Redux and react-redux library for state management and @reduxjs/toolkit for opinionating setting up the redux store and creating selector function on state.

Adding required libraries

Run npm i --save @reduxjs/toolkit react-redux @types/react-redux from root of the project to add required set of libraries for this post.

Which new functions we will be using in this example?

  • configureStore from @reduxjs/toolkit
  • createSlice from @reduxjs/toolkit
  • useDispatch from react-redux
  • useSelctor from react-redux
  • useEffect from react

@reduxjs/toolkit: configureStore

This function provides a convenient abstraction over createStore function of redux library. It adds good defaults to the store for better experience(e.g. DevTools, Redux-Thunk for async actions).

@reduxjs/toolkit: createSlice

This function accepts an initial state, list of reducers and a 'slice name' and automatically generates action creators and action types for the reducer. You can also pass extraReducers to it for handling other complex reductions.

react-redux: useDispatch

This hook let's you access dispatch function of redux.

react-redux: useSelector

This Hook let's you tap on redux state and filter content. It takes selector function and optional equality function for state. If you require complex selector (memoized), then reselect library is a good choice. In this example, we will use simple selector on state.

React: useEffect

This hook is a combination of componentDidMount, componentDidUpdate and componentWillUnmount lifecycle methods of React. This hooks accepts the imperative function which can return the cleanup function as return statement; which will get executed on before every re-render. You can read a detailed post in React docs.

Configuring redux store

Create store.ts under src/redux folder.

import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit';
import blogPostsReducer from './reducer/blogPostsSlice';

export const store = configureStore({
  reducer: {
    blogPosts: blogPostsReducer,
  },
});

// Defining the root state
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
  ReturnType,
  RootState,
  unknown,
  Action<string>
>;

We are passing blogPostsReducer in the reducer parameter to configureStore function. We will be creating this reducer shorlty. Also, We have defined two types; one is RootState which defines the type of Root reducer and other is Appthunk which defines the ThunkAction (Async function).

Creating reducer for blog posts

Create blogPostsSlice.ts under src/redux/reducer folder.

Let's first add interface BlogPostsState which defines the state this slice will hold.

interface BlogPostsState {
  posts: IBlogPost[]
}

// Initial state of the reducer
const initialState: BlogPostsState = {
  posts: []
}

We will use createSlice function from @reduxjs/toolkit.

const blogPostsSlice = createSlice({
  name: 'blogPosts',
  initialState,
  reducers: {
   // We will soon pass the reducers here
  }
});

In the previous post, we managed all the local state in the BlogPosts.tsx component. We will start by moving `posts` stored in the local state to redux state. Define the setPosts reducer function under reducers property of the slice that we are creating.

const blogPostsSlice = createSlice({
  name: 'blogPosts',
  initialState,
  reducers: {
    setPosts: (state, action: PayloadAction<IBlogPost[]>) => {
      /*1.*/state.posts = action.payload
      // Alternate solution
      // return { ...state, posts: action.payload }
    }
  }
});

//actions froms slice
/*Line 2*/const { setPosts } = blogPostsSlice.actions;

// Async action functions
/*Line 3*/const setPostsAsync = (posts: IBlogPost[]): AppThunk => dispatch => {
  setTimeout(() => {
    dispatch(setPosts(posts))
  }, 500)
}

// Selector functions
/*Line 4*/ const selectPosts = (state: RootState) => state.blogPosts.posts;

/*Line 5*/export { selectPosts };

//action functions
/*Line 6*/export { setPosts, setPostsAsync };

// reducer
/*Line 7*/ export default blogPostsSlice.reducer;

At line 1, we are mutating the redux state directly. Don't worry, the state passed in the function as first argument is not the actual redux state but proxy on it. It uses immer library under the hood to manage and update the state(recreate). Alternatively, you can return your state object but can't do both(Mutating state and return new state object).

At line 2, we are getting actions created by createSlice function.

At line 3, we are creating async function to update posts. We are mimicking the async nature by setTimeout method. But, in real world, it would be replaced by API call to backend.

At Line 4, we have created selector function for posts. We are exporting selector function, actions and reducer at line 5, 6 and 7 respectively.

Update BlogPosts.tsx component

import { useDispatch, useSelector } from 'react-redux';
import { selectPosts, setPostsAsync } from '../redux/reducer/blogPostsSlice';

Use useEffect hook to update redux state with posts.

function BlogPosts(props: IBlogPostsProps) {
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(setPostsAsync(props.posts))
  }, [ props.posts, dispatch ]);


  ...
}

Replace local state management for posts with selecting state from redux store

/*Remove this line*/ //const [ posts, setPosts ] = useState(props.posts)
/*Add this line*/ const posts = useSelector(selectPosts);

Update onSearch function and replace it setPosts method with dispatch method.

function onSearch() {
  if (searchText !== '') {
    const foundPosts = props.posts.filter(filterPost)
    setShowingPost(findFirstPost(foundPosts))
    dispatch(setPostsAsync(foundPosts))
  } else {
    setShowingPost(findFirstPost(props.posts))
    dispatch(setPostsAsync(props.posts))
  }
}

Update index.tsx

Update index.tsx and wrap component with Provider component.

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

Now, run the application with npm run start command. The application will load as before but only change is we are reffering posts from redux store.

Update BlogPosts.tsx and replace all the local state with redux management state.

interface IBlogPostsProps {
  posts: Array<IBlogPost>
}


function BlogPosts(props: IBlogPostsProps) {
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(setPostsAsync(props.posts))
    dispatch(setShowingPostsAsync(props.posts && props.posts.length > 0 ? props.posts[0].id : 0))
  }, [ props.posts, dispatch ]);

  function findFirstPost(posts: Array<IBlogPost>) : IBlogPost | null {
    return posts && posts.length > 0 ? posts[0] : null;
  }

  const posts = useSelector(selectPosts);
  const showingPost = useSelector(selectShowingPost);
  const searchText = useSelector(selectSearchText);
  const selectedSearchOn = useSelector(selectSelectedSearchOn);

  function onBlogPostLinkClick(id: number): void {
    dispatch(setShowingPostsAsync(id));
  }
  
  function onChangeHandler(value: string, searchType: SearchType) : void {
   if (SearchType.SEARCH_TEXT === searchType) {
     dispatch(setSearchText(value));
   } else {
     dispatch(setSelectedSearchOn(value === SearchOnFields.TAG ? SearchOnFields.TAG : SearchOnFields.TITLE))
   }
  }

  function isMatched(value: string) {
    return value.toLowerCase().includes(searchText.toLowerCase())
  }

  function filterPost(post: IBlogPost) {
    if (selectedSearchOn === 'title') {
      return isMatched(post.title)
    } else {
      return post.tags.some(isMatched)
    }
  }

  function onSearch() {
    if (searchText !== '') {
      const foundPosts = props.posts.filter(filterPost)
      dispatch(setShowingPostsAsync(findFirstPost(foundPosts)?.id ?? 0))
      dispatch(setPostsAsync(foundPosts))
    } else {
      dispatch(setPostsAsync(props.posts))
      dispatch(setShowingPostsAsync(findFirstPost(props.posts)?.id ?? 0))
    }
  }

  return (
    <div className="blog-container">
      <BlogListing
        showingPost={showingPost?.id ?? 0}
        blogPosts={posts.map(post => { return {id: post.id, title: post.title }})}
        onClick={onBlogPostLinkClick}
        searchText={searchText}
        onSearchChange={onChangeHandler}
        onSearchButtonClick={onSearch}
        selectedSearchOn={selectedSearchOn}
      />
      {!!showingPost ? <BlogPost post={showingPost}/>: null }
    </div>
  );
}

export default BlogPosts;

Refactoring components

Before introducing redux, we managed whole state in the top level component aka BlogPosts.tsx and were passing the various variables to the child components. After introducing redux for state management, we don't require to pass on the various variables to child components. They can query it directly from the redux store using selector functions. Let's update all the components.

Updating BlogPost.tsx

function BlogPost() {
  /*Line 1*/const post = useSelector(selectShowingPost);
  return !!post ? (
    <div className='blog-post'>
      <div className='blog-post-title'>{post.title}</div>
      <div className='blog-post-body'>{post.content}</div>
      <div className='blog-post-footer'>
        <div className='blog-author'>{`By ${post.author} at ${post.postedOn}`}</div>
        <div className='blog-tags'>
          <div key='tags-label'>Tags: </div>
          {post.tags.map(tag => <div key={tag}>{tag}</div>)}
        </div>
      </div>
    </div>
  ) : (<></>);
}

export default BlogPost;

Explanation: We have removed the props and is using useSelector hook to get the current selectedPost for showing.

Updating BlogSearch.tsx

function BlogSearch() {
  const dispatch = useDispatch();

  /*Line 1*/const searchText = useSelector(selectSearchText);
  /*Line 2*/const selectedSearchOn = useSelector(selectSelectedSearchOn);

  function onSearchTextChange(event: ChangeEvent<HTMLInputElement>): void {
    /*Line 3*/dispatch(setSearchText(event.target.value));
  }

  function onSearchOnChange(event: ChangeEvent<HTMLSelectElement>): void {
    /*Line 4*/dispatch(setSelectedSearchOn(event.target.value === SearchOnFields.TAG ? SearchOnFields.TAG: SearchOnFields.TITLE));
  }

  return(
    <div className="blog-search-container">
      <div className='blog-search-title'>Search Blog</div>
      <div className='blog-search-body'>
        <input type="text" className="form-control" autoComplete="off" value={searchText ?? ''} onChange={onSearchTextChange}/>
        <select value={selectedSearchOn} className='form-control' onChange={onSearchOnChange}>
          <option value={SearchOnFields.TAG}>Tags</option>
          <option value={SearchOnFields.TITLE}>Title</option>
        </select>
        <button type="button" className="form-button" onClick={() => { /*Line 5*/dispatch(onSearchAsync()) }}>Search</button>
      </div>
    </div>
  );
}

export default BlogSearch;

Explanation: We have removed the props proeprty and type. At Line 1 and 2, we are using selector functions and useSelector hook to get searchText and selectedSearchOn from redux state respectively. At Line 3 and 4, we are using setSearchText and setSelectedSearchOn actions(redux) to update searchText and selectedSearchOn state in redux store respectively. At Line 5, we are calling onSearch action(blogPostsSlice) and updates the state in redux store for searchResults.

Update BlogListing.tsx

function BlogListing() {
  /*Line 1*/const blogPosts: IBlogPostListing[] = useSelector(selectPostsForListing);
  /*Line 2*/const showingPostId = useSelector(selectShowingPostId);

  const dispatch = useDispatch();

  return(
    <div className='blog-listing'>
      <BlogSearch/>
      <ul className="blog-posts">
        {
          blogPosts.map(post => <li className={showingPostId === post.id ? 'active' : ''} key={post.id} onClick={() => /*Line 3*/dispatch(setShowingPostsAsync(post.id))}>{post.title}</li>)
        }
      </ul>
    </div>
  );
}

export default BlogListing;

We have removed the props from BlogListing and also deleted IBlogListing type. At Line 1 and 2, we are getting state directly using selector function for posts and currently showing Blog post id respectively. At Line 3, we are triggering setShowingPostsAsync action created in blogPostsSlice.ts.

Updating BlogPosts.tsx

function BlogPosts(props: IBlogPostsProps) {
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(setPostsAsync(props.posts))
    dispatch(setShowingPostsAsync(props.posts && props.posts.length > 0 ? props.posts[0].id : 0))
  }, [ props.posts, dispatch ]);

  return (
    <div className="blog-container">
      <BlogListing/>
      <BlogPost/>
    </div>
  );
}

export default BlogPosts;

We have removed all the local state and function which we were passing to the children components. Now, we only are using React useEffect hook to update the redux state with posts.

That's it. :). You can get the full source code of this example from github.

Recap

In this post, we first added new libraries (react-redux, @reduxjs/toolkit). We explained few specific functions that we will be using in this example. Then, we created store.ts, blogPostsSlice.ts and started with replacing local state of posts from BlogPosts.tsx. Then, we replaced searchText, selectedSearchOn and showingPost from local state to redux state. We also added few selector functions. At last, we refactored our example and removed most of the method and variable reference from the top level component BlogPosts.tsx and added those in the respective components.

What's next?

In the next post, we will introduce server side rendering with Next.js. So, stay tuned!

Introduction

React was first introducted to general public in May 2013; roughly three years after the first release of Angular JS (October 2010). Soon, it picked up the momentum and now is the highest stared(~150K) and forked(29.2K) repository on Github. The positive point of React with its contemporary libraries was the backward compatibility in all the released versions. It started as a class based library (extending React.Component) to pure functional library with React Hooks; still keeping backward compatibility. Now, new features include asyncronous rendering with Suspense. React's ecosystem is very vast with lots of frameworks available to choose from. We will start with building first simple a.ka. Welcome react application and then build full-stack application with React, Redux, reselect, Next.JS, express JS and Node.JS. So, stay tuned :)

Building your first React application

You can create React application with project like create-react-app or can create customize project intialiting the project with npm and then pick and choose libraries of your choice. In this post, we will use create-react-app.

create-react-app conviently configures the tools like webpack, babel and testing libraries, so that you can concentrate purely on application code.

npx create-react-app my-first-react-app

npx is a package runner tool that comes with npm 5.2+ and higher.

This single line of code will setup Javascript based project, configures webpack, babel and testing libraries.

my-first-react-app
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│   ├── favicon.ico
│   ├── index.html
│   └── manifest.json
└── src
    ├── App.css
    ├── App.js
    ├── App.test.js
    ├── index.css
    ├── index.js
    ├── logo.svg
    └── serviceWorker.js
    └── setupTests.js

It will also configure commands in package.json to start, build, test and eject (A command to remove transitive dependencies of webpack, babel, testing libraries and copies directly to package.json so that you can customize accordingly).

Let's run the application with npm run start and visit localhost:3000 on browser.

Creating first React component

Let's start with creating a component.

Create a new file Welcome.js and Welcome.css under src folder.

Add following lines to Welcome.js

import React from 'react';
import './Welcome.css';

function Welcome() {
  return <div className='welcome'>Welcome! My first react app</div>
}

export default Welcome;

Here, we have created a functional component which is equivalent to extending React.Component class and adding render function in it.

import React from 'react';
import './Welcome.css';

class Welcome extends React.Component {
  render() {
    return <div className='welcome'>Welcome! My first react app</div>
  }
}

export default Welcome;

Now, the first React component; let's add it to App.js

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <Welcome />
      </header>
    </div>
  );
}

Now, go to localhost:3000 and you will see the component loaded.

Creating Typescript based first React application

Typescript is a typed superset of Javascript which compiles to plain Javascript. It provides the type safety to Javascript. create-react-app provides convenient way to change template for generating React skeleton project. Just pass the template parameter with value as follows.

npx create-react-app my-first-react-app --template typescript

Adding first typed component

Create a new file Welcome.tsx and Welcome.css under src folder.

Add following lines to Welcome.tsx

import React from 'react';
import './Welcome.css';

interface IWelcomeProps {
  message?: string
}

function Welcome(props: IWelcomeProps) {
  const message = props?.message ?? 'Welcome! My first React app with Typescript.'
  return (<div className='welcome'>{message}</div>);
}

export default Welcome;

We have defined the interface IWelcomeProps with single optional field message. In the functional component, we have used the Nullish coalescing added in Typescript 3.7.

Let's add this component to App.tsx

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <Welcome />
        <Welcome message="Welcome Reader! My first react app with Typescript."/>
      </header>
    </div>
  );
}

We have added Welcome twice; with and without message field. The output after running the application will look like this.

Recap

We created our first React application using create-react-app project. We added first React component Welcome.js and Welcome.tsx in Javascript and Typescript based projects respectively.

What's next?

In the next post, we will build a BlogPost application using React's functional component and React hooks. We will use useState hook for state management. So, stay tuned!

Building a Blog Post application

Let's create a blog post application. It will have below features:

  • Option to search blog posts.
  • Option to list blog posts.
  • Option to show blog post.

Create a new project with npx create-react-app react-blog-posts --template typescript.

Create BlogPosts.tsx component under src/components folder and IBlogPost model under src/models.

import React from 'react';
import IBlogPost from '../models/IBlogPost';

interface IBlogPostsProps {
  posts: Array<IBlogPost>
}


function BlogPosts(props: IBlogPostsProps) {
  return (
    <div className="blog-container">
      <ul className="blog-posts">
        {
          props.posts.map(post => <li key={post.id}>{post.title}</li>)
        }
      </ul>
    </div>
  );
}

export default BlogPosts;
interface IBlogPost {
  id: number
  title: string
  content: string
  author: string
  postedOn: string
  tags: string[]
}

export default IBlogPost;

Explanation: We have created BlogPosts function which takes parameter of type IBlogPostsProps. This type contains array of posts of type IBlogPost. We are only showing title of the Blog Post in this component. Shortly, we will update this component and extract listing of BlogPosts as seperate component. For now, Let's update App.tsx and use BlogPosts to show dummy posts.

function App() {
  return (
    <div className="App-Container">
      <BlogPosts posts={POSTS}/>
    </div>
  );
}

You can get the dummy posts array from here

Run the application npm run start and you will see the page loaded with post titles.

Now, Let's create a new component BlogPost.tsx which will show the selected blog post.

import React from 'react';
import IBlogPost from '../models/IBlogPost';
import './BlogPost.css';

interface IBlogPostProps {
  post: IBlogPost
}

function BlogPost(props: IBlogPostProps) {
  const post = props.post
  return (
    <div className='blog-post'>
      <div className='blog-post-title'>{post.title}</div>
      <div className='blog-post-body'>{post.content}</div>
      <div className='blog-post-footer'>
        <div className='blog-author'>{`By ${post.author} at ${post.postedOn}`}</div>
        <div className='blog-tags'>
          <div key='tags-label'>Tags: </div>
          {post.tags.map(tag => <div key={tag}>{tag}</div>)}
        </div>
      </div>
    </div>
  );
}

export default BlogPost;

Create a new component BlogListing.tsx to list the available posts to read.

import React from 'react';

declare type IBlogPostData = {
  id: number
  title: string
}

interface IBlogListing {
  blogPosts: IBlogPostData[]
  selectedBlogPost: number
  onClick: (id: number) => void
}

function BlogListing(props: IBlogListing) {
  return(
    <div className='blog-listing'>
      <ul className="blog-posts">
        {
          props.blogPosts.map(post => <li className={props.selectedBlogPost === post.id ? 'active' : ''} key={post.id} onClick={() => props.onClick(post.id)}>{post.title}</li>)
        }
      </ul>
    </div>
  );
}

export default BlogListing;

In this component, we have declared IBlogPostData as type which holds id and title of the blog to be listed. This component takes collection of posts, selectedBlogPost(active post) and onClick function (action to perform when link is clicked) as arguments.

Now, update BlogPosts.tsx component and use BlogListing and BlogPost in it.

function BlogPosts(props: IBlogPostsProps) {
  /*1.*/const firsBlogPost = props.posts && props.posts.length > 0 ? props.posts[0] : null;
  /*2.*/const [ selectedBlogPost, setSelectedBlogPost ] = useState<IBlogPost | null>(firsBlogPost);

  /*3.*/function onBlogPostLinkClick(id: number): void {
    const selectedBlogPost = props.posts.find(post => post.id === id);
    setSelectedBlogPost(!!selectedBlogPost ? selectedBlogPost : null);
  }

  return (
    <div className="blog-container">
      <BlogListing
        selectedBlogPost={selectedBlogPost?.id ?? 0}
        blogPosts={props.posts.map(post => { return {id: post.id, title: post.title }})}
        /*4.*/onClick={onBlogPostLinkClick}
      />
      {!!selectedBlogPost ? <BlogPost post={selectedBlogPost}/>: null }
    </div>
  );
}

export default BlogPosts;

Explanation: At line 1, we retrieve the first post from list of posts passed in this component. In line 2, We are using React hook useState for local state management. We are using this to mamange state for selected post to be shown in BlogPost.tsx component. At line 3, we declared a function which updates the selectedBlogPost in local state. At line 4, we are passing onBlogPostLinkClick function as an argument to BlogListing.tsx. This function will get called when you click on the any of the post link in BlogListing.tsx component.

Run the application npm run start and you will see the page loaded with first post as selected as shown in below screenshot.

Now, we will add option to search blog posts either by title or tags. Create a component BlogSearch.tsx under src/components folder.

import React, { ChangeEvent } from 'react';
import { SearchType } from '../models/SearchType';

interface IBlogSearchProps {
  searchText: string
  selectedSearchOn: string
  onSearchChange: (searchText: string, searchType: SearchType) => void
  onSearchButtonClick: () => void
}

function BlogSearch(props: IBlogSearchProps) {
  function onSearchTextChange(event: ChangeEvent<HTMLInputElement>): void {
    props.onSearchChange(event.target.value, SearchType.SEARCH_TEXT)
  }

  function onSearchOnChange(event: ChangeEvent<HTMLSelectElement>): void {
    props.onSearchChange(event.target.value, SearchType.SEARCH_ON)
  }

  return(
    <div className="blog-search-container">
      <div className='blog-search-title'>Search Blog</div>
      <div className='blog-search-body'>
        <input type="text" className="form-control" autoComplete="off" value={props?.searchText ?? ''} onChange={onSearchTextChange}/>
        <select value={props.selectedSearchOn} className='form-control' onChange={onSearchOnChange}>
          <option value='tag'>Tags</option>
          <option value='title'>Title</option>
        </select>
        <button type="button" className="form-button" onClick={props.onSearchButtonClick}>Search</button>
      </div>
    </div>
  );
}

export default BlogSearch;

Explanation: This component expects four properties; searchText (text to be searched), selectedSearchOn(Whether it is tag or title search) and two functions one for whenever there is a change in the Search Text or Search On fields and other function for when Search button is clicked. These functions are passed on from top component BlogPosts.tsx because we are doing local state management in that component and all other components are stateless.

We also updated BlogListing.tsx to use BlogSearch.tsx component. We also changed this component; it takes the four more properties used by BlogSearch.tsx component. Finally, we have updated BlogPosts.tsx component.

import React, { useState } from 'react';
import IBlogPost from '../models/IBlogPost';
import './BlogPosts.css';
import BlogListing from './BlogListing';
import BlogPost from './BlogPost';
import { SearchType } from '../models/SearchType';

interface IBlogPostsProps {
  posts: Array<IBlogPost>
}


function BlogPosts(props: IBlogPostsProps) {
  function findFirstPost(posts: Array<IBlogPost>) : IBlogPost | null {
    return posts && posts.length > 0 ? posts[0] : null;
  }

  /*1.*/const [ posts, setPosts ] = useState(props.posts)
  /*2.*/const [ showingPost, setShowingPost ] = useState<IBlogPost | null>(findFirstPost(posts));
  /*3.*/const [ searchText, setSearchText ] = useState<string>('');
  /*4.*/const [ selectedSearchOn, setSelectedSearchOn ] = useState<string>('tag')

  /*5.*/function onBlogPostLinkClick(id: number): void {
    const newShowingPost = posts.find(post => post.id === id);
    setShowingPost(!!newShowingPost ? newShowingPost : null);
  }
  
  /*6.*/function onChangeHandler(value: string, searchType: SearchType) : void {
   if (SearchType.SEARCH_TEXT === searchType) {
    setSearchText(value)
   } else {
     setSelectedSearchOn(value)
   }
  }

  function isMatched(value: string) {
    return value.toLowerCase().includes(searchText.toLowerCase())
  }

  function filterPost(post: IBlogPost) {
    if (selectedSearchOn === 'title') {
      return isMatched(post.title)
    } else {
      return post.tags.some(isMatched)
    }
  }

  /*7.*/function onSearch() {
    if (searchText !== '') {
      const foundPosts = props.posts.filter(filterPost)
      setShowingPost(findFirstPost(foundPosts))
      setPosts(foundPosts)
    } else {
      setShowingPost(findFirstPost(props.posts))
      setPosts(props.posts)
    }
  }

  return (
    <div className="blog-container">
      <BlogListing
        showingPost={showingPost?.id ?? 0}
        blogPosts={posts.map(post => { return {id: post.id, title: post.title }})}
        onClick={onBlogPostLinkClick}
        searchText={searchText}
        onSearchChange={onChangeHandler}
        onSearchButtonClick={onSearch}
        selectedSearchOn={selectedSearchOn}
      />
      {!!showingPost ? <BlogPost post={showingPost}/>: null }
    </div>
  );
}

export default BlogPosts;

Line 1 to 4 declare posts, showingPost, searchText and selectedSearchOn respectively.

Line 5 defines onClick function whenever post link is clicked on BlogListing component. This function takes the blog id to be shown and search in the list of posts (see Line 1) in local state and updates the showingPost(see Line 2) field in the local state.

Line 6 defines a onChange function which get called whenever searchtext or searchon field is changing on BlogSearch component.Based on search type, it either updates searchText (see line 3) or selectedSearchOn (see line 4) in local state.

Line 7 defines onClick function for Search button on BlogSearch component. This function updates the posts (see line 1) and showingPost (see line 2) in the local state based on searchText (see line 3) and selectedSearchOn (See line 4) fields in the local state.

Recap

We used create-react-app module to create first React project (Javascript and Typescript based). Then, we added first React component (Welcome.js and Welcome.tsx) in the projects. We started building a blog website which have functionality to list posts, search posts and show post. Then, We created BlogPosts.tsx which was only showing the name of posts. Then, we created two components BlogListing.tsx to show the list of posts and BlogPost.tsx to show the currently viewing post. Then, we added statement management in BlogPosts.tsx to show the post whenever post link is clicked in BlogListing.tsx component. Next, we added BlogSearch.tsx component to search blog based on Title or Tags.

What's next?

In the next post, we will introduce Redux to manage the state and reselect to add selector in the application. Stay tuned!.

Note: You can download the final source code for this application from github.

In this post, we will look into how to retrieve auto-generated keys in JDBC. We will also explore usage of PreparedStatementCreator and PreparedStatementCallback in JdbcTemplate.

There are cases when you rely on Database server to auto generate values for some columns of the table. E.g. auto increment primary key, creation_date or any other column while inserting records. There is a way with which you can retrieve those auto-generated keys when you execute the insert statement. Let's see how you can do this using Spring JDBC but first we will see what PreparedStatementCreator and PreparedStatementCallback interfaces are.

What is PreparedStatementCreator?

There are cases when you want to create PreparedStatement yourself. One use case is to return auto generated keys. In that case, Spring JDBC provides you an option to do so by providing implementation for PreparedStatementCreator. Let's create an implementation of PreparedStatementCreator which sets those options.

public class ReturnGeneratedKeysPreparedStatementCreator implements PreparedStatementCreator, SqlProvider {
  private final String sql;
  private String[] generatedColumnNames;

  public ReturnGeneratedKeysPreparedStatementCreator(String sql) {
    this(sql, Collections.emptyList());
  }

  public ReturnGeneratedKeysPreparedStatementCreator(String sql, List<String> generatedColumnNames) {
    this.sql = sql;
    this.generatedColumnNames = Objects.nonNull(generatedColumnNames)
    ? generatedColumnNames.toArray(new String[generatedColumnNames.size()])
    : new String[0];
  }

  @Override
  public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
    return generatedColumnNames.length > 0 ? con.prepareStatement(this.sql, this.generatedColumnNames)
    : con.prepareStatement(this.sql, Statement.RETURN_GENERATED_KEYS);
  }

  @Override
  public String getSql() {
    return this.sql;
  }

}

There are two options which you can use to retrieve generated keys. One is to get all the generated keys and other is to pass the columsn you want to retrieve.

What is PreparedStatementCallback?

In normal usage, you might never need to implement this interface but will provide implementation only if you need to execute some other code e.g. retrieval of auto-generated keys. Let's see how you can do this with implementing this interface.

class GeneratedKeysPreparedStatementCallback implements PreparedStatementCallback<Integer> {

  @Override
  public Integer doInPreparedStatement(PreparedStatement ps) throws SQLException {
    int updated = ps.executeUpdate();
    if (updated > 0) {
      try (ResultSet rs = ps.getGeneratedKeys()) {
        if (rs.next())
          return rs.getInt("id");
      }
   
      throw new DataRetrievalFailureException("There was no key auto generated by the database");
    }
    throw new DataRetrievalFailureException("Nothing was updated");
  }
}

Usage

Integer key = jdbcTemplate.execute(new ReturnGeneratedKeysPreparedStatementCreator(
    "insert into product(name, category, description) values('Acer Laptop', 'laptop', 'Predator series')"),
    new GeneratedKeysPreparedStatementCallback());
log.info(() -> String.format("Product saved in database with key: %d", key));

That's it. You can find the complete code on Github.