I have simple React component: How I can improve this component by adding useCallback hook?
Is it even a good idea to add useCallback hook in this component? If yes, to which function should I add it.
import styled from "styled-components"
import React, { useState } from "react"
import { useGetFilteredBooks } from "../../hooks/useGetFilteredBooks"
import { useRecoilState } from "recoil"
import { Books, PageNumber, SearchText } from "../../recoil/globalState"
export const SearchBook = () => {
const [text, setText] = useRecoilState(SearchText)
const [pageNumber, setPageNumber] = useRecoilState(PageNumber)
const [setBooks] = useRecoilState(Books);
const { refetch } = useGetFilteredBooks(text, setBooks, setPageNumber);
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setText(event.target.value)
}
const handleOnSubmit = (e: any) => {
e.preventDefault();
refetch();
}
return (
<>
<form onSubmit={handleOnSubmit}>
<Input value={text} onChange={handleOnChange} placeholder="Enter the name of the book or author" />
<button type="submit">Show</button>
</form>
</>
)
}
const Input = styled.input`
padding: 10px 10px;
width: 300px;
`
It is a good idea to memoize a function in useCallback if you know that you will be going to use those functions as a dependency in other hooks like useEffect or useMemo, in your case, your functions are pretty trivial and you are not using them as deps to other hooks, the computation cost of recreating them on each re-render is negligible hence the usage of useCallback, specifically, here is not needed. Just be aware that if you put other non memoized functions in the deps array of a useCallback, that will be worth nothing. If you still plan to wrap those functions in useCallback to use them in other components / hooks:
import styled from "styled-components"
import React, { useState, useCallback } from "react"
import { useGetFilteredBooks } from "../../hooks/useGetFilteredBooks"
import { useRecoilState } from "recoil"
import { Books, PageNumber, SearchText } from "../../recoil/globalState"
export const SearchBook = () => {
const [text, setText] = useRecoilState(SearchText)
const [pageNumber, setPageNumber] = useRecoilState(PageNumber)
const [setBooks] = useRecoilState(Books);
const { refetch } = useGetFilteredBooks(text, setBooks, setPageNumber);
const handleOnChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
setText(event.target.value)
},[]) // No deps needed here according to ESLint, since react setState<s> do not change during re-renders.
const handleOnSubmit = useCallback((e: any) => {
e.preventDefault();
refetch();
},[refetch]) // refetch is needed here, since it might change during re-renders, you should make sure to memoize that function as well
return (
<>
<form onSubmit={handleOnSubmit}>
<Input value={text} onChange={handleOnChange} placeholder="Enter the name of the book or author" />
<button type="submit">Show</button>
</form>
</>
)
}
const Input = styled.input`
padding: 10px 10px;
width: 300px;
`
What does useCallback do?
Per the docs:
useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed.
So, memoized callbacks are essentially functions that survive rerender cycles.
Why would we need memoized callbacks?
Per the docs:
This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders (e.g. shouldComponentUpdate).
Combining memoized callbacks with components that rely on reference equality, useEffect is an example in functional components, avoids rerendering these child components unnecessarily.
Are you using components that rely on reference equality?
No. To make your application just "work", at the moment you don't need useCallback.
Should you still apply useCallback for performance?
One might think, useCallback might enable better performance since functions aren't recreated. This has been discussed in an article by Kent C. Doods or in this reddit. The bottom line is that premature, unscientific optimization is a waste of time, and potentially harmful. Refrain from applying memoization if you're application works and is fast enough.
Related
WHILE WRITING THIS POST I REALIZED WHAT THE SOLUTION WAS
Every time I dispatch a task to my store the following error occurs:
I have some idea of why it happens. It happens precisely when I try to get the to-do list using useSelector and then mapping through the list. However, the mapping is not the issue but rather returning a react component on the map function. It works just fine if I do not return a functional component and instead use HTML. So the issue, from my POV, is returning a react functional component while passing props to it on a map function.
Here's the code for my home component:
import Input from '../components/Input';
import TodoForm from '../components/TodoForm';
function Home() {
document.title = "MyTodo | Home"
return (
<div className="App">
<h1>MyTodo</h1>
<Input />
<TodoForm />
</div>
);
}
export default Home;
The input component where the action is being dispatched on key down:
import {useState} from 'react'
import { useDispatch } from 'react-redux';
import { todoActions } from '../store/todo';
const Input = () => {
const [inputText, setInputText] = useState("");
const dispatch = useDispatch();
const handleChange = (e) => setInputText(e.target.value)
const handleKeyPress = (event) => {
if (event.code === "Enter") {
// if the expression is false, that means the string has a length of 0 after stripping white spaces
const onlyWhiteSpaces = !inputText.replace(/\s/g, "").length;
!onlyWhiteSpaces &&
dispatch(
todoActions.addTask({ label: inputText, done: false })
);
setInputText("");
}
};
return (
<input
type="text"
onKeyDown={(e) => handleKeyPress(e)}
onChange={(e) => handleChange(e)}
value={inputText}
/>
);
}
export default Input
The TodoForm where I am using useSelector to get the todo list from the redux store and mapping thru it:
import { useSelector } from "react-redux";
import { v4 as uuidv4 } from "uuid";
import TodoTask from "./TodoTask";
const TodoForm = () => {
const tasks = useSelector((state) => state.todo.taskList);
const renderedListItems = tasks.map((task, index) => {
return (
<TodoTask
key={uuidv4()}
task={task}
targetIndex={index}
/>
);
});
return <div className="container">{renderedListItems}</div>;
};
export default TodoForm;
Finally the TodoTask component which is the child component being returned on the map function above:
import { useDispatch } from "react-redux";
import { todoActions } from "../store/todo";
const TodoTask = ({ task, targetIndex }) => {
const {text, done} = task;
console.log("Task: ", task);
const dispatch = useDispatch()
const removeTask = dispatch(todoActions.deleteTask(targetIndex))
return (
<div
className="alert alert-primary d-flex justify-content-between"
role="alert"
>
{text}
<button type="button" className="btn-close" onClick={()=>removeTask}></button>
</div>
);
};
export default TodoTask;
This is my first time facing this issue, and I know it has something to do with redux and how the useSelector hook forces a component to re-render. So the useSelector is re-rendering the TodoForm component, and since we are mapping and returning another component, that component is also being rendered simultaneously. At least, that is how I understand it. Let me know if I am wrong.
Things I have tried:
Wrapping the TodoTask in React.memo. Saw it somewhere as a possible solution to this kind of issue, but that did not work.
Passing shallowEqual as a second parameter on the TodoForm useSelector. This does prevent the page from going into an infinity loop, but the tasks show up empty but are being added to the redux store. However, with this method, the first warning stills shows up, and the console log in the TodoTask component does not execute.
Passing shallowEqual as a second parameter on the TodoForm useSelector. This does prevent the page from going into an infinity loop but the tasks show up empty but are being added to the redux store. However, with this method, the first warning stills shows up and the console log in the TodoTask component does not execute.
I realized what I was doing wrong while writing this part. The console log in the TodoTask component was working, but I had the browser console filtering for errors only. When I check the messages section, I saw everything working fine. Then when I checked the Task component, I noticed I was trying to read a property that did not exist and hence why the tasks had no text.
In other words, the solution was adding shallowEqual as second parameter of the useSelector hook in my TodoForm component that was the one mapping thru the todo tasks array. As I said, useSelector forces a component to re-render. shallowEquals checks if the existing state isn't the same as we already had and avoids unnecessary re-renders, which can lead my application to exceed the maximum update length.
Code fix [Solution]:
import { memo } from "react";
import { shallowEqual, useSelector } from "react-redux";
import { v4 as uuidv4 } from "uuid";
import TodoTask from "./TodoTask";
const TodoForm = () => {
// shallowEqual prevents unnecessary re-renders which can lead to an infinite loop
// it compares the current state with the previous one, if they are the same, it does not re-render the component
const tasks = useSelector((state) => state.todo.taskList, shallowEqual);
const renderedListItems = tasks.map((task, index) => {
return (
<TodoTask
key={uuidv4()}
task={task}
targetIndex={index}
/>
);
});
return <div className="container">{renderedListItems}</div>;
};
export default memo(TodoForm);
Honestly, I have been stuck on this since yesterday and I cannot believe I realize the solution just when I was about to ask for help. Hope this helps anyone else who faces a similar issue in the future.
I don't think I am using useEffect correctly in the function I have written. If I do the same with classes there is no problem and I am able to do ComponentDidMount(). But I don't know Hooks very well. So I think I am going wrong somewhere.
//import { onValue } from 'firebase/database';
import React, { useEffect } from "react";
import {db} from '../../firebase';
import {ref,onValue} from 'firebase/database'
import Grid from '#mui/material/Grid';
import Box from '#mui/material/Box';
export default function NiftyChart() {
React.useEffect(()=>{
const dbRef=ref(db,"1rh1Ta-8dqZKmh1xy5ans2lOqReoiVAT81WyDKqRaxl0/Nifty");
onValue(dbRef,(snapshot)=>{
let records=[];
snapshot.forEach(childSnapshot=>{
let keyName=childSnapshot.key;
let data=childSnapshot.val();
console.log(snapshot.val());
records.push({"key":keyName,"data":data})
console.log(records[records.length-1])
});
this.setState();
})
},[]);
return(
<div>
{this.state.map((row, index)=>{
return(
<Box component="span" sx={{}}>
<Grid > {row.Close}</Grid>
</Box>
)
})}
</div>
)
}
Also, no value is printed for row.Close. In the console I seem to be getting Cannot read properties of undefined (reading 'state') this error.
Any help is appreciated.
You need to use useState:
const [records, setRecords] = useState([]);
useEffect(() => ..get records and set to state, []);
So code would look like this:
export default function MyComponent() {
const [records, setRecords] = useState([]);
useEffect(() => {
// ... other code is omitted for the brevity
setRecords(records)
}, [])
return(
<div>
{records &&
records.map((row, index)=>{ //...other code is omitted
// for the brevity }
</div>
)
}
What's hook? The hook is:
A Hook is a special function that lets you “hook into” React features.
For example, useState is a Hook that lets you add React state to
function components
[] in useEffect means:
If you want to run an effect and clean it up only once (on mount and
unmount), you can pass an empty array ([]) as a second argument. This
tells React that your effect doesn’t depend on any values from props
or state, so it never needs to re-run. This isn’t handled as a special
case — it follows directly from how the dependencies array always
works.
Read more about API call in this great article
I'm attempting to create a React/Redux component that shows/hides an element when clicked.
I'm using this to trigger the function from another component:
import React from 'react'
//Some other code...
import { useSelector } from 'react-redux'
import onShowHelpClicked from '../help/AddHelpSelector'
<button onClick={onShowHelpClicked}>Help</button>
This this is AddHelpSelector:
import { useState } from 'react'
import { useDispatch } from 'react-redux'
import { helpVisible } from './HelpSlice'
export const AddHelp = () => {
const [isVisible, showHelp] = useState('')
const dispatch = useDispatch()
const onShowHelpClicked = () => {
dispatch(
helpVisible({
isVisible,
})
)
if (isVisible) {
showHelp(false)
} else {
showHelp(true)
}
}
return (
<section>
<h2 style={{ visibility: { isVisible } }}>Help section</h2>
</section>
)
}
export default AddHelp
Finally, this is HelpSlice
import { createSlice } from '#reduxjs/toolkit'
const initialState = [{ isVisible: false }]
const helpSlice = createSlice({
name: 'help',
initialState,
reducers: {
helpVisible(state, action) {
state.push(action.payload)
},
},
})
export const { helpVisible } = helpSlice.actions
export default helpSlice.reducer
I'm fairly certain I'm doing multiple things wrong, as this is my first attempt to do anything with Redux and I'm still struggling to wrap my mind around it after a week of learning.
But specifically, when clicking the help button I get this error.
"Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app"
The linked documentation provides a way to test a component to see if React is importing properly, and it's not. But I'm not really sure what I'm doing wrong.
I think it may be that I'm importing React multiple times, but if I don't then I can't use "useState."
What's the correct way to do this? I'm open to corrections on both my code as well as naming conventions. I'm using boilerplate code as a template as I try to understand this better after getting through the documentation as well as Mosh's 6 hour course which I just finished.
You're importing the < AddHelpSelector /> component here import onShowHelpClicked from '../help/AddHelpSelector', and then you try to use it as a callback handler for the button's onClick, which doesn't really make sense. I assume you actually wanted to only import the onShowHelpClicked function declared inside the < AddHelpSelector /> component (which is not really a valid way of doing it). Since you want to control the visibility using redux state, you could just grab the flag from the redux store inside the < AddHelpSelector /> component using useSelector hook. To set it, you're gonna do that in the component where your button is. For that, you just need to dispatch an action(like you already did), with the updated flag. No need for the local useState. Also, using the flag you could just conditionally render the element.
const App = () => {
const dispatch = useDispatch();
const { isVisible } = useSelector((state) => ({ isVisible: state.isVisible }));
const handleClick = () => {
dispatch(
helpVisible({
!isVisible,
})
)
}
return (<button onClick={handleClick}>Help</button>);
}
export const AddHelp = () => {
const { isVisible } = useSelector((state) => ({ isVisible: state.isVisible }));
return (
<section>
{isVisible && <h2>Help section</h2>}
</section>
)
}
export default AddHelp
I am using a react library for swiping: https://github.com/voronianski/react-swipe
Making it work is straight-forward:
import React, {useEffect, useState} from 'react';
import ReactDOM from 'react-dom';
import ReactSwipe from 'react-swipe'
import styled from 'styled-components'
const StyledBox = styled.div`
height: 50px;
width: 100%;
background-color: orange;
`
const Carousel = () => {
let reactSwipeEl;
const [position, setPosition] = useState(0)
console.log('position', position)
const swipeOptions = {
continuous: false,
transitionEnd() {
setPosition(reactSwipeEl.getPos())
}
}
return (
<div>
<ReactSwipe
className="carousel"
swipeOptions={swipeOptions}
ref={el => (reactSwipeEl = el)}
>
<StyledBox>PANE 1</StyledBox>
<StyledBox>PANE 2</StyledBox>
<StyledBox>PANE 3</StyledBox>
</ReactSwipe>
<Circles>
<Circle isActive={0 === position} />
<Circle isActive={1 === position}/>
</Circles>
</div>
);
};
export default Carousel
The code that I added is the one related with useState. The library works fine, but every time I swipe, I use the callback transitionEnd, in order to update the position state. It is updated, but since a state variable changes, the whole Carousel component gets updated, setting automatically the init value, which is 0.
I don't understand how can I avoid this problem which is, updating the state without re-rendering the whole component. Should I split it in two components and use a provider?
I tried it, but when the state of the provider changes, both components are also re-render
The swipeOptions is recreated on each render, which causes the ReactSwipe to rerender as well. Wrap the swipeOptions with useMemo() (sandbox);
const swipeOptions = useMemo(() => ({
continuous: false,
transitionEnd() {
setPosition(reactSwipeEl.current.getPos())
}
}), []);
I've some problem using the useRef hook with a styled component.
Linter alerts me that Object is possibly 'null' inside the didMount useEffect. Any idea about this?
This is not a duplicate for 2 reason:
The old answer refers to the ref used in a class component, that was the only way to use it before React hooks,
The innerRef props isn't supported anymore in the current version of styled components.
Here's a sample snippet of my component:
import React, { useRef, useEffect } from 'react';
import styled from 'styled-components';
const StyledInput = styled.input`
background: transparent;
`
const MyForm = () => {
const inputRef = useRef(null);
useEffect(() => {
if (inputRef && inputRef.current) {
inputRef.current.focus(); //Object is possibly 'null'
}
}, []);
return (
<StyledInput ref={inputRef}/>
);
}
I've finally found a solution:
const inputRef = useRef() as React.MutableRefObject<HTMLInputElement>;
It works for me:
import React, { useRef, useEffect } from 'react';
import styled from 'styled-components';
const StyledInput = styled.input`
background: transparent;
`
const MyForm = () => {
const inputRef = useRef() as React.MutableRefObject<HTMLInputElement>;
useEffect(() => {
if (inputRef && inputRef.current) {
inputRef.current.focus();
}
}, []);
return (
<StyledInput ref={inputRef}/>
);
}
As an alternative to the current accepted answer, you can also do:
const inputRef = useRef<HTMLInputElement>(null);
Pass your inputRef in the array argument of useEffect and let's see if that works, you aren't guaranteed to have a .current in your ref, so you should run the effect every time inputRef changes