Friday, August 25, 2023

Redux Thunk in React With Examples

In Redux Toolkit in React With Examples we saw how to use Redux in React for storing global state. In this tutorial we'll see what is Thunk in Redux and how to use redux-thunk with React.

What is Thunk in Redux

In terms of Redux, thunk is a pattern of writing function that takes dispatch and getState as arguments so it can interact with Redux store using dispatch and getState.

Thunk functions are not directly called by application code, you will dispatch them.

In Redux you normally use action creators to generate action objects for dispatching, you don't write action objects yourself. Same way, you will normally use thunk action creators to generate the thunk functions that are dispatched. Think of thunk action creator as a function that returns a thunk function.

//thunk action creator
export const fetchPostData = (postId) => {
   //thunk function
  return async (dispatch) => {
      const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`);
      const data = await response.json();
      dispatch(postActions.refreshPosts(fetchedPosts));
}

Then you may call it in your component

function PostComponent({ postId }) {
  const dispatch = useDispatch()

  const onGetPostClicked = () => {
    dispatch(fetchPostData(postId))
  }
}

Why Thunks

You must be thinking why go through all this extra logic of having function with in a function then dispatch that thunk action creator. Why not directly dispatch actions.

1. One of the most important use cases for thunk is to use async logic. One of the rules of the reducers is that reducers must not do any asynchronous logic or any other side effects but real applications require logic that has side effects. You can think of thunk as a function where you can have async logic but it can also facilitate interaction with the Redux store.

redux-thunk is the most common async middleware which lets you write plain functions that may contain async logic directly. Redux Toolkit's configureStore function automatically sets up the thunk middleware by default so you don't need to write applyMiddleware() explicitly when creating store.

Redux library recommends using thunks as a standard approach for writing async logic with Redux.

2. Another benefit of using thunk is to keep your components leaner having only the presentation logic. For example, you are supposed to get some data when the component renders and you want to use useEffect() hook. Rather than writing the whole logic of fetching data, checking for errors and dispatching action with in useEffect() in that component you can move this logic in a thunk function. From your useEffect() hook you just dispatch thunk action creator.

3. You can also use thunks for synchronous logic as per Redux library documentation "Thunks are best used for complex synchronous logic, and simple to moderate async logic such as making a standard AJAX request and dispatching actions based on the request results."

Redux-thunk React example

Let's try to make things clearer with an example. This redux-thunk example uses jsonplaceholder API to fetch posts and add new post. These async fetch requests are done in thunk functions.

1. Create state slices

We'll have two state slices one for storing post state and another for notification state.

src\slice\post-slice.js

import { createSlice } from "@reduxjs/toolkit";
import { notificationActions } from "./notification-slice";


const postSlice = createSlice({
    name: 'postItems',
    initialState: {
        posts: []
    },
    reducers: {
        refreshPosts: (state, action) => {
            state.posts = action.payload
        },
        addPost: (state, action) => {
            const newPost = action.payoad;
            state.posts.push({
                id: newPost.id,
                title: newPost.title,
                body: newPost.body,
                userId: newPost.userId
            })
        }
    }

});

// thunk action creator
export const fetchPostData = () => {
    return async (dispatch) => {
        const postData = async () => {
            const response = await fetch('https://jsonplaceholder.typicode.com/posts');
            if(!response.ok){
                throw new Error('Fetching posts failed.');
            }
            const data = await response.json();
            return data;
        }
        try{
            const fetchedPosts = await postData();
            dispatch(postActions.refreshPosts(fetchedPosts));

        } catch(error){
            dispatch(notificationActions.showNotification({
                status: 'Error',
                message: 'Error while fetching posts ' + error.message
            }));
        }
    }
}

// thunk action creator
export const addPostData = (post) => {
    return async (dispatch) => {
        const insertPost = async () => {
            const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
                method: 'POST',
                headers: {
                    'Accept': 'application/json',
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(post)
            });
            if(!response.ok){
                throw new Error('Inserting new posts failed.');
            }
            const data = await response.json();
            console.log(data);
            return data;
        }
        try{
            const insertedPost = await insertPost();
            dispatch(notificationActions.showNotification({
                status: 'Success',
                message: 'Post data successfully inserted, title is: ' + insertedPost.title
              })
            );

        } catch(error){
            dispatch(notificationActions.showNotification({
                status: 'Error',
                message: 'Error while inserting post' + error.message
              }));
            
        }
    }
}
export const postActions = postSlice.actions;
export default postSlice.reducer;

In the code for slice, points to observe are-

  1. Name to identify the slice is postItems.
  2. Initial state value is an empty array meaning no posts.
  3. There are two reducer functions refreshPosts and addPosts in those functions there is no async logic (no side effect).
  4. There are two thunk action creators that’s where fetch is used to get post data or to add post data. From thunk functions there is interaction with reducers by dispatching actions.
    dispatch(postActions.refreshPosts(fetchedPosts));
    
    dispatch(notificationActions.showNotification({
                status: 'Error',
                message: 'Error while fetching posts ' + error.message
    }));
    
  5. Export the reducers and actions so that they can be used where needed.
    export const postActions = postSlice.actions;
    export default postSlice.reducer;
    

src\slice\notification-slice.js

import { createSlice } from "@reduxjs/toolkit";
const notificationSlice = createSlice({
    name: 'notification',
    initialState: {notification: null},
    reducers: {
        showNotification: (state, action) => {
            state.notification = {
                status: action.payload.status,                
                message: action.payload.message
            };
        }
    }
})

export const notificationActions = notificationSlice.actions;
export default notificationSlice.reducer;

2. Add reducers to the store

src\store\postStore.js

import { configureStore } from "@reduxjs/toolkit";
import postReducer from "../slice/post-slice";
import notificationReducer from "../slice/notification-slice";

const store = configureStore({
    reducer: {
        post: postReducer,
        notification: notificationReducer
    }
});

export default store;

3. Provide store to React

React Redux includes a <Provider /> component, which makes the Redux store available to the React app. We'll wrap <App /> component with the Provider which guarantees that the store provided with this Provider is available to App component and all its child components. That change can be done in index.js file.

src\index.js

import { Provider } from 'react-redux';
import store from './store/postStore';

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

4. Using thunk in Components

For fetching posts and displaying them there are two components Posts.js and PostItem.js.

For showing notification there is Notification.js.

src\components\Notification\Notification.js

const Notification = (props) => {
  return (
    <div className="alert alert-primary" role="alert">
      <p>{props.status} : {props.message}</p>
    </div>
  );
};

export default Notification;

Here Bootstrap classes are used to show alert notification.

src\components\Post\Posts.js

This component dispatches thunk function in useEffect() and then loops over the fetched posts to display them using PostItem component.

import { Fragment, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux"
import { fetchPostData } from "../../slice/post-slice";
import Notification from "../Notification/Notification";
import PostItem from "./PostItem";

const Posts = () => {
    const posts = useSelector(state => state.post.posts);
    const notification = useSelector(state => state.notification.notification);
    
    const dispatch = useDispatch();

    useEffect(() => {
        dispatch(fetchPostData());
    }, [dispatch])
    
    return (
        <Fragment>
             { notification && <Notification status={notification.status} 
            title={notification.title} message={notification.message}/> }
            <h2>Posts</h2>
            <ul>
                {
                    posts.map(post=> (
                        <PostItem key={post.id} post={post}></PostItem>
                    )
                )}
            </ul>
        </Fragment>
    );
}

export default Posts;

Points to observe in the above code are-

  1. useSelector() hooks allows you to extract data from the Redux store state. useDispatch() hook is used to dispatch actions. So, these hooks are imported from react-redux
  2. Using useSelector() get the post state and notification state from the store.
  3. In useEffect() hook dispatch thunk action creator which in turn executes thunk action with async logic. Once posts are fetched, dispatch action to modify state array (refer fetchPostData in post-slice.js) or dispatch action to notification in case of error. Once state changes Posts component is re-rendered to show posts.
  4. There is also conditional rendering of notification if notification is not null.

src\components\Post\PostItem.js

const PostItem = (props) => {
    return (
        <li>
            <span>User: {props.post.userId}</span>
            <h2>{props.post.title}</h2> 
            <span>{props.post.body}</span>
        </li>
    );
}

export default PostItem;

I can test Posts component by adding this element to App.js

function App() {
return (
    <div className="App">
        <Posts />
    </div>
  );
}
redux thunk

If I just change the URL to get some error-

const response = await fetch('https://jsonplaceholder.typicode.com/pos');
redux thunk react example

Adding post

For adding new post following component is used.

src\components\Post\AddPost.js

import { Fragment } from "react";
import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { addPostData } from "../../slice/post-slice";
import Notification from "../Notification/Notification";
const AddPost = () => {
    const notification = useSelector(state => state.notification.notification);
    const dispatch = useDispatch();
    const [formField, setState] = useState({
        title: '',
        body: '',
        userId: ''
    });
    const handleInputChange = (event) => {
        const target = event.target;
        const value = target.value;
        const name = target.name;
    
        setState((prevState) => {
            return {...prevState, [name]: value};
        });
    }

    const formSubmitHandler = (event) => {
        event.preventDefault();
        dispatch(addPostData({
            title: formField.title,
            body: formField.body,
            userId: formField.userId
        }));
    }
    return(
        <Fragment>
            { notification && <Notification status={notification.status} 
            title={notification.title} message={notification.message}/> }
            <form onSubmit={formSubmitHandler}>
                Title: <input type="text" placeholder="Enter Title" name="title" onChange={handleInputChange}></input>
                Body: <input type="text" placeholder="Enter Post Content" name="body" onChange={handleInputChange}></input>
                User ID: <input type="number" placeholder="Enter User ID" name="userId" onChange={handleInputChange}></input>
                <button type="submit" onClick={formSubmitHandler}>Add Post</button>
            </form>
        </Fragment>
        
    );
}
export default AddPost;

Points to observe in the above code are-

  1. Uses useState() hook to get form fields state.
  2. At the click of “Add Post” button, formSubmitHandler() function is called where thunk function to add post is dispatched.
  3. If you notice in post-slice from thunk function actions are dispatched to change notification state.
redux-thunk react

Source- https://redux.js.org/usage/writing-logic-thunks

That's all for the topic Redux Thunk in React With Examples. If something is missing or you have something to share about the topic please write a comment.


You may also like

Monday, July 31, 2023

Redux Toolkit in React With Examples

In this tutorial we'll see an introduction of Redux Toolkit and how to use it with React application.

In the post Redux in React With Examples we saw how you can create store using Redux and react-redux library. Though that is not the suggested way to create redux store but I'll suggest to go through that post to get a better idea of how Redux works. With the old way you generally have to write most of the logic yourself so it gives you a better understanding of what is a store, reducer and action. But the suggested way now is to use configureStore() provided by @reduxjs/toolkit library so let's try to understand how that works.

Using Redux toolkit

Though Redux library helps in managing global state and making state management more efficient but you may still experience the following problems.

  1. As the complexity of your project increases complexity of Redux also increases.
  2. Writing reducers and actions become more complex and their number also increases with bigger applications.
  3. State object may become big which results in keeping state immutable quite a complex task.

In order to address these issues and making it more convenient to work with Redux, the same team developed Redux toolkit. With Redux toolkit you can say that some of the steps that were done manually with Redux are now automatically derived. It also makes it more convenient to modify the stored state without worrying about its immutability as Redux toolkit uses immer library which takes care of state immutability behind the scene rather than you ensuring it.

The Redux Toolkit package is intended to be the standard way to write Redux logic.

Using @reduxjs/toolkit with react

Let's try to create the same counter application using Redux toolkit which was done in this post using Redux.

1. Install packages

If you already have a React application just navigate to the app base folder and run the following command to install Redux toolkit

npm install @reduxjs/toolkit react-redux

react-redux is the React binding library for Redux.

2. Create store

src\store\tkStore.js

import { configureStore } from "@reduxjs/toolkit";


const store = configureStore({
    reducer: { } // that's where we'll add reducer
})

export default store;

With Redux toolkit you start using configureStore() to create store rather than createStore() used with Redux.

configureStore() is an abstraction over the standard Redux createStore function that adds good defaults to the store setup for a better development experience.

configureStore accepts a single configuration object parameter, with many options. Options of interest to us initially are-

  • reducer- To accept reucer functions.
  • devTools- To indicate whether configureStore should automatically enable support for the Redux DevTools browser extension. Defaults to true.

For more details you can see the API- https://redux-toolkit.js.org/api/configureStore

3. Provide store to React

React Redux includes a <Provider /> component, which makes the Redux store available to the React app. We'll wrap <App /> component with the Provider which guarantees that the store provided with this Provider is available to App component and all its child components. That change can be done in index.js file.

import { Provider } from 'react-redux';
import store from './store/tkStore';

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

4. Create a state slice

You can import the createSlice API from Redux Toolkit. This is the most important part as with slice you can write reducer function and actions with in the slice.

src\slice\counterSlice.js

import { createSlice } from "@reduxjs/toolkit";

const counterSlice = createSlice({
    name: 'counter',
    initialState: {value: 0},
    reducers: {
        increment: (state) => {
            state.value++;
        }, 
        decrement: (state) => {
            state.value--;
        },
        increaseByValue: (state, action) => {
            state.value += action.payload;
        }
    
    }
});

export const counterActions = counterSlice.actions;
export default counterSlice.reducer;

Some important points to note here are-

  • Creating a slice requires a name to identify the slice, which is given as ‘counter’ here.
    name: 'counter',
    
  • Slice also needs initial state value.
    initialState: {value: 0},
    
  • You can have one or more reducer functions to define how the state can be updated. You can think of reducer as a map where key is the action name and the value is the logic to update state. For example, with this piece of code.
    increment: (state) => {
                state.value++;
            }, 
    
    
    Redux generates an action named 'increment'. With the use of createSlice Redux toolkit makes it easier to define reducers and actions, you also don't need condition statements (if or switch) to determine the correct action type.
  • Another very important thing is how state is changed. It looks like, now we are mutating state rather than creating a new state altogether.
     state.value++;
    
    This is permitted with Redux toolkit because it uses immer as explained already.
  • Export the reducers and actions so that they can be used where needed.
    export const counterActions = counterSlice.actions;
    export default counterSlice.reducer;
    
    You can also use object destructuring to export the generated action creators separately.
    export const { increment, decrement, increaseByValue} = counterSlice.actions;
    

5. Add reducer to the store

Now we have defined and exported the reducer so let's revisit the store to add reducer there.

import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "../slice/counterSlice";
const store = configureStore({
    reducer: {
        counterReducer: counterReducer
    }
})

You can also add more than one slice reducers (an object of slice reducers)

configureStore({
    reducer: {
        counterReducer: counterReducer, 
	    userReducer: userReducer
    }
})

configureStore will automatically create the root reducer by passing this object to the Redux combineReducers utility.

6. Using actions in components

We'll use two hooks useSelector() and useDispatch() provided by react-redux. useSelector() hooks allows you to extract data from the Redux store state. useDispatch() hook is used to dispatch actions.

src\components\CounterWithRedux.js

import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { counterActions } from "../slice/counterSlice";

const CounterWithRedux = () => {
    const counter = useSelector((state) => state.counterReducer.value);
    const dispatch = useDispatch();
    const [incrementByValue, setIncrementByValue] = useState(0);
    const incrementHandler = () => {
        dispatch(counterActions.increment());
    }
    const decrementHandler =  () => {
        dispatch(counterActions.decrement());
    }
    const incrementByValueHandler = () => {
        dispatch(counterActions.increaseByValue(Number(incrementByValue)));
    }
    return (
        <div>
            <h2>Counter</h2>
            <div>
                <button className="btn btn-primary mx-2" onClick={incrementHandler}>+</button>            
                <span>{counter}</span>            
                <button className="btn btn-primary mx-2" onClick={decrementHandler}>-</button>
                <div className="mt-2">
                    <input value={incrementByValue} onChange={e => setIncrementByValue(e.target.value)}></input>
                    <button className="btn btn-primary mx-2" onClick={incrementByValueHandler}>Add Amount</button>
                </div>
            </div>
        </div>
    );
}

export default CounterWithRedux;

Some important points to note here-

  • We have used useSelector() to read data from store.
    const counter = useSelector((state) => state.counterReducer.value);
    

    What we are doing here is to go to the state produced by counterReducer.

    Remember that we have given 'counterReducer' identifier to our reducer which is also named counterReducer.

    import counterReducer from "../slice/counterSlice";
    const store = configureStore({
        reducer: {
            counterReducer: counterReducer
        }
    })
    

    From that state we are extracting data from 'value' property. Again, that's how we defined the state.

    initialState: {value: 0}
    
  • We can import generated action creators which is imported in CounterSlice.js file.
    import { counterActions } from "../slice/counterSlice";
    

    Now we can dispatch the required action by accessing it through counterActions.

    dispatch(counterActions.increment());
    dispatch(counterActions.decrement());
    
  • Note that Bootstrap is used here for styling.
Redux toolkit with React example

Source: https://redux-toolkit.js.org/tutorials/quick-start

That's all for the topic Redux Toolkit in React With Examples. If something is missing or you have something to share about the topic please write a comment.


You may also like

Wednesday, July 26, 2023

Redux in React With Examples

While creating a React application you would have definitely done some state management. If it is for a single component or passing data between parent and child component (not a big hierarchy of nested components) it makes sense to use useState() and props to keep it simple. But if there are lots of nested components then it becomes very complex and unwieldly to use useState() to manage state as that will require prop drilling to send data down to nested components.

One way to pass data without prop drilling is to use Context API in React, but that may also become unwieldly if you have many different context providers. As you will need to wrap those contexts around the component whose child components requires the value stored by context. So, you may end up in a scenario like this.

<Context1.Provider>
  <Context2.Provider>
    <Context3.Provider>
            <ComponentA></ComponentA>
    </Context3.Provider>
  </Context2.Provider>
</Context1.Provider>

Another option to store state is using Redux library that provides a centralized store for storing application wide state that can be accessed by any component with in the application. For example, an authentication state which may be required to change menu options in navbar. Redux gives you an option to easily manage global state which can be updated or accessed by any component. Doing that requires some work so let's see how to go with setting up Redux managed state.

How Redux works

In order to understand how Redux works you should know about three entities.
  1. Store- A single centralized store that contains the state. Components subscribe to this store, whenever there is a change in state components are notified. Then, components can get the required data from the state object. State object may have lot of properties and a specific component may require only a part of that data that's what I mean by getting the required data from the state object.
  2. Reducer function- The saved state in the store will also require changes. Be aware that components never directly change the state. That is done by using a reducer, which is just a simple function that takes two parameters- current state and action.
    const counterReducer = (state = {counter:0}, action) => {
    
    }
    

    Reducer function has the logic to update the state if necessary and it returns the new state. Please note one very important point that the reducer is not allowed to change the existing state. Change to the state must be immutable; copy the existing state and make changes to the copied values.

    Reducers are pure functions that take the previous state and an action, and return the next state.

  3. Action- So, reducer function is used to modify the state but how does reducer know what action to perform and when? That's where action comes, which is dispatched from a component. You can think of an action as a event that describes something that has happened in the application. Action is an object which, by convention, has two properties type and payload (optional). The type field should be a string that describes the action and any additional information if needed is passed in payload field. For example, a typical action object might look like this:
    const addTodoAction = {
      type: 'todos/todoAdded',
      payload: 'Write an article'
    }
    

    In the type string first part is the category that this action belongs to and the second part is the event that happened.

Redux data flow

Flow for state storage, change to the state and UI re-rendering based on the changed state can be described by following steps.

Initial setup:

  • A Redux store is created using a root reducer function. In most cases you will split the data handling logic and create multiple reducers. These multiple reducers are combined together to create a single root reducer.
  • The store calls the root reducer once, and saves the return value as its initial state.
  • When the UI is first rendered, UI components access the current state of the Redux store, and use that data to decide what to render. They also subscribe to any future store updates so they can know if the state has changed.

Updating state and re-rendering

  1. An event occurs that require a state change. For example, user clicks a button.
  2. The app code dispatches an action to the Redux store, like dispatch({type: 'counter/incremented'})
  3. The store runs the reducer function again with the previous state and the current action. Reducer function may have several conditions for different action types. Based on the passed action type it runs the appropriate logic and returns the new state which is stored in the store as new state.
  4. The store notifies all parts of the UI that are subscribed that the store has been updated
  5. Each UI component that needs data from the store checks to see if the parts of the state they need have changed.
  6. Each component that sees its data has changed forces a re-render with the new data, so it can update what's shown on the screen.
React-Redux example

Redux with React example

In this simple example we'll use a Redux store to store counter and dispatch increment and decrement actions to change the state.

First thing you need is to install redux and react-redux. Here redux is the core library and react-redux is the React binding library for Redux. Go to the app location where you need these libraries and run the following command.

npm install redux react-redux

Creating store

import { createStore} from 'redux'
import counterReducer from '../reducers/counterReducer';
export const appStore = createStore(counterReducer);

Here note that in this example createStore is used which is deprecated now and Redux suggests to use configureStore() of @reduxjs/toolkit package. In my opinion using createStore() gives a better understanding of the above explained steps so just go through it to understand working of Redux better but in real project use configureStore().

Refer this post- Redux Toolkit in React With Examples to know more about using @reduxjs/toolkit.

Reducer function

In the createStore() reducer is passed so that is function we'll write now.

counterReducer.js

const counterReducer = (state = {counter:0}, action) => {
    switch(action.type){
        case "INCREMENT":
            return {
                counter: state.counter + 1
            }
        case "DECREMENT":
            return {
                counter: state.counter - 1
            }
        default:
            return state;
    }   
}

export default counterReducer;

In the reducer function you should take note of the following points-

  1. As the parameters to the function current state (initialized with default state) and action is passed.
  2. Switch case is used to go through the action types and change the counter state accordingly and return the new state.
  3. There is also a default returning the existing state.

Provider

React Redux includes a <Provider /> component, which makes the Redux store available to the rest of your app. We'll wrap <App /> component with the Provider which guarantees that the store provided with this Provider is available to App component and all its child components. That change can be done in index.js file.

import { Provider } from 'react-redux';
import { appStore } from './store/appstore';

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

Provider has a store prop that's where we pass our store so that react-redux know which store to provide.

Component

Note that Component uses Bootstrap for styling

Components/Counter.js

import { useDispatch, useSelector } from "react-redux";

const Counter = () => {
    const counter = useSelector((state) => state.counter);
    const dispatch = useDispatch();
    const incrementHandler = () => {
        dispatch({type: 'INCREMENT'});
    }
    const decrementHandler =  () => {
        dispatch({type: 'DECREMENT'});
    }
    return (
        <div>
            <h2>Counter</h2>
            <div>
                <button className="btn btn-primary mx-2" onClick={incrementHandler}>+</button>            
                <span>{counter}</span>            
                <button className="btn btn-primary mx-2" onClick={decrementHandler}>-</button>
            </div>
        </div>
    );
}

export default Counter;

Some important points to note here-

  1. Two hooks useSelector() and useDispatch() are used here which are imported from react-redux.
  2. useSelector() hooks allows you to extract data from the Redux store state for use in this component. The selector will be called with the entire Redux store state as its only argument. The selector may return any value as a result, including directly returning a value that was nested inside state, or deriving new values.
  3. useSelector() hook also does the job of subscribing this component to the Redux store
  4. Selector runs whenever an action is dispatched. Component is re-rendered if the returned value of the selector is different from the previous selector value.
  5. useDispatch() hook returns a reference to the dispatch function from the Redux store. You may use it to dispatch actions.
  6. Different actions are dispatched based on whether increment or decrement button is clicked.
  7. In the Reducer appropriate case is executed based on the passed action type and the new state is returned.
  8. This new state is saved in the store and the subscribed components are notified of the change.
  9. useSelector() gets the state.counter value from the state since that value is different from the previous value so the component is re-rendered.
Redux with React

Redux with React example with action payload

In the previous example in the action object only 'type' field is used, in this example we'll add payload field too. For this we'll modify the previous example itself and provide the facility to add a passed value to the counter.

Redux counter example

counterReducer.js

A new case for increasing the counter by given amount is added.

const counterReducer = (state = {counter:0}, action) => {
    switch(action.type){
        case "INCREMENT":
            return {
                counter: state.counter + 1
            }
        case "DECREMENT":
            return {
                counter: state.counter - 1
            }
        case "INCREMENT_BY_VALUE":
            return {
                counter: state.counter + Number(action.payload)
            }
        default:
            return state;
    }   
}

export default counterReducer;

Component

In the Counter component a new action is dispatched with type as 'INCREMENT_BY_VALUE' and payload as the amount entered by user.

import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";

const Counter = () => {
    const counter = useSelector((state) => state.counter);
    const dispatch = useDispatch();
    const [incrementByValue, setIncrementByValue] = useState(0);
    const incrementHandler = () => {
        dispatch({type: 'INCREMENT'});
    }
    const decrementHandler =  () => {
        dispatch({type: 'DECREMENT'});
    }
    const incrementByValueHandler = () => {
        dispatch({type: 'INCREMENT_BY_VALUE',
                payload: incrementByValue});
    }
    return (
        <div>
            <h2>Counter</h2>
            <div>
                <button className="btn btn-primary mx-2" onClick={incrementHandler}>+</button>            
                <span>{counter}</span>            
                <button className="btn btn-primary mx-2" onClick={decrementHandler}>-</button>
                <div className="mt-2">
                    <input value={incrementByValue} onChange={e => setIncrementByValue(e.target.value)}></input>
                    <button className="btn btn-primary mx-2" onClick={incrementByValueHandler}>Add Amount</button>
                </div>
            </div>
        </div>
    );
}

export default Counter;

Source: https://redux.js.org/tutorials/fundamentals

That's all for the topic Redux in React With Examples. If something is missing or you have something to share about the topic please write a comment.


You may also like

Sunday, February 26, 2023

React Dynamic Form Using Formik's FieldArray Component

In this post you'll see how to create a React form with dynamic array field using Formik and Yup for object schema validation.

Many a times you need a form where you give user a functionality to add the same set of fields again and again which means giving an input or group of inputs as a dynamic array. For example, suppose you have a student form where you want to give a button to add qualification and whenever user clicks that button a new set of input fields for qualification should be added to the form.

Formik FieldArray

Dynamic React form using Formik's FieldArray

In Formik, which is a third party lightweight React form library, there is a <FieldArray> component that helps with common array/list manipulations. Using index of the array you can create input groups and do validations.

Formik's FieldArray example

In the example form we'll capture student details so the form fields are Student Name, Student Age and Qualification which is an array having two fields name and year. For the qualification field you want to provide the functionality that student should be able to add more than one qualification.

In this post we'll concentrate more on FieldArray, to get more details about Formik and Yup refer this post- React Form + Formik + Yup Validation Example

import { ErrorMessage, Field, FieldArray, Form, Formik, getIn } from "formik"
import * as Yup from 'yup';
import './form.css';
const StudentForm = () => {
  return (
    <Formik
      initialValues={{
        studentName: '',
        studentAge: '',
        qualification: [
          {
            name: '',
            year: ''
          }
        ]

      }}
      validationSchema={Yup.object({
        studentName: Yup.string()
            .max(20, 'Must be 20 characters or less')
            .required('Required'),
        studentAge: Yup.number()
            .min(18, 'Age must not be less than 18')
            .max(22, 'Age must not be greater than 22')
            .required('Required'),
        qualification: Yup.array().of(
            Yup.object().shape({
                name: Yup.string()
                    .required('Required'),
                year: Yup.number()
                    .required('Required'),
            })
        )
      })}
      onSubmit={(values, { setSubmitting }) => {
        setTimeout(() => {
            console.log(JSON.stringify(values, null, 2));
            setSubmitting(false);
        }, 400);
      }}

    >
    {({ errors, touched, values }) => (
      <Form>
          <h3>Student Form</h3>
          <div className="row">
              <div className="form-group col-sm-4 mb-2" >
                <label htmlFor="studentName" className="form-label">Student Name</label>
                <Field type="text" name="studentName" placeholder="Enter studentName"
                    className={'form-control' + (errors.studentName && touched.studentName ? ' is-invalid' : '')} />
                <ErrorMessage name="studentName" component="div" className="invalid-feedback" />
              </div>
              <div className="form-group col-sm-4 mb-2" >
                <label htmlFor="studentAge" className="form-label">Student Age</label>
                <Field type="text" name="studentAge" placeholder="Enter age"
                    className={'form-control' + (errors.studentAge && touched.studentAge ? ' is-invalid' : '')} />
                <ErrorMessage name="studentAge" component="div" className="invalid-feedback" />
              </div>
          </div>

          <FieldArray name="qualification">
            {({ remove, push }) => (

              <div>
                  <h4>Add qualifications</h4>
                  <button
                      type="button"
                      className="btn btn-secondary mb-2"
                      onClick={() => push({ name: '', year: '' })}
                  >
                      Add Qualification
                  </button>
                  {values.qualification.length > 0 &&
                    values.qualification.map((qual, index) => (

                    <div className="row" key={index}>
                      <div className="col-sm-4">
                        <label htmlFor={`qualification.${index}.name`} className="form-label">Name</label>
                        <Field
                            name={`qualification.${index}.name`}
                            placeholder="qualification"
                            type="text"
                            className={'form-control' + (getIn(errors, `qualification.${index}.name`) && getIn(touched, `qualification.${index}.name`) ? ' is-invalid' : '')}
                        />
                        <ErrorMessage
                            name={`qualification.${index}.name`}
                            component="div"
                            className="invalid-feedback"
                        />
                      </div>
                      <div className="col-sm-4">
                        <label htmlFor={`qualification.${index}.year`} className="form-label">Year</label>
                        <Field
                            name={`qualification.${index}.year`}
                            placeholder="year"
                            type="number"
                            className={'form-control' + (getIn(errors, `qualification.${index}.year`) && getIn(touched, `qualification.${index}.year`) ? ' is-invalid' : '')}
                        />
                        <ErrorMessage
                            name={`qualification.${index}.year`}
                            component="div"
                            className="invalid-feedback"
                        />
                      </div>
                      <div className="col-sm-1">
                        <label className="form-label">&nbsp;</label>
                        <button
                            type="button"
                            className="btn btn-danger btn-as-block"
                            onClick={() => remove(index)}
                        >
                            X
                        </button>
                      </div>
                  </div>
                ))}

              </div>
            )}
          </FieldArray>
          <button type="submit" className="btn btn-primary">Submit</button>
        </Form>
      )}

    </Formik>
  );
}

export default StudentForm;

There is also a small CSS code for aligning the delete button.

Form.css

.btn.btn-as-block {
  display: block;
}

Some important points to note here-

  1. In the example Bootstrap 5 is used for styling. You can import Bootstrap by adding following to the <head> section of the index.html file.
    <link rel="stylesheet" href=https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous" />
    	
  2. Initial values for the form fields are as empty fields. You can see that qualification is defined as an array of objects with two properties- name and year.
    qualification: [
      {
        name: '',
        year: ''
      }
    ]
       
  3. In the validation Yup.array() function is used to provide validation for the fields in qualification array.
  4. The submission function (onSubmit) logs the form values.
  5. In the <Form> section some of the Bootstrap classes like form-group, form-label, form-control, is-invalid, invalid-feedback are used.
  6. If there is an error then apart from form-control, is-invalid class is also added.
  7. For adding qualification, a button "Add Qualification" is provided. On clicking it a new object with two fields- name, year is pushed into an array.
    <button
      type="button"
      className="btn btn-secondary mb-2"
      onClick={() => push({ name: '', year: '' })}>
        Add Qualification
    </button>
    
  8. To give unique name to each group of fields in the array index is used
    name={`qualification.${index}.year`}
    
  9. getIn, which is a utility function included in Formik is used to get errors for the specific index.
  10. With each array index another button to remove that index is also added.
    	<button
      type="button"
      className="btn btn-danger"
      onClick={() => remove(index)}>
        X
    </button>
    

With field validation error messages

React dynamic form validation

That's all for the topic React Dynamic Form Using Formik's FieldArray Component. If something is missing or you have something to share about the topic please write a comment.


You may also like

Friday, February 10, 2023

React Form + Formik + Yup Validation Example

In the post React Form Using Formik's useFormik() Hook we have seen how to create a form in React using Formik library and how to do validation. In that post we used useFormik() hook and did the validation ourselves. Formik provides integration with Yup for object schema validation.

Formik has a special configuration prop for Yup called validationSchema which will automatically transform Yup's validation errors messages into an object whose keys match values/initialValues/touched so that it is easy to map error message with the corresponding form field.

Installing Yup

you can install Yup from NPM or yarn

With npm

npm install yup -save

With yarn

yarn add yup

Using Formik component

To further reduce code and make it more readable Formik comes with components like <Formik />, <Form />, <Field />, and <ErrorMessage />. These formik components use React Context implicitly.

React form example with Formik and Yup

In this example we'll create a form with 5 fields- First Name, Last Name, Join Date, Email and Employment Type.

You can swap useFormik() hook with Formik component, with that you can pass initial values for the formFields, validation schema and a submission function.

<Formik 
  initialValues= {{
      firstName: '',
      lastName: '',
      joinDate: '',
      email: '',
      employmentType: 'permanent'
  }}
  validationSchema={Yup.object({
      firstName: Yup.string()
      .max(10, 'Must be 10 characters or less')
      .required('Required'),
      lastName: Yup.string()
      .max(15, 'Must be 15 characters or less')
      .required('Required'),
      joinDate:  Yup.date().max(new Date(), "Join date can't be greater than current date").required('Required'),
      email: Yup.string().email('Invalid email address').required('Required'),
  })}
  onSubmit={(values, { setSubmitting }) => {
      setTimeout(() => {
      console.log(JSON.stringify(values, null, 2));
      setSubmitting(false);
      }, 400);
  }}
>

By using Field component you don't need to pass event handlers- onChange, onBlur, value explicitly.

<Field type="text" name="firstName" />

Here is the full React form example using Formik, Field, Form, ErrorMessage components and Yup for validation.

import { ErrorMessage, Field, Form, Formik } from "formik";
import * as Yup from 'yup';
const EmployeeInfoForm = () => {
  return(
    <Formik 
      initialValues= {{
          firstName: '',
          lastName: '',
          joinDate: '',
          email: '',
          employmentType: 'permanent'
      }}
      validationSchema={Yup.object({
          firstName: Yup.string()
          .max(10, 'Must be 10 characters or less')
          .required('Required'),
          lastName: Yup.string()
          .max(15, 'Must be 15 characters or less')
          .required('Required'),
          joinDate:  Yup.date().max(new Date(), "Join date can't be greater than current date").required('Required'),
          email: Yup.string().email('Invalid email address').required('Required'),
      })}
      onSubmit={(values, { setSubmitting }) => {
          setTimeout(() => {
          console.log(JSON.stringify(values, null, 2));
          setSubmitting(false);
          }, 400);
      }}
    >
    {({ errors, touched }) => (
      <Form>

        <div className="form-group col-sm-4 mb-2" >
            <label htmlFor="firstName" className="form-label">First Name</label>
            <Field type="text" name="firstName" placeholder="Enter firstname" 
            className={'form-control' + (errors.firstName && touched.firstName ? ' is-invalid' : '')} />
            <ErrorMessage name="firstName" component="div" className="invalid-feedback" />       
        </div>
        <div className="form-group col-sm-4 mb-2" >
            <label htmlFor="lastName" className="form-label">Last Name</label>
            <Field type="text" name="lastName" className={'form-control'+ (errors.lastName && touched.lastName ? ' is-invalid' : '')}/>
            <ErrorMessage name="lastName" component="div" className="invalid-feedback"/>  
        </div>
        <div className="form-group col-sm-4 mb-2" >
            <label htmlFor="joinDate" className="form-label">Join Date</label>
            <Field type="date" name="joinDate" className={'form-control'+ (errors.joinDate && touched.joinDate ? ' is-invalid' : '')}/>
            <ErrorMessage name="joinDate" component="div" className="invalid-feedback"/>  
        </div>
        <div className="form-group col-sm-4 mb-2" >
            <label htmlFor="email" className="form-label">Email</label>
            <Field type="email" name="email" className={'form-control'+ (errors.email && touched.email ? ' is-invalid' : '')}/>
            <ErrorMessage name="email" component="div" className="invalid-feedback"/>  
        </div>
        <div className="form-group col-sm-4 mb-2" >
            <label htmlFor="employmentType" className="form-label">Employment Type:</label>
            <Field as="select" name="employmentType" className="form-select " >              
                <option value="permanent">Permanent</option>
                <option value="contract">Contract</option>
            </Field>
        </div>
          <div className="form-group col-sm-4 mb-2" >
              <button type="submit" className="btn btn-primary">Submit</button>
          </div>          
      </Form>
     )}
    </Formik>
  );
}

export default EmployeeInfoForm;

Some important points to note here-

  1. In the example Bootstrap 5 is used for styling. You can import Bootstrap by adding following to the <head> section of the index.html file.
    <link rel="stylesheet" href=https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous" />
    
  2. Initial values for the form fields are as empty fields except for the employmentType which is a drop-down list where pre-selected option is given.
  3. Validation is done using Yup which is quite self-explanatory. For join date it should not go beyond the current date.
  4. The submission function (onSubmit) logs the form values.
  5. In the <Form> section some of the Bootstrap classes like form-group, form-label, form-control, is-invalid, invalid-feedback are used.
  6. If there is an error then apart from form-control, is-invalid class is also added.
  7. The <Field> component by default will render an <input> component. <Field> also accepts a few other props to let you render other elements like textarea, select.
    <Field name="message" as="textarea" />
    
    <Field name="employmentType" as="select"  />  
    

With Error Messages

React form with Formik

With All Form Values

React form Formik and Yup

That's all for the topic React Form + Formik + Yup Validation Example. If something is missing or you have something to share about the topic please write a comment.


You may also like

Wednesday, February 8, 2023

React Form Using Formik's useFormik() Hook

In the post React Form Creation and Validation Using React Hooks we have seen how to create a form using React and how to do validation. As you can notice the whole process of creating form in React is quite verbose if you do everything on your own.

You need to have a lot of repeated code for-

  1. Managing form fields states.
  2. Validation and when to show/not show error messages.
  3. Handling form submission

Formik, a third-party lightweight React form library addresses the above mentioned issues. Formik standardizes the creation of from fields and the form submission process.

Formik installation

You can install Formik with Yarn or NPM

With Yarn

yarn add formik

With npm

npm install formik -save

Formik React form example

In this example we'll create a form with 5 fields- First Name, Last Name, Join Date, Email and Employment Type. This example uses useFormik() which is a custom React hook.

Though using useFormik() hook is quite verbose and you can create a form using Formik in a more readable way using <Formik> component and using Yup for form validation. Check the post here- React Form + Formik + Yup Validation Example

Using useFormik() hook you can define the form fields, how to validate and form submission process at the same place.

const formik = useFormik({
  initialValues: {
    firstName: '',
    lastName: '',
    joinDate: '',
    email: '',
    employmentType: 'permanent'
  },
  validate,
  onSubmit: values => {
    alert(JSON.stringify(values, null, 2));
  },
});

As you can see form's initial values, a submission function (inSubmit) and a validation function (validate) is passed to the useFormik() hook.

Event handling

useFormik() hook returns form state and helper methods which can be used out of the box. Some of the helper methods you will use in your form are-

  • handleSubmit: A submission handler
  • handleChange: A change handler to pass to each <input>, <select>, or <textarea>
  • handleBlur: To keep track of which fields have been visited, stores this information in an object called touched which can store boolean values true or false
  • values: Our form’s current values

That's how you'll use handleSubmit

<form onSubmit={formik.handleSubmit}>
Using handleChange, handleBlur and value.
<input 
  type="text" 
  id="firstName" 
  name="firstName"
  onChange={formik.handleChange} 
  onBlur={formik.handleBlur}
  value={formik.values.firstName}>
</input>

With that Formik will take care of managing form fields state and event handling. You don't need to write custom event handlers for every single input.

Validation and error messages

Formik also keeps track of validation and error messages. You can specify a custom validation function and pass it as validate to the useFormik() hook. If an error exists, this custom validation function should produce an error object with properties matching the form values/initialValues.

You would generally show error once you come out of a form field. For that Formik has handleBlur() event handler to keep track of which fields have been visited.

That's how you'll write custom validate function.

const validate = values => {
    const errors = {};
    if (!values.firstName) {
        errors.firstName = 'Required';
    } else if (values.firstName.length > 10) {
        errors.firstName = 'Must be 10 characters or less';
    }
    // Validation for other fields

That's how you'll check if field visited and if field has error to display error message.

{formik.touched.firstName && formik.errors.firstName ? <div>{formik.errors.firstName}</div> : null}

Here is the full React form example created using useFormik() hook of Formik.

import { useFormik } from "formik"
import './form.css';
const validate = values => {
    const errors = {};
    if (!values.firstName) {
        errors.firstName = 'Required';
    } else if (values.firstName.length > 10) {
        errors.firstName = 'Must be 10 characters or less';
    }
    if (!values.lastName) {
        errors.lastName = 'Required';
    } else if (values.lastName.length > 15) {
        errors.lastName = 'Must be 15 characters or less';
    }
    if (!values.joinDate) {
        errors.joinDate = 'Required';
    }
    if (!values.email) {
        errors.email = 'Required';
    } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {
        errors.email = 'Invalid email address';
    }
    return errors;
}
const EmployeeForm = () => {
    const formik = useFormik({
        initialValues: {
            firstName: '',
            lastName: '',
            joinDate: '',
            email: '',
            employmentType: 'permanent'
        },
        validate,
        onSubmit: values => {
            console.log(JSON.stringify(values, null, 2));
        },
    });
    return (
        <form onSubmit={formik.handleSubmit}>
            <div>
            <label htmlFor="firstName">First Name</label>
            <input type="text" id="firstName" name="firstName"
                onChange={formik.handleChange} onBlur={formik.handleBlur}
                value={formik.values.firstName}></input>
            {formik.touched.firstName && formik.errors.firstName ? <div className="error">{formik.errors.firstName}</div> : null}
            </div>
            <div>
            <label htmlFor="lastName">Last Name</label>
            <input type="text" id="lastName" name="lastName"
                onChange={formik.handleChange} onBlur={formik.handleBlur}
                value={formik.values.lastName}></input>
             {formik.touched.lastName && formik.errors.lastName ? <div className="error">{formik.errors.lastName}</div> : null}        
            </div>
            <div>
            <label htmlFor="joinDate">Join Date</label>
            <input type="date" id="joinDate" name="joinDate"
                onChange={formik.handleChange} onBlur={formik.handleBlur}
                value={formik.values.joinDate}></input>      
             {formik.touched.joinDate && formik.errors.joinDate ? <div className="error">{formik.errors.joinDate}</div> : null}      
            </div>
            <div>
            <label htmlFor="email">Email</label>
            <input type="email" id="email" name="email"
                onChange={formik.handleChange} onBlur={formik.handleBlur}
                value={formik.values.email}></input>
            {formik.touched.email && formik.errors.email ? <div className="error">{formik.errors.email}</div> : null}    
            </div>
            <div>
            <label htmlFor="employmentType">Employment Type:</label>
                <select id="employmentType" name="employmentType" 
                  value={formik.values.employmentType} onChange={formik.handleChange} 
                  onBlur={formik.onBlur}>
                    <option value="permanent">Permanent</option>
                    <option value="contract">Contract</option>
                </select>
            </div>
            <button type="submit" disabled={!(formik.dirty && formik.isValid)}>Submit</button>
        </form>
    )
}
export default EmployeeForm;

To keep focus on the form styling is not used, only styling is for error message.

form.css

.error {
  color: red;
}

Some important points to note here-

  1. In the useFormik() hook, initialValues for the form fields are passed, all but one as empty fields, for the employmentType which is a drop-down list pre-selected option is there.
  2. A custom validation function is written to validate the form fields and create error messages. Reference for that function is also passed to the hook.
  3. onSubmit function just logs the form values.
  4. With form fields the same event handler functions (handleChange, handleBlur) are used, which is one of the benefit of using Formik library.
  5. Form values are passed to the validate function and the validation based on the requirement is done for the fields there. Error messages for the fields are also created.
  6. Formik keeps track of the visited fields and based on whether the field is touched or not and if it fails validation, error message for the field is displayed.

That's all for the topic React Form Using Formik's useFormik() Hook. If something is missing or you have something to share about the topic please write a comment.


You may also like

Saturday, January 28, 2023

Java Generics - Type Erasure

Using Java generics you can write generic programs and it also provide tighter type checks at compile time but these generics type remain only at source code level. When the source code is compiled all the generic type parameters are erased and this process is called type erasure in Java Generics.

How does Type erasure work

Type erasure in Java works as follows-

  1. Replace all type parameters in generic types with their bound type, if no explicit bound type is specified then replace generic type parameter with Object. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods with all the generic parameters replaced with actual types.
  2. Insert type casts if necessary to preserve type safety.
  3. Generate bridge methods to preserve polymorphism in extended generic types.

Type erasure in Generic class

Consider the following generic class with a generic type parameter T.

public class GenericClass<T> {
  T obj;
  GenericClass(T obj){
    this.obj = obj;
  }
  public T getObj() {
    return obj;
  } 
}

Because the type parameter T is unbounded, the Java compiler replaces it with Object and after compilation class looks like-

public class GenericClass {
  Object obj;
  GenericClass(Object obj){
    this.obj = obj;
  }
  public Object getObj() {
    return obj;
  } 
}

Consider another generic class with a bounded type parameter.

public class GenericClass<T extends String> {
  T obj;
  GenericClass(T obj){
    this.obj = obj;
  }
  public T getObj() {
    return obj;
  } 
}

Because the type parameter T is bounded, the Java compiler replaces it with the bound class String and after compilation class looks like-

public class GenericClass {
  String obj;
  GenericClass(String obj){
    this.obj = obj;
  }
  public String getObj() {
    return obj;
  } 
}

Type erasure in Generic method

The Java compiler also erases type parameters in generic method arguments. Consider the following generic method which counts the number of occurrences of passed element in the passed array.

public static <T> int count(T[] numberArray, T elem) {
  int cnt = 0;
  for (T e : numberArray){
    if (e.equals(elem))
      ++cnt;
  }
  return cnt;
}

Because T is unbounded, the Java compiler replaces it with Object and the compiled method looks like-

public static int count(Object[] numberArray, Object elem) {
  int cnt = 0;
  for (Object e : numberArray){
    if (e.equals(elem))
      ++cnt;
  }
  return cnt;
}

Type erasure and bridge methods

Sometimes, as part of the type erasure process compiler creates a synthetic method, called a bridge method. Consider the following classes to see an example of bridge method in Java.

public class GenClass<T> {
  T obj;
  public GenClass(T obj) { 
    this.obj = obj; 
  }
  public void setObj(T obj) {
    this.obj = obj;
  }  
}

This GenClass is then extended by another class as given below-

public class MyClass extends GenClass {
  public MyClass(Integer data) { 
    super(data); 
  } 
  public void setObj(Integer data) {
    System.out.println("MyClass.setData");
    super.setObj(data);
  } 
}

The intention here is to override the parent class setObj() method in the subclass. After type erasure, the GenClass and MyClass classes become-

public class GenClass {
  Object obj;
  public GenClass(Object obj) { 
    this.obj = obj; 
  }
  public void setObj(Object obj) {
    this.obj = obj;
  }  
}
public class MyClass extends GenClass {
  public MyClass(Integer data) { 
    super(data); 
  } 
  public void setObj(Integer data) {
    System.out.println("MyClass.setData");
    super.setObj(data);
  } 
}

After type erasure, the method signatures do not match. The GenClass method becomes setObj(Object obj) and the MyClass method becomes setObj(Integer data). Therefore, the GenClass setObj method does not override the MyClass setObj method.

To solve this problem and preserve the polymorphism of generic types after type erasure, Java compiler generates a bridge method to ensure that subtyping works as expected. For the MyClass class, the compiler generates the following bridge method for setObj().

public class MyClass extends GenClass {
  public MyClass(Integer data) { 
    super(data); 
  } 
  // Bridge method generated by the compiler
  public void setObj(Object data) {
    setObj((Integer) data);
  }
  public void setObj(Integer data) {
    System.out.println("MyClass.setData");
    super.setObj(data);
  } 
}

As you can see the bridge method has the same method signature as the GenClass’ setObj() method and it delegates to the original setObj() method.

That's all for the topic Java Generics - Type Erasure. If something is missing or you have something to share about the topic please write a comment.


You may also like