I was hoping to find some help with this issue I have been having converting a file into a class based Component. I've struggled with this one for awhile, and I just cannot get the onClick to work correctly. Everything else renders fine, but the click function simply does not work. I would greatly appreciate any tips, feedback, or help!
import * as React from 'react';
import "./bsi-tabs.scss"
import IBsiTabProps from "./ibsi-tab-props"
function Tab ({
onClick = function(){return; },
tabIndex = '',
tabID = ''
}: IBsiTabProps) {
return (
<li>
<a
onClick={(event) => {
event.preventDefault();
onClick(tabIndex);
}}
href="/#"
>
{tabID}
</a>
</li>
);
}
export default BsiTab;
my attempt at conversion :
import * as React from 'react';
import "./bsi-tabs.scss"
import IBsiTabProps from "./ibsi-tab-props"
/*
Credit https://gist.github.com/diegocasmo/5cd978e9c5695aefca0c6a8a19fa4c69 for original
js files, edited by Robert McDonnell to convert to typescript
*/
export class BsiTab extends React.Component<IBsiTabProps, any> {
onClick = function(){return; }
tabIndex = ''
tabID = ''
render(){
return (
<li>
<a
onClick={(event) => {
event.preventDefault();
this.onClick(this.props.tabIndex);
}}
href="/#"
>
{this.props.tabID}
</a>
</li>
);
}
}
export default BsiTab;
interface :
export default interface IBsiTabProps {
onClick ?: Function;
tabIndex ?: Number | string;
tabID: String;
children : React.ReactNode;
}
Related
I have an entire React app built with adding navigation to onClick method of <p> tag, for example: <p onClick={()=>history.push('/contact-us')}>Contact Us</p> (as I didn't want page reload on navigation, something which is expected from SPA). Now the client wants options on right click which the browser provides by default for <a> tag (like open link in new tab, open link in incognito, see image for more details). How can I achieve this by making least amount of change to my code base? The last thing that I would want to do is replace all <p onClick={}> with <a> tag.
Instead of using an tag, it is better to use <NavLink> for links. This behaves like a normal <a> tag but also does not reloads the page (as expected from a SPA).
As for opening the link in a new tab, add these attributes to your :
target="_blank" rel="noopener noreferrer". This will open the link in a new tab.
More on that here: https://www.freecodecamp.org/news/how-to-use-html-to-open-link-in-new-tab/#:~:text=The%20target%20attribute%20set%20to,depending%20on%20the%20browser's%20settings
Use Link tag you can import from react-router-dom library i.e.,
import { Link } from 'react-router-dom';
<Link to={'/contact-us'}>Contact Us</Link>
I build a components that you could customize however you like.
have a look below
import * as React from 'react'
import { Link, useSearchParams, useNavigate } from 'react-router-dom';
import usestate from '#alentoma/usestate';
export const parseLink = (to: string, args?: any[], encryptedArgs?: any[], clean?: boolean) => {
var url = to;
args?.forEach(x => {
var str = (x ?? "").toString();
if (clean)
str = x.replace(/[ ]/gmi, "-");
str = encodeURI(str.replace(/\?/gmi, ""));
url = str.uri(url)
});
encryptedArgs?.forEach(x => {
// encrypt the string you will have t implement this on you own if you use it
var item = HttpClient.enscriptString(x).toString();
url = item.uri(url);
})
return url;
}
export default ({ clean, className, title, to, children, args, encryptedArgs, onClick, disablenavigation }: { disablenavigation?: boolean, onClick?: Function, clean?: boolean, className?: string, title?: string, to: string, children?: string | JSX.Element | JSX.Element[], args?: string[], encryptedArgs?: string[] }) => {
const state = usestate({
path: "",
})
const navigate = useNavigate();
React.useEffect(() => {
state.path = parseLink(to, args, encryptedArgs, clean);
}, [])
return (
<a className={className} onClick={(e) => {
e.preventDefault();
if (onClick != undefined) {
onClick();
}
if (disablenavigation !== true)
navigate(state.path);
return false;
}} title={title} href={state.path}>
{children}
</a>
)
}
Now you could simple do.
<EncryptedLink to='/detail' args={["405"]} title='test title'>
<p>
Home
</p>
</EncryptedLink>
// You could also add click event and navigaion at the same times
<EncryptedLink to='/detail' args={["405"]} onClick={dosomething} title='test title'>
<p>
Home
</p>
</EncryptedLink>
// You could also disable navigation and add click event instead
<EncryptedLink to='/detail' args={["405"]} disablenavigation={true} onClick={dosomething} title='test title'>
<p>
Home
</p>
</EncryptedLink>
I am building a library of components and I need some of them to have a customizable tag name. For example, sometimes what looks like a <button> is actually a <a>. So I would like to be able to use the button component like so:
<Button onClick={onClick}>Click me!</Button>
<Button as="a" href="/some-url">Click me!</Button>
Ideally, I would like the available props to be inferred based on the "as" prop:
// Throws an error because the default value of "as" is "button",
// which doesn't accept the "href" attribute.
<Button href="/some-url">Click me!<Button>
We might need to pass a custom component as well:
// Doesn't throw an error because RouterLink has a "to" prop
<Button as={RouterLink} to="/">Click me!</Button>
Here's the implementation, without TypeScript:
function Button({ as = "button", children, ...props }) {
return React.createElement(as, props, children);
}
So, how can I implement a "as" prop with TypeScript while passing down the props?
Note: I am basically trying to do what styled-components does. But we are using CSS modules and SCSS so I can't afford adding styled-components. I am open to simpler alternatives, though.
New answer
I recently came across Iskander Samatov's article React polymorphic components with TypeScript in which they share a more complete and simpler solution:
import * as React from "react";
interface ButtonProps<T extends React.ElementType> {
as?: T;
children?: React.ReactNode;
}
function Button<T extends React.ElementType = "button">({
as,
...props
}:
ButtonProps<T>
& Omit<React.ComponentPropsWithoutRef<T>, keyof ButtonProps<T>>
) {
const Component = as || "button";
return <Component {...props} />;
}
Typescript playground
Old answer
I spent some time digging into styled-components' types declarations. I was able to extract the minimum required code, here it is:
import * as React from "react";
import { Link } from "react-router-dom";
type CustomComponentProps<
C extends keyof JSX.IntrinsicElements | React.ComponentType<any>,
O extends object
> = React.ComponentPropsWithRef<
C extends keyof JSX.IntrinsicElements | React.ComponentType<any> ? C : never
> &
O & { as?: C };
interface CustomComponent<
C extends keyof JSX.IntrinsicElements | React.ComponentType<any>,
O extends object
> {
<AsC extends keyof JSX.IntrinsicElements | React.ComponentType<any> = C>(
props: CustomComponentProps<AsC, O>
): React.ReactElement<CustomComponentProps<AsC, O>>;
}
const Button: CustomComponent<"button", { variant: "primary" }> = (props) => (
<button {...props} />
);
<Button variant="primary">Test</Button>;
<Button variant="primary" to="/test">
Test
</Button>;
<Button variant="primary" as={Link} to="/test">
Test
</Button>;
<Button variant="primary" as={Link}>
Test
</Button>;
TypeScript playground
I removed a lot of stuff from styled-components which is way more complex than that. For example, they have some workaround to deal with class components which I removed. So this snippet might need to be customized for advanced use cases.
I found that you can make the same thing with JSX.IntrinsicElements. I have Panel element:
export type PanelAsKeys = 'div' | 'label'
export type PanelAsKey = Extract<keyof JSX.IntrinsicElements, PanelAsKeys>
export type PanelAs<T extends PanelAsKey> = JSX.IntrinsicElements[T]
export type PanelAsProps<T extends PanelAsKey> = Omit<PanelAs<T>, 'className' | 'ref'>
I omitted native types like ref and className because i have my own types for these fields
And this how props will look look like
export type PanelProps<T extends PanelAsKey = 'div'> = PanelAsProps<T> & {}
const Panel = <T extends PanelAsKey = 'div'>(props: PanelProps<T>) => {}
Then you can use React.createElement
return React.createElement(
as || 'div',
{
tabIndex: tabIndex || -1,
onClick: handleClick,
onFocus: handleFocus,
onBlur: handleBlur,
onMouseEnter: handleMouseEnter,
onMouseLeave: handleMouseLeave,
'data-testid': 'panel',
...rest
},
renderChildren)
I see no ts errors in this case and have completions for label props like htmlFor
I'm building a toast component in React and I'm want to create it so the user can simply import the component and then not have to add any extra code other than calling methods.
For e.g.
Import Toast from './components/Toast;
And then call methods to show different toasts depending on what they want for e.g
Toast.success();
And this will return a toast with success styling and content
Or Toast.error();
And this will return a toast with error styling and content
I don't want them to do anything other than call the method after importing.
I'm struggling to think of the way you would do this.
Here is my current code:
If you look at the 'CSToastAPI' section that's where I'm struggling how I would be able to do that and call in another component. Thanks.
import React, { Fragment } from 'react';
import CSIcon from './CSIcon';
import classNames from 'classnames';
import ReactDOMServer from 'react-dom/server';
export interface CSToastProps {
className?: string;
closeButton?: boolean;
detail?: string;
iconName?: string;
iconVisibility?: boolean;
minWidth?: string;
onClose?: undefined;
text?: string;
textAlign?: string;
position?: string;
}
export interface CSToastState {
variant?: string;
}
const CSToastAPI = {
success: () => {
this.setState({
variant: 'success'
});
}
}
class CSToast extends React.Component<CSToastProps, CSToastState> {
public static defaultProps = {
iconVisibility: true
};
constructor(props: any) {
super(props);
this.state = {
variant: ''
}
}
render() {
const toastClasses = classNames(
'cs-toast-wrapper',
'top-right',
{
[`${this.props.position}`]: this.props.position
}
);
const CSToastHTML = (
<>
<div className={toastClasses}>
<div
style={{minWidth: this.props.minWidth}} className={`cs-toast ${this.state.variant ? `cs-toast-${this.state.variant}` : ''} ${this.props.textAlign ? this.props.textAlign : ''} ${this.props.className ? this.props.className : ''}`} role="alert"
>
{this.props.iconVisibility ? (this.props.iconName ? (
<CSIcon name={this.props.iconName}/>
) : (
<CSIcon name={this.state.variant}/>
)) : null}
<h4 className="cs-toast-text">
{this.props.text}
{this.props.detail ? (
<div className="cs-toast-detail">{this.props.detail}</div>
) : null}
{this.props.children}
</h4>
{this.props.closeButton ? (
<button className="cs-toast-close" onClick={this.props.onClose} aria-label="close">
<CSIcon name="close"/>
</button>
) : null}
</div>
</div>
</>
);
const CSToastAPIView = ReactDOMServer.renderToStaticMarkup(CSToastHTML);
document.body.insertAdjacentHTML("afterbegin", CSToastAPIView);
CSToastAPI.success();
return (
<div>
{CSToastHTML}
</div>
);
}
}
export default CSToast;
so im trying to export some classes from my musicplayer file, playlist, setMusicIndex and currentMusicIndex
const playlist = [
{name: 'September', src: september, duration: '3:47'},
{name: 'hello again', src: hello, duration: '04:19'},
]
const MusicPlayer = () => {
const [currentMusicIndex, setMusicIndex] = useState(0)
function handleClickPrevious() {
setMusicIndex(currentMusicIndex === 0 ? playlist.length - 1 : currentMusicIndex - 1)
}
function handleClickNext() {
setMusicIndex(currentMusicIndex < playlist.length - 1 ? currentMusicIndex + 1 : 0)
}
return (
<AudioPlayer
autoPlay={true}
showSkipControls={true}
showJumpControls={false}
volume="0.2"
onClickPrevious={handleClickPrevious}
onClickNext={handleClickNext}
onEnded={handleClickNext}
src={playlist[currentMusicIndex].src}
/>
)
}
export default MusicPlayer;
then i want to import those into my music file to make a playlist,
class Music extends Component {
render() {
return (
<div className="wrapper">
<div className="music-playlist">
<ul>
{playlist.map((song, i) => (
<li
role="menuitem"
tabIndex={0}
onClick={() => setMusicIndex(i)}
onKeyPress={() => { }}
key={i}
className={`${currentMusicIndex === i && 'playing'}`}>
{currentMusicIndex === i && <i className="fa fa-play" aria-hidden="true"></i>} {song.name}
<div className="song-duration">{song.duration}</div>
</li>
))}
</ul>
</div>
</div>
)
}
}
export default Music;
i did read up on exports since im new to js and react, and i tried
export default MusicPlayer, playlist, setMusicIndex, currentMusicIndex;
import MusicPlayer, {playlist, setMusicIndex, currentMusicIndex } from '../../Components/MusicPlayer/MusicPlayer'
import { playlist, setMusicIndex, currentMusicIndex } from '../../Components/MusicPlayer/MusicPlayer'
import * from '../../Components/MusicPlayer/MusicPlayer'
and im still getting error saying
Attempted import error: 'playlist' is not exported from '../../Components/MusicPlayer/MusicPlayer'.
anyone willing to export those for me
In your case these 3 are not classes, playlist is an array, currentMusicIndex is an integer and setMusicIndex is a function, and the three are not exported so you can't import them in another component like this.
you can:
Create a parent container that contains both MusicPlayer and Music, and set the playlist, currentMusicIndex and setMusicIndex in this <ParentContainer />, then pass them as props to its MusicPlayer and Music children
Use Redux
Use React Context
you are only declaring playlist variable but not exporting it. Just add the keyword export for your variable so it becomes a named export:
export const playlist = [
{name: 'September', src: september, duration: '3:47'},
{name: 'hello again', src: hello, duration: '04:19'},
]
then you can just import that using named import:
import {playlist} from ''path/to/your/jsFile'
Trying to set the font color through a variable inside a react component, but my <span> gives error:
Type '{ style: "color:yellow"; }' is not assignable to type 'HTMLProps<HTMLSpanElement>'
The yellow is just for testing, I will replace with {color} in the end.
import * as React from 'react';
export interface SkillListItemProps {
name: string
color: string
// Icons:
}
interface SkillListItemState{
}
export class SkillListItem extends React.Component<SkillListItemProps,SkillListItemState>{
public render(): JSX.Element{
const {name, color} = this.props;
return (
<div className="SkillListItem">
<span style="color:yellow">{name}</span>
</div>
)
}
}
You must use an object, like this:
<span style={{color: "yellow"}}>{name}</span>
JSFiddle Demo: https://jsfiddle.net/4wcmcpv3/1/