Transform SVGs into React components using svgr as vite plugin

npm install --save-dev vite-plugin-svgr
// vite.config.js
import svgr from "vite-plugin-svgr";

export default {
  // ...
  plugins: [svgr()],
};

Then SVG files can be imported as React components:

import Logo from "./logo.svg?react";

If you are using TypeScript, there is also a declaration helper for better type inference:

/// <reference types="vite-plugin-svgr/client" />

 

References
https://www.npmjs.com/package/vite-plugin-svgr

Save userId using React Redux Toolkit

To save userId using React Redux Toolkit in TypeScript, you can follow these steps:

  1. Set up your Redux store with Redux Toolkit:

    First, you’ll need to install the required dependencies if you haven’t already:

    npm install @reduxjs/toolkit react-redux
    
  2. Create a slice for the user state:

    Create a new file userSlice.ts for the user state slice.

    // src/features/user/userSlice.ts
    import { createSlice, PayloadAction } from '@reduxjs/toolkit';
    
    interface UserState {
      userId: string | null;
    }
    
    const initialState: UserState = {
      userId: null,
    };
    
    const userSlice = createSlice({
      name: 'user',
      initialState,
      reducers: {
        setUserId(state, action: PayloadAction<string>) {
          state.userId = action.payload;
        },
        clearUserId(state) {
          state.userId = null;
        },
      },
    });
    
    export const { setUserId, clearUserId } = userSlice.actions;
    export default userSlice.reducer;
    
  3. Configure the Redux store:

    Configure your Redux store by combining the user slice reducer.

    // src/app/store.ts
    import { configureStore } from '@reduxjs/toolkit';
    import userReducer from '../features/user/userSlice';
    
    const store = configureStore({
      reducer: {
        user: userReducer,
      },
    });
    
    export type RootState = ReturnType<typeof store.getState>;
    export type AppDispatch = typeof store.dispatch;
    export default store;
    
  4. Set up the provider in your main application file:

    Wrap your application with the Redux provider.

    // src/index.tsx
    import React from 'react';
    import ReactDOM from 'react-dom';
    import { Provider } from 'react-redux';
    import store from './app/store';
    import App from './App';
    
    ReactDOM.render(
      <Provider store={store}>
        <App />
      </Provider>,
      document.getElementById('root')
    );
    
  5. Create a component to use the user state and dispatch actions:

    Here’s an example component that uses the user state and dispatches actions to set and clear the userId.

    // src/components/UserComponent.tsx
    import React, { useState } from 'react';
    import { useDispatch, useSelector } from 'react-redux';
    import { RootState } from '../app/store';
    import { setUserId, clearUserId } from '../features/user/userSlice';
    
    const UserComponent: React.FC = () => {
      const dispatch = useDispatch();
      const userId = useSelector((state: RootState) => state.user.userId);
      const [inputUserId, setInputUserId] = useState('');
    
      const handleSetUserId = () => {
        dispatch(setUserId(inputUserId));
      };
    
      const handleClearUserId = () => {
        dispatch(clearUserId());
      };
    
      return (
        <div>
          <h1>User ID: {userId}</h1>
          <input
            type="text"
            value={inputUserId}
            onChange={(e) => setInputUserId(e.target.value)}
          />
          <button onClick={handleSetUserId}>Set User ID</button>
          <button onClick={handleClearUserId}>Clear User ID</button>
        </div>
      );
    };
    
    export default UserComponent;
    
  6. Use the component in your application:

    Finally, use the UserComponent in your application.

    // src/App.tsx
    import React from 'react';
    import UserComponent from './components/UserComponent';
    
    const App: React.FC = () => {
      return (
        <div>
          <UserComponent />
        </div>
      );
    };
    
    export default App;
    

This setup allows you to manage the userId state using Redux Toolkit in a TypeScript React application.

Vite Server is running but not working on localhost

vite.config.ts

import {defineConfig} from 'vite'
import react from '@vitejs/plugin-react'
import dns from 'node:dns'

dns.setDefaultResultOrder('verbatim')

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [react()],
    server: {
        host: '127.0.0.1'
    }
})

References
https://stackoverflow.com/questions/70694187/vite-server-is-running-but-not-working-on-localhost
https://vitejs.dev/config/server-options.html#server-host

Understanding ‘useClient’ and ‘useServer’ in Next.js

useClient and useServer are React hooks introduced in Next.js to optimize and clarify the execution context of components or logic within an application. These hooks are part of Next.js’s ongoing enhancements to support React Server Components, enabling developers to specify more clearly whether a component should run on the client-side or server-side.

useServer

The useServer hook is a clear indication that the enclosed code or component is intended to run only on the server. This is particularly useful for operations that are sensitive or need direct access to server-side resources such as databases or environment variables that should not be exposed to the client. Here’s a quick example:

'use server'

function ServerComponent() {
  const data = useServer(() => {
    // Fetch data or perform operations that are server-only
    return fetchSecretData();
  });

  return <div>Secret Data: {data}</div>;
}
import { useServer } from 'next/server';

function ServerComponent() {
  const serverData = useServer(() => {
    // Simulate fetching server-only data
    const data = fetchServerData();
    return data;
  });

  return <div>Loaded server-only data: {serverData}</div>;
}

function fetchServerData() {
  // Pretend to fetch data that should not be exposed to the client
  return "Secret server info";
}

In this example, fetchSecretData is a function that you wouldn’t want to expose to the client-side due to security concerns or computational reasons. By using useServer, you ensure that this function only runs on the server.

useClient

Conversely, useClient is used to denote that the enclosed code or component should run only on the client-side. This is suitable for interactions that depend solely on the browser’s capabilities, such as DOM manipulations or client-side state handling that doesn’t need to pre-render on the server. Here’s how you might use it:

'use client'

function ClientComponent() {
  const [count, setCount] = useClient(() => {
    // Only run this hook in the client-side environment
    const [localCount, setLocalCount] = useState(0);
    return [localCount, setLocalCount];
  });

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      Count: {count}
    </div>
  );
}
import { useClient } from 'next/client';
import { useState } from 'react';

function ClientComponent() {
  const [count, setCount] = useClient(() => {
    // Initialize state only on the client
    const [localCount, setLocalCount] = useState(0);
    return [localCount, setLocalCount];
  });

  // Button click handler for incrementing the count
  function handleClick() {
    setCount(count + 1);
  }

  return (
    <div>
      <button onClick={handleClick}>Increment</button>
      Count: {count}
    </div>
  );
}

In this example, the state management for count is purely client-side, which makes useClient ideal for encapsulating client-specific logic.

When to Use useServer vs. useClient

Deciding whether to use useServer or useClient boils down to understanding where your code needs to execute for optimal performance and security. Here are some guidelines:

  • Use useServer if:
    • You need to access server-side resources or perform actions securely, away from the client’s reach.
    • You want to pre-render data or perform computations during server-side rendering (SSR) for SEO benefits or faster page loads.
  • Use useClient if:
    • Your component or logic must interact with browser-specific APIs or client-side resources like the local storage.
    • You are handling state or effects that should only occur in the client’s environment, such as animations or user input events.

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>
  );
}

 

Redux Fundamentals, Part 8: Modern Redux with Redux Toolkit

  • Redux Toolkit (RTK) is the standard way to write Redux logic
    • RTK includes APIs that simplify most Redux code
    • RTK wraps around the Redux core, and includes other useful packages
  • configureStore sets up a Redux store with good defaults
    • Automatically combines slice reducers to create the root reducer
    • Automatically sets up the Redux DevTools Extension and debugging middleware
  • createSlice simplifies writing Redux actions and reducers
    • Automatically generates action creators based on slice/reducer names
    • Reducers can “mutate” state inside createSlice using Immer
  • createAsyncThunk generates thunks for async calls
    • Automatically generates a thunk + pending/fulfilled/rejected action creators
    • Dispatching the thunk runs your payload creator and dispatches the actions
    • Thunk actions can be handled in createSlice.extraReducers
  • createEntityAdapter provides reducers + selectors for normalized state
    • Includes reducer functions for common tasks like adding/updating/removing items
    • Generates memoized selectors for selectAll and selectById

References
https://redux.js.org/tutorials/fundamentals/part-8-modern-redux