Use case for useLayoutEffect + useState vs useMemo - reactjs

I've seen this answer: useMemo vs. useEffect + useState , and it sums it up well for useEffect, but in my case I want to perform an expensive operation that will change the DOM as early as possible. Would useMemo() still be recommended instead of useLayoutEffect() with a state update? Does the double render of effect -> state-update negate any performance boost?
EDIT
useLayoutEffect() scenario:
useLayoutEffect(() => {
const tokens = expensiveOperationGeneratingClasses(param1)
setTokens(tokens)
},
[param1])
render (
<>
{
tokens.map(token => <span className={token.id}/>)
}
</>
)
useMemo scenario:
const tokens = useMemo(() => {
return expensiveOperationGeneratingClasses(param1)
},
[param1]
render (
<>
{
tokens.map(token => <span className={token.id}/>)
}
</>
)
Actually I realised that I'm not doing DOM operations but rather just generating the class names before the rendering of the <span> tags to avoid flickering, so I think i'm better off using useMemo, am I right?

I will try to explain where you can use LayoutEffect and Memo. Let's start with the using of LayoutEffect.
The using of LayoutEffect has some drawbacks says Dan Abramov Link 1, Link 2.It's a good explanation of where you can use these gives Kent C. Dodds.If you need an example, you can see it here Chris. Don't forget about reading for understand the difference.
Now about of the using Memo. It's also has a drawback. For what we use Memo ,and where it is used you can found here.
And now in practice.
option 1 use LayoutEffect
import React, { useState, useLayoutEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const Control = () => {
const [add, setAdd] = useState(1);
return (
<div>
<div>
<PostOffice add={add} />
</div>
<div onClick={() => setAdd(add + 1)}>{"Click"}</div>
</div>
);
};
function PostOffice({ add }) {
const [letter, setLetter] = useState(add);
useLayoutEffect(() => {
console.log("useLayoutEffect");
setLetter(add);
}, [add]);
console.log(letter);
return <div className="App">{console.log(letter, "DOM")}</div>;
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Control />, rootElement);
I'm not sure about this option 1, because there is an anti-pattern effect here.
option 2 use LayoutEffect
import React, { useState, useLayoutEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const Control = () => {
const [add, setAdd] = useState(1);
return (
<div>
<div>
<PostOffice add={add} />
</div>
<div onClick={() => setAdd(add + 1)}>{"Click"}</div>
</div>
);
};
function PostOffice({ add }) {
const [letter, setLetter] = useState(0);
useLayoutEffect(() => {
console.log("useLayoutEffect");
setLetter(add);
}, [add]);
console.log(letter);
return <div className="App">{console.log(letter, "DOM")}</div>;
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Control />, rootElement);
there will be a meaningless rendering
option useMemo
import React, { useState, useMemo } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const Control = () => {
const [add, setAdd] = useState(1);
return (
<div>
<div>
<PostOffice add={add} />
</div>
<div onClick={() => setAdd(add + 1)}>{"Click"}</div>
</div>
);
};
function PostOffice({ add }) {
const Letter = useMemo(() => {
console.log("useMemo");
return add + 1;
}, [add]);
console.log(Letter);
return <div className="App">{console.log(Letter, "DOM")}</div>;
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Control />, rootElement);
And here everything works perfectly
Total
Minus useMemo 1,
Minus useLayoutEffect, 1,anti-pattern effect or meaningless rendering,adding useState,
This is why you should use useMemo.
but if there is a way not to use these hooks, it will be perfect.

Related

Data in React Component Not refreshing when Path/Location changes

I have a react app that has a "Bread Crumb Header" component, the data for this component comes from an API end point.
I use the bread crumb header component inside mulitiple components within the app, and based on the current path/window.location the bread crumb componet will get the data from the API and render the correct HTML via JSX.
The problem I have is when I navigate to diffent paths/window.location's within the application the bread crum component data doesn't update.
This is what the bread crumb component looks like:
import React, { useState, useEffect } from 'react';
import API from "../../API";
import { useLocation } from 'react-router-dom';
import { BreadCrumbTitleSection, SubtitleSection, Subtitle } from './breadCrumbHeaderStyle';
import { Breadcrumb } from 'react-bootstrap';
function BreadCrumbHeader() {
const location = useLocation();
const [breadCrumbData, setBreadCrumbData] = useState([]);
const getBreadCrumbData = async () => {
const breadCrumbHeaderResponse = await API.fetchBreadCrumbHeader(location.pathname);
setBreadCrumbData(breadCrumbHeaderResponse);
};
useEffect(() => {
getBreadCrumbData();
}, []);
return (
<div>
<BreadCrumbTitleSection backgroundUrl={breadCrumbData.BreadCrumbBgImage}>
<div className="container">
<div className="row no-gutters">
<div className="col-xs-12 col-xl-preffix-1 col-xl-11">
<h1 className="h3 text-white">{breadCrumbData.BreadCrumbTitle}</h1>
<Breadcrumb>
{breadCrumbData.BreadCrumbLinks.map(breadCrumbLink => (
<Breadcrumb.Item href={breadCrumbLink.LinkUrl} key={breadCrumbLink.Id} active={breadCrumbLink.IsActive}>
{breadCrumbLink.LinkText}
</Breadcrumb.Item>
))}
</Breadcrumb>
</div>
</div>
</div>
</BreadCrumbTitleSection>
<SubtitleSection>
<Subtitle> {breadCrumbData.SubTitle}</Subtitle>
</SubtitleSection>
</div>
);
}
export default BreadCrumbHeader;
and this is an example of how I am using it inside other components:
import React, { useContext } from 'react';
import { useParams } from "react-router-dom";
import { MenuContext } from '../context/menuContext';
import RenderCmsComponents from '../../components/RenderCmsComponents/';
import BreadCrumbHeader from '../../components/BreadCrumbHeader/';
import { CategorySection, CategoryContainer, CategoryItemCard, CategoryItemCardBody, CategoryItemCardImg, CategoryItemTitle, CategoryRow, AddToCartButton, ProductDescription} from './categoryStyle';
function Category() {
const [categoryItems] = useContext(MenuContext);
const { id } = useParams();
const category = categoryItems.find(element => element.CategoryName.toLowerCase() === id.toLowerCase());
var dynamicProps = [];
{
category && category.Products.map(productItem => (
dynamicProps.push(productItem.ProductOptions.reduce((acc, { OptionName, OptionsAsSnipCartString }, i) => ({
...acc,
[`data-item-custom${i + 1}-name`]: OptionName,
[`data-item-custom${i + 1}-options`]: OptionsAsSnipCartString
}), {}))));
}
return (
<div>
<BreadCrumbHeader /> << HERE IT IS
<CategorySection backgroundurl="/images/home-slide-4-1920x800.jpg" fluid>
<CategoryContainer>
<CategoryRow>
{category && category.Products.map((productItem, i) => (
<CategoryItemCard key={productItem.ProductId}>
<CategoryItemTitle>{productItem.ProductName}</CategoryItemTitle>
<CategoryItemCardBody>
<ProductDescription>{productItem.Description}</ProductDescription>
<div>
<CategoryItemCardImg src={productItem.ProductImageUrl} alt={productItem.ProductName} />
</div>
</CategoryItemCardBody>
<AddToCartButton
data-item-id={productItem.ProductId}
data-item-price={productItem.Price}
data-item-url={productItem.ProductUrl}
data-item-description={productItem.Description}
data-item-image={productItem.ProductImageUrl}
data-item-name={productItem.ProductName}
{...dynamicProps[i]}>
ADD TO CART {productItem.Price}
</AddToCartButton>
</CategoryItemCard>
))}
</CategoryRow>
</CategoryContainer>
</CategorySection>
<RenderCmsComponents />
</div>
);
}
export default Category;
I found this post on stack overflow:
Why useEffect doesn't run on window.location.pathname changes?
I think this may be the solution to what I need, but I don't fully understand the accepted answer.
Can someone breakdown to be how I can fix my issue and maybe give me an explaination and possible some reading I can do to really understand how hooks work and how to use them in my situation.
It seems that you should re-call getBreadCrumbData every time when location.pathname was changed. In the code below I've added location.pathname to useEffect dependency list
const location = useLocation();
const [breadCrumbData, setBreadCrumbData] = useState([]);
const getBreadCrumbData = async () => {
const breadCrumbHeaderResponse = await API.fetchBreadCrumbHeader(location.pathname);
setBreadCrumbData(breadCrumbHeaderResponse);
};
useEffect(() => {
getBreadCrumbData();
}, [location.pathname]); // <==== here

check state of another functional component

i am new to react, i want to call the state of an outside function, for example :
export default function Child() {
const [succeeded, setSucceeded] = useState(false);
}
export default function Parent() {
if(Child.succeeded){
// do the following
}
}
i know that props are used for const objects only, and i don't want to merge both functions in a signle one to keep things organised, i would like to check for child's state to do the next step, or to callback the parent function with the new state to notify it. is there any way to do it ? Thanks a lot for your time.
Another approach is that you can use the useRef, which is very handy in some cases.
import React, {useState} from "react";
export default function Child({nameRef}) {
const [name, setName] = useState('');
React.useEffect(() => {
nameRef.current = name;
}, [name]);
return (
<>
<input nameRef={nameRef} type="text" onChange={event => setName(event.target.value)} />
</>
);
}
import React, { useState, useRef } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import Child from './Child';
function App() {
let [name, setName] = useState("Nate");
let nameRef = useRef();
const submitButton = () => {
console.log(nameRef.current);
};
return (
<div className="App">
<p>{name}</p>
<div>
<Child nameRef={nameRef} />
<button type="button" onClick={submitButton}>
Submit
</button>
</div>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

HOC's and Render Props With Functional Components in React 16

I'm somewhat confused on the relationship between functional components in React and the Render Props and HOC patterns.
That is,
is it true that the only way to create Render Prop is with a class component?
is it true that the only way to create an HOC is with a class component?
And the same for usage.
I'm trying to find examples of Render Props and HOC's with functional components and all I find are class components. I get that React Hooks do a lot of the same, but I'm trying to understand how the Render Props and HOC patterns can apply to functional components (or if they do at all)
Edit Below:
Applying what #chaimFriedman suggested, this is what I came up with using no class or component for an HOC hoping it makes sense.
import React, { useState, useEffect } from 'react';
import useAxios from 'axios-hooks';
function withFetching(url) {
return function(Speakers) {
return () => {
const [speakerData, setSpeakerData] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [{ data, loading }, refetch] = useAxios('http://localhost:4000/speakers');
useEffect(() => {
setSpeakerData(data);
setIsLoading(loading);
}, [loading]);
if (isLoading) return <div>loading..</div>;
return <Speakers data={speakerData}></Speakers>;
};
};
}
const Speakers = function(props) {
//debugger;
return (
<ul>
{props.data.map((speaker) => (
<li key={speaker.id}>
<span>
{speaker.firstName} {speaker.lastName}
</span>
</li>
))}
</ul>
);
};
const API = 'http://localhost:4000/speakers';
export default withFetching(API)(Speakers);
Both render props and HOC can absolutely apply to functional components. Let's think a little more about what each one really is to see why they do in fact work with functions as well as classes.
Render props is when you have a prop that is a function which returns JSX. This of course should work for function components because aside from life cycle methods there really isnt much that is different than class components. Here is an example with code.
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function Renderer(props) {
return (
props.children()
);
}
function App() {
return (
<div className="App">
<Renderer>
{() => {
return (
<h1>I am being rendered by Renderer</h1>
);
}}
</Renderer>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Now for HOC.
A HOC really is just a higher order function, but because we use it in react we call it a higher order component. A higher order function is a function which either accepts a function as an argument, or returns a function. Now a functional component can absolutely do this. Here is an example.
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function Renderer(Wrapped) {
return function New(props) {
return <Wrapped {...props} />
}
}
function Child(props) {
return (
<h1>Hello {props.name}</h1>
);
}
function App() {
const C = Renderer(Child)
return (
<div className="App">
<C name="john" />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
EDIT: I realized my HOC example was wrong so updated.
I hope this helps.
Here is the sample code converting example from React documentation into function component. https://reactjs.org/docs/render-props.html
import React from "react";
const Cat = ({mouse}) => {
return (
<img
src="/cat.png"
alt="cat"
style={{ position: "absolute", left: mouse.x, top: mouse.y }}
/>
);
};
const Mouse = (props) => {
const [state, setState] = React.useState();
const handleMouseMove = (event) => {
setState({
x: event.clientX,
y: event.clientY
});
};
return (
<div style={{ height: "100vh" }} onMouseMove={handleMouseMove}>
{props.render(state)}
</div>
);
};
const MouseTracker = () => {
return (
<div>
<h1>Move the mouse around!</h1>
<Mouse render={(mouse) => <Cat mouse={mouse} />} />
</div>
);
};
export const App = () => {
return (
<div className="App">
<MouseTracker />
</div>
);
}

Debouncing and Setting text data not happening at the same time

I am trying to use the debounce feature as well as the ability to retain the value of textbox that I typed but it's not happening for some reason. If I comment out setMyval(e.target.value); on line #20 then the debounce works without any issue but the value I type does not show up. Whereas if I uncomment it, then the value shows in the textbox but debounce feature does not work (meaning there are multiple console logs). Please if someone can tell me why is this happening and how can I make it work, it would help me.
Below is my reactjs code:
// App.js
import React, { useState } from "react";
import ReactDOM from "react-dom";
import debounce from "./debounce";
function App() {
const [myval, setMyval] = useState("");
const handleChange = debounce(() => {
console.log("This log msg should be debounced");
}, 2000);
return (
<div className="App">
<input
type="text"
value={myval}
onChange={e => {
setMyval(e.target.value);
handleChange(e.target.value);
}}
/>
<button onClick={() => setMyval("my new value")}>Change Value</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
// debounce.js
export default function debounce(fn, wait) {
let timer;
return function() {
const context = this;
const args = arguments;
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(context, args);
}, wait);
};
}
Code at https://codesandbox.io/s/affectionate-wind-0ef3y
What I expect:
If I type a word in the textbox, the value should remain in the textbox.
Debounce should work i.e. few console logs should appear.
If I click on the "Change Value" button, it should update the value "my new value" in the textbox.
https://codesandbox.io/s/sharp-turing-w4hu9
Your debounce doesnt work, because your debounce function gets redeclared on every component update. Use useCallback to keep the reference the same and avoid reinitialization
import React, { useState, useCallback } from "react";
import ReactDOM from "react-dom";
import debounce from "./debounce";
import "./styles.css";
function App() {
const [myval, setMyval] = useState("");
const handleChange = useCallback(debounce(() => {
console.log("This log msg should be debounced");
}, 2000), []);
return (
<div className="App">
<input
type="text"
value={myval}
onChange={e => {
setMyval(e.target.value);
handleChange(e.target.value);
}}
/>
<button onClick={() => setMyval("my new value")}>Change Value</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

React hook useState's setter inside function doesn't work

I'm trying to do a refactor of a countdown component that a project I'm working on has.
When I finished the migration of the logic the value of the counter didn't work. I decided to start from zero in codesandbox, so I tought of the simplest implementation and came out with this:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const [counter, setCounter] = useState(60);
useEffect(() => {
const interval = setInterval(() => setCounter(counter - 1), 1000);
return () => clearInterval(interval);
}, []);
return (
<div className="App">
<h1>Hello CodeSandbox {counter}</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App timerSeconds={360} />, rootElement);
What it happens here is that the value of counter stays on 59 after the first run of the interval.
Codesandbox: https://codesandbox.io/embed/flamboyant-moon-ogyqr
Second iteration on issue
Thank you for the response Ross, but the real issue happens when I link the countdown to a handler:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const [counter, setCounter] = useState(60);
const [countdownInterval, setCountdownInterval] = useState(null);
const startCountdown = () => {
setCountdownInterval(setInterval(() => setCounter(counter - 1), 1000));
};
useEffect(() => {
return () => clearInterval(countdownInterval);
});
return (
<div className="App" onClick={startCountdown}>
<h1>Hello CodeSandbox {counter}</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App timerSeconds={360} />, rootElement);
Add the counter variable within the second parameter (the array) of the useEffect function. When you pass in an empty array, it will only update the state once on the initial render. An empty array is often used when you're making an HTTP request or something along those lines instead. (Edited for second iteration)
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const [counter, setCounter] = useState(5);
const [counterId, setCounterId] = useState(null);
useEffect(() => {
return () => clearInterval(counterId);
}, []);
const handleClick = () => {
/*
* I'd take startCountdown and make
* it's own component/hook out of it,
* so it can be easily reused and expanded.
*/
const startCountdown = setInterval(() => {
return setCounter((tick) => {
if (tick === 0) {
clearInterval(counterId);
setCounter(0);
return setCounterId(null);
};
return tick - 1;
});
}, 1000)
setCounterId(startCountdown);
};
return (
<div className="App" onClick={handleClick}>
<h1>Hello CodeSandbox {counter}</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App timerSeconds={360} />, rootElement);
For more information on this implementation, read about React Hooks and skipping effects at https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects.
You can use the Functional Updates version of the function returned by useState to compute the new state based on the previous state.
Your updated code would look like this:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const [counter, setCounter] = useState(60);
useEffect(() => {
const interval = setInterval(() => {setCounter(counter => counter - 1);}, 1000);
return () => clearInterval(interval);
}, []);
return (
<div className="App">
<h1>Hello CodeSandbox {counter}</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App timerSeconds={360} />, rootElement);
UPDATE EDIT
Here is a version that starts the countdown with a click handler:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const [counter, setCounter] = useState(60);
const [countdownInterval, setCountdownInterval] = useState(null);
const startCountdown = () => {
setCountdownInterval(setInterval(() => setCounter(counter => counter - 1), 1000));
};
useEffect(() => {
return () => clearInterval(countdownInterval);
}, [countdownInterval]);
return (
<div className="App" onClick={startCountdown}>
<h1>Hello CodeSandbox {counter}</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App timerSeconds={360} />, rootElement);

Resources