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!
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:
Conditionally set background color in React component with Tailwind CSS
(1 answer)
Closed last month.
I want to use MUI theming in my otherwise Tailwind-styled page so I can centrally handle the color palettes for dark and light mode since I found the decentral dark and light mode theming provided by Tailwind not very practicable in case I want to change colors.
My approach is to use the Tailwind syntax for arbitrary values and pass the hex code from the theme.js color palette.
the Tailwind arbitrary syntax is:
bg-[#hex]
e.g.:
bg-[#50d71e]
which just works fine in normal classNames, e.g.:
<div className={`bg-[#50d71e]`}>
<div/>
gives me a green background (#50d71e) in that container.
On the other side, I have a mui-themed template page. Here I can reference colors as follows:
import {useTheme} from "#mui/material";
import { tokens } from "../../theme";
const AnyName = () => {
const theme = useTheme();
const colors = tokens(theme.palette.mode);
return (
<Box backgroundColor={colors.grey[400]}></Box>
<Box sx={{backgroundColor: colors.grey[500],}}></Box>
}
export default AnyName;
And again, it just works fine.
Now, if I want to fuse these two approaches, I am struggling to query the hex code out of the const colors = tokens(theme.palette.mode);.
The theme.js holds all color values as hex, so I think this should be possible. Here is a look inside:
export const tokens = (mode) => ({
...(mode === 'dark'
?
{
grey: {
900: "#141414",
800: "#292929",
700: "#3d3d3d",
600: "#525252",
500: "#666666",
400: "#858585",
300: "#a3a3a3",
200: "#c2c2c2",
100: "#e0e0e0",
},
} : {
grey: {
100: "#141414",
200: "#292929",
300: "#3d3d3d",
400: "#525252",
500: "#666666",
600: "#858585",
700: "#a3a3a3",
800: "#c2c2c2",
900: "#e0e0e0",
},
}),
});
** What I have tried: **
const AnyName = ({ anyProp }) => {
const theme = useTheme();
const colors = tokens(theme.palette.mode);
return (
<div
className={`bg-[${colors.grey[400]}]`}
></div>
);
};
export default AnyName;
This doesn't work. I guess it is a matter of scoping, but I am not sure, and I honestly do not know what I am doing.
So I tried a few more things like:
className={`bg-[{colors.grey[400]}]`}
Which doesn't seem to reference colors at all.
And:
className={`bg-[colors.grey[400]]`}
Which also does not reference colors.
All three approaches result in a transparent background.
Now I am here, hoping for your help. Thanks.
I suspect this to work, But you are not able to see the background color because the there is no children for this div, which makes it height of 0
Try adding height:
<div className={`bg-[${colors.grey[400]}] h-24`}/>
Thanks for your answer. Up there is not the complete code. I tried to keep it short for the post. There are children, and if I set the background using normal Tailwind syntax, e.g., bg-[#50d71e], it works as expected. Here is the full return statement, unmodified:
return (
<div
className={`bg-[#50d71e] shadow-lg rounded-lg p-0 lg:p-8 pb-12 mb-8`}
>
<div className="relative overflow-hidden shadow-md pb-80 mb-6">
<img
src={post.featuredImage.url}
alt={post.title}
className="object-top absolute h-80 w-full object-cover shadow-lg rounded-t-lg lg:rounded-lg"
/>
</div>
<h1
className="transition duration-700 text-center mb-8 cursor-pointer
hover:[text-cyan-400] text-3xl font-semibold"
>
<Link href={`/post/${post.slug}`}>{post.title}</Link>
</h1>
<div className="block lg:flex text-center items-center justify-center mb-8 w-full">
<div className="flex items-center justify-center mb-4 lg:mb-0 w-full lg:w-auto mr-8">
<img
alt={post.author.name}
height="30px"
width="30px"
className="align-middle rounded-full"
src={post.author.photo.url}
/>
<p className="inline align-middle text-gray-700 ml-2 text-lg">
{post.author.name}
</p>
</div>
<div className="font-medium text-gray-700">
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6 inline mr-2 text-cyan-600"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
<span>{moment(post.createAt).format("DD. MMM, YYYY")}</span>
</div>
</div>
<p className="text-center text-lg text-gray-700 font-normal px-4 lg:px-20 mb-8">
{post.excerpt}
</p>
<div className="text-center">
<Link href={`/post/${post.slug}`}>
<span className="transition duration-500 transform hover:-translate-y-1 inline-block bg-cyan-500 text-lg font-medium rounded-full text-white px-8 py-3 cursor-pointer">
Continue Reading
</span>
</Link>
</div>
</div>
);
Since the VS Code autocompletion works as intended when using the $-approach, I guess maybe it is not or not only a matter of scope.
I messed around a bit more and found something strange.
In the following example I will be using colors.grey[900] which is #141414.
If I manually enter bg-[#141414], the background will be dark-grey (obviously).
If I then enter bg-[${colors.grey[900]}], it works just fine.
So I guessed, maybe that's just some caching.
I double-checked --> chrome --> F12 --> disable cache --> page refresh --> still works fine.
I double-checked again, differently by changing the hex to a different value, e.g. bg-[${colors.grey[800]}] --> doesn't work. Changing it back to bg-[${colors.grey[900]}] --> works again.
Checked it on a different Browser which always clears cache and never loaded the page before: still works.
Restart Server: and bg-[${colors.grey[900]}] also does not work anymore.
Steps to reproduce:
doesn't show grey background
const AnyName = ({ anyProp }) => {
const theme = useTheme();
const colors = tokens(theme.palette.mode);
const hex = colors.grey[900];
return (
<div
className={`bg-[${hex}]`}
></div>
);
};
export default AnyName;
shows grey Background
const AnyName = ({ anyProp }) => {
const theme = useTheme();
const colors = tokens(theme.palette.mode);
const hex = colors.grey[900];
return (
<div
className={`bg-[#141414]`}
></div>
);
};
export default AnyName;
shows grey Background
const AnyName = ({ anyProp }) => {
const theme = useTheme();
const colors = tokens(theme.palette.mode);
const hex = colors.grey[900];
return (
<div
className={`bg-[${hex}]`}
></div>
);
};
export default AnyName;
doesn't show grey background (changed colors.grey to 800)
const AnyName = ({ anyProp }) => {
const theme = useTheme();
const colors = tokens(theme.palette.mode);
const hex = colors.grey[800];
return (
<div
className={`bg-[${hex}]`}
></div>
);
};
export default AnyName;
shows grey background even in different browser without caching
const AnyName = ({ anyProp }) => {
const theme = useTheme();
const colors = tokens(theme.palette.mode);
const hex = colors.grey[900];
return (
<div
className={`bg-[${hex}]`}
></div>
);
};
export default AnyName;
So I guess there must be some weird server-side binding issue that I overcome by once providing directly the correct value and afterwards it seems to be cached on server-side and works. Once I restart the server, everything is gone and I have to go through steps 1,2,3 to make it work again.
Any ideas? Workarounds also welcome.
edit:
I found this thread:
JIT tailwindcss using variable in bg-[] not rendering color
Unfortunately, I did not find it before, even after reviewing related threads.
This sheds a lot of light on the issue.
I am having some problems with the useState async behavior that can also be related to Redux which I am new at it.
import React, { useState, useEffect } from "react"
import { useSelector, useDispatch } from "react-redux"
import { Link } from "react-router-dom"
import {
getAlltopics,
joinAtopic,
leaveAtopic,
} from "../../../redux/actions/topicActions"
import Icon from "../Icon"
const TopicCard = ({ topics }) => {
const user = useSelector((state) => state.user)
const [join, setJoin] = useState(false)
const dispatch = useDispatch()
console.log(join)
const leaveTopicHandler = async () => {
setJoin(false)
dispatch(leaveAtopic(topics._id))
}
const JoinTopicHandler = () => {
setJoin(true)
dispatch(joinAtopic(topics._id))
}
useEffect(() => {
const checkJoinedUser = () => {
topics.members.map((member) => {
if (member._id === user?._id) setJoin(true)
})
}
checkJoinedUser()
dispatch(getAlltopics())
}, [join, dispatch])
return (
<div
key={topics._id}
className="flex flex-col justify-between w-48 h-72 bg-white shadow-xl rounded-br-3xl rounded-bl-3xl rounded-tr-3xl"
>
<Link to={`/topics/${topics._id}`}>
<section className="">
<img
src={topics.bannerImage}
alt="topic_Image"
className="object-cover h-48 w-full rounded-tr-3xl"
/>
</section>
<section className="border-b-2 border-grey-light ml-3 mr-3 h-12 flex items-center">
<h1 className="text-lg">{topics.title}</h1>
</section>
</Link>
<section>
<div className="flex justify-between">
<div className="flex p-3">
<Icon iconName="member" iconStyle="fill-inactive text-grey-dark" />
<span>{topics.members?.length}</span>
<Icon iconName="file" iconStyle="fill-inactive text-grey-dark" />
<span>{topics.recources?.length}</span>
</div>
<div className="p-3">
{join ? (
<button type="button" onClick={leaveTopicHandler}>
<Icon
iconName="follow"
iconStyle="fill-active text-grey-dark"
/>
</button>
) : (
<button type="button" onClick={JoinTopicHandler}>
<Icon
iconName="follow"
iconStyle="fill-inactive text-grey-dark"
/>
</button>
)}
</div>
</div>
</section>
</div>
)
}
I have defined a join variable to handle a button that depending on if it is true or false will show or not some aspect, also, if it is false the user can join the topic, if it is true the user can leave the topic as it is noticeable in the functions JoinTopicHandler and leaveTopicHandler. Before joining a topic it looks like this:before joining a topic, as it is possible to see, the join variable it's set to false, because I am not in the topic. When joining the topic, after joining the topic, the joinvariable is set to true, the button changed, although the user count didn't changed for 2 (sometimes it does, sometimes I have to refresh the page for it to render), but the weirdest thing is when leaving the topic, as it's shown in the console,leaving the topic the join variable turns to false but then by it self turns again to true and the button still looks the same and I can not fix this...
Without knowning what the leaveAtopic function does exactly, my guess is that since join is in your useEffect hook dependencies, what happens from clicking the leave button is:
leaveTopicHandler is run
setJoin(false) causes re-render and since join is a dependency of the useEffect, we run it again
dispatching leaveAtopic starts but I'm assuming there is async logic there
topics hasn't changed yet so when the useEffect runs topics.members still contains the user
You probably don't even need useState/useEffect for join, and instead could do something like:
const join = !!topics.members.find((member) => member._id === user?._id))
So I had a few mistakes starting from the backend of my app, first of all, I wasn't returning the updated topic because I didn't set the call correctly.
const addUserToTopicDb = async (topicId, user) => {
try {
return await Topic.findByIdAndUpdate(topicId, {
$push: { members: user },
}, {new: true});
} catch (error) {
throw new Error(error);
}
};
I had to add the {new: true} to get the updated topic. This was a major error. Second, my code on the reducer wasn't working properly as I am new at it and learning by solving this kind of problems.
I changed my reducer code to:
case JOIN_TOPIC:
return {
allTopics: state.allTopics.map((eachTopic) =>
eachTopic._id === action.payload.topic._id
? action.payload.topic
: eachTopic
),
}
case LEAVE_TOPIC:
return {
allTopics: state.allTopics.map((eachTopic) =>
eachTopic._id === action.payload.topic._id
? action.payload.topic
: eachTopic
),
}
This basically means that if I have a topic with the same _id, replace it with the new updated topic that I wasn't returning in the beginning.
After this, everything started to work smoothly. Shame on me to assume that the problem was due to the useState... when it was all along in my database set up and reducer...
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.
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> )
}