I a bit new to react but trying to pass data from one page into another.
Page A:
const PlanAction = tw.div`px-4 sm:px-8 xl:px-16 py-8`;
const BuyNowButton = styled(PrimaryButtonBase)`
${tw`rounded-full uppercase tracking-wider py-4 w-full text-sm hover:shadow-xl transform hocus:translate-x-px hocus:-translate-y-px focus:shadow-outline`}
`;
...
<PlanAction state={plan}>
<BuyNowButton
css={!plan.featured && highlightGradientsCss[index]}
as="a"
href="/payment"
state={plan}
>
{primaryButtonText}
</BuyNowButton>
</PlanAction>
Page B:
const location = useLocation();
I used location but only pathname is present, the state is undefined. I tried for couple of hours but don't understand why the data is not pass in other page. Thank you
edit Full code:
<Container>
<ContentWithPaddingXl>
<HeaderContainer>
{subheading && <Subheading>{subheading}</Subheading>}
<Heading>{heading}</Heading>
{description && <Description>{description}</Description>}
</HeaderContainer>
<PlansContainer>
{(plans|| []).map((plan, index) => (
<Plan key={index} featured={plan.featured}>
{!plan.featured && <div className="planHighlight" css={highlightGradientsCss[index % highlightGradientsCss.length]} />}
<PlanHeader>
<span className="name">{plan.product_name}</span>
<span className="price">$ {plan.price}</span>
<span className="duration">{plan.duration}</span>
</PlanHeader>
<PlanFeatures>
<span className="feature mainFeature">{plan.main_feature}</span>
{Object.values(plan.features_representation).map((feature, index) => (
<span key={index} className="feature">
{feature}
</span>
))}
</PlanFeatures>
<PlanAction state={plan}>
<BuyNowButton css={!plan.featured && highlightGradientsCss[index]}
as="a" href="/payment" state={"test"}
>
{primaryButtonText}
</BuyNowButton>
</PlanAction>
</Plan>
))}
<DecoratorBlob/>
</PlansContainer>
</ContentWithPaddingXl>
</Container>
You need to use a react-router link to pass a state, you could style a react-router link instead of a tw.button, maybe create something like a PrimaryButtonLink
import { Link } from "react-router-dom";
import styled from 'styled-components';
export const PrimaryButtonLink = styled(Link)`
${tw`px-8 py-3 font-bold rounded bg-primary-500 text-gray-100 hocus:bg-primary-700 hocus:text-gray-200 focus:shadow-outline focus:outline-none transition duration-300`}
`;
Also, note that the Link component receives a to prop, not a href
const BuyNowButton = styled(PrimaryButtonLink)`
${tw`rounded-full uppercase tracking-wider py-4 w-full text-sm hover:shadow-xl transform hocus:translate-x-px hocus:-translate-y-px focus:shadow-outline`}
`;
...
<BuyNowButton
css={!plan.featured && highlightGradientsCss[index]}
to={{
pathname:"/payment",
state: "test", // not sure if state can be a string, try an object if it doesn't work
}}
>
{primaryButtonText}
</BuyNowButton>;
Related
I made a function in my project that allows me to add and remove a file that will be uploaded, the intended function of adding and removing is implemented, however, when I try adding the file that I previously added (for the testing of adding and removing same files because it is a possible action of the user) it is not showing up. The removed file can only be added again if I select a different file first for preview then remove that different selected file, and selecting again the first removed file; which is not a good ux.
remove file function, I turned it into a global function because different pages uses this.
`
export const handleRemoveFile = (setSelectedFile) => {
setSelectedFile({ fileName: undefined, fileUrl: undefined, isImage: false });
};
`
attachment input component, (if you need to see it), I turned it into a global function because different pages uses this.
`
import React from "react";
export default function AttachmentInput(props) {
return (
<label
className={`${
props.hasSubmitted && "hidden"
} custom-input w-full custom-flex bg-white cursor-pointer text-sm font-Poppins font-semibold hover:shadow-md
tablet:text-base
laptop-l:text-lg`}
htmlFor={props.htmlFor}
>
<input
className="hidden"
type="file"
name={props.name}
id={props.id}
onChange={(e) => {
props.onChange1(e.target); // post data for upload
props.onChange2(e); // for file preview
}}
/>
<div>{props.selectedFile.fileUrl ? props.secondaryLabel : props.primaryLabel}</div>
</label>
);
}
`
**attachment preview **component, (if you need to see it), I turned it into a global function because different pages uses this.
import React from "react";
import logo from "../../";
export default function AttachmentPreview(props) {
return (
props.selectedFile.fileUrl && (
<div className="custom-flex w-full h-fit">
<div
className={
props.className
? `${props.className}`
: "custom-flex flex-col bg-white custom-light-border py-3 px-0"
}
>
{props.selectedFile.isImage ||
props.selectedFile.fileUrl.endsWith(".jpg") ||
props.selectedFile.fileUrl.endsWith(".png") ? (
<img
className={`max-h-36 rounded-lg`}
src={props.selectedFile.fileUrl}
alt="selected file"
/>
) : (
<img className="max-h-16" src={logo} alt="holder" />
)}
<div className="custom-divider my-4 w-full" />
<div className="font-Poppins font-light">
{props.selectedFile.fileName
? props.selectedFile.fileName
: props.postData.file_name
? props.postData.file_name
: "Current Uploaded File"}
</div>
</div>
</div>
)
);
}
usage of remove file function
{selectedFile.fileUrl && (
<CancelButton
onClick={() => file_fns.handleRemoveFile(setSelectedFile)}
label={"REMOVE FILE"}
/>
)}
Based on the toggle switch in the headlessui, I have like to place a text in between the switch. I came out with something like below but the text simply follow the inner rounded button. Is there a way that I can place the text(enable/disable) on the left or right hand side of the inner button?
https://headlessui.dev/react/switch
import React, { useState } from 'react'
import { Switch } from '#headlessui/react'
const App = () => {
const [enabled, setEnabled] = useState(false)
return (
<Switch.Group>
<div className='flex items-center'>
<Switch
checked={enabled}
onChange={setEnabled}
className={`${
enabled ? 'bg-blue-600' : 'bg-gray-200'
} relative inline-flex items-center h-12 rounded-full w-48 transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500`}
>
<span
className={`${
enabled ? 'translate-x-32' : 'translate-x-1'
} inline-block w-12 h-11 transform bg-white rounded-full transition-transform`}
>
{enabled ? 'Enable' : 'Disable'}
</span>
</Switch>
</div>
</Switch.Group>
)
}
export default App
You could give the span absolute class then replace translate-x-32 with left-0 and translate-x-1 with right-0
then add another span for the text and then add flex justify-center items-center to the Switch to justify the text in the center look here
I'm just learning React and Tailwind CSS and had a strange experience with CSS grid using Tailwind classes. I've made the buttons for a calculator, with the last Button spanning two columns:
App.js:
export default function App() {
return (
<div className="flex min-h-screen items-center justify-center bg-blue-400">
<Calculator />
</div>
);
}
Calculator.js
import { IoBackspaceOutline } from "react-icons/io5";
export const Calculator = () => {
return (
<div className="grid grid-cols-4 grid-rows-5 gap-2">
<Button>AC</Button>
<Button>
<IoBackspaceOutline size={26} />
</Button>
<Button>%</Button>
<Button>÷</Button>
<Button>7</Button>
<Button>8</Button>
<Button>9</Button>
<Button>x</Button>
<Button>4</Button>
<Button>5</Button>
<Button>6</Button>
<Button>-</Button>
<Button>1</Button>
<Button>2</Button>
<Button>3</Button>
<Button>+</Button>
<Button>0</Button>
<Button>.</Button>
<Button colSpan={2}>=</Button>
</div>
);
};
const Button = ({ colSpan = 1, rowSpan = 1, children }) => {
return (
<div
className={`col-span-${colSpan} row-span-${rowSpan} bg-white p-3 rounded`}
>
<div className="flex items-center justify-center">{children}</div>
</div>
);
};
This doesn't work (tested in Chrome):
Now here comes the weird part. I replaced the returned JSX from the App component with HTML from a Tailwind tutorial and deleted it again.
<div className="bg-blue-400 text-blue-400 min-h-screen flex items-center justify-center">
<div className="grid grid-cols-3 gap-2">
<div className="col-span-2 bg-white p-10 rounded">1</div>
<div className="bg-white p-10 rounded">2</div>
<div className="row-span-3 bg-white p-10 rounded">3</div>
<div className="bg-white p-10 rounded">4</div>
<div className="bg-white p-10 rounded">5</div>
<div className="bg-white p-10 rounded">6</div>
<div className="col-span-2 bg-white p-10 rounded">7</div>
<div className="bg-white p-10 rounded">8</div>
<div className="bg-white p-10 rounded">9</div>
</div>
</div>
After I Ctrl-Z'd a bunch of times, so I had only the previous code, my button suddenly spans two columns as intended:
I checked to make sure that there were no changes in the code:
My friend even cloned my repo, followed the same steps and got the same result.
He suspects that it has something to do with the variable classNames in my Button component with regards to Tailwind's JIT compiler, but none of us can pinpoint the error.
Am I using variable CSS classes wrong?
This has been a WTF moment. What could be the reason for this?
The CSS file generated by Tailwind will only include classes that it recognizes when it scans your code, which means that dynamically generated classes (e.g. col-span-${colSpan}) will not be included.
If you only need to span 2 columns, you could pass boolean values which will trigger the addition of a full col-span-2 or row-span-2 utility class to be added:
const Button = ({ colSpan = false, rowSpan = false, children }) => {
return (
<div
className={`${colSpan ? 'col-span-2' : ''} ${rowSpan ? 'row-span-2' : ''} bg-white p-3 rounded`}
>
<div className="flex items-center justify-center">{children}</div>
</div>
);
};
Otherwise, you could pass the values as classes to the Button component:
<Button className='col-span-2 row-span-1'>=</Button>
const Button = ({ className, children }) => {
return (
<div
className={`${className} bg-white p-3 rounded`}
>
<div className="flex items-center justify-center">{children}</div>
</div>
);
};
More information: https://tailwindcss.com/docs/content-configuration#dynamic-class-names
Another tricky solution that worked for me is to use variable with forced type of the possible className values (in typescript) like :
export type TTextSizeClass =
'text-xl' |
'text-2xl' |
'text-3xl' |
'text-4xl' |
'text-5xl' |
'text-6xl' |
'text-7xl' |
'text-8xl' |
'text-9xl'
;
...
const type : number = 6 ;
const textSizeClass : TTextSizeClass = type != 1 ? `text-${type}xl` : 'text-xl';
...
<div className={`font-semibold ${textSizeClass} ${className}`}>text</div>
As Ed Lucas said:
The CSS file generated by Tailwind will only include classes that it recognizes when it scans your code, which means that dynamically generated classes (e.g. col-span-${colSpan}) will not be included
But now could use safeListing
and
tailwind-safelist-generator package to "pregenerate" our dynamics styles.
With tailwind-safelist-generator, you can generate a safelist.txt file for your theme based on a set of patterns.
Tailwind's JIT mode scans your codebase for class names, and generates
CSS based on what it finds. If a class name is not listed explicitly,
like text-${error ? 'red' : 'green'}-500, Tailwind won't discover it.
To ensure these utilities are generated, you can maintain a file that
lists them explicitly, like a safelist.txt file in the root of your
project.
I am using this code snippet in a Gatsby project, It seems using AniLink inside another AniLink is not allowed, but I can not figure out the solution:
import React from 'react'
import AniLink from "gatsby-plugin-transition-link/AniLink";
const titleStyle = {
fontWeight: "700",
}
const authorLinkStyle = {
color: "#00BCD4"
}
const Author = ({ children, to }) => (
<AniLink style={authorLinkStyle} to={to} className="font-weight-bold" fade>
{children}
</AniLink>
)
const Card = ({ title, description, timeStamp, authorName, slug }) => {
const cardTextColor = typeof window !== "undefined" && getComputedStyle(document.documentElement).getPropertyValue("--card-text-color")
const cardLinkStyle = { color: cardTextColor, textDecoration: "none" }
return (
<AniLink
style={cardLinkStyle}
to={slug}
cover
bg="#00BCD4"
>
<div className="card my-4" >
<div className="card-body" >
<h5 className="card-title" style={titleStyle}>{title}</h5>
<p className="card-text">{description}</p>
<h6 className="card-subtitle text-muted">
<Author to="/about">{authorName}</Author> on {timeStamp}
</h6>
</div>
</div>
</AniLink>)
}
export default Card
I think errors happen from this line:
<Author to="/about">{authorName}</Author> on {timeStamp}
The error in F12:
Have anyone using AniLink and see this error?
Any suggestion would be great..
Thanks
Well, basically you spotted the mistake. You are wrapping a component (AniLink, which is an anchor, <a>) inside another anchor (Author component).
This, when compiled and transpiled into the output HTML, is creating an invalid structure of an anchor as a descendant of another anchor (<a><a>Link</a></a>). Is not an issue of AniLink nor React or Gatsby, is an issue of your component's structure.
The solution is simple, don't wrap it. You may want to do the something like:
<div className="card my-4" >
<div className="card-body" >
<AniLink
style={cardLinkStyle}
to={slug}
cover
bg="#00BCD4"
>
<h5 className="card-title" style={titleStyle}>{title}</h5>
<p className="card-text">{description}</p>
</AniLink>
<h6 className="card-subtitle text-muted">
<Author to="/about">{authorName}</Author> on {timeStamp}
</h6>
</div>
</div>
The snippet above won't create the error but you may need to tweak the layout to fit your specifications and design.
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> )
}