useRef() does not redraw the value - reactjs

I have a useRef hook and two components. In one component, I increase the value on click by 1 unit, and in the second component, I draw the value. I pass the value itself through useContext.
Now the problem is that the value is not being redrawn. How can this be fixed?
export const ContactContext = React.createContext();
function App() {
const countItem = useRef(1);
const value = { countItem };
return (
<ContactContext.Provider value={value}>
<div>
<AddValue />
</div>
<div>
<Logo />
</div>
</ContactContext.Provider>
);
}
const AddValue = () => {
const { countItem } = useContext(ContactContext);
const addItemHandler = () => {
countItem.current = countItem.current + 1;
};
return (
<>
<div>
<button
onClick={addItemHandler}
>
<img src="plus.svg" alt="plus logo" />
</button>
</div>
</>
);
};
function Logo() {
const { countItem } = useContext(ContactContext);
return (
<p data-testid="statistics">
{`Count of channels: ${countItem.current}`} <br />
</p>
);
}

useRef wont cause components in React to rerender
function App() {
const [countItem, setCountItem] = useState(1)
const value = { countItem, setCountItem };
In AddValue
const AddValue = () => {
const { countItem, setCountItem } = useContext(ContactContext);
const addItemHandler = () => {
setCountItem(c => c +1)
};
Reading the new React docs for state management will help
Hope it helps

Replace useRef with useState.
useRef update the value but does not rerender.

Related

React - How to prevent parent re-render on prop change

I am making a calculator using react.
Every time I press a number button, the whole application re-renders, instead of the <Display />.
To prevent it, I tried 2 different approaches for App, But neither of them worked.
Here is the sandbox link.
Any help would be appreciated.
Put clickHandler inside of useCallback()
const App = () => {
const [screen, setScreen] = useState("0");
console.log("render");
const clickHandler = useCallback(
(val) => {
if (val === "AC") {
setScreen("");
return;
}
screen === "0" ? setScreen(val) : setScreen(screen + val);
},
[screen]
);
return (
<div className="App">
<div className="display">{screen}</div>
<ButtonList clickHandler={clickHandler} />
</div>
);
};
Put Display component inside of React.memo
const App = () => {
const [screen, setScreen] = useState("0");
console.log("render");
const clickHandler = (val) => {
if (val === "AC") {
setScreen("");
return;
}
screen === "0" ? setScreen(val) : setScreen(screen + val);
};
const displayComponent = () => {
return (
<>
<div className="display">{screen}</div>
<ButtonList clickHandler={clickHandler} />
</>
);
};
const MemoizedComponent = React.memo(displayComponent);
return (
<div className="App">
<MemoizedComponent />
</div>
);
};
And here's the ButtonList & Button component.
export const ButtonList = ({ clickHandler }) => {
const arr = [...Array.from(Array(10).keys()).reverse(), "AC"];
return (
<div className="buttons">
<div className="numbersWrapper">
{arr.map((item) => (
<Button
key={item}
clickHandler={clickHandler}
value={item.toString()}
/>
))}
</div>
</div>
);
};
export const Button = ({ value, clickHandler }) => {
return (
<button
name={value}
onClick={() => {
clickHandler(value); //where the clickEvent happens
}}
>
{value}
</button>
);
};
If you don't want a component re-render,You would have to define the click handler in another component that you would like to re-render.
So do it like this:
const App = () => {
console.log("render");
return (
<div className="App">
<childComponent />
</div>
);
};
export const childComponent = () => {
const [screen, setScreen] = useState("0");
const clickHandler = (val) => {
if (val === "AC") {
setScreen("");
return;
}
screen === "0" ? setScreen(val) : setScreen(screen + val);
};
return (
<>
<div className="display">{screen}</div>
<ButtonList clickHandler={clickHandler} />
</>
);
}
This way you prevent a particular component from re-rendering. But note that if you update a state or do anything from which causes re-renders from the parent component, It would equally re-render the child component.

how to handle onClick event with multiple component with the same class name in React?

I'm new to react.js and I want to apply the toggle feature at 'place-box' by using 'isOpen' state and my intention is it only works when I click single place-box div so I added onClick event at 'place-box' div. but all of the elements are toggled at the same time.
I guess it's because they all have the same class name.
how can I fix this?
import React, { useState, useEffect } from "react";
import { useQuery } from "#apollo/client";
import { FETCH_CITIES_QUERY } from "../../server/Data/RentQueries";
import PlaceResult from "../Rent/PlaceResult";
const CityResult = (props) => {
const [placeId, setPlaceId] = useState();
const [isOpen, setIsOpen] = useState(false);
const { loading, error, data } = useQuery(FETCH_CITIES_QUERY, {
variables: { cityName: cityName },
});
const showPlaceInfo = (placeId, e) => {
e.preventDefault();
setPlaceId(placeId);
setIsOpen((isOpen) => !isOpen);
};
return (
<div>
{data &&
data.cities.map((city) => {
return (
<div className="city-box">
{city.places.map((place) => {
return (
// this is place-box div and I added onClick event here
<div
className="place-box"
key={place.id}
onClick={(e) => {
e.stopPropagation();
showPlaceInfo(place.id, e);
}}
>
<li className="place-name">{place.name}</li>
{isOpen && (
<PlaceResult className="place-indiv" placeId={placeId} />
)}
{!isOpen && (
<div className="place-info-box">
<li>{place.address}</li>
{conditionCheck(city.condition)}
<li>{place.phone}</li>
</div>
)}
</div>
);
})}
</div>
);
})}
</div>
);
};
export default CityResult;
Your variable isOpen is used for all cities. If you change isOpen to true all place-boxes are opened. You should store the id of the currently opened city inside a variable and compare against it to check if the current city in the for loop should be opened.
import React, { useState, useEffect } from "react";
import { useQuery } from "#apollo/client";
import { FETCH_CITIES_QUERY } from "../../server/Data/RentQueries";
import PlaceResult from "../Rent/PlaceResult";
const CityResult = (props) => {
const [placeId, setPlaceId] = useState();
const [openedPlaceId, setOpenedPlaceId] = useState(undefined);
const { loading, error, data } = useQuery(FETCH_CITIES_QUERY, {
variables: { cityName: cityName },
});
const showPlaceInfo = (placeId, e) => {
e.preventDefault();
setPlaceId(placeId);
setOpenedPlaceId(placeId);
};
return (
<div>
{data &&
data.cities.map((city) => {
return (
<div className="city-box">
{city.places.map((place) => {
return (
// this is place-box div and I added onClick event here
<div
className="place-box"
key={place.id}
onClick={(e) => {
e.stopPropagation();
showPlaceInfo(place.id, e);
}}
>
<li className="place-name">{place.name}</li>
{openedPlaceId === place.id && (
<PlaceResult className="place-indiv" placeId={placeId} />
)}
{!(openedPlaceId === place.id) && (
<div className="place-info-box">
<li>{place.address}</li>
{conditionCheck(city.condition)}
<li>{place.phone}</li>
</div>
)}
</div>
);
})}
</div>
);
})}
</div>
);
};
export default CityResult;
This way only the clicked place will be opened.

Call function inside component from different component

I'm using react and I have 2 components. Inside Comp1 I have a function that I want to call inside Comp2. How could I do this?
Code:
const Comp1 = () => {
// Call handleSetSearch in Comp2
const handleSetSearch = () => {
const value = document.querySelector('.ais-SearchBox-input').value;
console.log(value)
};
return (
<>
../
</>
);
}
const Comp2 = (handleSetSearch) => {
return (
<div>
<p onClick={ComparisonSearch}></p>
</div>
)
};
In react, using props to communicate with components is very common. For example, if the relation between Comp 1 and Comp 2 is parent to child, you can acquire the function of handleSetSearch by using props.
Const Comp1 = () => {
const handleSetSearch = () => {
const value = document.querySelector('.ais-SearchBox-input').value;
console.log(value)
};
return (
<>
<Comp2 handleSetSearch={handleSetSearch} />
</>
)
}
Const Comp2 = (props) => {
return (
<div>
<p onClick={props.handleSetSearch}></p>
</div>
)
}
If you want to add parameter in the function of handleSetSearch. Here is a example.
const handleSetSearch = (params) => {
console.log(params)
};
<p onClick={() => props.handleSetSearch('123')}></p>
You should use Comp2 as a child in Comp1 and pass Comp1 function into Comp2 as props.
const Comp1 = () => {
const handleSetSearch = () => {};
return (
<>
<Comp2 handleSetSearch={handleSetSearch} />
</>
);
}
const Comp2 = ({handleSetSearch}) => {
return (
<div>
<p onClick={() => handleSetSearch()}></p>
</div>
)
};

React.memo isn't working - what am I missing?

I'm in the process of refactoring some of our components so I'm trying to incorporate memoization as some components may re-render with the same values (for example, hotlinked image URLs unless they are the same).
I have a simple component:
const CardHeader = props => {
// img is a stringand showAvatar is a boolean but it's always true
const { ..., showAvatar, img } = props;
return (
<CardHeader>
<ListItem>
// AvatarImage shouldn't re-render if img is the same as previous
{showAvatar && <AvatarImage img={img} />
</ListItem>
</CardHeader>
);
}
And then the AvatarImage:
const AvatarImage = React.memo(props => {
console.log("why is this still re-rendering when the img value hasn't changed?");
const { img } = props;
return (
<ListItemAvatar>
{img ?
<Avatar src={img} />
:
<Avatar>
Some initials
</Avatar>
}
</ListItemAvatar>
);
});
I have also tried passing in second argument of memo:
(prevProps, nextProps) => {
return true; // Don't re-render!
}
But the console.log still shows every time. I'm obviously missing something here or don't quite understand how this works. This component is a few levels down, but it passes in the img if it's available every time so I'd expect it to know that if the img was passed in the previous render and it's the same it knows not to re-render it again but for some reason it does?
Thanks all. It's much appreciated.
Well it is either showAvatar is not always true or CardHeader ListItem component magically decides whether show children or not
Example
const { useState, useEffect, memo, createContext, useContext } = React;
const getAvatars = () => Promise.resolve([
{
src: 'https://i.picsum.photos/id/614/50/50.jpg'
},
{
src: 'https://i.picsum.photos/id/613/50/50.jpg'
}
])
const Avatar = ({src}) => {
console.log('avatar render');
return <img src={src} alt="avatar"/>
}
const MemoAvatarToggle = memo(({src}) => {
console.log('memo avatar with \'expression &&\' render');
return <div>
{src ? <img src={src} alt="avatar"/> : <div>Test </div>}
</div>
})
const CardHeader = ({children}) => {
const luck = Boolean(Math.floor(Math.random() * 1.7));
return <div>
{luck && children}
</div>
}
const ListItem = ({children}) => {
return <div>
{children}
</div>
}
const ShowAvatarContext = createContext()
const App = (props) => {
const [avatars, setAvatars] = useState([]);
const [toggle, setToggle] = useState(false);
const [showAvatar, setShowAvatar] = useContext(ShowAvatarContext);
useEffect(() => {
let isUnmounted = false;
let handle = null;
setTimeout(() => {
if(isUnmounted) {
return;
}
setShowAvatar(true);
}, 500);
getAvatars()
.then(avatars => {
if(isUnmounted) {
return;
}
setAvatars(avatars)
})
const toggle = () => {
setToggle(prev => !prev);
handle = setTimeout(toggle, 1000);
//setShowAvatar(prev => !prev);
}
handle = setTimeout(toggle, 1000);
return () => {
isUnmounted = true;
clearTimeout(handle);
}
}, []);
return <div>
<CardHeader>
<ListItem>
{showAvatar && avatars.map((avatar, index) => <MemoAvatarToggle key={index} src={avatar.src}/>)}
</ListItem>
</CardHeader>
{toggle ? 1 : 0}
</div>
}
const ShowAvatarProvider = ({children}) => {
const state = useState(false);
return <ShowAvatarContext.Provider value={state}>
{children}
</ShowAvatarContext.Provider>
}
ReactDOM.render(
<ShowAvatarProvider>
<App/>
</ShowAvatarProvider>,
document.getElementById('root')
);
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<div id="root"></div>
Do you have StrictMode enabled? That will cause a component memoized with React.memo to render twice.
More information:
https://reactjs.org/docs/strict-mode.html
My React Component is rendering twice because of Strict Mode
memo will not block re-render if the component is actually referenced the changing props or functions.
In your scenario your AvatarImage referenced img, in this case if parent's state's img is changed, then your component will be re-rendered.
Alternatively, if your parent is just changed other props instead of img, then the AvatarImage will NOT be re-rendered.
Alternatively, if any props but you didn't add memo to AvatarImage, then AvatarImage will be re-rendered for each of parent's state updated.
You need to memorized img props too.
const CardHeader = props => {
const { showAvatar, img } = props;
const updatedIMG = React.useMemo(() => img, []);
return (
<CardHeader>
<ListItem>
{showAvatar && <AvatarImage img={updatedIMG} />
</ListItem>
</CardHeader>
);
}
Above one would work

React Hook : Send data from child to parent component

I'm looking for the easiest solution to pass data from a child component to his parent.
I've heard about using Context, pass trough properties or update props, but I don't know which one is the best solution.
I'm building an admin interface, with a PageComponent that contains a ChildComponent with a table where I can select multiple line. I want to send to my parent PageComponent the number of line I've selected in my ChildComponent.
Something like that :
PageComponent :
<div className="App">
<EnhancedTable />
<h2>count 0</h2>
(count should be updated from child)
</div>
ChildComponent :
const EnhancedTable = () => {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Click me {count}
</button>
)
};
I'm sure it's a pretty simple thing to do, I don't want to use redux for that.
A common technique for these situations is to lift the state up to the first common ancestor of all the components that needs to use the state (i.e. the PageComponent in this case) and pass down the state and state-altering functions to the child components as props.
Example
const { useState } = React;
function PageComponent() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1)
}
return (
<div className="App">
<ChildComponent onClick={increment} count={count} />
<h2>count {count}</h2>
(count should be updated from child)
</div>
);
}
const ChildComponent = ({ onClick, count }) => {
return (
<button onClick={onClick}>
Click me {count}
</button>
)
};
ReactDOM.render(<PageComponent />, document.getElementById("root"));
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
You can create a method in your parent component, pass it to child component and call it from props every time child's state changes, keeping the state in child component.
const EnhancedTable = ({ parentCallback }) => {
const [count, setCount] = useState(0);
return (
<button onClick={() => {
const newValue = count + 1;
setCount(newValue);
parentCallback(newValue);
}}>
Click me {count}
</button>
)
};
class PageComponent extends React.Component {
callback = (count) => {
// do something with value in parent component, like save to state
}
render() {
return (
<div className="App">
<EnhancedTable parentCallback={this.callback} />
<h2>count 0</h2>
(count should be updated from child)
</div>
)
}
}
To make things super simple you can actually share state setters to children and now they have the access to set the state of its parent.
example:
Assume there are 4 components as below,
function App() {
return (
<div className="App">
<GrandParent />
</div>
);
}
const GrandParent = () => {
const [name, setName] = useState("i'm Grand Parent");
return (
<>
<div>{name}</div>
<Parent setName={setName} />
</>
);
};
const Parent = params => {
return (
<>
<button onClick={() => params.setName("i'm from Parent")}>
from Parent
</button>
<Child setName={params.setName} />
</>
);
};
const Child = params => {
return (
<>
<button onClick={() => params.setName("i'm from Child")}>
from Child
</button>
</>
);
};
so grandparent component has the actual state and by sharing the setter method (setName) to parent and child, they get the access to change the state of the grandparent.
you can find the working code in below sandbox,
https://codesandbox.io/embed/async-fire-kl197
IF we Have Parent Class Component and Child function component this is how we going to access child component useStates hooks value :--
class parent extends Component() {
constructor(props){
super(props)
this.ChildComponentRef = React.createRef()
}
render(){
console.log(' check child stateValue: ',
this.ChildComponentRef.current.info);
return (<> <ChildComponent ref={this.ChildComponentRef} /> </>)
}
}
Child Component we would create using
React.forwardRef((props, ref) => (<></>))
. and
useImperativeHandle(ref, createHandle, [deps])
to customizes the instance value that is exposed to parent components
const childComponent = React.forwardRef((props, ref) => {
const [info, setInfo] = useState("")
useEffect(() => {
axios.get("someUrl").then((data)=>setInfo(data))
})
useImperativeHandle(ref, () => {
return {
info: info
}
})
return (<> <h2> Child Component <h2> </>)
})
I had to do this in type script. The object-oriented aspect would need the dev to add this callback method as a field in the interface after inheriting from parent and the type of this prop would be Function. I found this cool!
Here's an another example of how we can pass state directly to the parent.
I modified a component example from react-select library which is a CreatableSelect component. The component was originally developed as class based component, I turned it into a functional component and changed state manipulation algorithm.
import React, {KeyboardEventHandler} from 'react';
import CreatableSelect from 'react-select/creatable';
import { ActionMeta, OnChangeValue } from 'react-select';
const MultiSelectTextInput = (props) => {
const components = {
DropdownIndicator: null,
};
interface Option {
readonly label: string;
readonly value: string;
}
const createOption = (label: string) => ({
label,
value: label,
});
const handleChange = (value: OnChangeValue<Option, true>, actionMeta: ActionMeta<Option>) => {
console.group('Value Changed');
console.log(value);
console.log(`action: ${actionMeta.action}`);
console.groupEnd();
props.setValue(value);
};
const handleInputChange = (inputValue: string) => {
props.setInputValue(inputValue);
};
const handleKeyDown: KeyboardEventHandler<HTMLDivElement> = (event) => {
if (!props.inputValue) return;
switch (event.key) {
case 'Enter':
case 'Tab':
console.group('Value Added');
console.log(props.value);
console.groupEnd();
props.setInputValue('');
props.setValue([...props.value, createOption(props.inputValue)])
event.preventDefault();
}
};
return (
<CreatableSelect
id={props.id}
instanceId={props.id}
className="w-100"
components={components}
inputValue={props.inputValue}
isClearable
isMulti
menuIsOpen={false}
onChange={handleChange}
onInputChange={handleInputChange}
onKeyDown={handleKeyDown}
placeholder="Type something and press enter..."
value={props.value}
/>
);
};
export default MultiSelectTextInput;
I call it from the pages of my next js project like this
import MultiSelectTextInput from "../components/Form/MultiSelect/MultiSelectTextInput";
const NcciLite = () => {
const [value, setValue] = useState<any>([]);
const [inputValue, setInputValue] = useState<any>('');
return (
<React.Fragment>
....
<div className="d-inline-flex col-md-9">
<MultiSelectTextInput
id="codes"
value={value}
setValue={setValue}
inputValue={inputValue}
setInputValue={setInputValue}
/>
</div>
...
</React.Fragment>
);
};
As seen, the component modifies the page's (parent page's) state in which it is called.
I've had to deal with a similar issue, and found another approach, using an object to reference the states between different functions, and in the same file.
import React, { useState } from "react";
let myState = {};
const GrandParent = () => {
const [name, setName] = useState("i'm Grand Parent");
myState.name=name;
myState.setName=setName;
return (
<>
<div>{name}</div>
<Parent />
</>
);
};
export default GrandParent;
const Parent = () => {
return (
<>
<button onClick={() => myState.setName("i'm from Parent")}>
from Parent
</button>
<Child />
</>
);
};
const Child = () => {
return (
<>
<button onClick={() => myState.setName("i'm from Child")}>
from Child
</button>
</>
);
};

Resources