useCallback and memoization - reactjs

How the memorized callback function works? In some articles I read that the function is recreated if we do not use useCallback. But if it is recreated, should it be different from the prev version? In my code I didn't notice that there was a difference in callback functions.
My question is: Why in both cases, my set size is 1?
from off doc useCallback
Returns a memoized callback.
Pass an inline callback and an array of dependencies. useCallback will
return a memoized version of the callback that only changes if one of
the dependencies has changed. This is useful when passing callbacks to
optimized child components that rely on reference equality to prevent
unnecessary renders (e.g. shouldComponentUpdate).
import { useCallback } from "react";
const dataSource = [
{
id: 1,
model: "Honda",
color: "red",
},
{
id: 2,
model: "Mazda",
color: "yellow",
},
{
id: 3,
model: "Toyota",
color: "green",
},
];
const Car = ({ model, color, set, onCarClick }) => {
const onClick = () => onCarClick(model, color);
set.add(onCarClick);
console.log(set.size);
return (
<div onClick={onClick}>
Model: {model} Color: {color}
</div>
);
};
const CarsCallback = ({ cars, set }) => {
const onCarClick = (model, color) => {
console.log(model, color);
};
console.log("CarsCallback");
return (
<>
{cars.map((car) => {
return (
<Car
key={car.id}
set={set}
{...car}
onCarClick={onCarClick}
/>
);
})}
</>
);
};
const CarsUseCallback = ({ cars, set }) => {
const onCarClick = useCallback((model, color) => {
console.log(model, color);
}, []);
console.log("CarsUseCallback");
return (
<>
{cars.map((car) => {
return (
<Car
key={car.id}
{...car}
set={set}
onCarClick={onCarClick}
/>
);
})}
</>
);
};
export default function App() {
return (
<div className="App">
<CarsCallback cars={dataSource} set={new Set()} />
<CarsUseCallback cars={dataSource} set={new Set()} />
</div>
);
}

Because CarsUseCallback and CarsCallback was triggered once.
We can see the only one log of CarsUseCallback and CarsCallback.
If we re-render the CarsUseCallback and CarsCallback, we can see the size is 1 and 2.
const CarsCallback = ({ cars, set }) => {
const [count, setCount] = useState(1);
console.log('CarsCallback');
useEffect(() => {
setCount(2);
}, []);
// ...
};
const CarsUseCallback = ({ cars, set }) => {
const [count, setCount] = useState(1);
console.log('CarsUseCallback');
useEffect(() => {
setCount(2);
}, []);
// ...
}

Related

Cannot pass selected date from MUI DateTimePicker into SWR hook

I have a component that holds a MUI DataGrid.
Now, for any row of the DataGrid I need to render a DateTimePicker column. There is data coming
in with a SWR call which data could contain a datetime for the column or not.
mainPage/sdk.js
export const useSomeData = (
params: TUseFetchOptions['params']
) => {
const { data, isLoading, mutate } = useFetch<TPagination<TSomeData>>('/some-data/');
const approve = useCallback(
(someData: TSomeData) => {
const data = { published_at: someData.published_at }
return requestHandler(
post(`/some-data/update/`, data)
)
.then((someData) => {
mutate((prev) => {
if (!prev) {
return prev;
}
// some irrelevant mutation for swr caching happens here
return prev;
}
return prev;
}, false);
})
.catch((error) =>
// some irrelevant alerting happens here
);
},
[mutate]
);
return useMemo(
() => ({ someData: data, isLoading, approve,mutate }),
[data, isLoading, mutate, approve]
);
};
mainPage/index.tsx
import {useSomeData} from './sdk'
const SomeDataPublish = () => {
// const {params} = ....
// const dataGridProps = ...
const { someData, isLoading, approve } = useSomeData(params);
return (
<Stack>
{someData && (
<SomeDataDataGrid
someData={someData}
params={params}
DataGridProps={dataGridProps}
handleApprove={approve}
/>
)}
</Stack>
);
};
export default SomeDataPublish;
mainPage/componenets/someDataDataGrid.tsx
export const columns: GridColumns = [
{
// some field
},
{
// some field
},
{
// some field
},
// ...
];
const buildColumnsData = (
handleApprove: ReturnType<typeof useSomeData>['approve'],
): GridColumns => {
return [
...columns,
{
field: 'published_at',
headerName: 'Publish at',
flex: 0.75,
renderCell: (params: any) => <RowDatePicker params={params} />
},
{
field: '',
type: 'actions',
flex: 0.4,
getActions: (params: any) => [
<RowActions
params={params}
handleApprove={handleApprove}
/>
]
}
];
};
const buildRows = (someData: TSomeData[]): GridRowsProp => {
return someData.map((row) => ({
id: row.id,
// ...
published_at: _.get(row, 'published_at'),
}));
};
const SomeDataDataGrid: FC<{
someData: TPagination<TSomeData>;
params: TUseFetchOptions['params'];
DataGridProps: Partial<MuiDataGridProps>;
handleApprove: ReturnType<typeof useSomeData>['approve'];
}> = ({ someData, params, DataGridProps, handleApprove }) => {
return (
<Paper>
<DataGrid
// ...
columns={buildColumnsData(handleApprove)}
rows={someData ? buildRows(someData.results) : []}
// ...
{...DataGridProps}
/>
</Paper>
);
};
export default SomeDataDataGrid;
mainPage/componenets/rowDatePicker.tsx
const RowDatePicker: React.FC<{
params: GridRowParams;
}> = ({ params }) => {
const [publishedAt, setPublishedAt] = React.useState(params.row.published_at);
return (
<>
<DateTimeField
label={'Pick Date'}
value={publishedAt}
onChange={setPublishedAt}
/>
</>
);
};
export default RowDatePicker;
mainPage/componenets/rowAction.tsx
const RowActions: React.FC<{
params: GridRowParams;
handleApprove: ReturnType<typeof useSomeData>['approve'];
}> = ({ params, handleApprove }) => {
return (
<>
<Tooltip title="Approve">
<IconButton
color="success"
disabled={false}
onClick={(e) => {
console.log(params.row)}
handleApprove(params.row)
>
<AppIcons.CheckCircle />
</IconButton>
</Tooltip>
</>
);
};
export default RowActions;
The problem that I have - if I change the date from the date picker, on clicking the <AppIcons.CheckCircle /> in the RowActions component I expect the row.published_at to be updated with the new value. Then I pass the new updated object (with the updated published_at attribute) to the handleApprove hook so I can make some mutations and pass the updated object (with new published_at value) to the back end.
However, on examining the someData object that is passed to the approve hook the published_at field has its old value ( the one that came from the SWR fetcher).
I know that I need to mutate somehow params.row.published_at = publishedAt in the onChange callback of the RowDatePicker.DateTimePicker, but I am not sure how to do it. Any help would be appreciated.

Context variable not updating in child component React Context

I have multiple child components (component generate through recursion) inside my main component. Now the problem is when I updated the context variable in the parent component the child component doesn't render the updated value
here is my context provider
function MainLayoutProvider({ children }) {
const [mainJson, setMainJson] = useState([
{
component:'section',
id:'1111',
content:null,
type:'section',
cmType:'normal',
class:'',
style:{},
props:{}
}
]);
return (
<MainLayout.Provider value={mainJson}>
<MainDispatchLayout.Provider value={setMainJson}>
{children}
</MainDispatchLayout.Provider>
</MainLayout.Provider>
);
}
Here I included it on my main component
function App() {
return (
<DndProvider backend={HTML5Backend}>
<MainLayoutProvider>
<PlayGround></PlayGround>
</MainLayoutProvider>
</DndProvider>
);
}
inside the PlayGround component, there is another component name DropSection, where I am updating the 'mainJson' value
function DropSection() {
const board = useContext(MainLayout);
const setBoard = useContext(MainDispatchLayout);
const [{ isOver }, drop] = useDrop(() => ({
accept: "image",
drop(item, monitor) {
const didDrop = monitor.didDrop();
if (!didDrop ) {
addItemToBoard(item.sectionName)
}
},
collect: (monitor) => ({
isOver: !!monitor.isOver(),
}),
}));
const addItemToBoard = (sectionName) => {
let newJson = {
component:sectionName,
id:IdGenerator(),
content:null,
type:'text',
cmType:'normal',
class:'',
style:{},
props:{}
}
setBoard((board) => [...board, newJson]);
};
return (
<div ref={drop}>
<h4 className="text-center">DropZone</h4>
{board.map((config,index) => <RenderCard key={config.id} config={config} />)}
</div>
);
}
but in the RenderCard component, the value of 'mainJson' is not updating or rendering, I am getting the old value which initializes in MainLayoutContext
function RenderCard({config}) {
const board =useContext(MainLayout);
const setBoard = useContext(MainDispatchLayout);
const [{ isOver }, drop] = useDrop(() => ({
accept: "image",
drop(item, monitor) {
const didDrop = monitor.didDrop();
if (!didDrop ) {
addItemToBoard(item.sectionName)
}
},
collect: (monitor) => ({
isOver: !!monitor.isOver(),
}),
}));
const addItemToBoard = async (sectionName) => {
let newJson = {
component:sectionName,
id:IdGenerator(),
content:null,
type:'text',
cmType:'normal',
class:'',
style:{},
props:{}
}
setBoard((board) => [...board, newJson]);
};
if(config.cmType == 'complex'){
return RenderComplexCard(config)
}
var configProperty = {
style: config.style,
id:config.id,
};
return React.createElement(
config.component,
configProperty,
config.content &&
( config.type == "section" && Array.isArray(config.content) ? config.content.map(c => <RenderCard key={c.id} config={c} />) : config.content )
);
}
I had similar issue and solved by using useRef hook, if you do not want to use useState.
Do not forget to reference the variable by using .current when accessing it.

Show slider controlling all content once when returning map items in React

I have some CMS content being returned and my goal is to have a year slider controlling the content depending on the year that the user selects by clicking the minus/plus arrow.
This is my code:
import "./styles.css";
import React from "react";
export default function App() {
return (
<div className="App">
<DatesProvider>
{data.map((item, index) => {
const Slice = slices[item.type];
return <Slice section={item.section} key={index} />;
})}
</DatesProvider>
</div>
);
}
const DateContext = React.createContext({});
const DatesProvider = ({ children }) => {
const [dates, setDates] = React.useState({});
return (
<DateContext.Provider value={{ dates, setDates }}>
{children}
</DateContext.Provider>
);
};
const DatePicker = ({ section }) => {
const { dates, setDates } = React.useContext(DateContext);
React.useEffect(() => {
// Set initial date
setDates((prevDates) => {
prevDates[section] = 2021;
return { ...prevDates };
});
// Clean up on dismount
return () => {
setDates((prevDates) => {
delete prevDates[section];
return { ...prevDates };
});
};
}, []);
const handlePlus = () => {
setDates((prevDates) => ({
...prevDates,
[section]: prevDates[section] + 1
}));
};
const handleMinus = () => {
setDates((prevDates) => ({
...prevDates,
[section]: prevDates[section] - 1
}));
};
return (
<div style={{ marginTop: 30 }}>
<button onClick={handleMinus}>-</button>
<span>{dates[section]}</span>
<button onClick={handlePlus}>+</button>
</div>
);
};
const Item = ({ section }) => {
const { dates } = React.useContext(DateContext);
return (
<div>
Section: {section} | Year: {dates[section]}
</div>
);
};
const data = [
{ type: "DatePicker", section: "foo" },
{ type: "Item", section: "foo" },
{ type: "Item", section: "foo" },
{ type: "DatePicker", section: "bar" },
{ type: "Item", section: "bar" },
{ type: "Item", section: "bar" }
];
const slices = { DatePicker, Item };
The result is currently this:
As you can tell it's returning the year slider several times and the structure is similar to this:
<slider> - 2021 + </slider>
<section class= "container-of-all-items">
<all-items></all-items>
</section>
<slider> - 2021 + </slider>
<section class= "container-of-all-items">
<all-items></all-items>
</section>
My goal is to have only one year slider wrapping/controlling the whole content items rather than the above repetition of sliders:
<slider> - 2021 + </slider>
<section class= "container-of-all-items">
<all-items></all-items>
</section>
Any idea how to achieve it by maintaining a map through the Slices?
I see, took me a while to understand, you basically want to have one set of + and - but list of items.
Then in your case, you code actually simplifies.
function Lists() {
const { dates, setDates } = React.useContext(DateContext);
const onClick = () => { setDates(...) }
return (
<>
<div onClick={onClick}>+</div>
<>
{dates.map((item, index) => {
return <Slice section={item.section} key={index} />
})}
</>
<div>-</div>
</div>
);
}
Then change your App.
export default function App() {
return (
<div className="App">
<DatesProvider value={...}>
<Lists />
</DatesProvider>
</div>
);
}
Actually you might not need the context at all, since the logic has been promoted to the parent. But it's up to you.

Best way to use useMemo/React.memo for an array of objects to prevent rerender after edit one of it?

I'm struggling with s performance issue with my React application.
For example, I have a list of cards which you can add a like like facebook.
Everything, all list is rerendering once one of the child is updated so here I'm trying to make use of useMemo or React.memo.
I thought I could use React.memo for card component but didn't work out.
Not sure if I'm missing some important part..
Parent.js
const Parent = () => {
const postLike= usePostLike()
const listData = useRecoilValue(getCardList)
// listData looks like this ->
//[{ id:1, name: "Rose", avararImg: "url", image: "url", bodyText: "text", liked: false, likedNum: 1, ...etc },
// { id:2, name: "Helena", avararImg: "url", image: "url", bodyText: "text", liked: false, likedNum: 1, ...etc },
// { id: 3, name: "Gutsy", avararImg: "url", image: "url", bodyText: "text", liked: false, likedNum: 1, ...etc }]
const memoizedListData = useMemo(() => {
return listData.map(data => {
return data
})
}, [listData])
return (
<Wrapper>
{memoizedListData.map(data => {
return (
<Child
key={data.id}
data={data}
postLike={postLike}
/>
)
})}
</Wrapper>
)
}
export default Parent
usePostLike.js
export const usePressLike = () => {
const toggleIsSending = useSetRecoilState(isSendingLike)
const setList = useSetRecoilState(getCardList)
const asyncCurrentData = useRecoilCallback(
({ snapshot }) =>
async () => {
const data = await snapshot.getPromise(getCardList)
return data
}
)
const pressLike = useCallback(
async (id) => {
toggleIsSending(true)
const currentList = await asyncCurrentData()
...some api calls but ignore now
const response = await fetch(url, {
...blabla
})
if (currentList.length !== 0) {
const newList = currentList.map(list => {
if (id === list.id) {
return {
...list,
liked: true,
likedNum: list.likedNum + 1,
}
}
return list
})
setList(newList)
}
toggleIsSending(false)
}
},
[setList, sendYell]
)
return pressLike
}
Child.js
const Child = ({
postLike,
data
}) => {
const { id, name, avatarImg, image, bodyText, likedNum, liked } = data;
const onClickPostLike = useCallback(() => {
postLike(id)
})
return (
<Card>
// This is Material UI component
<CardHeader
avatar={<StyledAvatar src={avatarImg} />}
title={name}
subheader={<SomeImage />}
/>
<Image drc={image} />
<div>{bodyText}</div>
<LikeButton
onClickPostLike={onClickPostLike}
liked={liked}
likedNum={likedNum}
/>
</Card>
)
}
export default Child
LikeButton.js
const LikeButton = ({ onClickPostLike, like, likedNum }) => {
const isSending = useRecoilValue(isSendingLike)
return (
<Button
onClick={() => {
if (isSending) return;
onClickPostLike()
}}
>
{liked ? <ColoredLikeIcon /> : <UnColoredLikeIcon />}
<span> {likedNum} </span>
</Button>
)
}
export default LikeButton
The main question here is, what is the best way to use Memos when one of the lists is updated. Memorizing the whole list or each child list in the Parent component, or use React.memo in a child component...(But imagine other things could change too if a user edits them. e.g.text, image...)
Always I see the Parent component is highlighted with React dev tool.
use React.memo in a child component
You can do this and provide a custom comparator function:
const Child = React.memo(
({
postLike,
data
}) => {...},
(prevProps, nextProps) => prevProps.data.liked === nextProps.data.liked
);
Your current use of useMemo doesn't do anything. You can use useMemo as a performance optimization when your component has other state updates and you need to compute an expensive value. Say you have a collapsible panel that displays a list:
const [expand, setExpand] = useState(true);
const serverData = useData();
const transformedData = useMemo(() =>
transformData(serverData),
[serverData]);
return (...);
useMemo makes it so you don't re-transform the serverData every time the user expands/collapses the panel.
Note, this is sort of a contrived example if you are doing the fetching yourself in an effect, but it does apply for some common libraries like React Apollo.

How to add a function in const Target = props => {

How do I add a function to connect to one of my components onChange? Creating a function like this returns an error code of 'cardActionResponse' is not defined.
What the benefit of using a const class like this?
const Target = props => {
const { markAsDone } = useContext(ItemContext);
const [{ isOver }, drop] = useDrop({
accept: 'Item',
drop: (item, monitor) => console.log(item),
collect: monitor => ({
isOver: !!monitor.isOver()
})
})
//Cannot do this. How else can I make a function to connect to CreateVideoCard?
cardActionResponse = (event) => {
console.log(event);
}
return (
<div className="target top80 right30" ref={drop} style={{ backgroundColor: isOver ? 'black' : '' }} >
<TitleDescription class="z1"/>
<div class="right10 left10">
<CreateVideoCard onChange={this.cardActionResponse} />
<CreateDescriptionCard></CreateDescriptionCard>
<CreateAudioCard></CreateAudioCard>
<CreateTermsCard></CreateTermsCard>
</div>
</div>
);
};
export default Target;
Functional components don't have it's own context (this), so you should simply use const variable.
Please use
const cardActionResponse = (event) => {
console.log(event);
}
and then
<CreateVideoCard onChange={cardActionResponse} />

Resources