DEV Community

Cover image for Global Modal in react using redux toolkit
Arshan Nawaz
Arshan Nawaz

Posted on • Edited on

Global Modal in react using redux toolkit

To build a global modal using Redux Toolkit where you can dynamically render different components based on a componentName passed to the modal, we'll break the process down into the following steps:

1. Create the Components Index File: This file will import all the components and export them as an object.
2. Set Up Redux Slice: This slice will handle the state of the modal, including whether it's open and which component should be rendered.
3. Create the Modal Component: This will be the global modal component that checks which component should be rendered based on the state.
4. Dispatching Modal Actions: You'll call these actions to open the modal and specify which component to render.

Step 1: Components Index File (components/Index.jsx)
This file will import all your components and export them as an object.

// components/Index.jsx
import ComponentA from './ComponentA';
import ComponentB from './ComponentB';
// Import all other components

const components = {
  ComponentA,
  ComponentB,
  // Add all other components
};

export default components;

Enter fullscreen mode Exit fullscreen mode

Step 2: Redux Slice for Modal (features/modal/modalSlice.js)
This slice will handle opening and closing the modal, as well as tracking which component to render.

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

const initialState = {
  isOpen: false,
  componentName: null,
  componentProps: {},
};

const modalSlice = createSlice({
  name: 'modal',
  initialState,
  reducers: {
    openModal: (state, action) => {
      state.isOpen = true;
      state.componentName = action.payload.componentName;
      state.componentProps = action.payload.componentProps || {};
    },
    closeModal: (state) => {
      state.isOpen = false;
      state.componentName = null;
      state.componentProps = {};
    },
  },
});

export const { openModal, closeModal } = modalSlice.actions;

export default modalSlice.reducer;
Enter fullscreen mode Exit fullscreen mode

Step 3: Global Modal Component (components/GlobalModal.jsx)
This component will check the current state and render the appropriate component from the components index.

// src/components/Modal/GlobalModal.jsx
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { FaTimes } from 'react-icons/fa'; // Importing the cross icon from react-icons
import components from '../Index';
import { closeModal } from '../../redux/features/modal/modal.slice';

const GlobalModal = () => {
  const dispatch = useDispatch();
  const modal = useSelector(state => state.modal);
  const { isOpen, componentName, componentProps } = modal;

  if (!isOpen || !componentName) return null;

  const ComponentToRender = components[componentName];

  return (
    <div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50">
      <div className="bg-white p-6 rounded-lg relative shadow-lg">
        <button
          className="absolute top-2 right-2 text-gray-400 hover:text-gray-600"
          onClick={() => dispatch(closeModal())}
        >
          <FaTimes size={24} />
        </button>
        <ComponentToRender {...componentProps} />
      </div>
    </div>
  );
};

export default GlobalModal;

Enter fullscreen mode Exit fullscreen mode

Step 4: Using the Modal in Your App
To open the modal and render a specific component, you would dispatch the openModal action from anywhere in your app.

import { useDispatch } from 'react-redux';
import { openModal } from './features/modal/modalSlice';

const SomeComponent = () => {
  const dispatch = useDispatch();

  const handleOpenModal = () => {
    dispatch(openModal({ componentName: 'ComponentA', componentProps: { someProp: 'value' } }));
  };

  return (
    <div>
      <button onClick={handleOpenModal}>Open Modal</button>
    </div>
  );
};

export default SomeComponent;
Enter fullscreen mode Exit fullscreen mode

Integration
Finally, ensure your GlobalModal component is included in your app's root component so that it's always available.

import React from 'react';
import { Provider } from 'react-redux';
import store from './store';
import GlobalModal from './components/Modal/GlobalModal';
import SomeComponent from './SomeComponent';

const App = () => (
  <Provider store={store}>
    <SomeComponent />
    <GlobalModal />
  </Provider>
);

export default App;
Enter fullscreen mode Exit fullscreen mode

To build a global modal using Redux Toolkit where you can dynamically render different components based on a componentName passed to the modal, we'll break the process down into the following steps:

2nd method => implementation with the onSave functionality and React.memo for optimizing re-renders.

Step 1: Components Index File (components/Index.jsx)
This file will import all your components and export them as an object.

// components/Index.jsx
import React from 'react';
import ComponentA from './ComponentA';
import ComponentB from './ComponentB';
// Import all other components

const components = {
  ComponentA: React.memo(ComponentA),
  ComponentB: React.memo(ComponentB),
  // Add all other components and memoize them
};

export default components;


Enter fullscreen mode Exit fullscreen mode

Step 2: Redux Slice for Modal (features/modal/modalSlice.js)
This slice will handle opening and closing the modal and onSave, as well as tracking which component to render.
import { createSlice } from '@reduxjs/toolkit';

// features/modal/modalSlice.js
import { createSlice } from '@reduxjs/toolkit';

const initialState = {
  isOpen: false,
  componentName: null,
  componentProps: {},
  onSave: null, // Add onSave to the state
};

const modalSlice = createSlice({
  name: 'modal',
  initialState,
  reducers: {
    openModal: (state, action) => {
      if (!state.isOpen) {
        state.isOpen = true;
        state.componentName = action.payload.componentName;
        state.componentProps = action.payload.componentProps || {};
        state.onSave = action.payload.onSave || null;
      }
    },
    closeModal: (state) => {
      if (state.isOpen) {
        state.isOpen = false;
        state.componentName = null;
        state.componentProps = {};
        state.onSave = null; // Reset onSave when modal is closed
      }
    },
  },
});

export const { openModal, closeModal } = modalSlice.actions;

export default modalSlice.reducer;

Enter fullscreen mode Exit fullscreen mode

Step 3: Global Modal Component (components/GlobalModal.jsx)
This component will check the current state ,onSave and render the appropriate component from the components index.

import React, { useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { closeModal } from '../features/modal/modalSlice';
import components from './Index';

// Selector to get the modal state
const selectModalState = (state) => ({
  isOpen: state.modal.isOpen,
  componentName: state.modal.componentName,
  componentProps: state.modal.componentProps,
  onSave: state.modal.onSave,
});

const GlobalModal = () => {
  const dispatch = useDispatch();
  const { isOpen, componentName, componentProps, onSave } = useSelector(selectModalState);

  if (!isOpen || !componentName) return null;

  const ComponentToRender = components[componentName];

  const handleSave = useCallback(() => {
    if (onSave) {
      onSave(componentProps); // Pass componentProps to the onSave callback
    }
    dispatch(closeModal()); // Optionally close the modal after saving
  }, [onSave, componentProps, dispatch]);

  return (
    <div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50">
      <div className="bg-white p-4 rounded shadow-lg">
        <ComponentToRender {...componentProps} />
        <div className="mt-4 flex justify-end space-x-2">
          <button className="text-blue-500" onClick={handleSave}>
            Save
          </button>
          <button className="text-red-500" onClick={() => dispatch(closeModal())}>
            Close
          </button>
        </div>
      </div>
    </div>
  );
};

export default React.memo(GlobalModal);

Enter fullscreen mode Exit fullscreen mode

Step 4: Opening the Modal with the Save Action
When you want to open the modal and use the onSave function, you do so as follows:

import React from 'react';
import { useDispatch } from 'react-redux';
import { openModal } from './features/modal/modalSlice';

const SomeComponent = () => {
  const dispatch = useDispatch();

  const handleOpenModal = () => {
    const onSave = (props) => {
      console.log('Saved with props:', props);
      // Perform your save logic here
    };

    dispatch(openModal({
      componentName: 'ComponentA',
      componentProps: { someProp: 'value' },
      onSave: onSave,
    }));
  };

  return (
    <div>
      <button onClick={handleOpenModal}>Open Modal</button>
    </div>
  );
};

export default React.memo(SomeComponent);

Enter fullscreen mode Exit fullscreen mode

Integration
Finally, ensure your GlobalModal component is included in your app's root component so that it's always available.

import React from 'react';
import { Provider } from 'react-redux';
import store from './store';
import GlobalModal from './components/GlobalModal';
import SomeComponent from './SomeComponent';

const App = () => (
  <Provider store={store}>
    <SomeComponent />
    <GlobalModal />
  </Provider>
);

export default App;
Enter fullscreen mode Exit fullscreen mode

Explanation

  • Components Index: This file maps all your components to their names, so you can render them dynamically.
  • Modal Slice: The slice manages the state of the modal, including which component to render and its props.
  • Global Modal: This component renders the appropriate component inside a modal based on the Redux state.
  • Usage: You can open the modal by dispatching the openModal action with the desired component name and props.

Top comments (1)

Collapse
 
talha_gamingyt_1998dfdac profile image
TALHA Gaming yt

Amazing explanation brother