Skip to content

Instantly share code, notes, and snippets.

@yogithesymbian
Last active December 14, 2024 07:18
Show Gist options
  • Save yogithesymbian/ca957c638c0f3fede389bb7dd9bad7c9 to your computer and use it in GitHub Desktop.
Save yogithesymbian/ca957c638c0f3fede389bb7dd9bad7c9 to your computer and use it in GitHub Desktop.

Key Takeaways Using useReducer is an excellent choice for managing complex state logic. Use useCallback and useMemo strategically to optimize performance. Avoid inline arrow functions in JSX for cleaner and more maintainable code. Regularly profile the app for potential re-renders or performance issues. Let me know if you need further clarifications! 😊

console.log Debugging:

The console.log statements inside the render function are helpful for debugging but can clutter the production code. Suggestion: Remove or replace these with debugging tools like React DevTools or controlled logging with conditions.

Button Click Handlers:

Each button click handler directly invokes the reducer or state updater, which is good.

Suggestion: To avoid inline arrow functions and improve readability, consider defining these handlers as separate functions:

const handleReducerUpdate = () => updateSearch('yogi aw');

UI Improvement:

The display of random values (Math.random()) is suitable for debugging but does not add much to the user experience. Suggestion: Replace it with meaningful state values or remove it for clarity. Performance Consideration:

Currently, there are no major performance bottlenecks, but as the application grows, excessive state updates or unnecessary re-renders might impact performance. Use tools like React Profiler to monitor. Dependency Arrays:

Ensure that all hooks like useCallback and useMemo include correct dependencies to prevent stale closures.

import React, { useState, useCallback, useMemo } from 'react';
import { render } from 'react-dom';
import React, { useReducer } from 'react';
const tableSearchReducer = (state, action) => {
switch (action.type) {
case 'SET_SEARCH':
return { ...state, search: action.payload };
case 'SET_COLUMN_SEARCH':
return { ...state, column_search: action.payload };
case 'RESET':
return { search: '', column_search: [] };
default:
throw new Error(`Unhandled action type: ${action.type}`);
}
};
const App = () => {
const [tableSearch, dispatchTableSearch] = useReducer(tableSearchReducer, {
search: '',
column_search: [],
});
const updateSearch = (value) => {
console.log('function reCreate [outer] [deep]');
dispatchTableSearch({ type: 'SET_SEARCH', payload: value });
};
const updateColumnSearch = (columns) => {
console.log('function reCreate [outer] [deep]');
dispatchTableSearch({ type: 'SET_COLUMN_SEARCH', payload: columns });
};
const resetSearch = () => {
console.log('function reCreate [outer] [deep]');
dispatchTableSearch({ type: 'RESET' });
};
const [state, changeState] = useState({});
const memoizedValue = useMemo(() => {
return Math.random();
}, []);
const memoizedCallback = useCallback(() => console.log(memoizedValue), []);
const unMemoizedCallback = () => {
const dataMemoizedValue = memoizedValue;
console.log(dataMemoizedValue);
return dataMemoizedValue;
};
const { prevMemoizedCallback, prevUnMemoizedCallback } = state;
/* if outside this re render / re run console.log('function reCreate [outer]'); thats mean all useEffect is on reDefine const [state, changeState] = useState({}); thats mean call again and again ? */
console.log('function reCreate [outer]');
return (
<>
<p>Memoized value: {memoizedValue}</p>
<p>New update {Math.random()}</p>
<p>
is prevMemoizedCallback === to memoizedCallback:{' '}
{String(prevMemoizedCallback === memoizedCallback)}
</p>
<p>
is prevUnMemoizedCallback === to unMemoizedCallback:{' '}
{String(prevUnMemoizedCallback === unMemoizedCallback)}
</p>
<p>
<button onClick={memoizedCallback}>memoizedCallback</button>
</p>
<p>
<button onClick={unMemoizedCallback}>unMemoizedCallback</button>
</p>
<p>
<button
onClick={() =>
changeState({
prevMemoizedCallback: memoizedCallback,
prevUnMemoizedCallback: unMemoizedCallback,
})
}
>
update State
</button>
</p>
<p>
<button
onClick={() => {
updateSearch('yogi aw');
}}
>
reducer
</button>
</p>
</>
);
};
render(<App />, document.getElementById('root'));

State Management (state, changeState):

The state object and changeState function seem to serve as a reference for previous callbacks. This is fine, but updating state to store functions (prevMemoizedCallback, prevUnMemoizedCallback) is not a common practice, as it can lead to unnecessary re-renders.

Suggestion: If you need to compare previous and current values, consider using a useRef instead. This avoids triggering a re-render:

const prevCallbacksRef = useRef({
  prevMemoizedCallback: null,
  prevUnMemoizedCallback: null,
});

useEffect(() => {
  prevCallbacksRef.current = {
    prevMemoizedCallback: memoizedCallback,
    prevUnMemoizedCallback: unMemoizedCallback,
  };
}, [memoizedCallback, unMemoizedCallback]);
import React, { useState, useCallback, useMemo, useReducer } from "react";
import { render } from "react-dom";
const tableSearchReducer = (state, action) => {
switch (action.type) {
case "SET_SEARCH":
return { ...state, search: action.payload };
case "SET_COLUMN_SEARCH":
return { ...state, column_search: action.payload };
case "RESET":
return { search: "", column_search: [] };
default:
throw new Error(`Unhandled action type: ${action.type}`);
}
};
const App = () => {
const [tableSearch, dispatchTableSearch] = useReducer(tableSearchReducer, {
search: "",
column_search: [],
});
const updateSearch = useCallback(
(value) => {
console.log("Updating search value...");
dispatchTableSearch({ type: "SET_SEARCH", payload: value });
},
[dispatchTableSearch]
);
const updateColumnSearch = useCallback(
(columns) => {
console.log("Updating column search...");
dispatchTableSearch({ type: "SET_COLUMN_SEARCH", payload: columns });
},
[dispatchTableSearch]
);
const resetSearch = useCallback(() => {
console.log("Resetting search...");
dispatchTableSearch({ type: "RESET" });
}, [dispatchTableSearch]);
const memoizedValue = useMemo(() => Math.random(), []);
const memoizedCallback = useCallback(() => console.log(memoizedValue), [memoizedValue]);
console.log("Rendering App component...");
return (
<div>
<p>Memoized value: {memoizedValue}</p>
<button onClick={memoizedCallback}>Log Memoized Value</button>
<button onClick={() => updateSearch("example search")}>Set Search</button>
<button onClick={resetSearch}>Reset Search</button>
</div>
);
};
render(<App />, document.getElementById("root"));

Use of useMemo and useCallback:

The memoizedCallback is properly memoized and will not change unless its dependencies change.

The unMemoizedCallback will create a new function on every render, which may not always be efficient.

Suggestion: Use useCallback for unMemoizedCallback as well to ensure consistency and prevent unnecessary function recreation:

const unMemoizedCallback = useCallback(() => {
  console.log(memoizedValue);
  return memoizedValue;
}, [memoizedValue]);

useReducer Implementation:

The tableSearchReducer is correctly implemented for managing search state with multiple actions (SET_SEARCH, SET_COLUMN_SEARCH, RESET). Suggestion: To improve type safety (especially in TypeScript), you can define action types and payloads explicitly. javascript

type Action =
  | { type: 'SET_SEARCH'; payload: string }
  | { type: 'SET_COLUMN_SEARCH'; payload: string[] }
  | { type: 'RESET' };

const tableSearchReducer = (state, action: Action) => {
  // existing logic...
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment