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> )
}
Related
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]);
This question already has an answer here:
How to prevent default when click on icon inside a anchor link list?
(1 answer)
Closed 10 months ago.
I have a component entirely linked in a <Link> tag but I have inside an icon that I want clickable without activating the link. Is there a way to escape the icon?
<Link to={`/post/${id}`} className='flex flex-row px-3 py-2'>
<div>Some content</div>
<i onClick={anAction}>An Icon</i>
<div>Some content</div>
</Link>
You should use e.preventDefault()
<Link to={`/post/${id}`} className='flex flex-row px-3 py-2'>
<div>Some content</div>
<i onClick={(e) => {e.preventDefault(); //rest of your function}}>An Icon</i>
<div>Some content</div>
</Link>
This is an example of a component that stops the default behavior, but I highly discourage doing this kind of stuff overall. I would rather wrap only what can be clicked in the <Link> if possible (in your use case it’s fine) .
import { Link } from "react-router-dom";
const Test = () => {
const handleOnClick = (e) => {
e.preventDefault();
console.log("hello");
// ...rest
};
return (
<Link to={"/another-page"}>
Some text
<i onClick={handleOnClick}>Hello</i>
</Link>
);
};
export default Test;
Generally you should avoid rendering interactive elements inside other interactive elements. If you just want the icon alone to be clickable and not activate the link, but anywhere else within the link should activate it, then prevent the click event in the icon from propagating and allowing the default link action to occur.
Example:
const anAction = e => {
e.preventDefault();
e.stopPropagation();
...
};
...
<Link to={`/post/${id}`} className='flex flex-row px-3 py-2'>
<div>Some content</div>
<i onClick={anAction}>An Icon</i>
<div>Some content</div>
</Link>
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>
</>
)
}
I have a multi step form and would like my path to be changed depending on form step. I found out there is smth. like shallow routing which can be used for this purpose. So in my form step number zero component I added Router and the inscrease function.
export default function ContractInfo({ formStep, prevFormStep, nextFormStep, selected }) {
const router = useRouter();
const { query } = router;
const { formStepsNumer } = query;
const increase = () => {
var newStepNumber = + 1;
router.push(`/?step=${newStepNumber}`, undefined, {
shallow: true
});
};
........ more irrelevant code
Attention, I am not giving all the code, otherwise it will get too complicated. Before adding shallow routing, everything worked. Problem now is, that my URL gets changed onClick, as I wished, but the next form step won't render anymore.
..... above irrelevant code
<div className="mt-3 sm:mt-0 sm:ml-3">
<a
//onClick={nextQuizStep}
onClick={() => nextFormStep(contracts?.[isSelected]), increase }
className="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-white bg-yellow-500 hover:bg-yallow-600 md:py-4 md:text-lg md:px-10"
>
Next
</a>
</div>
Basically NextFormStep()which worked before, now is broken. Any ideas why?
Somehow I had to write my function directly inside on onClick:
onClick={() => {
nextFormStep(contracts?.[isSelected]);
{router.push(`/?step=${+1}`, undefined, {
shallow: true
});
};
}
}
I know this code really is ugly. If you have any refractoring suggestions, please welcome!
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.