I tried to increment the count whenever i click the button. When click the button it is getting rendered twice. But it should be render only once.
Here is my code
https://codesandbox.io/s/async-pine-3z2ty3?file=/src/App.js
import { useCallback, useMemo, useState } from "react";
import Button from "./Button";
export default function App() {
const [count, setCount] = useState(0);
const [count1, setCount1] = useState(0);
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
const MyButton1 = useMemo(
() => <Button handleClick={handleClick} title="Increment Count" />,
[handleClick]
);
const MyButton2 = useMemo(
() => (
<Button handleClick={() => setCount1(count1 + 1)} title="Click here" />
),
[count1]
);
return (
<div className="App">
<div>count : {count}</div>
{MyButton1}
<div>count1 : {count1}</div>
{MyButton2}
</div>
);
}
import React from "react";
const Button = React.memo(({ handleClick, title }) => {
console.log(title);
return <button onClick={handleClick}>{title}</button>;
});
export default Button;
Problem
Your handleClick function changes count
If count changes new handleClick is created
If handleClick changes you create new <Button>
Solution
Remove redundant useMemos
Pass a function to setCount
Remove dependency from useCallback
export default function App() {
const [count, setCount] = useState(0);
const [count1, setCount1] = useState(0);
const handleClick = useCallback(() => {
setCount((count) => count + 1);
}, []);
return (
<div className="App">
<div>count : {count}</div>
<Button handleClick={handleClick} title="Increment Count" />
</div>
);
}
Now your component will be rendered once at the beginning and never again
If you want to have two buttons, you have to have two callbacks
export default function App() {
const [count, setCount] = useState(0);
const [count1, setCount1] = useState(0);
const handleClick = useCallback(() => {
setCount((count) => count + 1);
}, []);
const handleClick1 = useCallback(() => {
setCount1((count) => count + 1);
}, []);
return (
<div className="App">
<div>count : {count}</div>
<Button handleClick={handleClick} title="Increment Count" />
<div>count : {count}</div>
<Button handleClick={handleClick1} title="Click here" />
</div>
);
}
sandbox
Remove <StrictMode></StrictMode> from your index.js file
Related
I am trying to create a debounce search and initially when the field is empty the component renders after the provided setTimeout delay. But if I continue to search with the existing keyword it re-renders the List component on each key stroke. How to avoid that?
import { useEffect, useState } from 'react';
import useDebounce from './hooks/useDebounce';
import List from './components/List';
const App: React.FC = () => {
const [todo, setTodo] = useState<string>("");
const [query, setQuery] = useState<string | null>("");
let deBounceSearch = useDebounce(query, 2000);
useEffect(() => {
if (deBounceSearch) {
console.log('Searching...');
} else {
console.log('...');
}
}, [deBounceSearch]);
return (
<div className="App">
<input type="text" placeholder='Search anything' onChange={(e) => setQuery(e.target.value)} />
{deBounceSearch !== '' && (
<List />
)}
</div>
);
}
useDebounce.tsx
const useDebounce = (value: any, delay: number) => {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => { setDebouncedValue(value) }, delay);
return () => {
clearTimeout(handler);
}
}, [value, delay]);
return debouncedValue;
}
export default useDebounce;
You can use useMemo to avoid re-render the List component every time query value changes:
const App: React.FC = () => {
const [todo, setTodo] = useState<string>("");
const [query, setQuery] = useState<string | null>("");
const deBounceSearch = useDebounce(query, 2000);
// ->
const cachedList = React.useMemo(() => <List />, [debouncedValue]);
...
return (
<div className="App">
<input type="text" placeholder='Search anything' onChange={(e) => setQuery(e.target.value)} />
{deBounceSearch !== '' && cachedList}
</div>
);
}
You also can take a look at React.memo
hello man this is not the best to use debounce i suggest u try lodash debounce with useMemo.
but for now the solution for your code is that you forgot to clear the timeout on every time the value change.
here the solution:
import { useEffect, useState, useRef } from "react";
import "./styles.css";
const useDebounce = (value, delay) => {
const [debouncedValue, setDebouncedValue] = useState(value);
const handler = useRef();
useEffect(() => {
if (handler.current) {
clearTimeout(handler.current);
}
handler.current = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler.current);
};
}, [value, delay]);
return debouncedValue;
};
export default function App() {
const [value, setValue] = useState("");
const debounceSearch = useDebounce(value, 2000);
console.log(debounceSearch);
return (
<div className="App">
<input value={value} onChange={(e) => setValue(e.target.value)} />
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
hope this help .
I need to get a suma of the total amount of customers, the customer amount is entered through a button onClick and in total I have three shops all as different components, that are then called, how do I do it?
Component Shop
render(props) {
//##viewOn:private
//const [isOn, setIsOn] = useState(props.isOn);
const [count, setCount] = useState(0);
//##viewOff:private
//##viewOn:interface
//##viewOff:interface
//##viewOn:render
return (
<>
<div>
<h1>Kaufland</h1>
<p>Customers {count}</p>
<Uu5Elements.Button onClick={() => setCount((current) => current + 1)< 10}>+1</Uu5Elements.Button>
</div>
</>
//##viewOff:render
)},
});
//##viewOn:exports
export { Shop };
export default Shop;
//##viewOff:exports
and then I have pretty much copy-pastes of the component 2 more times for 2 more shops, fe.:
Component Shop2
render(props) {
//##viewOn:private
const [count, setCount] = useState(0);
//##viewOff:private
//##viewOn:interface
//##viewOff:interface
//##viewOn:render
return (
<>
<div>
<h1>Lidl</h1>
<p>Customers {count}</p>
<Uu5Elements.Button onClick={() => setCount((current) => current + 1)< 10}>+1</Uu5Elements.Button>
</div>
</>
//##viewOff:render
)},
});
//##viewOn:exports
export { Shop2 };
export default Shop2;
//##viewOff:exports
And then I cant piece together how to get the amount of the Shop and add it to the amount of the Shop2...
return (
<>
<RouteBar />
<div className={Css.mainContainer()}>
<div>{currentTime.toLocaleString("cs")}</div>
<h1>Total Customers</h1>
<Total sum={Shop + Shop2 />
<Shop />
<Shop2 />
</div>
</>
);
You need to define the state in the common parent component of the two shop components (Lifting State Up) so that data can be shared through props. I created an example for your reference:
Parent.js
import { useState } from "react";
import Shop1 from "./Shop1";
import Shop2 from "./Shop2";
const Parent = () => {
const [count, setCount] = useState(0);
return (
<>
<h1>Sum: {count}</h1>
<Shop1 setCount={setCount} />
<Shop2 setCount={setCount} />
</>
);
};
export default Parent;
Shop1.js
const Shop1 = ({ setCount }) => {
const add = () => {
setCount((prevCount) => prevCount + 1);
};
return <button onClick={add}>Click</button>;
};
export default Shop1;
Shop2.js
const Shop2 = ({ setCount }) => {
const add = () => {
setCount((prevCount) => prevCount + 1);
};
return <button onClick={add}>Click</button>;
};
export default Shop2;
App.js
import "./styles.css";
import Parent from "./Parent";
export default function App() {
return (
<div className="App">
<Parent />
</div>
);
}
View the online example here.
I'm trying to build edit form fields. Here, you have to press a button before you can edit the form field. However, I'm having problems toggling the disabled prop and setting the focus of the element. This is some sample code. The input will only focus after I've clicked the button twice.
export default function App() {
const [isDisabled, setIsDisabled] = useState(true);
const inputEl = useRef(null);
const onBlur = () => {
setIsDisabled(true);
};
const handleEditClick = () => {
setIsDisabled(false);
inputEl.current.focus();
};
return (
<div className="App">
<h1>Focus problem</h1>
<h2>Focus only happens on the scond click!</h2>
<input ref={inputEl} onBlur={onBlur} disabled={isDisabled} />
<button onClick={() => handleEditClick()}>Can edit</button>
</div>
);
}
Here is a code-sandbox
Setting focus on a DOM element is a side-effect, it should be done within useEffect:
import React, { useState, useRef, useEffect } from "react";
import "./styles.css";
export default function App() {
const [isDisabled, setIsDisabled] = useState(true);
const inputEl = useRef(null);
useEffect(() => {
inputEl.current.focus();
}, [isDisabled]);
const onBlur = () => {
setIsDisabled(true);
};
const handleEditClick = () => {
setIsDisabled(false);
};
return (
<div className="App">
<h1>Focus problem</h1>
<h2>Focus only happens on the scond click!</h2>
<input ref={inputEl} onBlur={onBlur} disabled={isDisabled} />
<button onClick={() => handleEditClick()}>Can edit</button>
</div>
);
}
This is my custom component. Basically what is does is it shows modal on failure or success of the api calls.
export const useMessageModal = () => {
const [IsModalVisible, setIsModalVisible] = useState(false);
const [Message, setMessage] = useState(null);
return [
() =>
IsModalVisible ? (
<CModal
isVisible={IsModalVisible}
modalMsg={Message}
onPressModal={() => setIsModalVisible(false)} //hideModal
/>
) : null,
() => setIsModalVisible(true), //showModal
msg => setMessage(msg),
];
};
In one of the components, I want to navigate to another page or call some context action on the modal button for that I want to pass some functions to this custom hook.
Does anyone have any idea?
according to Shubham Verma answer, I've updated my code
import React, { useState, useEffect } from "react";
import "./styles.css";
const CModal = ({ onPressModal }) => {
return (
<div
onClick={() => {
console.log("TEst");
onPressModal();
}}
>
Click me to check function call(open console)
</div>
);
};
export const useMessageModal = (customFuntion) => {
const [IsModalVisible, setIsModalVisible] = useState(false);
const [Message, setMessage] = useState(null);
return [
() =>
IsModalVisible ? (
<CModal
isVisible={IsModalVisible}
modalMsg={Message}
onPressModal={customFuntion} //hideModal
/>
) : null,
() => setIsModalVisible(true), //showModal
(msg) => setMessage(msg)
];
};
export default function App() {
const [test2, setTest2] = useState(false);
const [test, check] = useMessageModal(
() => (test2 ? console.log("gjghjgj") : console.log("hello")),
[test2]
);
useEffect(() => {
check();
}, []);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<button onClick={() => [console.log(test2), setTest2(!test2)]}>
test
</button>
{test()}
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
You can simply pass function as a arguement ins useMessageModal. Here is the poc:
import React, { useState, useEffect } from "react";
import "./styles.css";
const CModal = ({ onPressModal }) => {
return (
<div
onClick={() => {
console.log("TEst");
onPressModal();
}}
>
Click me to check function call(open console)
</div>
);
};
export const useMessageModal = (customFuntion) => {
const [IsModalVisible, setIsModalVisible] = useState(false);
const [Message, setMessage] = useState(null);
return [
() =>
IsModalVisible ? (
<CModal
isVisible={IsModalVisible}
modalMsg={Message}
onPressModal={customFuntion} //hideModal
/>
) : null,
() => setIsModalVisible(true), //showModal
(msg) => setMessage(msg)
];
};
export default function App() {
const [test, check, anotherCheck] = useMessageModal(() =>
console.log("hello")
);
useEffect(() => {
check();
}, []);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
{test()}
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
Here is the demo: https://codesandbox.io/s/custom-hook-function-37kc8?file=/src/App.js
Please consider the following code:
import React from "react";
import "./styles.css";
const Component = ({ title }) => {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
console.log("Mounted");
}, []);
return (
<div>
<h2>{title}</h2>
<p>{count}</p>
<button onClick={() => setCount(c => c + 1)}>count up</button>
</div>
);
};
export default function App() {
const [index, setIndex] = React.useState(0);
const changeComponent = () => {
setIndex(c => (c === 1 ? 0 : 1));
};
const components = [
{
render: () => <Component title="one" />
},
{
render: () => <Component title="two" />
}
];
return (
<>
<button onClick={changeComponent}>toggle component</button>
{components[index].render()}
</>
);
}
https://codesandbox.io/s/mystifying-hermann-si7cn
When you click toggle component, title changes, but component is not unmounted, you can see it because count is not reset.
How to make it so that new component is mounted on toggle component click?
React needs a way to differentiate one component instance from the other. This will fix it
const components = [
{
render: () => <Component key={1} title="one" />
},
{
render: () => <Component key={2} title="two" />
}
];
Its the same reason react requires dynamically rendered lists to have a key prop. It informs react of which component to update.