I have a counter app. I need to prevent re-render component. I want to execute Childcompnent only when I clicking on update, but here it is executing both time when I click count or update.
import { useCallback, useMemo, useState } from "react";
export const App = () => {
const [count, setCount] = useState(0);
const [updatecount, setUpdateCount] = useState(0);
const incCount = () => {
setCount(parseInt(count) + 1);
};
const updCount = useCallback(() => {
return setUpdateCount(parseInt(updatecount) + 1);
}, [updatecount]);
return (
<>
<button onClick={incCount}>count</button>
<button onClick={updCount}>update</button>
<Childcompnent count={count} />
<p>{updatecount}</p>
</>
);
};
export default App;
export function Childcompnent({ count }) {
console.log("pressed");
return <p>{count}</p>;
}
Wrap your Childcompnent in React.memo:
const Childcompnent = React.memo(({ count }) => {
console.log("pressed");
return <p>{count}</p>;
});
Here is the sandbox:
Related
So I am trying to store a global state using context to allow me to use the same state across different components.
The issue I am having is that when I set the global state in 1 component and try to access it in the other component to use the state. It appears to be null and I cannot figure out why?
The first component where I set the global state in will always be rendered before the component shown that seems to have an empty value for the global state.
GlobalStateProvider component:
import React from "react";
import { useState, useEffect } from "react";
import axios from "axios";
const defaultActivitiesState = [];
const globalStateContext = React.createContext(defaultActivitiesState);
const dispatchStateContext = React.createContext([]);
export const useGlobalState = () =>
[
React.useContext(globalStateContext),
React.useContext(dispatchStateContext)
];
const GlobalStateProvider = ({ children }) => {
const [state, dispatch] = React.useReducer((state, newValue) => (state, newValue),
defaultActivitiesState
);
return (
<globalStateContext.Provider value={state}>
<dispatchStateContext.Provider value={dispatch}>
{children}
</dispatchStateContext.Provider>
</globalStateContext.Provider>
);
}
export default GlobalStateProvider;
Component I set the global state in:
import react from "react";
import { useState, useEffect, useMemo } from "react";
import { MapContainer, TileLayer, Popup, Polyline } from "react-leaflet";
import axios from "axios";
import polyline from "#mapbox/polyline";
import MapComp from "./MapComp";
import { useGlobalState } from "./GlobalStateProvider";
function Map() {
// ------- global state
const [activities, setActivities] = useGlobalState(); // global state
//const [activities, setActivities] = useState([]);
//const [polylines, setPolylines] = useState(null); // as empty array value is still truthy
const [isLoading, setIsLoading] = useState(true);
const [mapMode, setMapMode] = useState("light");
const [mapStyle, setMapStyle] = useState(
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
);
const [showMarkers, setShowMarkers] = useState(true);
useEffect(() => {
setActivitieData();
console.log("activities after useEffect", activities)
}, []);
const polylines = useMemo(() => {
console.log("activities inside memo", activities)
console.log("activities.len =", activities.length);
if (activities.length) {
console.log("past len");
const polylineArray = [];
for (const item of activities) {
const polylineData = item.map.summary_polyline;
const activityName = item.name;
const activityType = item.type;
polylineArray.push({
positions: polyline.decode(polylineData),
name: activityName,
activityType: activityType,
});
}
setIsLoading(false);
return polylineArray;
}
return null;
}, [activities]);
const toggleMarkers = () => {
setShowMarkers((show) => !show);
};
const getActivityData = async () => {
console.log("calling")
const response = await axios.get(
"http://localhost:8800/api/"
);
return response.data;
};
const setActivitieData = async () => {
const activityData = await getActivityData();
setActivities(activityData);
console.log("Global activities state = ", activities);
};
return !isLoading && polylines ? (
<>
<div className="select-container">
<button className="toggle-markers" onClick={() => toggleMarkers()}>
Toggle Markers
</button>
</div>
<MapComp
className={`${mapMode}`}
activityData={{ polylines }}
showMarkers={showMarkers}
/>
</>
) : (
<div>
<p>Loading...</p>
</div>
);
}
export default Map;
component that has an empty value for global state:
import React from 'react';
import { useGlobalState } from './GlobalStateProvider';
function ActivityList() {
const [activities, setActivities] = useGlobalState();
let displayValues;
displayValues =
activities.map((activity) => {
return (
<div>
<p>{activity.name}</p>
<p>{activity.distance}m</p>
</div>
);
})
return (
<>
<p>Values</p>
{displayValues}
</>
);
}
export default ActivityList;
App.js:
function App() {
return (
<GlobalStateProvider>
<div className="App">
<NavBar />
<AllRoutes />
</div>
</GlobalStateProvider>
);
}
export default App;
Say I have a hook function that provides a useEffect
export function useTokenCheckClock(authState: AuthState, timeout: number = 60) {
const [lastCheckTime, updateLastCheckTime] =
useReducer(updatePerSecondReducer, Math.ceil(Date.now()/1000) * 1000);
useEffect(()=>{
... do something ...
return ()=> { ... do cleanup ... }
}, [authState, timeout] // <-- trying to determine if I need this
}
And a component uses this function as:
function MyComponent() {
const [authState, setAuthState] = useState(...);
useTokenCheckClock(authState);
... some handler logic or useEffect that may alter authState ...
return <>...</>
}
When the authState changes and triggers a render. Would the following occur?
the useEffect hook cleanup function in useTokenCheckClock is called
then the useEffect hook in useTokenCheckClock is called again
It needs it otherwise the effects won't trigger when the parameters change.
This is demonstrated by the following Jest file
import { cleanup, fireEvent, render } from '#testing-library/react-native';
import React, { useEffect, useState } from 'react';
import { Pressable, Text } from 'react-native';
afterEach(cleanup);
beforeEach(() => {
jest.useFakeTimers({ advanceTimers: true });
});
it("does not work with empty dep list", () => {
const useEffectCallback = jest.fn();
const useEffectCleanup = jest.fn();
const rendered = jest.fn();
function useMyHook(state: string, nonState: number) : number{
const [ myState, setMyState ] = useState(Date.now());
useEffect(()=>{
useEffectCallback();
setMyState(Date.now());
return () => useEffectCleanup();
}, [])
return myState;
}
function MyComponent() {
const [authState, setAuthState] = useState("AuthState.INITIAL");
const lastCheckTime = useMyHook(authState, 10);
rendered({authState, lastCheckTime});
return <Pressable onPress={() => { setAuthState("AuthState.AUTHENTICATED") }} ><Text testID="lastCheckTime">{lastCheckTime}</Text></Pressable>
}
jest.setSystemTime(new Date("2025-01-01T20:00:00Z"));
const {getByTestId, unmount} = render(<MyComponent />);
expect(getByTestId("lastCheckTime")).toHaveTextContent("1735761600000")
expect(useEffectCallback).toBeCalledTimes(1)
expect(useEffectCleanup).toBeCalledTimes(0)
expect(rendered).toBeCalledTimes(1)
jest.advanceTimersByTime(1000);
fireEvent.press(getByTestId("lastCheckTime"))
// remains the same
expect(getByTestId("lastCheckTime")).toHaveTextContent("1735761600000")
expect(useEffectCallback).toBeCalledTimes(1)
expect(useEffectCleanup).toBeCalledTimes(0)
// rendered still because of auth state change
expect(rendered).toBeCalledTimes(2)
unmount();
expect(useEffectCallback).toBeCalledTimes(1)
expect(useEffectCleanup).toBeCalledTimes(1)
expect(rendered).toBeCalledTimes(2)
})
it("works with dep list", () => {
const useEffectCallback = jest.fn();
const useEffectCleanup = jest.fn();
const rendered = jest.fn();
function useMyHook(state: string, nonState: number) : number{
const [ myState, setMyState ] = useState(Date.now());
useEffect(()=>{
useEffectCallback();
setMyState(Date.now());
return () => useEffectCleanup();
}, [state, nonState])
return myState;
}
function MyComponent() {
const [authState, setAuthState] = useState("AuthState.INITIAL");
const lastCheckTime = useMyHook(authState, 10);
rendered({authState, lastCheckTime});
return <Pressable onPress={() => { setAuthState("AuthState.AUTHENTICATED") }} ><Text testID="lastCheckTime">{lastCheckTime}</Text></Pressable>
}
jest.setSystemTime(new Date("2025-01-01T20:00:00Z"));
const {getByTestId, unmount} = render(<MyComponent />);
expect(getByTestId("lastCheckTime")).toHaveTextContent("1735761600000")
expect(useEffectCallback).toBeCalledTimes(1)
expect(useEffectCleanup).toBeCalledTimes(0)
expect(rendered).toBeCalledTimes(1)
jest.advanceTimersByTime(1000);
fireEvent.press(getByTestId("lastCheckTime"))
expect(getByTestId("lastCheckTime")).toHaveTextContent("1735761601000")
expect(useEffectCallback).toBeCalledTimes(2)
expect(useEffectCleanup).toBeCalledTimes(1)
// authenticated which then calls set state to trigger two renders
expect(rendered).toBeCalledTimes(3)
unmount();
expect(useEffectCallback).toBeCalledTimes(2)
expect(useEffectCleanup).toBeCalledTimes(2)
expect(rendered).toBeCalledTimes(3)
})
I'm trying to make app that will take the value of input element and use this value in inc/dec component. I have 2 components and I use props.
Component have input and a set button.
Component have inc/dec buttons and reset button
I'm enter number to first component set it with button. Then I want to inc/dec it in second component and I have NaN error. When I reset it the element take the value of first component and after that inc/dec works well.
first component:
import React, { useState } from "react";
import Sample from "../Sample/Sample";
const SetSample = () => {
const [value, setValue] = useState();
const formChange = (e) => {
e.preventDefault();
}
const handlerChange = () => {
const foo = document.querySelector("input").value * 1;
setValue(foo);
}
return (
<div>
<form onSubmit={formChange}>
<input type="text" />
<button type="submit" onClick={handlerChange}>Set start value</button>
</form>
<p>Start value is: {value || 'Please enter a number'}</p>
<hr />
<Sample start={value} />
</div>
);
}
export default SetSample;
second component:
import React, { useState } from "react";
const Sample = (props) => {
const point = props.start;
const [counter = 0, setCounter] = useState(point);
const decrement = () => {
setCounter(counter - 1);
}
const increment = () => {
setCounter(counter + 1);
}
const reset = () => {
setCounter(point);
}
return (
<div>
<button onClick={decrement}>-</button>
<div>{counter}</div>
<button onClick={increment}>+</button>
<div><button onClick={reset}>reset</button></div>
</div>
);
}
export default Sample;
It seems to me that the problem is in counter and point values.
You first have to import useEffect hook from react, then in the dependency array, you have to enter your point variable.
Nothing interacts with your Sample.jsx after you click Set Start Value.
And when you click the reset button there is some interaction.
Due to that and you are able to see the number only after clicking reset.
import React, { useState , useEffect } from "react";
const Sample = (props) => {
const point = props.start;
const [counter = 0, setCounter] = useState(point);
// edited here
useEffect(()=>{
setCounter(props.start);
},[point]);
const decrement = () => {
setCounter(counter - 1);
}
const increment = () => {
setCounter(counter + 1);
}
const reset = () => {
setCounter(point);
}
return (
<div>
<button onClick={decrement}>-</button>
<div>{counter}</div>
<button onClick={increment}>+</button>
<div><button onClick={reset}>reset</button></div>
</div>
);
}
export default Sample;
In the first render you need to set the value of the input to be 0 just not to run into NaN error in SetSample component:
const [value, setValue] = useState(0);
and in Sample component you need to set the counter if it changes using useEffect and don't set 0 for it because it already has 0 from props.start:
const point = props.start;
const [counter, setCounter] = useState(point);
useEffect(() => {
setCounter(point);
}, [point]);
I'm am working on a large React application where performance is critical and unnecessary re-renders are costly.
I have the following example:
const CounterContext = React.createContext();
const CounterProvider = ({children}) => {
const [counterA, setCounterA] = React.useState(0);
const [counterB, setCounterB] = React.useState(0);
return (
<CounterContext.Provider value={{counterA, counterB}}>
{children}
<button onClick={() => setCounterA(counterA + 1)}>Counter A ++</button>
<button onClick={() => setCounterB(counterB + 1)}>Counter B ++</button>
<button onClick={() => {setCounterA(0); setCounterB(0)}}>reset</button>
</CounterContext.Provider>
)
}
const CounterA = () => {
const value = React.useContext(CounterContext);
console.log('CounterA re-render');
return <p>Counter A: {value.counterA}</p>;
}
const CounterB = () => {
const value = React.useContext(CounterContext);
console.log('CounterB re-render');
return <p>Counter B: {value.counterB}</p>;
};
const App = () => {
return (
<CounterProvider>
<CounterA />
<CounterB />
</CounterProvider>
)
};
jsfiddle: https://jsfiddle.net/mitchkman/k3hm0vfq/1/
When clicking the CounterA button, both CounterA and CounterB components will re-render. I'd like to only re-render CounterA, if the counterA property in value changes.
I'd also like to have the ability to have some form of flexibility for conditional re-rendering. This is a pseudocode of what I am trying to do:
const MyComponent = () => {
// Only re-render MyComponent if value.property equals 42
const value = useContext(MyContext, (value) => value.property === 42);
...
};
You may have to wrap the counterProvider to your App component. And use React.memo to achieve re-rendering only on value changes for that component.
I have done something like here in this sandbox
index.js
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import { CounterProvider } from "./AppContext";
import App from "./App";
const rootElement = document.getElementById("root");
ReactDOM.render(
<StrictMode>
<CounterProvider>
<App />
</CounterProvider>
</StrictMode>,
rootElement
);
Moved the counterContext to a separate file so that you can import and wrap your App component.
AppContext.js
import React, { useCallback } from "react";
const CounterContext = React.createContext();
export const CounterProvider = ({ children }) => {
const [counterA, setCounterA] = React.useState(0);
const [counterB, setCounterB] = React.useState(0);
const incrementA = () => {
setCounterA(counterA + 1);
};
const incrementB = () => {
setCounterB(counterB + 1);
};
return (
<CounterContext.Provider value={{ counterA, counterB }}>
{children}
<button onClick={incrementA}>Counter A ++</button>
<button onClick={incrementB}>Counter B ++</button>
<button
onClick={useCallback(() => {
setCounterA(0);
setCounterB(0);
}, [])}
>
reset
</button>
</CounterContext.Provider>
);
};
export default CounterContext;
Pass the counterA and counterB values to the Counter components as a props, so that React.memo can check that value before re-rendering the component.
App.js
import React from "react";
import CounterContext from "./AppContext";
const CounterA = React.memo(({ value }) => {
console.log("CounterA re-render");
return <p>Counter A: {value}</p>;
});
const CounterB = React.memo(({ value }) => {
console.log("CounterB re-render");
return <p>Counter B: {value}</p>;
});
const App = () => {
const value = React.useContext(CounterContext);
const counterA = value.counterA;
const counterB = value.counterB;
console.log(counterA);
return (
<>
<CounterA value={counterA} />
<CounterB value={counterB} />
</>
);
};
export default App;
I am trying to call a function from a different component but when I console.log('hi') it appear but it didn't call the messageContext.
Here is my follwing code from Invitees.js:
const [showPreview, setShowPreview] = useState(false);
const toggleUserPreview = () => {
setShowPreview(!showPreview);
};
{showPreview && (
<ResultsWrappers togglePreview={toggleUserPreview}>
<UserPreview
userInfo={applicant}
skillStr={applicant.Skills}
togglePreview={toggleUserPreview}
/>
</ResultsWrappers>
)}
Here is the component have the function I want to call UserPreview.js:
import { useMessageContextProvider } from "../context/MessageContext";
const UserPreview = ({ userInfo, skillStr, togglePreview }) => {
const messageContextProvider = useMessageContextProvider();
const messageUser = () => {
togglePreview();
messageContextProvider.updateActiveUserToMessage(userInfo);
console.log('hi');
};
...
};
Here is my messageContext:
import { createContext, useContext, useState } from "react";
const messageContext = createContext();
export const MessageContextProvider = ({ children }) => {
const [activeUserToMessage, setActiveUserToMessage] = useState({});
const [isOpenMobileChat, toggleMobileChat] = useState(false);
const updateActiveUserToMessage = (user) => {
setActiveUserToMessage(user);
};
return (
<messageContext.Provider
value={{
updateActiveUserToMessage,
activeUserToMessage,
isOpenMobileChat,
toggleMobileChat,
}}
>
{children}
</messageContext.Provider>
);
};
export const useMessageContextProvider = () => {
return useContext(messageContext);
};
When the messageContext called it should open the chatbox like this:
The code you showing is not enough to say it for 100%, but it seems like toggleUserPreview - function called twice, so it reverted to original boolean value.
One time as <ResultsWrappers togglePreview={toggleUserPreview}/>
and second time as <UserPreview togglePreview={toggleUserPreview}/>.