Spring Boot – Multiple Spring Data modules found, entering strict repository configuration mode

@EnableJpaRepositories(basePackages = {"com.ecommerce.core.repository.jpa"})
@EnableElasticsearchRepositories(basePackages= {"com.ecommerce.core.repository.elastic"})
@EnableRedisRepositories(basePackages = {"org.springframework.data.redis.connection.jedis"})

Since we are explicitly enabling the repositories on specific packages we can include this in the application.properties to avoid these errors:

spring.data.redis.repositories.enabled=false

We can do the same for the other repositories as well. If you encounter similar errors:

spring.data.elasticsearch.repositories.enabled=false
spring.data.jpa.repositories.enabled=false

References
https://stackoverflow.com/questions/47002094/spring-multiple-spring-data-modules-found-entering-strict-repository-configur

Using container class in Tailwind CSS

The container class sets the max-width of an element to match the min-width of the current breakpoint. This is useful if you’d prefer to design for a fixed set of screen sizes instead of trying to accommodate a fully fluid viewport.

Note that unlike containers you might have used in other frameworks, Tailwind’s container does not center itself automatically and does not have any built-in horizontal padding.

<div class="container mx-auto px-4">
  <!-- ... -->
</div>

References
https://tailwindcss.com/docs/container

Serialize and Deserialize Joda Time in Gson

register a TypeAdapter with GSON to wrap the use of a Joda preconfigured Formatters :

    public static Gson gsonDateTime() {
    Gson gson = new GsonBuilder()
            .registerTypeAdapter(DateTime.class, new JsonSerializer<DateTime>() {
                @Override
                public JsonElement serialize(DateTime json, Type typeOfSrc, JsonSerializationContext context) {
                    return new JsonPrimitive(ISODateTimeFormat.dateTime().print(json));
                }
            })
            .registerTypeAdapter(DateTime.class, new JsonDeserializer<DateTime>() {
                @Override
                public DateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
                    DateTime dt = ISODateTimeFormat.dateTime().parseDateTime(json.getAsString());
                    return dt;
                }
            })
            .create();
    return gson;
}

References
https://stackoverflow.com/questions/14996663/is-there-a-standard-implementation-for-a-gson-joda-time-serialiser

Spring Data MongoDB Transactions

MongoDB Configuration

@Configuration
@EnableMongoRepositories(basePackages = "com.baeldung.repository")
public class MongoConfig extends AbstractMongoClientConfiguration{

    @Bean
    MongoTransactionManager transactionManager(MongoDatabaseFactory dbFactory) {
        return new MongoTransactionManager(dbFactory);
    }

    @Override
    protected String getDatabaseName() {
        return "test";
    }

    @Override
    public MongoClient mongoClient() {
        final ConnectionString connectionString = new ConnectionString("mongodb://localhost:27017/test");
        final MongoClientSettings mongoClientSettings = MongoClientSettings.builder()
            .applyConnectionString(connectionString)
            .build();
        return MongoClients.create(mongoClientSettings);
    }
}

Synchronous Transactions

@Test
@Transactional
public void whenPerformMongoTransaction_thenSuccess() {
    userRepository.save(new User("John", 30));
    userRepository.save(new User("Ringo", 35));
    Query query = new Query().addCriteria(Criteria.where("name").is("John"));
    List<User> users = mongoTemplate.find(query, User.class);

    assertThat(users.size(), is(1));
}

Note that we can’t use listCollections command inside a multi-document transaction – for example:

References
https://www.baeldung.com/spring-data-mongodb-transactions

async / await in TypeScript

"use strict";
// printDelayed is a 'Promise<void>'
async function printDelayed(elements: string[]) {
  for (const element of elements) {
    await delay(400);
    console.log(element);
  }
}
async function delay(milliseconds: number) {
  return new Promise<void>((resolve) => {
    setTimeout(resolve, milliseconds);
  });
}
printDelayed(["Hello", "beautiful", "asynchronous", "world"]).then(() => {
  console.log();
  console.log("Printed every element!");
});

References
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-1-7.html

Writing Thunks using createAsyncThunk in React Redux

We can write thunks that dispatch “loading”, “request succeeded”, and “request failed” actions. We had to write action creators, action types, and reducers to handle those cases.

Because this pattern is so common, Redux Toolkit has a createAsyncThunk API that will generate these thunks for us. It also generates the action types and action creators for those different request status actions, and dispatches those actions automatically based on the resulting Promise.

booksSlices.ts

import {createAsyncThunk, createEntityAdapter, createSlice} from "@reduxjs/toolkit";
import {RootState} from "../../app/store";

export type BookState = { bookId: number, bookTitle: string, };

export const addOneBookAsync = createAsyncThunk(
    "books/addOneBookAsync",
    async (bookTitle: string) => {
        let result;
        setTimeout(result = function () {
            const newBook: BookState = {bookId: Math.floor(Math.random() * 1000), bookTitle: bookTitle};
            return newBook;
        }, 1000);
        return result();
    }
);

const booksAdapter = createEntityAdapter<BookState>({
    selectId: model => model.bookId,
    sortComparer: (a, b) => a.bookTitle.localeCompare(b.bookTitle),
});

export const booksSlice = createSlice({
    name: "books",
    initialState: booksAdapter.getInitialState,
    reducers: {
        addOne: booksAdapter.addOne,
    },
    extraReducers: builder => {
        builder
            .addCase(addOneBookAsync.pending, state => {
                // we can update state while loading data
                console.log("loading");
            })
            .addCase(addOneBookAsync.fulfilled, (state, action) => {
                // we can update state after successful response or we can call another reducer
                // here we are going to call another reducer
                booksSlice.caseReducers.addOne(state, action);
                console.log("successful");
            }).addCase(addOneBookAsync.rejected, state => {
            // we can update state after failed
            console.log("failed");
        })
    }
});

export const booksActions = booksSlice.actions;
export const booksSelectors = booksAdapter.getSelectors<RootState>(
    (state) => state.books
)

export default booksSlice.reducer;

Books.tsx

import {useAppSelector, useAppDispatch} from '../../app/hooks';
import {BookState, booksActions, booksSelectors, addOneBookAsync} from "./booksSlice";
import {useState} from "react";

function Books() {

    const dispatch = useAppDispatch();
    const books = useAppSelector(booksSelectors.selectAll);
    const [bookTitle, setBookTitle] = useState("");

    const addHandler = () => {
        const newBook: BookState = {
            bookId: Math.floor(Math.random() * 1000),
            bookTitle: bookTitle
        };
        dispatch(booksActions.addOne(newBook));
    }

    const addAsyncHandler = async () => {
        try {
            const result = await dispatch(addOneBookAsync(bookTitle)).unwrap();
            console.log(result);
        } catch (rejectedValueOrSerializedError) {
            // handle error here
        }
    }

    return (
        <div>

            <div className="mx-2 my-2">
                <input type="text" placeholder="Books Title" className="input input-bordered w-full max-w-xs"
                       value={bookTitle} onChange={event => setBookTitle(event.target.value)}/>
                <button className="btn mx-2" onClick={addHandler}>Add</button>
                <button className="btn mx-2" onClick={addAsyncHandler}>Add Async</button>
            </div>

            <ul>
                {books.map((value, index) => (
                    <li key={value.bookId}>Title : {value.bookTitle}, Id : {value.bookId}</li>
                ))}
            </ul>
        </div>
    );
}

export default Books;

Return Value

createAsyncThunk returns a standard Redux thunk action creator. The thunk action creator function will have plain action creators for the pendingfulfilled, and rejected cases attached as nested fields.

Handling Thunk Results​

The promise returned by the dispatched thunk has an unwrap property which can be called to extract the payload of a fulfilled action or to throw either the error or, if available, payload created by rejectWithValue from a rejected action:

const onClick = () => {
  dispatch(fetchUserById(userId))
    .unwrap()
    .then((originalPromiseResult) => {
      // handle result here
    })
    .catch((rejectedValueOrSerializedError) => {
      // handle error here
    })
}

Or with async/await syntax:

const onClick = async () => {
  try {
    const originalPromiseResult = await dispatch(fetchUserById(userId)).unwrap()
    // handle result here
  } catch (rejectedValueOrSerializedError) {
    // handle error here
  }
}

References
https://redux-toolkit.js.org/api/createAsyncThunk
https://redux.js.org/tutorials/fundamentals/part-8-modern-redux#writing-thunks
https://redux-toolkit.js.org/api/createslice#extrareducers
https://stackoverflow.com/questions/65106681/redux-toolkit-dispatch-an-action-from-an-extrareducers-listener

Normalizing State with createEntityAdapter in React Redux

We can read how to “normalize” state from here, but simply we can do it  by keeping items in an object keyed by item IDs. This gives us the ability to look up any item by its ID without having to loop through an entire array. However, writing the logic to update normalized state by hand was long and tedious. Writing “mutating” update code with Immer makes that simpler, but there’s still likely to be a lot of repetition – we might be loading many different types of items in our app, and we’d have to repeat the same reducer logic each time.

Redux Toolkit includes a createEntityAdapter API that has prebuilt reducers for typical data update operations with normalized state. This includes adding, updating, and removing items from a slice. createEntityAdapter also generates some memoized selectors for reading values from the store.

createEntityAdapter is a function that generates a set of prebuilt reducers and selectors for performing CRUD operations on a normalized state structure containing instances of a particular type of data object. These reducer functions may be passed as case reducers to createReducer and createSlice. They may also be used as “mutating” helper functions inside of createReducer and createSlice.

booksSlice.ts

import {createEntityAdapter, createSlice} from "@reduxjs/toolkit";
import {RootState} from "../../app/store";

export type BookState = { bookId: number, bookTitle: string, };

const booksAdapter = createEntityAdapter<BookState>({
    selectId: model => model.bookId,
    sortComparer: (a, b) => a.bookTitle.localeCompare(b.bookTitle),
});

export const booksSlice = createSlice({
    name: "books",
    initialState: booksAdapter.getInitialState,
    reducers: {
        addOne: booksAdapter.addOne,
    }
});

export const booksActions = booksSlice.actions;
export const booksSelectors = booksAdapter.getSelectors<RootState>(
    (state) => state.books
)

export default booksSlice.reducer;

Books.tsx

import {useAppSelector, useAppDispatch} from '../../app/hooks';
import {BookState, booksActions, booksSelectors} from "./booksSlice";
import {useState} from "react";

function Books() {

    const dispatch = useAppDispatch();
    const books = useAppSelector(booksSelectors.selectAll);
    const [bookTitle, setBookTitle] = useState("");

    const addHandler = () => {
        const newBook: BookState = {
            bookId: Math.floor(Math.random() * 1000),
            bookTitle: bookTitle
        };
        dispatch(booksActions.addOne(newBook));
    }

    return (
        <div>

            <div className="mx-2 my-2">
                <input type="text" placeholder="Books Title" className="input input-bordered w-full max-w-xs"
                       value={bookTitle} onChange={event => setBookTitle(event.target.value)}/>
                <button className="btn mx-2" onClick={addHandler}>Add</button>
            </div>

            <ul>
                {books.map((value, index) => (
                    <li key={value.bookId}>Title : {value.bookTitle}, Id : {value.bookId}</li>
                ))}
            </ul>
        </div>
    );
}

export default Books;

References
https://redux-toolkit.js.org/api/createEntityAdapter
https://redux.js.org/tutorials/fundamentals/part-8-modern-redux#normalizing-state
https://redux.js.org/usage/structuring-reducers/normalizing-state-shape

Implementing Counter Example in Typescript React using Redux Toolkit

store.ts

import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';

export const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
});

export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
  ReturnType,
  RootState,
  unknown,
  Action<string>
>;

hooks.ts

import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './store';

// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

counterApi.ts

// A mock function to mimic making an async request for data
export function fetchCount(amount = 1) {
  return new Promise<{ data: number }>((resolve) =>
    setTimeout(() => resolve({ data: amount }), 500)
  );
}

counterSlice.ts

import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
import { RootState, AppThunk } from '../../app/store';
import { fetchCount } from './counterAPI';

export interface CounterState {
  value: number;
  status: 'idle' | 'loading' | 'failed';
}

const initialState: CounterState = {
  value: 0,
  status: 'idle',
};

// The function below is called a thunk and allows us to perform async logic. It
// can be dispatched like a regular action: `dispatch(incrementAsync(10))`. This
// will call the thunk with the `dispatch` function as the first argument. Async
// code can then be executed and other actions can be dispatched. Thunks are
// typically used to make async requests.
export const incrementAsync = createAsyncThunk(
  'counter/fetchCount',
  async (amount: number) => {
    const response = await fetchCount(amount);
    // The value we return becomes the `fulfilled` action payload
    return response.data;
  }
);

export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  // The `reducers` field lets us define reducers and generate associated actions
  reducers: {
    increment: (state) => {
      // Redux Toolkit allows us to write "mutating" logic in reducers. It
      // doesn't actually mutate the state because it uses the Immer library,
      // which detects changes to a "draft state" and produces a brand new
      // immutable state based off those changes
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    // Use the PayloadAction type to declare the contents of `action.payload`
    incrementByAmount: (state, action: PayloadAction<number>) => {
      state.value += action.payload;
    },
  },
  // The `extraReducers` field lets the slice handle actions defined elsewhere,
  // including actions generated by createAsyncThunk or in other slices.
  extraReducers: (builder) => {
    builder
      .addCase(incrementAsync.pending, (state) => {
        state.status = 'loading';
      })
      .addCase(incrementAsync.fulfilled, (state, action) => {
        state.status = 'idle';
        state.value += action.payload;
      })
      .addCase(incrementAsync.rejected, (state) => {
        state.status = 'failed';
      });
  },
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;

// The function below is called a selector and allows us to select a value from
// the state. Selectors can also be defined inline where they're used instead of
// in the slice file. For example: `useSelector((state: RootState) => state.counter.value)`
export const selectCount = (state: RootState) => state.counter.value;

// We can also write thunks by hand, which may contain both sync and async logic.
// Here's an example of conditionally dispatching actions based on current state.
export const incrementIfOdd =
  (amount: number): AppThunk =>
  (dispatch, getState) => {
    const currentValue = selectCount(getState());
    if (currentValue % 2 === 1) {
      dispatch(incrementByAmount(amount));
    }
  };

export default counterSlice.reducer;

Counter.tsx

import React, { useState } from 'react';

import { useAppSelector, useAppDispatch } from '../../app/hooks';
import {
  decrement,
  increment,
  incrementByAmount,
  incrementAsync,
  incrementIfOdd,
  selectCount,
} from './counterSlice';
import styles from './Counter.module.css';

export function Counter() {
  const count = useAppSelector(selectCount);
  const dispatch = useAppDispatch();
  const [incrementAmount, setIncrementAmount] = useState('2');

  const incrementValue = Number(incrementAmount) || 0;

  return (
    <div>
      <div className={styles.row}>
        <button
          className={styles.button}
          aria-label="Decrement value"
          onClick={() => dispatch(decrement())}
        >
          -
        </button>
        <span className={styles.value}>{count}</span>
        <button
          className={styles.button}
          aria-label="Increment value"
          onClick={() => dispatch(increment())}
        >
          +
        </button>
      </div>
      <div className={styles.row}>
        <input
          className={styles.textbox}
          aria-label="Set increment amount"
          value={incrementAmount}
          onChange={(e) => setIncrementAmount(e.target.value)}
        />
        <button
          className={styles.button}
          onClick={() => dispatch(incrementByAmount(incrementValue))}
        >
          Add Amount
        </button>
        <button
          className={styles.asyncButton}
          onClick={() => dispatch(incrementAsync(incrementValue))}
        >
          Add Async
        </button>
        <button
          className={styles.button}
          onClick={() => dispatch(incrementIfOdd(incrementValue))}
        >
          Add If Odd
        </button>
      </div>
    </div>
  );
}