Post Component in React

This is the sixth part of Create react app with GraphQL in WordPress

In this part, we will mainly develop Post component for out application. But, before dive in main part, we will develop Loading component for see landing or search status in our application. Let’s dive in Loading component development.

Create a file named styled.js in components directory. In this file first import styled and keyframes class from styled-components package. Styled class is familiar to us. It helps us to style component efficient way. Beside, keyframes helper class will help us to develop animation. Keyframes in this package works as CSS3 @keyframes rule.

import styled, { keyframes } from "styled-components";

Then, assign styled in a exported constant named LoadingState. In this constant first declare some general properties and in after psuedo element define animation-name loading in special way. So, create a constant variable named loading. In this variable, we use keyframes and define animation as usual. Finally, styled.js is look like this

import styled, { keyframes } from "styled-components";

const loading = keyframes`
    from {
      transform: scale(1);
    }
    to {
      transform: scale(1.5);
    }
`;

const LoadingState = styled.div`
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  position: relative;
  &:after {
    content: "";
    position: absolute;
    width: 50px;
    height: 50px;
    border-radius: 50%;
    background-color: #dedede;
    opacity: 0.4;
    animation: ${loading} 1s linear 0.1s infinite alternate;
  }
`;

export default LoadingState;

Now, we can import LoadingState both in App.js and Post.js. Awesome way to reuse.
Open src/components/App.js and delete styled-component import lines and related code. Just import LoadingState from styled.js file and use it second return in render method. Now src/components/App.js looks like this-

import React from "react";
import styled from "styled-components";
import Posts from "./Posts";
import PostWidget from "./PostWidget";
import LoadingState from "./styled";

const Widget = styled.div`
  border: 1px solid gray;
  padding: 10px;
  p {
    margin-bottom: 0;
    border-top: 1px solid gray;
    }
  }
`;

class App extends React.Component {
  render() {
    if (this.props.state.length) {
      return (
        <div className="row">
          <div className="column column-80">
            {this.props.state.map(post => (
              <Posts key={post.node.id} post={post} />
            ))}
          </div>
          <div className="column column-20">
            <Widget>
              <h3>Recent posts</h3>
              {this.props.state.slice(0, 2).map(post => (
                <PostWidget key={post.node.id} post={post} />
              ))}
            </Widget>
          </div>
        </div>
      );
    }
    return <LoadingState>Loading...</LoadingState>;
  }
}
export default App;

Let’s dive in Post component development.

Post component development

Open src/components/post.js. First import React from react package, Dompurify from dompurify package and LoadingState from styled.js which we developed just.

import React from "react";
import DOMPurify from "dompurify";
import LoadingState from "./styled";

Then, in our Post’s render method, we have to write a little bit complex (for me) code block.

  • Check component have state in its props.
  • During the checking time render LoadingState component.
  • Get post ID from props match object.
  • Loop every state and check this node id is equal to post id.
  • If found, store this state objecte in a predefined variable postInfo.
  • If not found, just render Post Not Found
  • When postInfo found, return post tiltle, body in defined format.

In code, this algorithm will be,

if (this.props.state.length) {
      let postInfo = {};
      const state = this.props.state;
      const postID = this.props.match.params.postID;
      state.forEach(function(post, index) {
        if (post.node.id === postID) {
          postInfo = post;
        }
      });
      if (postInfo.node) {
        return (
          <div className="column">
            <h2
              dangerouslySetInnerHTML={{
                __html: DOMPurify.sanitize(postInfo.node.title)
              }}
            />
            <p
              dangerouslySetInnerHTML={{
                __html: DOMPurify.sanitize(postInfo.node.content)
              }}
            />
          </div>
        );
      } else {
        return <div>Post Not Found</div>;
      }
    }
    return <LoadingState>Searching...</LoadingState>;

So, final code of src/component/post.js

import React from "react";
import DOMPurify from "dompurify";
import LoadingState from "./styled";

class Post extends React.Component {
  render() {
    if (this.props.state.length) {
      let postInfo = {};
      const state = this.props.state;
      const postID = this.props.match.params.postID;
      state.forEach(function(post, index) {
        if (post.node.id === postID) {
          postInfo = post;
        }
      });
      if (postInfo.node) {
        return (
          <div className="column">
            <h2
              dangerouslySetInnerHTML={{
                __html: DOMPurify.sanitize(postInfo.node.title)
              }}
            />
            <p
              dangerouslySetInnerHTML={{
                __html: DOMPurify.sanitize(postInfo.node.content)
              }}
            />
          </div>
        );
      } else {
        return <div>Post Not Found</div>;
      }
    }
    return <LoadingState>Searching...</LoadingState>;
  }
}
export default Post;

PostWidget component in React

This is the fifth part of Create react app with GraphQL in WordPress

In this part we will develop PostWidget component and a small edit of src/components/AppRouter.js

First small tweek of AppRouter.js

  • First import milligram in AppRouter to use in everywhere.
  • We will use a common component in header area of our application. Create src/components/Header.js
  • Update state to store title
state = {
  title: "",
  posts: []
};
  • Update query to fetch Blog title
client
      .query({
        query: gql`
          {
            generalSettings {
              title
            }
            posts {
              edges {
                node {
                  id
                  title
                  date
                  link
                  content
                  excerpt
                }
              }
            }
          }
        `
      })
      .then(result => {
        this.setState({
          posts: result.data.posts.edges,
          title: result.data.generalSettings.title
        });
      });
  • Add title props in render method of AppRouter.
      <div className="container">
        <BrowserRouter>
          <Header title={this.state.title} />
          <Switch>
            <Route
              exact
              path="/"
              render={props => <App {...props} state={this.state.posts} />}
            />
            <Route
              path="/post/:postID"
              render={props => <Post {...props} state={this.state.posts} />}
            />
            <Route component={NotFound} />
          </Switch>
        </BrowserRouter>
      </div>

So, now AppRouter.js file will

import React from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import ApolloClient, { gql } from "apollo-boost";
import "milligram";
import Header from "./components/Header";
import App from "./components/App";
import Post from "./components/Post";
import NotFound from "./components/NotFound";

class AppRouter extends React.Component {
  state = {
    title: "",
    posts: []
  };

  componentDidMount() {
    const client = new ApolloClient({
      uri: "https://mrinalbd.com/?graphql"
    });
    client
      .query({
        query: gql`
          {
            generalSettings {
              title
            }
            posts {
              edges {
                node {
                  id
                  title
                  date
                  link
                  content
                  excerpt
                }
              }
            }
          }
        `
      })
      .then(result => {
        this.setState({
          posts: result.data.posts.edges,
          title: result.data.generalSettings.title
        });
      });
  }
  render() {
    return (
      <div className="container">
        <BrowserRouter>
          <Header title={this.state.title} />
          <Switch>
            <Route
              exact
              path="/"
              render={props => <App {...props} state={this.state.posts} />}
            />
            <Route
              path="/post/:postID"
              render={props => <Post {...props} state={this.state.posts} />}
            />
            <Route component={NotFound} />
          </Switch>
        </BrowserRouter>
      </div>
    );
  }
}

export default AppRouter

Develop Header Component
In src/components/Header.js

import React from "react";
import { Link } from "react-router-dom";

class Header extends React.Component {
  render() {
    return (
      <div>
        <Link to="/">
          <h1>{this.props.title}</h1>
        </Link>
      </div>
    );
  }
}
export default Header;

Develop PostWidget
Let’s open src/components/PostWidget.js.
First import { Link } and DomPurify

import { Link } from "react-router-dom";
import DOMPurify from "dompurify";

Then, render the title in link wrapper like this

<div>
  <Link to={`/post/${this.props.post.node.id}`}>
    <p
    dangerouslySetInnerHTML={{
    __html: DOMPurify.sanitize(this.props.post.node.title)
    }}
    />
  </Link>
</div>

So, full code of src/components/PostWidget.js is

import React from "react";
import { Link } from "react-router-dom";
import DOMPurify from "dompurify";

class PostWidget extends React.Component {
  render() {
    return (
      <div>
        <Link to={`/post/${this.props.post.node.id}`}>
          <p
            dangerouslySetInnerHTML={{
              __html: DOMPurify.sanitize(this.props.post.node.title)
            }}
          />
        </Link>
      </div>
    );
  }
}
export default PostWidget;

At last of this our application will be

Posts component in React

This is the fourth part of Create react app with GraphQL in WordPress

In this part, we will develop Posts component lives at src/components/Posts.js

First, we import Link component from react-router-dom and dompurify

import { Link } from "react-router-dom";
import DOMPurify from "dompurify";

In Posts component, first declare excerpt as

excerpt = this.props.post.node.excerpt;

Now, in return we create a link. Link component of react-router-dom creates link.

<Link to={`/post/${this.props.post.node.id}`}>
  <h2
    dangerouslySetInnerHTML={{
      __html: DOMPurify.sanitize(this.props.post.node.title)
    }}
  />
</Link>

Then render excerpt as

<article>
  <p
  dangerouslySetInnerHTML={{
    __html: DOMPurify.sanitize(
       this.excerpt.slice(0, this.excerpt.indexOf("Continue reading"))
    )
  }}
  />
</article>

Finally, src/components/Posts.js full codes are

import React from "react";
import { Link } from "react-router-dom";
import DOMPurify from "dompurify";

class Posts extends React.Component {
  excerpt = this.props.post.node.excerpt;

  render() {
    return (
      <div>
        <Link to={`/post/${this.props.post.node.id}`}>
          <h2
            dangerouslySetInnerHTML={{
              __html: DOMPurify.sanitize(this.props.post.node.title)
            }}
          />
        </Link>
        <article>
          <p
            dangerouslySetInnerHTML={{
              __html: DOMPurify.sanitize(
                this.excerpt.slice(0, this.excerpt.indexOf("Continue reading"))
              )
            }}
          />
        </article>
      </div>
    );
  }
}
export default Posts;

At now, our app is like this

App Component in React

This is the third part of Create react app with GraphQL in WordPress

In this part, we will build App component in src/components/App.js

Here kick on styled-components. And we use Posts and PostWidget component here. For that, we can introduce with props.

First, import CSS library, styled-componets. After that, we will import two components- Posts and PostWidget component from respective file. So, let’s write

import styled from "styled-components";
import Posts from "./components/Posts";
import PostWidget from "./components/PostWidget";

In App component, we will use render method. Here, return our defined JSX. Firstly, we design as simple. Two area in page. Details of grid can find here, https://milligram.io/#grids. So, we write here,

render() {
    return(
        <div className="row">
          <div className="column column-80">Post Archive</div>
          <div className="column column-20">Widget</div>
        </div>
    );
  }

At our App component, we will use two return. One return is used before state has any post. After return it return main component. So, our render function will be,

render() {
   if (this.props.state.length) {
    return(
        <div className="row">
          <div className="column column-80">Post Archive</div>
          <div className="column column-20">Widget</div>
        </div>
    );
  }
  return <p>Loading...</p>;
  }

At post archive, we will use posts array of state of this component. posts array map every items with render Posts. We render Posts component with props name state in this map. For clear about array map, we will read this.

However, in code it will be

{this.props.state.map(post => (
  <Posts key={post.node.id} post={post} />
))}

At Widget, we use same code. But, before array map, we will slice this array for that only first two posts render here. In code

{this.props.state.slice(0, 2).map(post => (
  <PostWidget key={post.node.id} post={post} />
))}

Lets, try styled-components.
Define Widget with styled. styled create an div tag and it styled as our defined style.

const Widget = styled.div`
  border: 1px solid gray;
  padding: 10px;
  p {
    margin-bottom: 0;
    border-top: 1px solid gray;
    }
  }
`;

Use this Widget component like this

<Widget>
   <h3>Recent posts</h3>
     {this.props.state.slice(0, 2).map(post => (
       <PostWidget key={post.node.id} post={post} />
     ))}
</Widget>

So, the final code is-

import React from "react";
import styled from "styled-components";
import Posts from "./Posts";
import PostWidget from "./PostWidget";

const Widget = styled.div`
  border: 1px solid gray;
  padding: 10px;
  p {
    margin-bottom: 0;
    border-top: 1px solid gray;
    }
  }
`;

class App extends React.Component {
  render() {
    if (this.props.state.length) {
      return (
          <div className="row">
            <div className="column column-80">
              {this.props.state.map(post => (
                <Posts key={post.node.id} post={post} />
              ))}
            </div>
            <div className="column column-20">
              <Widget>
                <h3>Recent posts</h3>
                {this.props.state.slice(0, 2).map(post => (
                  <PostWidget key={post.node.id} post={post} />
                ))}
              </Widget>
            </div>
          </div>
      );
    }
    return <div>Loading...</div>;
  }
}
export default App;

Screenshot at this point will be