Can't stop widget chart from rerendering in React - reactjs

We are making a crypto trading calculator and I am struggling with a React rerendering issue because my React knowledge is somewhat limited and I don’t have lots of practice.
There are 10 calculator inputs on the page, one drop-down menu (implemented via React Select) for the selection of any crypto coin by the user, and one chart widget from TradingView implemented via https://www.npmjs.com/package/react-ts-tradingview-widgets.
The idea is that the user selects the coin of their preference via the drop-down menu, triggering the TradingView chart rerendering to show the chart of the coin that the user chose.
Everything works well, however the chart is rerendering every time that any state changes on the page. And with 10 inputs for calculations it happens very often, sometimes a couple of times per second.
So I’d like the chart to rerender only upon the selection of a new coin by the user, which is represented by state symbol, which is defaulted to BTC upon mounting.
const [symbol, setSymbol] = useState('BTCUSDT');
Here’s the TradingView widget chart
<div className="mx-4 mb-14 mt-4 w-full xl:w-1/2 h-72 xl:pr-3 mb-4 mx-0 mt-0 md:w-full lg:px-3 md:px-1 px-3">
<AdvancedRealTimeChart theme="dark" symbol={symbol} interval="60" autosize </AdvancedRealTimeChart>
</div>
I was wondering if it’s possible to use useEffect hook in order to make it rerender only upon symbol state change. Maybe something like this?
useEffect(() => {
const widgetChart = <div className="mx-4 mb-14 mt-4 w-full xl:w-1/2 h-72 xl:pr-3 mb-4 mx-0 mt-0 md:w-full lg:px-3 md:px-1 px-3">
<AdvancedRealTimeChart theme="dark" symbol={symbol} interval="60" autosize </AdvancedRealTimeChart>
</div>
}, [symbol])
And then placing {widgetChart} in the JSX?
Or maybe by creating a function and then running it inside useEffect and placing it in JSX?
function returnWidget() {
return ( <div className="mx-4 mb-14 mt-4 w-full xl:w-1/2 h-72 xl:pr-3 mb-4 mx-0 mt-0 md:w-full lg:px-3 md:px-1 px-3">
<AdvancedRealTimeChart theme="dark" symbol={symbol} interval="60" autosize></AdvancedRealTimeChart>
</div>
)
}
useEffect(() => {
returnWidget()
}, [symbol])
And I think I can use something like React.memo(). What would be the most modern and easiest way to do this? Thanks a lot everyone!

React re-render a component every time its state changes as well as re-render children.
It is difficult to provide you code examples without having your code (maybe you could provide your code?).
You could wrap your expensive children (like chart) in React.memo, so it would not re-rendered until it's props changes. This way the parent still re-renders as often as it's state changes but the child will skip this re-render if it's props the same.
Let's say the code bellow: you can click on the button and MyWidget re-renders because someState changed. But MyWidget is not re-rendered because the data is not changed.
function MyWidget(props) {
console.log("I was rendered");
return <div>{JSON.stringify(props.data)}</div>
}
const MyWidgetMemoized = React.memo(MyWidget);
function MyPage() {
const [someState, setSomeState] = useState(0);
const [data, setData] = useState({});
return (
<>
<button onClick={()=>setSomeState(s => s+1)}>
Counter {someState}
</button>
<MyWidgetMemoized data={data} />
</>
);
}

Finally managed to resolve this and I was on the right path before. It can be done using useEffect hook really easily just like I previously proposed.
useEffect(() => {
const widgetChart = <div className="mx-4 mb-14 mt-4 w-full xl:w-1/2 h-72 xl:pr-3 mb-4 mx-0 mt-0 md:w-full lg:px-3 md:px-1 px-3">
<AdvancedRealTimeChart theme="dark" symbol={symbol} interval="60" autosize </AdvancedRealTimeChart>
</div>
}, [symbol])
All you need to do apart from that is to add state, set it to widgetChart inside the hook, and then simply place the {widgetChart} in the JSX where it should be displayed.
const [chart, setChart] = useState(null);
useEffect(() => {
const widgetChart = (
<div className="mx-4 mb-14 mt-4 w-full xl:w-1/2 h-72 xl:pr-3 mb-4 mx-0 mt-0 md:w-full lg:px-3 md:px-1 px-3">
<AdvancedRealTimeChart theme="dark" symbol={symbol} interval="60" autosize />
</div>
);
setChart(widgetChart);
}, [symbol]);

Related

Tailwind CSS custom gradient with React state and FastAverageColor

I'm trying to add a custom gradient over an image using React state, Tailwind CSS and the FastAverageColor package (https://www.npmjs.com/package/fast-average-color) into my Next JS app.
I'm using an useEffect hook for this:
const [avgColor, setAvgColor] = useState("")
useEffect(() => {
const fac = new FastAverageColor()
fac.getColorAsync(songData.track.album.images[0].url, { algorithm: 'dominant' }).then(result => {
setAvgColor(`w-full h-full absolute bg-gradient-to-tr from-[${result.hex}] to-transparent`)
})
}, [avgColor, songData.track.album.images])
The JSX is presented below:
<div className="relative w-full h-full">
<Image priority alt="test" layout='fill' objectFit='cover' src={songData.track.album.images[0].url} />
{avgColor ? <div className={avgColor}></div> : null}
</div>
The problem is that my gradient doesn't appear over the image. Do you know maybe why this happens?
It's not possible to do with JIT classes but you can use from-current and set an inline color to set the from color.
So, try this:
setAvgColor(result.hex)
and
<div style={{color: avgColor}} className="w-full h-full absolute bg-gradient-to-tr from-current to-transparent"></div>
basic demo

React useEffect not updating when switching between pages

I have two states (userCustomers and loans) which are depending on a state coming from a custom hook (customers).
const [customers] = useLoanCustomers(state.contents.date)
const [userCustomers, setUserCustomers] = useState<DwCustomerHeadline[]>([])
const [loans, setLoans] = useState<DwLoanContract[]>([])
So I filter data depending on the output of the useLoanCustomers and have an empty array as initial state.
When I refresh the page, the useEffect works fine and sets the states of "userCustomers" and "loans"
But when I switch between pages/routes, the useEffect does not work and keeps the initial state [].
e.g this component is on 'http://localhost:3000/user/overview'.
if I go to another route like 'http://localhost:3000/clients' and then back to '/user/overview', the states "userCustomers" and "loans" are empty. So the useEffect does not set those states.
"useLoanCustomers" is loading as expected and holds all the data.
when I console log inside the useEffect, I can see that useEffect is running properly depending on the dependency change.
But it is not setting the other two states. As said, only when I refresh the page.
Has anyone a clue what's wrong, because I've already tried so much but can't figure it out.
This is my entire component
export const Overview: React.FC<OverviewProps> = ({ user }) => {
const { keycloak } = useKeycloak()
const { state } = useContext(DashboardContext)
const [customers] = useLoanCustomers(state.contents.date)
const [userCustomers, setUserCustomers] = useState<DwCustomerHeadline[]>([])
const [loans, setLoans] = useState<DwLoanContract[]>([])
useEffect(() => {
const filteredCustomers = customers.filter((customer) => customer.stafferEmail === user?.email)
setUserCustomers(filteredCustomers)
Promise.all(filteredCustomers.map((customer) => getCustomerLoans(keycloak, customer.counterpartyId))).then((res) =>
setLoans(res.flat())
)
}, [customers])
return (
<>
<div className="grid gap-4 p-2 md:grid-cols-3 sm:grid-cols-1">
<div className="col-span-2 shadow-sm">
<KeyMetrics />
</div>
<div className="shadow-sm">
<NextReview customers={userCustomers} />
</div>
</div>
<div className="grid gap-4 p-2 md:grid-cols-3 sm:grid-cols-1 grid-rows-2">
<div className="md:h-80 col-span-2 shadow-sm">
<ErpOverview customers={userCustomers} />
</div>
<div className="row-span-2 shadow-sm" style={{ height: '34rem' }}>
<Outflow loans={loans} endDate={state.contents.date} />
</div>
<div className="h-52 col-span-2 shadow-sm">
<LoanArrears customers={userCustomers} />
</div>
</div>
</>
)
}

Pass search value from one component to another container and use it in React

I am pretty new to React.
Previously the search functionality and the productsPage code lived in one file and search worked as expected.
Now I want to decouple the search functionality from productsPage.
This is products.js file where I need items to be searched.
function ProductsPage() {
var categories = data.categories;
const [search, setSearch] = useState(null);
return (
<>
<Container fluid id="searchBar">
<SearchForm searchValue={search} />
</Container>
<Container fluid id="productPage">
<h2 className="text-center py-5">Our Products</h2>
<CardColumns className="pt-3 mb-5">
{categories.map((catNames) => (
<Fragment key={uuid()}>
{catNames.sub_categories.map((subCat) => (
<Fragment key={uuid()}>
{subCat.items
.filter((item) => {
if (search == null) return item;
else if (
item.itemName
.toLowerCase()
.includes(search.toLowerCase())
) {
return item;
}
})
.map((item) => {
return (
<Card className="mb-4 p-3" key={uuid()}>
<div className="prodBorder">
<Card.Title className="pt-5 pb-4 text-center">
{item.itemName}
</Card.Title>
<Card.Img
src={`${item.image.url}`}
className="d-block w-50 mx-auto my-3"
/>
<Card.Subtitle className="pb-4 text-center text-muted">
{item.itemDesc}
</Card.Subtitle>
</div>
</Card>
);
})}
</Fragment>
))}
</Fragment>
))}
</CardColumns>
</Container>
</>
);
}
export default ProductsPage;
This one is search.js file where the onChange handler lives
function SearchForm(props) {
const [search, setSearch] = useState(null);
const searchSpace = (event) => {
let search = event.target.value;
setSearch(search);
};
return (
<Form inline className="position-relative mt-4 mx-auto w-75">
<FontAwesomeIcon icon={faSearch} className="productsSearchIcon" />
<FormControl
id="searchfield"
type="text"
placeholder="Search products..."
onChange={(e) => searchSpace(e)}
className="ml-0 ml-md-3 w-100"
/>
</Form>
);
}
export default SearchForm;
There are two ways you could do this. You could use state lifting.
Docs: https://reactjs.org/docs/lifting-state-up.html
I've made this example to show how that works:
https://codesandbox.io/s/stupefied-noether-rb1yi?file=/src/App.js
If your app is not small or is going to grow you are better using some sort of global state management such as redux or react context API
Docs: https://redux.js.org/tutorials/essentials/part-1-overview-concepts
Docs: https://reactjs.org/docs/context.html
I have made this example using the react context api to give you an idea of how global state management works.
https://codesandbox.io/s/confident-satoshi-upo4c?file=/src/App.js
You will see in this example you can pass data to nested components without the need to pass data through props. When your app grows in size this will make your code a lot cleaner, more efficient and less prone to error. Redux is an alternative (earlier and more popular solution) for global state management.
As a beginner you should try to understand the concepts of state lifting before global state management.
However, if your app is big with nested components, a global state management approach would be the preferred final solution.
I stumbled upon this question after searching a lot
How to pass state back to parent in React?
This is the explanation and answer that worked
https://stackoverflow.com/a/67084024/3807542

Tailwind conditional transition

I'm creating a collapse/accordion component that shows the content when clicked. I want to add a little delay to that action but what I've doesn't seem to work. Here is what I have:
const Accordion = ({ children, open }) => {
const contentSpace = useRef(null);
return (
<div className="flex flex-col">
<div
ref={contentSpace}
className={clsx(
'overflow-auto overflow-y-hidden h-0 transition-height duration-700 ease-in-out',
{
'h-full': open,
}
)}
>
<div className="py-2">{children}</div>
</div>
</div>
);
};
Any ideas?
Seems like you have to use numeric values in order to make the animation work. So I had to change h-full with a numeric height.

Gatsby don't change state with react hooks

I created a component for my gatsby site.
And I wanted to write a simply toggle functionality for it:
import React, { useState } from "react"
import './css/header.css'
function clicker(state) {
console.log(state);
}
const header = () => {
const [isExpanded, toggleExpansion] = useState(false)
return (
<div>
<nav className="main-nav">
<div className={'container container-wide'}>
{/* responsive toggle */}
<div className="block lg:hidden">
<button onClick={() => clicker(!isExpanded)}
className="flex items-center px-3 py-2 border rounded text-teal-200 border-teal-400 hover:text-white hover:border-white">
<svg className="fill-current h-3 w-3" viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"><title>Menu</title>
<path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z" />
</svg>
</button>
</div>
{/* Main Menu*/}
<div className={`${isExpanded ? `block` : `hidden`} w-full lg:flex lg:items-center lg:w-auto justify-end`} >
<div className="text-sm lg:flex-grow">
<a href="#responsive-header"
className="block mt-4 lg:inline-block lg:mt-0 text-teal-lightest hover:text-white mx-6">
Docs
</a>
<a href="#responsive-header"
className="block mt-4 lg:inline-block lg:mt-0 text-teal-lightest hover:text-white mx-6">
Examples
</a>
<a href="#responsive-header"
className="block mt-4 lg:inline-block lg:mt-0 text-teal-lightest hover:text-white mx-6">
Blog
</a>
</div>
</div>
</div>
</nav>
</div>
)
}
export default header;
The problem is: the state seems to be there.
When I click the link the clicker(state) function gives back,
whatever the initial state is.
But the toggleExpansion function does simple not work or trigger.
Or ... the component does not render according to the new state ..
I dont know.
Can somebody help?
When I use a class component it works fine - can someone tell my why?
First of all you are doing the you should capitalize the name of your component as recommended in the React Docs . The second thing is you should move your event handler logic into your Header component since it belongs to that component.
Inorder to trigger the state change fix the code in your event handler as shown in code below.
From your code I also see that you might also be missing another important concept about React Hooks. Note that inside any particular render, props and state forever stay the same and that each render has its own event handlers. Your event handlers only 'see' or 'close over(see closures)' the values of state and props for its particular render. Therefore console logging the state inside your event handler will always give you the state for the particular render. The call to setToggleExpansion only calls your function component again with the updated state and its own event handlers.
Hope that helps.
const Header = () => {
const [isExpanded, setToggleExpansion] = useState(false)
// event handler
function clicker() {
console.log(isExpanded); // logs false for the initial render
setToggleExpansion(prev => !prev);
console.log(isExpanded); // logs false for the initial render
}
return (
<button onClick={clicker}
//...
</button>
)
}
First your component name should start with capital letter as React recommended Just change it to Header
Second, change your toggle function like this:
const Header = () => {
const [isExpanded, toggleExpansion] = useState(false)
return (
<button onClick={() => toggleExpansion(!isExpanded)}
//...
</button>
)
}
And to use it just use && logical operator like this:
{
isExpanded && ( <div> ... </div> )
}

Resources