Wednesday, February 26, 2020

Using a Custom React Hook for a Central Application Refresh

In this post we will review how to create a custom react hook to handle a refresh of an application.
This post is base on the react application that is presented at the post React Bootstrap with NavBar React Router and Styled Components.
We will extend this application by adding a refresh on the top navigation bar.




We want any page in our application to bet able to refresh by clicking on a refresh button that is at the navigation bar. To do this, we create a custom react hook.

hook.js:

import {useEffect, useState} from 'react'
export function useRefreshEvents(loader, refreshVersion) {
  useEffect(() => {
    loader()
  }, [refreshVersion])


  return null}


Notice that a custom hook must have its name starting with "use" word.
The hooks is based on an increment version. Any click on refresh button, causes the state version to increment, hence forcing rerun of the useEffect entry, which on its turn rerun the load callback.


Next, we update the navigation to keep state of the refreshing version.
The refreshing version is updated whenever the refresh is clicked.


app.js:

import 'bootstrap/dist/css/bootstrap.min.css'
import React, {useState} from 'react'
import {Nav, Navbar} from 'react-bootstrap'
import {BrowserRouter, Link, NavLink, Redirect, Route, Switch} from 'react-router-dom'
import Page1 from './page1'
import Page2 from './page2'
import Page3 from './page3'
function App() {
  const [refreshVersion, setRefreshVersion] = useState(0)
  return (
    <BrowserRouter>
      <Navbar bg="dark" variant="dark" expand="sm">
        <Navbar.Brand as={Link} to="/">React Demo App</Navbar.Brand>
        <Navbar.Toggle aria-controls="basic-navbar-nav"/>
        <Navbar.Collapse id="basic-navbar-nav">
          <Nav className="mr-auto">
            <Nav.Link as={NavLink} to="/page1">Page 1</Nav.Link>
            <Nav.Link as={NavLink} to="/page2">Page 2</Nav.Link>
            <Nav.Link as={NavLink} to="/page3">Page 3</Nav.Link>
          </Nav>
          <Nav pullright="true">
            <div style={{color: 'white'}} onClick={() => setRefreshVersion(refreshVersion+1)}>Refresh</div>
          </Nav>
        </Navbar.Collapse>
      </Navbar>
      <Switch>
        <Route exact path='/page1' component={Page1}/>
        <Route exact path='/page2' component={Page2}/>
        <Route exact path='/page3' render={() =>
          <Page3            refreshVersion={refreshVersion}
            setRefreshVersion={setRefreshVersion}
          />
        }/>
        <Redirect from="/" to="/page1"/>
      </Switch>
    </BrowserRouter>
  )
}

export default App



And lastly, we create the page that fetch information whenever it is activated by the hook.


page3.js:

import React, {useState} from 'react'
import {Text} from './style'
import {useRefreshEvents} from './refresher/hook'
function Page3(props) {
  const [activity, setActivity] = useState('')
  useRefreshEvents(load, props.refreshVersion, props.setRefreshVersion)

  async function load() {
    const resp = await fetch('https://www.boredapi.com/api/activity')
    const json = await resp.json()
    setActivity(json.activity)
  }

  return (
    <Text>{activity}    </Text>
  )
}

export default Page3


Final Words


We have reviewed a method of a central refresh mechanism.

This can be even simpler in case our application includes a redux integration.
In this case, there is no need to send the refresh version from the main app.js down to each page.




Don't Mess With The Hibernate


Last week I've assist a friend solving a Hibernate voodoo.
He was getting an "integrity constraint violation" trying to delete a table entry.
The root cause for the problem was that he was messing with the hibernate entities.
This is an example of what you should NOT do: do not mix business logic into hibernate layer.
Let's review a simplified example of this problem.


The Data Model


We want to create a recipe book, so we keep a list of recipes and ingredients.
Each recipe includes a list of ingredients, so we need two entities.



The recipe:

package org.demo.hibernate.entities;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import java.util.HashSet;
import java.util.Set;

@Entity
public class Recipe {
    @Id
    @GeneratedValue
    @Column
    private Long id;

    @Column(nullable = false, unique = true)
    private String name;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "recipe")
    private Set<Ingredient> ingredients = new HashSet<>();

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Set<Ingredient> getIngredients() {
        return ingredients;
    }

    public void setIngredients(Set<Ingredient> ingredients) {
        this.ingredients = ingredients;
    }
}


The Ingredient:


package org.demo.hibernate.entities;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import java.util.Objects;

@Entity
public class Ingredient {
    @Id
    @GeneratedValue
    @Column
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(nullable = false)
    private Recipe recipe;

    @Column(nullable = false)
    private String name;

    public Recipe getRecipe() {
        return recipe;
    }

    public void setRecipe(Recipe recipe) {
        this.recipe = recipe;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Ingredient ingredient = (Ingredient) o;
        return name.equals(ingredient.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}



Notice that we want to avoid a recipe having the same ingredient twice, so we keep a set of ingredients, and implement equals() and hashCode() methods in the ingredient class.


The Repositories


To simplify database access, we use JPA, and implement two repositories.

The Recipe Repository:


package org.demo.hibernate.repositories;

import org.demo.hibernate.entities.Recipe;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

@Repository
public interface RecipeRepository extends JpaRepository<Recipe, Integer> {
    @Transactional
    Recipe findByName(String name);
}


The Ingredient Repository:


package org.demo.hibernate.repositories;

import org.demo.hibernate.entities.Ingredient;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface IngredientRepository extends JpaRepository<Ingredient, Integer> {
}


The Service


We implement a service to add, delete, and update ingredient name, as well as printing all of the recipes.



package org.demo.hibernate;

import org.demo.hibernate.entities.Ingredient;
import org.demo.hibernate.entities.Recipe;
import org.demo.hibernate.repositories.IngredientRepository;
import org.demo.hibernate.repositories.RecipeRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;


@Service
public class RecipeService {

    @Autowired
    private IngredientRepository ingredientRepository;

    @Autowired
    private RecipeRepository recipeRepository;

    @Transactional
    public void createRecipe(String name, String... ingredients) {
        Recipe recipe = new Recipe();
        recipe.setName(name);
        recipeRepository.save(recipe);

        for (String ingredient : ingredients) {
            Ingredient i = new Ingredient();
            i.setName(ingredient);
            i.setRecipe(recipe);
            ingredientRepository.save(i);
        }
    }

    @Transactional
    public void updateRecipe(String name, String oldIngredientName, String newIngredientName) {
        Recipe recipe = recipeRepository.findByName(name);
        for (Ingredient ingredient : recipe.getIngredients()) {
            if (ingredient.getName().equals(oldIngredientName)){
                ingredient.setName(newIngredientName);
                ingredientRepository.save(ingredient);
            }
        }

    }

    @Transactional
    public void deleteRecipe(String name) {
        Recipe recipe = recipeRepository.findByName(name);
        for (Ingredient ingredient : recipe.getIngredients()) {
            ingredientRepository.delete(ingredient);
        }

        recipeRepository.delete(recipe);
    }

    @Transactional(readOnly = true)
    public void printAll() {
        System.out.println("=== all recipes ===");
        for (Recipe recipe : recipeRepository.findAll()) {
            System.out.println(recipe.getName());
            for (Ingredient ingredient : recipe.getIngredients()) {
                System.out.println("   " + ingredient.getName());
            }

        }

    }
}


The Business Logic


Next, we add recipes, update, and print them:


package org.demo.hibernate;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(MyApplication.class, args);
        RecipeService service = context.getBean(RecipeService.class);

        service.createRecipe("soup", "onion", "oil", "salt");
        service.createRecipe("salad", "cucumber", "tomato");
        service.printAll();

        service.updateRecipe("soup", "oil", "salt");
        service.printAll();

        service.deleteRecipe("soup");
    }
}


The first printing of the recipes looks fine:


=== all recipes ===
soup
   oil
   salt
   onion
salad
   cucumber
   tomato


But then we update int the soup recipe the oil to be salt.

That's not nice, as we expects unique ingredients names, but hey, we've added the business logic into hibernate, so it should be automatically solved, right?

The recipes print now shows:

=== all recipes ===
soup
   oil
   onion
salad
   cucumber
   tomato


Great, right?

Well, actually not.
The database still has 3 ingredients, but we've made hibernate aware of only two of them.
That's the next line, deleting the recipe, fails with error:


Exception in thread "main" org.springframework.dao.DataIntegrityViolationException: 
could not execute statement; SQL [n/a]; constraint ["FKJ0S4YWMQQQW4H5IOMMIGH5YJA: PUBLIC.INGREDIENT FOREIGN KEY(RECIPE_ID) REFERENCES PUBLIC.RECIPE(ID) (1)"; SQL statement:
delete from recipe where id=? [23503-200]]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
 at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:298)
 at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:255)
 at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:538)
 at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:744)
 at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:712)
 at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:631)
 at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:385)
 at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:99)
 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
 at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747)
 at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)
 at org.demo.hibernate.RecipeService$$EnhancerBySpringCGLIB$$57709a2e.deleteRecipe()
 at org.demo.hibernate.MyApplication.main(MyApplication.java:20)
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
 at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:59)
 at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
 at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113)
 at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:99)
 at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:200)
 at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:45)
 at org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:3542)
 at org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:3801)
 at org.hibernate.action.internal.EntityDeleteAction.execute(EntityDeleteAction.java:100)
 at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:604)
 at org.hibernate.engine.spi.ActionQueue.lambda$executeActions$1(ActionQueue.java:478)
 at java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:684)
 at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:475)
 at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:348)
 at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:40)
 at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:108)
 at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1344)
 at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:435)
 at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3221)
 at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2389)
 at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:447)
 at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:183)
 at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$300(JdbcResourceLocalTransactionCoordinatorImpl.java:40)
 at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:281)
 at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:101)
 at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:534)
 ... 10 more
Caused by: org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: 
Referential integrity constraint violation: "FKJ0S4YWMQQQW4H5IOMMIGH5YJA: PUBLIC.INGREDIENT FOREIGN KEY(RECIPE_ID) REFERENCES PUBLIC.RECIPE(ID) (1)"; 
SQL statement:
delete from recipe where id=? [23503-200]
 at org.h2.message.DbException.getJdbcSQLException(DbException.java:459)
 at org.h2.message.DbException.getJdbcSQLException(DbException.java:429)
 at org.h2.message.DbException.get(DbException.java:205)
 at org.h2.message.DbException.get(DbException.java:181)
 at org.h2.constraint.ConstraintReferential.checkRow(ConstraintReferential.java:373)
 at org.h2.constraint.ConstraintReferential.checkRowRefTable(ConstraintReferential.java:390)
 at org.h2.constraint.ConstraintReferential.checkRow(ConstraintReferential.java:265)
 at org.h2.table.Table.fireConstraints(Table.java:1057)
 at org.h2.table.Table.fireAfterRow(Table.java:1075)
 at org.h2.command.dml.Delete.update(Delete.java:153)
 at org.h2.command.CommandContainer.update(CommandContainer.java:198)
 at org.h2.command.Command.executeUpdate(Command.java:251)
 at org.h2.jdbc.JdbcPreparedStatement.executeUpdateInternal(JdbcPreparedStatement.java:191)
 at org.h2.jdbc.JdbcPreparedStatement.executeUpdate(JdbcPreparedStatement.java:152)
 at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61)
 at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java)
 at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:197)
 ... 31 more



Final Thoughts


The root cause of this problem is mixing business logic into hibernate.

  1. Do not use  equals() and hashCode() methods in hibernate entities.
  2. Do not use Set on hibernate entities.
  3. Keep the business logic out of hibernate classes.







Wednesday, February 19, 2020

Akka and Spring Boot



Both Akka and Spring, and more specifically Spring Boot are common frameworks in the Java domain. However the integration of Akka and Spring is not strait forward. There are other guides about this integration, for example: baeldung's post, but I felt a need to simplify the process.

In this post we will create a Spring Boot application that includes Spring integration with Akka.
The application includes a REST controller that upon activation, sends a message to an Akka actor.
The Akka actor includes Spring injection enabling it to use a Spring service.


The pom.xml file


To create the application we will start with the pom.xml file.

The pom.xml file includes:

  1. The Spring boot as a parent
  2. Dependency for spring starter web to enable usage of Spring REST controllers
  3. Dependency for Akka



<?xml version="1.0" encoding="UTF-8"?><project>
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.4.RELEASE</version>
    </parent>

    <groupId>org.demo</groupId>
    <artifactId>akka</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.3.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>com.typesafe.akka</groupId>
            <artifactId>akka-actor_2.13</artifactId>
            <version>2.6.3</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>


The Application


The next step is to create the Spring entry point, which is the Spring boot application.
Nothing special here, just invoking the spring boot application.


package org.demo.akka;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}


The Actors System Producer


The actors system producer provides an ActorSystem bean.
This means that we can later auto wire the actors system.


package org.demo.akka;

import akka.actor.ActorSystem;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
public class MyActorSystemProducer {
    @Bean    
    public ActorSystem actorSystem() {
        return ActorSystem.create("demo-akka");
    }
}


The Actors Producer


The actors producer is a service that enables us to create new actors bean that we need.
It uses an auto wired ActorsSystem (and we can auto wire thanks to the actors system producer).


package org.demo.akka;

import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.actor.Props;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;

@Service
public class MyActorsProducer {
    @Autowired
    private volatile ApplicationContext applicationContext;

    @Autowired
    private ActorSystem actorSystem;

    public ActorRef createActor(String actorBeanName, String actorName) {
        Props props = Props.create(MySpringActorProducer.class, applicationContext, actorBeanName);
        return actorSystem.actorOf(props, actorName);
    }
}


Notice that it uses a MySpringActorProducer class.
This class is an Akka "indirect actor producer, which implement the actual actor instance creation using Spring beans.


package org.demo.akka;

import akka.actor.Actor;
import akka.actor.IndirectActorProducer;
import org.springframework.context.ApplicationContext;

public class MySpringActorProducer implements IndirectActorProducer {

    private ApplicationContext applicationContext;

    private String beanActorName;

    public MySpringActorProducer(ApplicationContext applicationContext, String beanActorName) {
        this.applicationContext = applicationContext;
        this.beanActorName = beanActorName;
    }

    @Override    
    public Actor produce() {
        return (Actor) applicationContext.getBean(beanActorName);
    }

    @Override    
    public Class<? extends Actor> actorClass() {
        return (Class<? extends Actor>) applicationContext.getType(beanActorName);
    }
}


The Actor and the Service


Let's create an actor that uses a service. Notice that the actor uses auto wire to find the service. This is possible due to the actors producer that we have previously implemented. The actor class should be implemented using a prototype scope, which means that we create a new instance upon each bean request.


The (simple) service is:


package org.demo.akka;

import org.springframework.stereotype.Service;

@Service
public class MyService {
    public String greet(String name) {
        return "Hello " + name;
    }
}



And the Actor is:


package org.demo.akka;

import akka.actor.UntypedAbstractActor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class MyActor extends UntypedAbstractActor {

    @Autowired    
    private MyService myService;

    @Override
    public void onReceive(Object message) {
        String name = (String) message;
        getSender().tell(myService.greet(name), getSelf());
    }
}


The REST Controller


To complete the implementation, let's create a REST controller that creates an actor.


package org.demo.akka;

import akka.actor.ActorRef;
import akka.pattern.Patterns;
import akka.util.Timeout;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import scala.concurrent.Await;
import scala.concurrent.Future;
import scala.concurrent.duration.FiniteDuration;

import java.util.concurrent.TimeUnit;

@RestController
public class MyRestController {
    @Autowired
    private MyActorsProducer actorsProducer;

    @RequestMapping("/actor")
    public Object sendToActor(@RequestBody String name) throws Exception {
        ActorRef greeter = actorsProducer.createActor("myActor", "myActorName");

        FiniteDuration duration = FiniteDuration.create(1, TimeUnit.SECONDS);
        Timeout timeout = Timeout.durationToTimeout(duration);

        Future<Object> future = Patterns.ask(greeter, name, timeout);
        return Await.result(future, duration);
    }
}


Final Notes


We can test the application using curl:

$ curl localhost:8080/actor --data-binary "John"
Hello John


And see that everything works.

If you have found this post useful, please leave a comment. Thanks!

Wednesday, February 12, 2020

React Bootstrap with NavBar React Router and Styled Components

Recently I've used react bootstrapreact router and styled components for a new project.
I believe this is the "must have" framework for any new project startup.

I found the documentation for the implementation pretty good, but it does not cover the entire process. Hence, I list the steps for creation of such a project in this post.

Create a New Project


The simplest way to start is using the create-react-app tool.
The create a new project, install the latest NPM version, and then run the following


npx create-react-app demo
cd demo
npm start


Once this is done, we get the react template application startup page running on our browser.


Install the Dependencies


Now you have a project template to start from.
Next, let's install the required libraries:


npm install --save react-bootstrap bootstrap react-router-dom styled-components


Create the Actual Pages

For the purpose of this demo, we will create a navigation bar of two pages: page1 and page2.
Lets create these two pages, and use styled components to style them.

The styled components enables specifying CSS in a direct and simple manner. Our style.js file is the following.


import styled from 'styled-components'
export const Text = styled.div`
  height: 100%;  
  width:100%;  
  color: aqua;  
  background-color: darkkhaki;
`



Next, let's create the pages.

Page 1:


import React from 'react'
import {Text} from './style'
function Page1() {
  return (
    <Text>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
      magna aliqua. Mauris augue neque gravida in fermentum et sollicitudin. Viverra ipsum nunc aliquet bibendum enim
      facilisis gravida. Neque gravida in fermentum et sollicitudin ac orci phasellus egestas. Tortor consequat id porta
      nibh. Risus sed vulputate odio ut. Mollis nunc sed id semper risus in hendrerit gravida. Nulla facilisi cras
      fermentum odio. Feugiat pretium nibh ipsum consequat nisl vel pretium lectus quam. Mi ipsum faucibus vitae
      aliquet. A lacus vestibulum sed arcu non. Laoreet id donec ultrices tincidunt arcu non sodales neque. Condimentum
      mattis pellentesque id nibh tortor id aliquet. Mauris pellentesque pulvinar pellentesque habitant morbi. At
      elementum eu facilisis sed odio.
    </Text>
  )
}

export default Page1



Page 2:


import React from 'react'
import {Text} from './style'
function Page2() {
  return (
    <Text>
      Preventing the exploitation of animals is not the only reason for becoming vegan, but for many it remains the key
      factor in their decision to go vegan and stay vegan. Having emotional attachments with animals may form part of
      that reason, while many believe that all sentient creatures have a right to life and freedom. Specifics aside,
      avoiding animal products is one of the most obvious ways you can take a stand against animal cruelty and animal
      exploitation everywhere.
    </Text>
  )
}

export default Page2


Create the Main Navigation Page


We use the app.js as our application main page.


import 'bootstrap/dist/css/bootstrap.min.css'
import React from 'react'
import {Nav, Navbar} from 'react-bootstrap'
import {BrowserRouter, Link, NavLink, Redirect, Route, Switch} from 'react-router-dom'
import Page1 from './page1'
import Page2 from './page2'
function App() {
  return (
    <BrowserRouter>
        <Navbar bg="dark" variant="dark" expand="sm" >
          <Navbar.Brand as={Link} to="/">React Demo App</Navbar.Brand>
          <Navbar.Toggle aria-controls="basic-navbar-nav"/>
          <Navbar.Collapse id="basic-navbar-nav">
            <Nav className="mr-auto">
              <Nav.Link as={NavLink} to="/page1">Page 1</Nav.Link>
              <Nav.Link as={NavLink} to="/page2">Page 2</Nav.Link>
            </Nav>
          </Navbar.Collapse>
        </Navbar>
        <Switch>
          <Route exact path='/page1' component={Page1}/>
          <Route exact path='/page2' component={Page2}/>
          <Redirect from="/" to="/page1"/>
        </Switch>
    </BrowserRouter>
  );
}

export default App;


Some important points:


1. To enable bootstrap styling we need to include the bootstrap style sheet:

import 'bootstrap/dist/css/bootstrap.min.css'

2. To enable integration of react-router-dom and react-bootstrap navigation, we replace the render using the "as" property:

<Nav.Link as={NavLink} to="/page1">Page 1</Nav.Link>



Our application is now fully operative.
we can use navigation to move to the related pages without reload of the entire pages, just as a single page application should work.




Docker


The last task to handle it to run the application as a docker container.
In this case, we'll use NGINX as the web server. The Dockerfile is as follows:


FROM node:12.7 as build
WORKDIR /app
COPY src/package.json ./
COPY src/package-lock.json ./
RUN npm install

COPY src/ ./
RUN npm run build

FROM nginx:1.17-alpine
COPY --from=build /app/build /usr/share/nginx/html
CMD ["nginx", "-g", "daemon off;"]

Summary


In this post we have lay the foundation for a react application.
We have used libraries that enables us to quickly progress in future developments.

This does not include all the required tasks. We will probably need to use react hooks, and also configure proxy of the requests on the NGINX side.
However, this is a great start.
I hope you'll find it useful.



Thursday, February 6, 2020

Kubernetes build automation tool

Today I've invested some time in creating a small utility to automate build and test of images on kubernetes environment.

You can see the sources in GitHub:

https://github.com/alonana/playkube