I'm trying to convert this cool <canvas> animation I found here into a React reusable component. It looks like this component would require one parent component for the canvas, and many children components for the function Ball().
It would probably be better for performance reasons to make the Balls into stateless components as there will be many of them. I'm not as familiar with making stateless components and wondered where I should define the this.update() and this.draw functions defined in function Ball().
Do functions for stateless components go inside the component or outside? In other words, which of the following is better?
1:
const Ball = (props) => {
const update = () => {
...
}
const draw = () => {
...
}
return (
...
);
}
2:
function update() {
...
}
function draw() {
...
}
const Ball = (props) => {
return (
...
);
}
What are the pros and cons of each and is one of them better for specific use cases such as mine?
The first thing to note is that stateless functional components cannot have methods: You shouldn't count on calling update or draw on a rendered Ball if it is a stateless functional component.
In most cases you should declare the functions outside the component function so you declare them only once and always reuse the same reference. When you declare the function inside, every time the component is rendered the function will be defined again.
There are cases in which you will need to define a function inside the component to, for example, assign it as an event handler that behaves differently based on the properties of the component. But still you could define the function outside Ball and bind it with the properties, making the code much cleaner and making the update or draw functions reusable:
// you can use update somewhere else
const update = (propX, a, b) => { ... };
const Ball = props => (
<Something onClick={update.bind(null, props.x)} />
);
If you're using hooks, you can use useCallback to ensure the function is only redefined when any of its dependencies change (props.x in this case):
const Ball = props => {
const onClick = useCallback((a, b) => {
// do something with a, b and props.x
}, [props.x]);
return (
<Something onClick={onClick} />
);
}
This is the wrong way:
const Ball = props => {
function update(a, b) {
// props.x is visible here
}
return (
<Something onClick={update} />
);
}
When using useCallback, defining the update function in the useCallback hook itself or outside the component becomes a design decision more than anything: You should take into account if you're going to reuse update and/or if you need to access the scope of the component's closure to, for example, read/write to the state. Personally I choose to define it inside the component by default and make it reusable only if the need arises, to prevent over-engineering from the start. On top of that, reusing application logic is better done with more specific hooks, leaving components for presentational purposes. Defining the function outside the component while using hooks really depends on the grade of decoupling from React you want for your application logic.
Another common discussion about useCallback is whether to always use it for every function or not. That is, treat is as opt-in or always recommendable. I would argue to always use useCallback: I've seen many bugs caused by not wrapping a function in useCallback and not a single scenario where doing so affects the performance or logic in any way. In most cases, you want to keep a reference while the dependencies don't change, so you can use the function itself as a dependency for other effects, memos or callback. In many cases the callback will be passed as a prop to other elements, and if you memoized it with useCallback you won't change the props (thus re-render) other components independently of how cheap or costly that would be. I've seen many thousands of functions declared in components and not a single case in which using useCallback would have any down side. On the other hand most functions not memoized with useCallback would eventually be changed to do so, causing serious bugs or performance issues if the developer doesn't recognize the implications of not doing so. Technically there is a performance hit by using useCallback, as you would be creating and additional function but it is negligible compared to the re-declaration of the function that always has to happen either you use useCallback or not and the overall footprint of React and JavaScript. So, if you are really concerned about the performance impact of useCallback versus not using it, you should be questioning yourself if React is the right tool for the job.
You can place functions inside stateless functional components:
function Action() {
function handlePick(){
alert("test");
}
return (
<div>
<input type="button" onClick={handlePick} value="What you want to do ?" />
</div>
)
}
But it's not a good practice as the function handlePick() will be defined every time the component is rendered.
It would be better to define the function outside the component:
function handlePick(){
alert("test");
}
function Action() {
return (
<div>
<input type="button" onClick={handlePick} value="What you want to do ?" />
</div>
)
}
If you want to use props or state of component in function, that should be defined in component with useCallback.
function Component(props){
const onClick=useCallback(()=>{
// Do some things with props or state
},[])
return <Something {...{onClick}} />
}
On the other hand, if you don't want to use props or state in function, define that outside of component.
const computeSomethings=()=>{
// Do some things with params or side effects
}
function Component(props){
return <Something onClick={computeSomethings} />
}
For HTML tags you don't need useCallback because that will handle in react side and will not be assigned to HTML
function Component(props){
const onClick=()=>{
// Do some things with props or state
}
return <div {...{onClick}} />
}
Edit: Functions in hooks
For the use function in hooks for example useEffect, my suggestion is defining function inside useEffect, if you're worried about DRY, make your function pure call it in hook and give your params to it.
What about hooks deps? You should/could add all of your params to hooks deps, but useEffect just needs deps which should affect for them changes.
We can use the React hook useCallback as below in a functional component:
const home = (props) => {
const { small, img } = props
const [currentInd, setCurrentInd] = useState(0);
const imgArrayLength = img.length - 1;
useEffect(() => {
let id = setInterval(() => {
if (currentInd < imgArrayLength) {
setCurrentInd(currentInd => currentInd + 1)
}
else {
setCurrentInd(0)
}
}, 5000);
return () => clearInterval(id);
}, [currentInd]);
const onLeftClickHandler = useCallback(
() => {
if (currentInd === 0) {
}
else {
setCurrentInd(currentInd => currentInd - 1)
}
},
[currentInd],
);
const onRightClickHandler = useCallback(
() => {
if (currentInd < imgArrayLength) {
setCurrentInd(currentInd => currentInd + 1)
}
else {
}
},
[currentInd],
);
return (
<Wrapper img={img[currentInd]}>
<LeftSliderArrow className={currentInd > 0 ? "red" : 'no-red'} onClick={onLeftClickHandler}>
<img src={Icon_dir + "chevron_left_light.png"}></img>
</LeftSliderArrow>
<RightSliderArrow className={currentInd < imgArrayLength ? "red" : 'no-red'} onClick={onRightClickHandler}>
<img src={Icon_dir + "chevron_right_light.png"}></img>
</RightSliderArrow>
</Wrapper>);
}
export default home;
I'm getting 'img' from it's parent and that is an array.
import React, { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
const a = () => {
setCount(count + 1);
};
return (
<div>
<p>You clicked {count} times</p>
<button onClick={a}>Click me</button>
</div>
);
}
export default Example;
Related
I'm trying to access React state inside a function enclosed inside of useRef. However, even with a helper function bound to App to access the state, the state never updates inside of the useRef function.
getCount outside 0
// after clicking, getCount inside is still 0, even though count now equals 1
getCount outside 1
getCount inside 0
import React, { useState, useRef } from 'react'
import ReactDOM from 'react-dom'
const App = function () {
const [count, setCount] = useState(0)
const getCount = function () {
return count
}.bind(App)
console.log('getCount outside', getCount())
const onClick = useRef(() => {
console.log('getCount inside', getCount())
})
return (
<>
<div onClick={() => setCount(count + 1)}>
increment count
</div>
<div onClick={onClick.current}>
{count}
</div>
</>
)
}
const wrapper = document.getElementById('root')
ReactDOM.render(<App />, wrapper)
The argument passed to useRef is only considered when the component mounts. Only at that time is the value assigned to the ref; it won't update when the component re-renders.
When the component mounts, the count variable that the ref's function closes over is the initial state value, which is 0. No matter how many times the component re-renders, the ref's function, if not reassigned, will still close over that original state value.
If you want the ref's function to result in an up-to-date value, assign it anew each time there's a re-render.
// assign nothing notable on mount, just create a ref
const onClickRef = useRef();
// on mount and on re-render, assign a function
// with an up-to-date reference to the state variable
onClickRef.current = () => {
console.log(count);
};
Though, in React, usually it'd be better to pass the state itself down and use it, rather than a ref - refs are generally for things that you can't accomplish using the more functional tools provided by React.
Using callback with previousValue in setter solves this problem:
const onClick = useRef(() => {
console.log(count); <----- always 0 (initial state)
setCount((previousValue)=> {
console.log(previousValue); <---- correct current value each time
return previousValue+1;
}
})
Source: idea was from #bogdanoff's first comment from the question. Upvoted.
I've read a few articles on when to optimize rendering in React, however, I still have some doubts.
const RepairNoticeContainer = () => {
const dispatch = useDispatch();
const history = useHistory();
const { siteType, siteId } = useParams();
const data = useSelector(pageSelectors.getData);
const showRepairNotice = data.grid.cols.lg !== 36;
const handleRepairClick = () => {
dispatch(
pagesActions.copyAndRepairCurrentPage(newPageId => {
history.push(`/editor/${siteType}/${siteId}/pages/${newPageId}`);
})
);
};
return showRepairNotice ? <RepairNotice onRepairClick={handleRepairClick} /> : null;
};
As far as I can understand, it would be beneficial to use useCallbackfor handleRepairClick to avoid rerenders of <RepairNotice/>. But what about showRepairNoticevariable? Should it be wrapped in a useMemo for optimization?
const RepairNotice = ({ onRepairClick }) => {
const translate = useTranslator();
let message = translate("repair_warning");
message = message.charAt(0).toLowerCase() + message.slice(1);
return (
<MessageBox type="warning" icon="information11" className="mb-0 mt-2">
<div className="row">
<div className="col">
<b>{translate("warning")}:</b> {message}
</div>
<div className="col-auto text-right">
<Button color="danger" onClick={onRepairClick} size="small">
{translate("repair_now")}
</Button>
</div>
</div>
</MessageBox>
);
A simillar question for this example. Would it be beneficial to wrap message inside of useMemo?
const Page = ({ status }) => {
const unsavedData = status?.unsavedData ? true : false;
return (
<Fade>
<div className="Page">
<NavConfirmModal active={unsavedData} onSavePage={onSavePage} />
</div>
</Fade>
);
};
Lastly, should useMemo be used unsavedData?
Explanations would be much appreciated.
As far as I can understand, it would be beneficial to use useCallback for handleRepairClick to avoid rerenders of
That's right. while wrapping handleRepairClick you will, simply speak, prevent creating a new instance of this function so it will save RepairNotice nested component from redundant rerenders because it relies on this function in props. Another good case for useMemo is when you're rendering a list of items and each relies on the same handler function declared in their parent.
Very good useCallback explanation here.
But what about showRepairNotice variable? Should it be wrapped in a
useMemo for optimization?
It's just a simple "equation" check which is really cheap from performance side - so there is really no need in useMemo here.
Would it be beneficial to wrap message inside of useMemo?
Yes, it would. Since there are at least 3 actions javascript has to fire upon the message (charAt, toLowerCase, slice) and you don't really want this calculations to fire every time the RepairNotice component gets rerendered.
should useMemo be used unsavedData?
It might be preferable to wrap unsavedData into useMemo if NavConfirmModal will be wrapped in React.Memo or in the case of "heavy calculations". So for the current case - it would not really make a difference. (btw calculating unsavedData could be written just like !!status?.unsavedData to get boolean).
And very good useMemo explanation here.
I learn react, and I dont understand what is the difference between placing data or function inside or outside function components ?
What is the best practice, and when use the first or second example ?
Declare in component :
import React from 'react'
const SomeComponent = () => {
const list = ['foo', 'bar']
function add(foo) {
return foo + 1
}
// other logics...
return (
// list...
)
}
Declare outside the component :
import React from 'react'
const list = ['foo', 'bar']
function add(foo) {
return foo + 1
}
const SomeComponent = () => {
// other logics...
return (
// list...
)
}
Have a nice day :)
If your function is a pure function that is not accessing the component's context, you can just put it outside of the component.
It doesn't matter in that case.
But when you need to use this inside the function, it's necessary to define it inside the component.
This post could be helpful too.
Functions in stateless components?
I think the 2nd option is a better cause when react re-renders the "SomeComponent", it doesn't have to create foo() again and again.
If you don't want to use class components like state and other methods than declare outside component else inside the component. In inside component call function as this.functionName
I was playing with ReacJS. I noticed a thing-
In case of Class Component during re-rendering class variable's updated value is updated in screen like:
import React, { Component } from "react";
class Temp extends Component {
constructor(props) {
super(props);
this.count = 0;
this.state = {
foo: 0,
};
}
render() {
return (
<button
onClick={() => {
this.setState({ foo: this.state.foo + 1 });
this.count++;
}}
>
{this.count} - {this.state.foo}
</button>
);
}
}
export default Temp;
But in case of function component the updated value of the ordinary variable is not updated in the screen during re-rendering.
import React, { useRef, useState } from "react";
const RefComponent = () => {
const [stateNumber, setStateNumber] = useState(0);
let refVar = 0;
function incrementAndDelayedLogging() {
setStateNumber(stateNumber + 1);
refVar++;
}
return (
<div>
<button onClick={incrementAndDelayedLogging}>Click</button>
<h4>state: {stateNumber}</h4>
<h4>refVar: {refVar}</h4>
</div>
);
};
export default RefComponent;
Is this how React was implemented or I'm messing around something? I'm curious to know about it.
Thanks
Functional components in React don't have instances, so things like class or instance variables don't necessarily make sense; like others have pointed out in the comments here, React will render (call) functional components and "reset" any local variables that are not explicitly state. Behavior like instance variables for functional components are achieved with useRef.
From the docs:
The useRef() Hook isn’t just for DOM refs. The “ref” object is a generic container whose current property is mutable and can hold any value, similar to an instance property on a class.
This is a consequence of functional components.
Think about it like this: Each time a state var is updated or a prop is updated your function gets called again. All variable declaration will happen again (states are a special case), so let refVar = 0; gets called again.
If you need that variable to live for multiple renders you'll need to declare it in a context that survives re-renders.
You have at least 2 ways of achieving this
declare a state for it with useState
declare it at the module level, but know all your instances of RefComponent will share the same instance
So I've got this hook to return the windowWidth for my App components. I'll call this Option #1.
import {useEffect, useState} from 'react';
function useWindowWidth() {
const [windowWidth,setWindowWidth] = useState(window.innerWidth);
useEffect(() => {
function handleResize() {
setWindowWidth(window.innerWidth);
}
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return windowWidth;
}
export default useWindowWidth;
And right now I'm basically using it on every component that depends on the window width to render, like:
function Component(props) {
const windowWidth = useWindowWidth();
return(
// RETURN SOMETHING BASED ON WINDOW WIDTH
);
}
And since the hook has an event listener for the resize events, the component stays responsive even after window resizes.
But I'm worried that I'm attaching a new listener for every component that uses that hook and it might slow things down at some point. And I've though of other approach:
Option #2
I use the useWindowWidth() hook only one time, inside a top level component like <App/> and I'll provide the windowWidth value down the chain via context.
Like:
function App() {
const windowWidth = useWindowWidth();
return(
<WindowWidthContext.Provider value={windowWidth}>
<Rest_of_the_app/>
</WindowWidthContext.Provider>
);
}
And then, every component that needs it could get it via:
function Component() {
const windowWidth = useContext(WindowWidthContext);
return(
// SOMETHING BASED ON WINDOW WIDTH
);
}
QUESTION
Am I right in being bothered by that fact that I'm setting up multiple resize listeners with Option #1 ? Is Option #2 a good way to optmize that flow?
If your window with is used by so many components as you mentioned, you must prefer using context. As it reads below:
Context is for global scope of application.
So, #2 is perfect choice here per react.
First approach #1 might be good for components in same hierarchy but only up-to 2-3 levels.
I'm not sure if adding and removing event listeners is a more expensive operation than setting and deleting map keys but maybe the following would optimize it:
const changeTracker = (debounceTime => {
const listeners = new Map();
const add = fn => {
listeners.set(fn, fn);
return () => listeners.delete(fn);
};
let debounceTimeout;
window.addEventListener('resize', () => {
clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(
() => {
const width=window.innerWidth;
listeners.forEach(l => l(width))
},
debounceTime
);
});
return add;
})(200);
function useWindowWidth() {
const [windowWidth, setWindowWidth] = useState(
() => window.innerWidth
);
useEffect(
() =>//changeTracker returns a remove function
changeTracker((width) =>
setWindowWidth(width)
),
[]
);
return windowWidth;
}
As HMR said in an above thread, my solution was to use redux to hold the width value. With this strategy you only need one listener and you can restrict how often you update with whatever tool you like. You could check if the width value is within the range of a new breakpoint and only update redux when that is true. This only works if your components dont need a steady stream of the window width, in that case just debounce.