Tailwind conditional transition - reactjs

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.

Related

Can't stop widget chart from rerendering in React

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]);

How do I put my button on the same line as my Component Title?

how can I modify my Title.jsx code in order to have the possibility to give to this component another component i.e a button inside my Title component ?
Because if I want to display a button in the same line (on the right) as my title it's not possible, it's displayed under it (see picture). So do I have to go through this way or just using tailwindcss (which is not working if do right-0 left-0) ??
export default function Title({ className, title, children }) {
return (
<Format className={className}>
<h1>{title}</h1>
<hr />
{children}
</Format>
)
}
export default function Format({ className, children }) {
return (
<div className={`${className ?? ''}`}>
{children}
</div>
)
}
export default Display() {
return (
<Title>
<button> Button </button>
</Title>
}
Here is my code
Here the picture:
Maybe you should try to change order and encapsulate items inside Title's component:
export default function Title({ className, title, children }) {
return (
<Format className={className}>
<Format className="flex">
<h1 className="flex-1">{title}</h1>
{children}
<Format/>
<hr/>
</Format>
)
}
And add className what you want for new div wrapper, for example, flex and h1 title with flex-1 to expand it. If children is null, title will expand fullWidth.
I've updated answer and assume you will use div wrapper with Format component.
Guide in tailwind
You can use the flex property to achieve the same layout that you want. The idea is to keep the Title and Button in the same div with each of them are also enclosed within their separate div or span. And add flex justify-between items-center in the main div.
<script src="https://cdn.tailwindcss.com"></script>
<div class="p-4 flex items-center bg-slate-200 justify-between">
<div><h4 class="font-semibold"> My Title</h3></div>
<div><button class="bg-purple-700 text-white px-4 py-2 rounded-md">Click</div>
</div>

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>
</>
)
}

React accordion with correlating image outside accordion section

I can't find any examples of accordions where the active class is related to an element outside of the accordion. I'm trying to get an image to change on the side of the accordion, where each image is related to a specific object. I managed to get something working using absolute positioning, but I'm looking for a more elegant solution so I can manipulate styling better.
I can get it to work while the image is inside the accordion under the info text, but can't figure out the styling issue. I think I need to do some refactoring or do away with the array mapping to get it to work but I'm not sure.
Here is a codesandbox of more or less what I want to achieve but without the restriction of absolute positioning - https://codesandbox.io/s/ecstatic-taussig-f084t?file=/src/App.js
You can remove your img tag from your renderedItems and do something like this:
import React, { useState } from "react";
const Accordion = ({ items }) => {
const [activeIndex, setActiveIndex] = useState(0);
const onTitleClick = (index) => {
setActiveIndex(index);
};
const renderedItems = items.map((item, index) => {
const active = index === activeIndex ? "active" : "";
return (
<div key={item.title}>
<div className={`title ${active}`} onClick={() => onTitleClick(index)}>
<i className="dropdown icon"></i>
{item.title}
</div>
<div className={`content ${active}`}>
<p>{item.content}</p>
</div>
</div>
);
});
return (
<div className="container-gallery">
<div className="ui styled accordion">{renderedItems}</div>
<img
className={`content `}
src={`${items[activeIndex].image}`}
style={{
height: "200px",
width: "200px"
}}
alt="img"
/>
</div>
);
};
export default Accordion;
And for the style I don't know what you are using so I made css for the example:
.container-gallery{
display:flex;
flex-wrap:no-wrap;
justify-content: space-between;
}
here a sandBox link

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