How to avoid re-renders at the App level when I make a change to a child component (like a searchbar)? - javascript

I'm trying to make a searchbar React component that doesn't trigger an App-wide re-render when I type, yet allows me to use the query in other components/to make an API call.
Background:
I learned that stateless input components are good for reusability and creating controlled components. So state stays at parent (or App) level and the component's value gets passed in via props.
On the other hand, tracking the query's state at the App level causes ALL components to re-render (when the input's handleChange calls setQuery) and feels like a needless use of resources.
What am I missing here? Do I leave the query piece of state at the SearchBar level instead? Should I use React.memo or useCallback?
SearchBar component:
import React from 'react';
const Searchbar = ({ query, handleQueryChange }) => {
return (
<div className="field">
<label>Enter search term</label>
<input type="text" onChange={handleQueryChange} value={query}></input>
</div>
);
};
export default Searchbar;
And the App component
const App = () => {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const handleQueryChange = (e) => {
setQuery(e.currentTarget.value);
};
useEffect(() => {
function search() {
...makeAPIcallwith(query).then((result) => {setResults(result)})
};
if (query) {
const timer = setTimeout(() => {
search()}, 1000);
return () => clearTimeout(timer);
}
}, [query]);
return (
<div className="content-container">
<SearchBar query={query} handleQueryChange={handleQueryChange} />
<...Other React component not needing to re-render... />
</div>
);
};
export default App;

The tiniest optimization that you could make here is this:
const handleQueryChange = useCallback((e) => {
setQuery(e.currentTarget.value);
},[]);
It's not worth making. What you've shown is good idomatic react code.
I guess the other thing that you could do, if you haven't already because you haven't shown the code, is to help React out by encapsulating the results in a component like this:
return (
<div className="content-container">
<SearchBar query={query} handleQueryChange={handleQueryChange} />
<ListOfThings results={results}/>
</div>
);
Super tiny components, so tiny they seem almost trivially simple, is the name of the game in React. If your components are over 30-40 lines long, then 👎

Related

Is good to use React.useMemo or React.useCallback inside component props?

I was thinking about how to code TailwindCSS cleaner in React. Since Tailwind is utility-first, it makes us inevitably end up with components (ex: className="w-full bg-red-500"). So, I tried to create a utility like this:
utils/tailwind.ts
const tw = (...classes: string[]) => classes.join(' ')
and call it inside:
components/Example.tsx
import { useState } from 'react'
import tw from '../utils/tailwind'
const Example = () => {
const [text, setText] = useState('')
return (
<div>
<input onChange={(e: any) => setText(e.target.value)} />
<div
className={tw(
'w-full',
'h-full',
'bg-red-500'
)}
>
hello
</div>
</div>
)
}
But, it will cause tw() to be re-called as always as text state is updated.
So, I decided to wrap tw() function using useMemo to prevent re-call since the tw() always returns the same value. But the code is like this:
import { useState, useMemo } from 'react'
import tw from '../utils/tailwind'
const Example = () => {
const [text, setText] = useState('')
return (
<div>
<input onChange={(e: any) => setText(e.target.value)} />
<div
className={useMemo(() => tw(
'w-full',
'h-full',
'bg-red-500'
), [])}
>
hello
</div>
</div>
)
}
Is it correct or good practice if I put useMemo like that? Thank you 🙏 .
Is it correct or good practice if I put useMemo like that?
Short answer - yes.
Long answer - it depends. It depends on how heavy the operation is. In your particular case, joining a couple of strings may not be such heavy calculation to make the useMemo worth to be used - it's good to remember that useMemo memoizes stuff and it takes memory.
Consider example below. In the first case, without useMemo, the tw function will be called with every App re-render, to calculate new className. However, if useMemo is used (with empty dependency array), tw will not be called and new className will not be calculated even if the App re-renders, due to the basic memoization. It will be called only once, on component mount.
Conclusion - it's a good practice to use useMemo, but rather for heavy operations, like mapping or reducing huge arrays.
export default function App() {
const [_, s] = useState(0);
return (
<div className="App">
<div className={tw(false, 'w-full', 'h-full', 'bg-red-500')}>div1</div>
<div
className={useMemo(
() => tw(true, 'w-full', 'h-full', 'bg-red-500'),
[],
)}
>
div2
</div>
<button onClick={() => s(Math.random())}>re-render</button>
</div>
);
}
Playground: https://codesandbox.io/s/distracted-liskov-tfm72c?file=/src/App.tsx
The issue here is that React will re-render the component every time it's state changes. (each time you setText).
If you want to prevent that from happening, then see if you really need this re-render hence what do you really need the input text for?
you do not HAVE to use state here to use the input value.
you could call another function on change which will not update the state, and use the input value there for whatever you need.
for example:
const Example = () => {
const onInputChange = (e) => {
const text = e.target.value
// do something with text
}
return (
<div>
<input onChange={(e: any) => onInputChange(e)} />
<div
className={useMemo(() => tw(
'w-full',
'h-full',
'bg-red-500'
), [])}
>
hello
</div>
</div>
)
}

How do I create an HOC that would forward refs and not have infinite rerenders?

Related to
How can I avoid unnecessary re rendering of an HOC component, when the parameter component renders in ReactJS with react router
HOC inside react hook, causes infinite rerenders (solves initial problem, but does not handle a specific case with the forwardRef)
ref from React.forwardRef is null but for HOC specifically and functional components
Given an app that's implemented as follows which triggers a state change every second:
export default function App() {
const [username, setUsername] = useState('');
const [now, setNow] = useState(format(Date.now(), 'PPPPpppp'));
useEffect(() => {
const c = setInterval(
() => setNow(format(Date.now(), 'PPPPpppp')),
1000
);
return () => clearInterval(c);
}, []);
return (
<View style={styles.container}>
<MyView>
<TextInput
placeholder="Username"
defaultValue={username}
onChangeText={setUsername}
/>
</MyView>
<Text>{now}</Text>
</View>
);
}
Given a simple
function MyView(props) {
return createElement(View, props);
}
The text input field does not lose focus when clicked
Now if I wrap it in an HOC with a useCallback to prevents infinite rerenders causing focus lost:
function withNothing(WrappedComponent) {
return useCallback((props) => <WrappedComponent {...props} />, [])
}
function MyView(props) {
return createElement(withNothing(View), props);
}
Now the last bit was to allow forwarding refs, but this is the part I couldn't get working.
My attempts so far https://snack.expo.dev/#trajano/hoc-and-rerender

Ways to prevent heavy computation on every render in React.js

I have an app made with React. I have an array of games, which I need to display in certain way. The way I'm processing those games in my view is rather heavy, so I decided to prevent calculating it on every render cycle.
I'm new to React, so I'm not sure what is the "React way" to do it. I recently found a useMemo() hook, which seems to do what I need. Here's my simplified code:
import React, { useState, useMemo } from 'react';
const GamesPage = () => {
const [games, setGames] = useState([]);
const [gameTitle, setGameTitle] = useState('');
const gamesGrouped = useMemo(() => games.reduce(
(games, game) => {
// heavy computation
},
[]
), [games]);
return (
<div>
<input
type="text"
value={gameTitle}
onChange={event => setGameTitle(event.target.value)}
/>
<button onClick={() => setGames(
[...games, {title: gameTitle}]
)}>Add game</button>
<ul>
{gamesGrouped.map(game => (
<li key={game.title}>{game.title}</li>
))}
</ul>
</div>
);
};
export default GamesPage;
Am I doing it right? Is there some potential trap behind my idea, like not computing when needed or computing when not necessary? Or is there some more straightforward way, without using useMemo()?
Bonus question: how to achieve this goal in class components?

React / Functional component / Conditional render on callback / Not Working

Why this does not work ?
import React from 'react';
function Room() {
let check = null;
const ibegyouwork = () => {
check = <button>New button</button>;
}
return (
<div>
<button onClick={ibegyouwork}>Display my button now !!!!</button>
{check}
</div>
);
}
export default Room;
And this works fine ?
import React from 'react';
function Room() {
let check = null;
return (
<div>
<button>No need for this button because in this case the second button is auto-displayed</button>
{check}
</div>
);
}
export default Room;
Basically I try to render a component based on a condition. This is a very basic example. But what I have is very similar. If you wonder why I need to update the check variable inside that function is because in my example I have a callback function there where I receive an ID which I need to use in that new component.
The example that I provided to you is basically a button and I want to show another one when I press on this one.
I am new to React and despite I searched in the past 2 hours for a solution I couldn't find anything to address this issue.
Any tips are highly appreciated !
Your component has no idea that something has changed when you click the button. You will need to use state in order to inform React that a rerender is required:
import React, {useState} from 'react'
function Room() {
const [check, setCheck] = useState(null);
const ibegyouwork = () => {
setCheck(<button>New button</button>);
}
return (
<div>
<button onClick={ibegyouwork}>Display my button now !!!!</button>
{check}
</div>
);
}
export default Room;
When you call setCheck, React basically decides that a rerender is required, and updates the view.
The latter is working because there are no changes to the check value that should appear on the DOM.
If check changes should impact and trigger the React render function, you would want to use a state for show/hide condition.
import React from 'react';
const Check = () => <button>New button</button>;
function Room() {
const [show, setShow] = React.useState(false);
const ibegyouwork = () => {
setShow(true);
}
return (
<div>
<button onClick={ibegyouwork}>Display my button now !!!!</button>
{show && <Check />}
</div>
);
}
export default Room;

Trigger usePosition() custom hook onClick element

I'm facing some troubles when I try to trigger usePosition hook in a onClick event.
What I want to achieve is to delay the geolocation permission prompt triggered by the browser until the user clicks some element.
What I've tried so far is a bunch of variations of the following code, but without success:
const IndexPage = () => {
const [geolocation, setGeolocation] = useState({});
const [isGeolocationActive, triggerGeolocation] = useState(false);
let currentPosition = usePosition();
function handleGeolocation() {
if (isGeolocationActive) {
setGeolocation(currentPosition)
console.log('geolocation', geolocation)
} else triggerGeolocation(true);
}
return (
<Layout>
<Wrapper type={`list`} classNames={`wrapper pet__cards cards__list`}>
<Card>
<Image/>
<div onClick={() => handleGeolocation()}>Click me to share my location</div>
I've tried to set a useState hook (initially set to false) which should control and handle the usePosition hook if is set to true but the browser still asking for geolocation permission as soon as I land on the page.
Edit: I've also tried to put the usePosition hook in another component and call it onClick event. However, in this case, I face some hooks rules error such as:
"Invalid hook call. Hooks can only be called inside of the body of a
function component. This could happen for one of the following
reasons..."
Finally I've solved the issue using usePosition hook in another component, something like this:
import React from "react";
import {usePosition} from "use-position";
const Geolocation = () => {
let currentPosition = usePosition();
return (
<div>
<p>{currentPosition.latitude}</p>
</div>
)
};
export default Geolocation;
While in my main component I've used a useState hook which controls the rendering like this:
const IndexPage = () => {
const [geolocation, setGeolocation] = useState('');
function handleGeolocation() {
setGeolocation(<Geolocation/>);
}
return (
<Layout>
<Card>
<Image/>
<div onClick={() => handleGeolocation()}>Click me to share my location</div>
{geolocation}
...

Categories

Resources