Problem with rendering object prop sent to child component - javascript

The problem is: Every time I click the button, I see "Button render" in the console. But I want to see this post only once
The problem is: Every time I click the button, I see "Button render" in the console. But I want to see this post only once
import React, { useState, useCallback } from "react";
import Button from "./Button";
const UseCallback = () => {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount((prevState) => prevState + 1);
}, []);
return (
<div>
<p>{count}</p>
<Button
deneme={{ aaa: "aaa", bbb: "bbb" }}
handleClick={handleClick}
></Button>
</div>
);
};
export default UseCallback;
import React from "react";
const Button = ({ handleClick }) => {
console.log("Button - rerender");
return (
<div>
<button onClick={handleClick}>Sayacı artır</button>
</div>
);
};
export default React.memo(Button);

It is suppose to re-render since the callback is changing on render and you are passing a new object reference on each render to button. You already have React.memo but since you have a new object reference every time it's a re-rendering button.
Try wrapping object in useMemo to keep the same reference or create a variable at top and pass it in deneme prop
const UseCallback = () => {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount((prevState) => prevState + 1);
}, []);
const deneme = useMemo(() => {
return { aaa: "aaa", bbb: "bbb" };
}, []);
return (
<div>
<p>{count}</p>
<Button deneme={deneme} handleClick={handleClick}></Button>
</div>
);
};
or
const deneme = {
aaa: "aaa",
bbb: "bbb"
};
const UseCallback = () => {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount((prevState) => prevState + 1);
}, []);
return (
<div>
<p>{count}</p>
<Button handleClick={handleClick}></Button>
</div>
);
};

So only thing you are missing is to pass a compareFunction which will tell React.Memo exact condition when to rerender the component. You can see your code working here.
https://codesandbox.io/s/romantic-cherry-bv5y0?file=/src/App.js
import "./styles.css";
import React, { useState, useCallback } from "react";
const Button1 = ({ handleClick }) => {
console.log("Button - rerender");
return (
<div>
<button onClick={handleClick}>Sayacı artır</button>
</div>
);
};
const compareFunction = (prevProps, nextProps) => {
return true; //As props only have a function which we are sure doesn't change.
};
const Button = React.memo(Button1, compareFunction);
const UseCallback = () => {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount((prevState) => prevState + 1);
}, []);
return (
<div>
<p>{count}</p>
<Button
deneme={{ aaa: "aaa", bbb: "bbb" }}
handleClick={handleClick}
></Button>
</div>
);
};
export default UseCallback;

Related

Simple Notes App Not Working With Local Storage

I have a bare bones project that displays a list of notes and a button that will add a new note to that list to be displayed. My issue is that the new notes are not added/retrieved from local storage and I'm not sure what is causing it.
import React, { useEffect, useState } from 'react'
function App() {
const [notesList, setNotesList] = useState([])
useEffect(() => {
const notesJSON = localStorage.getItem('ayo')
if (notesJSON != null) setNotesList(JSON.parse(notesJSON))
console.log(notesList)
}, [])
useEffect(() => {
localStorage.setItem('ayo', JSON.stringify(notesList))
}, [notesList])
function addNote(e) {
e.preventDefault()
const newNote = {
id: Date.now() + Math.random(),
text: 'this is a new note',
status: false,
}
setNotesList([...notesList, newNote])
}
return (
<>
<h1>Hello</h1>
{notesList.map((note) => {
return <h1 key={note.id}>{note.text}</h1>
})}
<form onSubmit={addNote}>
<button type='submit'>Add Note</button>
</form>
</>
)
}
export default App;
Just remove the console log from the first useEffect where we retrieve data from localStorage and where we update notesList state if there is data previously stored in it. For logging purposes, add a button to check the data that is stored in the localStorage like below.
We can'not watch the changes of localStorage by using useEffect with deps notesList. If you try, you will always get data that one step behind the notesList state value.
import React, { useCallback, useEffect, useState } from "react";
export default function App() {
const [notesList, setNotesList] = useState([]);
useEffect(() => {
const notesJSON = localStorage.getItem("ayo");
notesJSON && setNotesList(JSON.parse(notesJSON));
}, []);
// this will always log the data that one step behind the notelist state
useEffect(() => {
const notesJSON = localStorage.getItem("ayo");
notesList && console.log(JSON.parse(notesJSON));
}, [notesList]);
useEffect(() => {
if (notesList.length > 0) {
localStorage.setItem("ayo", JSON.stringify(notesList));
}
}, [notesList]);
const addNote = useCallback(() => {
const newNote = {
id: Date.now() + Math.random(),
text: "this is a new note",
status: false,
};
setNotesList([...notesList, newNote]);
}, [notesList]);
const resetNote = () => {
localStorage.setItem("ayo", JSON.stringify([]));
setNotesList([]);
};
const logNote = () => {
console.log(localStorage.getItem("ayo"));
};
return (
<>
<h1>Hello</h1>
{notesList.map((note) => {
return (
<div key={note.id}>
<h1>{note.text}</h1>
<p>{note.id}</p>
</div>
);
})}
<button onClick={addNote}>Add Note</button>
<button onClick={resetNote}>Reset Note</button>
<button onClick={logNote}>Log Note</button>
</>
);
}
The second useEffect is unnecessary, you should set the appended list to your state and the storage in the addNote function and the first useEffect hook should be used to load your initial state and that's it.
import React, { useEffect, useState } from "react";
function App() {
const [notesList, setNotesList] = useState([]);
useEffect(() => {
const notesJSON = localStorage.getItem("ayo");
if (notesJSON != null) {
setNotesList(JSON.parse(notesJSON));
}
}, []);
function addNote(e) {
e.preventDefault();
const newNote = {
id: Date.now() + Math.random(),
text: "this is a new note",
status: false
};
const appendedList = [...notesList, newNote];
setNotesList(appendedList);
localStorage.setItem("ayo", JSON.stringify(appendedList));
}
return (
<>
<h1>Hello</h1>
{notesList.map((note) => {
return <h1 key={note.id}>{note.text}</h1>;
})}
<form onSubmit={addNote}>
<button type="submit">Add Note</button>
</form>
</>
);
}
export default App;

React memo creates rerender

I'm having an issue with react memo when using nextjs. In the _app e.g. I have a button imported:
import { ChildComponent } from './ChildComponent';
export const Button = ({ classN }: { classN?: string }) => {
const [counter, setCounter] = useState(1);
const Parent = () => {
<button onClick={() => setCounter(counter + 1)}>Click me</button>
}
return (
<div>
{counter}
<Parent />
<ChildComponent />
</div>
);
};
Child component:
import React from 'react';
export const ChildComponent = React.memo(
() => {
React.useEffect(() => {
console.log('rerender child component');
}, []);
return <p>Prevent rerender</p>;
},
() => false
);
I made one working in React couldn't figure it out in my own app:
https://codesandbox.io/s/objective-goldwasser-83vb4?file=/src/ChildComponent.js
The second argument of React.memo() must be a function that returns true if the component don't need to be rerendered and false otherwise - or in the original definition, if the old props and the new props are equal or not.
So, in your code, the solution should be just change the second argument to:
export const ChildComponent = React.memo(
() => { ... },
// this
() => true
);
Which is gonna tell React that "the props didn't change and thus don't need to rerender this component".
So my issue was that I made a function called Button and returned inside a button or Link. So I had a mouseEnter inside the button which would update the state and handle the function outside the function. Kinda embarrassing. This fixed it. So the only change was I moved usestate and handlemousehover inside the button function.
const Button = () => {
const [hover, setHover] = useState(false);
const handleMouseHover = (e: React.MouseEvent<HTMLElement>) => {
if (e.type === 'mouseenter') {
setHover(true);
} else if (e.type === 'mouseleave') setHover(false);
};
return (
<StyledPrimaryButton
onMouseEnter={(e) => handleMouseHover(e)}
onMouseLeave={(e) => handleMouseHover(e)}
>
<StyledTypography
tag="span"
size="typo-20"
>
{title}
</StyledTypography>
<ChildComponent />
</StyledPrimaryButton>
);
};

Using React useRef Hook

I have been building Select Component of my own using button components in React. I am stuck on how to close the menu when the user clicks outside the menu. The code sandbox can be found here:
I am thinking of adding an event listener when the component is mounted, which would track whether the click was made inside of the menu or not. I am expecting to solve this using useRef Hook.
Try this:
import { useEffect, useRef, useState } from "react";
import "./styles.css";
export default function App() {
const options = [
{
label: "Apple",
value: "apple"
},
{
label: "Ball",
value: "ball"
},
{
label: "Car",
value: "car"
}
];
return (
<div className="App">
<SelectComponent options={options} />
</div>
);
}
function SelectComponent(props) {
const ref = useRef(null)
const [selectedVal, setSelectedVal] = useState("");
const [isMenuOpen, setIsMenuOpen] = useState(false);
useEffect(() => {
document.addEventListener("click", (e) => {
//Insert code here.
});
}, []);
useEffect(() => {
document.addEventListener('click', handleClickOutside, false)
return () => document.removeEventListener('click', handleClickOutside, false)
})
const handleClickOutside = (e) => {
if (ref.current && !ref.current.contains(e.target)) setIsMenuOpen(false)
}
const onSelectClick = () => {
setIsMenuOpen((prevState) => !prevState);
};
const onValueSelect = (e) => {
const {
target: { id }
} = e;
setSelectedVal(id);
setIsMenuOpen((prevState) => !prevState);
};
const computeClass = isMenuOpen ? "close-icon" : "open-icon";
return (
<div {...{ref}}>
<button
className={`select-btn selected-val ${computeClass}`}
onClick={onSelectClick}
>
{selectedVal}
</button>
{isMenuOpen &&
props.options.map((item) => {
return (
<button
key={item.value}
id={item.label}
value={item.value}
className={"select-btn"}
onClick={onValueSelect}
>
{item.label}
</button>
);
})}
</div>
);
}
Simple Way to handle the useRef in the component is shown below :-
Here our target is to resize the input filed on submit button click.
import React, { useState, useRef } from "react";
const UseRef = () => {
const inputRef = useRef();
const [name, setName] = useState("");
const handleSize = ()=>{
console.log("submit");
inputRef.current.style.width="300px"
}
return (
<div>
<label>Name:</label>
<input
ref={inputRef}
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
style={{ padding: "10px" }}
/>
<br />
<button onClick={handleSize} >submit</button>
</div>
);
};
export default UseRef;
inputRef is the instance of the useRef which I have passed in the specific DOM element which we are going to manipulate on click event of submit button.
inputRef.current.style.width="300px" this is the manipulation done with the DOM element which is passed in the function handleSize()
When onClcik event is invoked from submit button size of the button will be increased to 300px .
We can also get the same result by avoiding import of useState & by eleminating onChange & value props from the input filed.

Get previous props value with React Hooks

I am using usePreviousValue custom hook to get previous props value from my component:
const usePreviousValue = value => {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
};
const MyComponent = ({ count }) => {
const prevCount = usePreviousValue(count)
return (<div> {count} | {prevCount}</div>)
}
But in this case, in prevCount I always have only the first count prop value when a component was rendered, and the next updated prop value is never assigned to it. Are there any ways to properly compare nextProp and prevProp with functional React components?
Your code sample seems to be working just fine. How exactly are you using the component? Try to run the snippet below:
const { useEffect, useRef, useState } = React;
const usePreviousValue = value => {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
};
const MyComponent = ({ count }) => {
const prevCount = usePreviousValue(count);
return (<div> {count} | {prevCount}</div>);
}
function App() {
const [count, setCount] = useState(0);
return (
<div>
<MyComponent count={count} />
<button
onClick={() => setCount((prevCount) => prevCount + 1)}
>
Count++
</button>
</div>
);
}
ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
As previously answered, the easiest way to do it is using a custom hook:
import isEqual from "lodash/isEqual";
import { useEffect, useRef } from "react";
const useComponentDidUpdate = (callback, data, checkIfIsEqual) => {
const prevData = useRef(data);
useEffect(() => {
const isTheSame = checkIfIsEqual ? isEqual(data, prevData) : undefined;
callback(prevData.current, isTheSame);
prevData.current = data;
}, [data]);
return null;
};
export default useComponentDidUpdate;
Then in your component:
const Component = ({age})=>{
const [state, setState] = useState({name: 'John', age})
useComponentDidUpdate(prevStateAndProps=>{
if(prevStateAndProps.age !== age || prevStateAndProps.state.name !== state.name){
// do something
}
}, {state, age})
...
}

How to use callback with useState hook in react [duplicate]

This question already has answers here:
How to use `setState` callback on react hooks
(22 answers)
Closed 2 years ago.
I am using functional component with hooks. I need to update state in parent from a child. I am using a prop function in Parent.
All works fine except my prop function is getting the previous state and not the current state. My prop function gets executed before useState hook setting current state.
How can I can I wait for my call back function to be executed after useState call. I am looking for something like setState(state,callback) from class based components.
Here is the code snippet:
function Parent() {
const [Name, setName] = useState("");
getChildChange = getChildChange.bind(this);
function getChildChange(value) {
setName(value);
}
return <div> {Name} :
<Child getChildChange={getChildChange} ></Child>
</div>
}
function Child(props) {
const [Name, setName] = useState("");
handleChange = handleChange.bind(this);
function handleChange(ele) {
setName(ele.target.value);
props.getChildChange(collectState());
}
function collectState() {
return Name;
}
return (<div>
<input onChange={handleChange} value={Name}></input>
</div>);
}
You can use useEffect/useLayoutEffect to achieve this:
const SomeComponent = () => {
const [count, setCount] = React.useState(0)
React.useEffect(() => {
if (count > 1) {
document.title = 'Threshold of over 1 reached.';
} else {
document.title = 'No threshold reached.';
}
}, [count]);
return (
<div>
<p>{count}</p>
<button type="button" onClick={() => setCount(count + 1)}>
Increase
</button>
</div>
);
};
If you want to prevent the callback from running on first render, adjust the previous version:
const SomeComponent = () => {
const [count, setCount] = React.useState(0)
const didMount = React.useRef(false);
React.useEffect(() => {
if (!didMount.current) {
didMount.current = true;
return;
}
if (count > 1) {
document.title = 'Threshold of over 1 reached.';
} else {
document.title = 'No threshold reached.';
}
}, [count]);
return (
<div>
<p>{count}</p>
<button type="button" onClick={() => setCount(count + 1)}>
Increase
</button>
</div>
);
};
More about it over here.
setState(updater, callback) for useState
Following implementation comes really close to the original setState callback of classes.
Improvements made to accepted answer:
Callback execution is omitted on initial render - we only want to call it on state updates
Callback can be dynamic for each setState invocation, like with classes
Usage
const App = () => {
const [state, setState] = useStateCallback(0); // same API as useState
const handleClick = () => {
setState(
prev => prev + 1,
// second argument is callback, `s` being the *updated* state
s => console.log("I am called after setState, state:", s)
);
};
return <button onClick={handleClick}>Increment</button>;
}
useStateCallback
function useStateCallback(initialState) {
const [state, setState] = useState(initialState);
const cbRef = useRef(null); // init mutable ref container for callbacks
const setStateCallback = useCallback((state, cb) => {
cbRef.current = cb; // store current, passed callback in ref
setState(state);
}, []); // keep object reference stable, exactly like `useState`
useEffect(() => {
// cb.current is `null` on initial render,
// so we only invoke callback on state *updates*
if (cbRef.current) {
cbRef.current(state);
cbRef.current = null; // reset callback after execution
}
}, [state]);
return [state, setStateCallback];
}
TypeScript version
function useStateCallback<T>(
initialState: T
): [T, (state: T, cb?: (state: T) => void) => void] {
const [state, setState] = useState(initialState);
const cbRef = useRef<((state: T) => void) | undefined>(undefined); // init mutable ref container for callbacks
const setStateCallback = useCallback((state: T, cb?: (state: T) => void) => {
cbRef.current = cb; // store current, passed callback in ref
setState(state);
}, []); // keep object reference stable, exactly like `useState`
useEffect(() => {
// cb.current is `undefined` on initial render,
// so we only invoke callback on state *updates*
if (cbRef.current) {
cbRef.current(state);
cbRef.current = undefined; // reset callback after execution
}
}, [state]);
return [state, setStateCallback];
}
Further info: React Hooks FAQ: Is there something like instance variables?
Working example
const App = () => {
const [state, setState] = useStateCallback(0);
const handleClick = () =>
setState(
prev => prev + 1,
// important: use `s`, not the stale/old closure value `state`
s => console.log("I am called after setState, state:", s)
);
return (
<div>
<p>Hello Comp. State: {state} </p>
<button onClick={handleClick}>Click me</button>
</div>
);
}
function useStateCallback(initialState) {
const [state, setState] = useState(initialState);
const cbRef = useRef(null);
const setStateCallback = useCallback((state, cb) => {
cbRef.current = cb;
setState(state);
}, []);
useEffect(() => {
if (cbRef.current) {
cbRef.current(state);
cbRef.current = null;
}
}, [state]);
return [state, setStateCallback];
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<script>var { useReducer, useEffect, useState, useRef, useCallback } = React</script>
<div id="root"></div>
With React16.x and up, if you want to invoke a callback function on state change using useState hook, you can use the useEffect hook attached to the state change.
import React, { useEffect } from "react";
useEffect(() => {
props.getChildChange(name); // using camelCase for functions is recommended.
}, [name]); // this will call getChildChange on initial render and when ever name changes.
Actually, you should avoid using this when using react hooks. It causes side effects. That's why react team create react hooks.
If you remove codes that tries to bind this, you can just simply pass setName of Parent to Child and call it in handleChange. Cleaner code!
function Parent() {
const [Name, setName] = useState("");
return <div> {Name} :
<Child setName={setName} ></Child>
</div>
}
function Child(props) {
const [Name, setName] = useState("");
function handleChange(ele) {
setName(ele.target.value);
props.setName(ele.target.value);
}
return (<div>
<input onChange={handleChange} value={Name}></input>
</div>);
}
Moreover, you don't have to create two copies of Name(one in Parent and the other one in Child). Stick to "Single Source of Truth" principle, Child doesn't have to own the state Name but receive it from Parent. Cleanerer node!
function Parent() {
const [Name, setName] = useState("");
return <div> {Name} :
<Child setName={setName} Name={Name}></Child>
</div>
}
function Child(props) {
function handleChange(ele) {
props.setName(ele.target.value);
}
return (<div>
<input onChange={handleChange} value={props.Name}></input>
</div>);
}
we can write customise function which will call the callBack function if any changes in the state
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const useStateCallbackWrapper = (initilValue, callBack) => {
const [state, setState] = useState(initilValue);
useEffect(() => callBack(state), [state]);
return [state, setState];
};
const callBack = state => {
console.log("---------------", state);
};
function App() {
const [count, setCount] = useStateCallbackWrapper(0, callBack);
return (
<div className="App">
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>+</button>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
`
Another way to achieve this:
const [Name, setName] = useState({val:"", callback: null});
React.useEffect(()=>{
console.log(Name)
const {callback} = Name;
callback && callback();
}, [Name]);
setName({val:'foo', callback: ()=>setName({val: 'then bar'})})
you can utilize useCallback hook to do this.
function Parent() {
const [name, setName] = useState("");
const getChildChange = useCallback( (updatedName) => {
setName(updatedName);
}, []);
return <div> {name} :
<Child getChildChange={getChildChange} ></Child>
</div>
}
function Child(props) {
const [name, setName] = useState("");
function handleChange(ele) {
setName(ele.target.value);
props.getChildChange(ele.target.value);
}
function collectState() {
return name;
}
return (<div>
<input onChange={handleChange} value={name}></input>
</div>);
}
function Parent() {
const [Name, setName] = useState("");
getChildChange = getChildChange.bind(this);
function getChildChange(value) {
setName(value);
}
return <div> {Name} :
<Child getChildChange={getChildChange} ></Child>
</div>
}
function Child(props) {
const [Name, setName] = useState("");
handleChange = handleChange.bind(this);
collectState = collectState.bind(this);
function handleChange(ele) {
setName(ele.target.value);
}
function collectState() {
return Name;
}
useEffect(() => {
props.getChildChange(collectState());
});
return (<div>
<input onChange={handleChange} value={Name}></input>
</div>);
}
useEffect act as componentDidMount, componentDidUpdate, so after updating state it will work

Categories

Resources