Skip to main content

Extend functionality - Detailed

It's not as complicated as it first seems

Extending the functionality in redux is the hardest part of this process, at the end there is an example of how to use Redux in your component, that part is fairly easy.

Step 1: Create a New Slice File

Create a new file for your slice, e.g., newFeatureSlice.ts, inside the src/client/src/store/ directory.

Step 2: Import Dependencies

Import the necessary modules from Redux Toolkit and any other dependencies you may need.

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

Step 3: Define Initial State

Define the initial state for your slice. This can be an object, array, or any other data type.

const initialState = {
data: null,
loading: false,
error: null,
};

Step 4: Create the Slice

Use the createSlice function to create a slice. Define your reducers within the reducers object.

const newFeatureSlice = createSlice({
name: "newFeature",
initialState,
reducers: {
fetchDataStart: (state) => {
state.loading = true;
},
fetchDataSuccess: (state, action: PayloadAction<any>) => {
state.data = action.payload;
state.loading = false;
},
fetchDataFailure: (state, action: PayloadAction<string>) => {
state.error = action.payload;
state.loading = false;
},
},
});

Step 5: Export Actions

Export the actions generated by createSlice.

export const { fetchDataStart, fetchDataSuccess, fetchDataFailure } =
newFeatureSlice.actions;

Step 6: Export Reducer

Export the reducer so it can be combined with other reducers in the store.

export default newFeatureSlice.reducer;

Step 7: Update Root Reducer

In your commonReducer.ts or equivalent, import the new reducer and add it to the root reducer.

import newFeatureReducer from "./newFeatureSlice";

const rootReducer = combineReducers({
// ...other reducers,
newFeature: newFeatureReducer,
});

Step 8: Create and Export Selectors

Create selectors to read specific pieces of state. You can place these in a selectors.ts file or within your slice file.

export const selectNewFeatureData = (state: RootState) => state.newFeature.data;

Step 9: Use Actions in Components

Now you can use the actions and selectors in your React components using useDispatch and useSelector.

const dispatch = useDispatch();
const data = useSelector(selectNewFeatureData);

dispatch(fetchDataStart());

New Redux component

Below is a demo component that can serve as a new tab in the InspectorModule. This component, named DemoTab, demonstrates how to properly handle state changes in Redux without direct state mutations.

DemoTab.tsx

import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { updateProject } from "../../../store/reducers/projectReducer"; // Import your projectReducer
import { RootState } from "../../../store"; // Import your RootState

const DemoTab: React.FC = () => {
const dispatch = useDispatch();
const projectState = useSelector((state: RootState) => state.project); // Replace with your project state selector
const [newLabel, setNewLabel] = useState("");

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setNewLabel(e.target.value);
};

const updateLabel = () => {
// Create a copy of the projectState and update the label
const updatedProjectState = {
...projectState,
label: newLabel,
};

// Dispatch the updated project state
dispatch(updateProject(updatedProjectState));
};

return (
<div>
<h2>Demo Tab</h2>
<input
type="text"
placeholder="New Label"
value={newLabel}
onChange={handleInputChange}
/>
<button onClick={updateLabel}>Update Label</button>
</div>
);
};

export default DemoTab;

Adding DemoTab to InspectorModule.tsx

To add this new DemoTab to the InspectorModule, you can update the InspectorModule.tsx file as follows:

  1. Import DemoTab at the top of your InspectorModule.tsx:
import DemoTab from "./tabs/DemoTab";
  1. Add a new case for DemoTab in the switch statement that renders the tabs:
switch (selectedTab) {
case "admin":
return <AdminTab />;
case "attribute":
return <AttributeTab />;
case "terminal":
return <TerminalTab />;
case "demo": // New case
return <DemoTab />;
default:
return null;
}
  1. Update the useState and onTabChange to include the new 'demo' tab:
const [selectedTab, setSelectedTab] = useState<
"admin" | "attribute" | "terminal" | "demo"
>("admin");

const onTabChange = (value: "admin" | "attribute" | "terminal" | "demo") => {
setSelectedTab(value);
};
  1. Add a new button or tab UI element to switch to the DemoTab.