Using React hook in exported const - reactjs

I have 6 buttons as my action buttons so I decided to use an array with object in them and then mapping over them like this :
export const ACTION_BUTTONS = [
{
btnColor: "error",
onclick,
Icon: <Cancel fontSize="large" />,
title:"Cancel Operation",
className: eachAction,
},
......
]
but my buttons have onClick property too. for example :
onClick={() => setCancelRedirect(true)}
to use setCancelRedirect I need to define my state inside of my ACTION_BUTTONS.js file but it is not possible because it is not a custom hook or react function as they mentioned in https://reactjs.org/docs/hooks-rules.html
how can I solve this problem?
I tried to add onclick empty as you can see and then defining it during mapping but this is not possible.
{ACTION_BUTTONS.map((Btn) => (
<span className={Btn.className}>
<IconButton color={Btn.btnColor}>{Btn.Icon}</IconButton>
<p>{Btn.title}</p>
</span>
))}
a certain onClick will apply on all elements and I don't want this.

Related

Defining Components in React

I am creating a react website in which many pages have a specific button that should look the same for all. Should this button be its own component? If so, how would I specify the onClick events to be different for each button if it is a component?
Yes, it should be its own component.
Create it in a separate file so you can import them where you need to use.
The component should receive a onClick prop that you pass to the internal button tag.
See this example: https://reactjs.org/docs/components-and-props.html#composing-components
export const Button = ({ label, onClick, disabled }) => {
return (
<button
onClick={onClick}
disabled={disabled}
>
{label}
</button>
)
}
and then you can export this component inside any of the components you want, and pass in values like onClick and label to make it more dynamic
export const DemoFunction () => {
const onClickHandler = () => {}
return (
<Button label="Click" onClick={onClickHandler} />
)
}

react-popper incorrect position on mount

I have built a custom tree view in React, and each item contains a dropdown which is positioned using Popper. Since the child elements are not visible on render, Popper is not positioning the dropdown correctly, for example:
When the tree is open on mount (i.e the children are visible), the positioning is correct:
Each level in the tree is rendered via a CategoryNavItem component, which essentially looks like this:
<div className={ className.join(' ') }>
<div className={ `collection-nav_item-link depth${depth}` } style={{ paddingLeft: `${paddingLeft}px`}}>
<Link to={ linkTo } onClick={() => { setIsOpen(!isOpen) }}>
<i className="collection-nav_item-link_icon"></i>
<span className="collection-nav_item-link_text">{ category.name }</span>
</Link>
<Dropdown
toggleClassName="btn-icon-white btn-sm"
toggleContent={ <Icon name="ellipsis-h" />}
position="bottom-end"
size="small"
items={[
{ text: 'Edit category' },
{ text: 'Add subcategory', onClick: (() => { dispatch(openAddSubcategory(category)) }) }
]} />
</div>
{ children }
</div>
The Dropdown component is where we use Popper, and it works well everywhere else. The visibility of a CategoryNavItem is handled via the component's state in React.
Is there any way to trigger Popper's update() method programmatically in React? We should force update when toggling the item's visibility.
It turns out we just need to expose the update property from the usePopper hook, and then call it when setting the dropdown's visibility, for example:
const { styles, attributes, update } = usePopper(referenceElement, popperElement, {
placement: placement,
modifiers: [
{ name: 'arrow', options: { element: arrowElement } },
{ name: 'offset', options: { offset: [ 0, 3 ] } }
]
});
And similarly:
const toggleDropdown = (e) => {
e.preventDefault();
e.stopPropagation();
setVisible(!visible);
update();
};
According to the docs, you can manually update the propper instance so that it recomputes the tooltip position:
Manual update
You can ask Popper to recompute your tooltip's position by running instance.update().
This method will return a promise, that will be resolved with the updated State, from where you will optionally be able to read the updated positions.
const state = await popperInstance.update();
When clicking on your item visibility toggle, you could add your popper manual update, like the line of code above.
Here is the reference.

Using Stateful React classes in typescipt

I am trying to create a Stateful class in which you can call methods such as createHeaderButton() where after calling it would update the state and re-render with these new updates in the component.
Im using Material-UI and so most of their styling utilizes Reacts hook API which of course classes cant use. Ive tried to get around this by using;
export default withStyles(useStyles)(HeaderBar)
Which exports the class separately with the Styles(withStyles(useStyles) useStyles as the defined styles) And the class(HeaderBar). Now the only issue is that i need to access the styles in my class. Ive found a JS example online that wont work for me because of the strong typed syntax of TS. Additionally When initializing my Class component in other places i try to get the ref=(ref:any)=>{} And with that call the create button methods when i get a response from my server, Which doesnt work because of this new way of exporting the class component!
Thanks for the help, Heres my component class: https://pastebin.pl/view/944070c7
And where i try to call it: https://pastebin.com/PVxhKFHJ
My personal opinion is that you should convert HeaderBar to a function component. The reason that it needs to be a class right now is so you can use a ref to call a class method to modify the buttons. But this is not a good design to begin with. Refs should be avoided in cases where you can use props instead. In this case, you can pass down the buttons as a prop. I think the cleanest way to pass them down is by using the special children prop.
Let's create a BarButton component to externalize the rendering of each button. This is basically your this.state.barButtons.forEach callback, but we are moving it outside of the HeaderBar component to keep our code flexible since the button doesn't depend on the HeaderBar (the header bar depends on the buttons).
What is a bar button and what does it need? It needs to have a label text and a callback function which we will call on click. I also allowed it to pass through any valid props of the material-ui Button component. Note that we could have used children instead of label and that's just down to personal preference.
You defined your ButtonState as a callback which takes the HTMLButtonElement as a prop, but none of the buttons shown here use this prop at all. But I did leave this be to keep your options open so that you have the possibility of using the button in the callback if you need it. Using e.currentTarget instead of e.target gets the right type for the element.
import Button, {ButtonProps as MaterialButtonProps} from "#material-ui/core/Button";
type ButtonState = (button: HTMLButtonElement) => void;
type BarButtonProps = {
label: string;
callback: ButtonState;
} & Omit<MaterialButtonProps, 'onClick'>
const BarButton = ({ label, callback, ...props }: BarButtonProps) => {
return (
<Button
color="inherit" // place first so it can be overwritten by props
onClick={(e) => callback(e.currentTarget)}
{...props}
>
{label}
</Button>
);
};
Our HeaderBar becomes a lot simpler. We need to render the home page button, and the rest of the buttons will come from props.childen. If we define the type of HeaderBar as FunctionComponent that includes children in the props (through a PropsWithChildren<T> type which you can also use directly).
Since it's now a function component, we can get the CSS classes from a material-ui hook.
const useStyles = makeStyles({
root: {
flexGrow: 1
},
menuButton: {
marginRight: 0
},
title: {
flexGrow: 1
}
});
const HeaderBar: FunctionComponent = ({ children }) => {
const classes = useStyles();
return (
<div className={classes.root}>
<AppBar position="static">
<Toolbar>
<HeaderMenu classes={classes} />
<Typography variant="h6" className={classes.title}>
<BarButton
callback={() => renderModule(<HomePage />)}
style={{ color: "white" }}
label="Sundt Memes"
/>
</Typography>
{children}
</Toolbar>
</AppBar>
</div>
);
};
Nothing up to this point has used state at all, BarButton and HeaderBar are purely for rendering. But we do need to determine whether to display "Log In" or "Log Out" based on the current login state.
I had said in my comment that the buttons would need to be stateful in the Layout component, but in fact we can just use state to store an isLoggedIn boolean flag which we get from the response of AuthVerifier (this could be made into its own hook). We decide which buttons to show based on this isLoggedIn state.
I don't know what this handle prop is all about, so I haven't optimized this at all. If this is tied to renderModule, we could use a state in Layout to store the contents, and pass down a setContents method to be called by the buttons instead of renderModule.
interface LayoutProp {
handle: ReactElement<any, any>;
}
export default function Layout(props: LayoutProp) {
// use a state to respond to an asynchronous response from AuthVerifier
// could start with a third state of null or undefined when we haven't gotten a response yet
const [isLoggedIn, setIsLoggedIn] = useState(false);
// You might want to put this inside a useEffect but I'm not sure when this
// needs to be re-run. On every re-render or just once?
AuthVerifier.verifySession((res) => setIsLoggedIn(res._isAuthenticated));
return (
<div>
<HeaderBar>
{isLoggedIn ? (
<BarButton
label="Log Out"
callback={() => new CookieManager("session").setCookie("")}
/>
) : (
<>
<BarButton
label="Log In"
callback={() => renderModule(<LogInPage />)}
/>
<BarButton
label="Sign Up"
callback={() => renderModule(<SignUpPage />)}
/>
</>
)}
</HeaderBar>
{props.handle}
</div>
);
}
I believe that this rewrite will allow you to use the material-ui styles that you want as well as improving code style, but I haven't actually been able to test it since it relies on so many other pieces of your app. So let me know if you have issues.

How to only select element from current component in React

I am completely new to react. I created a component like this:
import React from "react"
function Product(props) {
return (
<div>
<h2>{props.product.name}</h2>
<button onClick={() => document.querySelector(".desc").style.display="block"}>More info</button>
<p className="desc" style={{display: "none"}}>{props.product.price.toLocaleString("en-US", { style: "currency", currency: "USD" })} - {props.product.description}</p>
</div>
)
}
export default Product
Then I render a few of the components in the App. The purpose of the button is that it should show the information about the specific component. However this way, Im always selecting the first element with "desc" class name.
How to select the element from the current component easily?
How to select the element from the current component easily?
You wouldn't do it like that with react. The way to alter what your app shows is to alter the state. Create a component that has state that reflects what should be rendered. Then when an interaction happens (e.g. a button click) alter the state. React will re-render and show your app according to the new state:
function Product(props) {
// create a state that holds the information if details should be displayed
// we initially set it to false
const [showDetails, setShowDetails] = useState(false);
// create a callback that toggles the state
// we will pass that to the onClick handler of our button
const handleToggleInfo = () => setShowDetails(current => !current);
return (
<div>
<h2>{props.product.name}</h2>
<button onClick={handleToggleInfo}>More info</button>
{showDetails && (
/* this will only get rendered if showDetails is true */
/* you don't need any css to "hide" it, it will just not render in the first place */
<p className="desc">
{`${props.product.price.toLocaleString("en-US", {
style: "currency",
currency: "USD"
})} - ${props.product.description}`}
</p>
)}
</div>
);
}
Generally speaking it isn't necessary with react to select DOM elements and alter them. This is all handled by state updates and re-rendering the components. A component describes the DOM given a current state and props. If you want your DOM to change describe that with state and props.
If you are not familiar with state in react you should really search for a good tutorial that covers that. It is a basic core concept of react. Without it you will not be able to create an app that has interactivity.
Tip: If you want to display a string that is constructed from multiple parts use template strings.
This is not reactish, you need to leverage your UI changes to the state
function Product(props) {
const [isDescVisible, setIsDescVisible] = useState(false);
return (
<div>
<h2>{props.product.name}</h2>
<button onClick={() => setIsDescVisible(true)}>More info</button>
{isDescVisible &&
<p className="desc">
{props.product.price.toLocaleString("en-US", { style: "currency", currency: "USD" })} - {props.product.description}
</p>
}
</div>
)
}

Best way to design a modal form in react / ionic

I'm learning react so I'm building a weight tracker.
I have different pages where i ask some datas. So i want to develop a modal form.
I have already have a Modal component from Ionic.
So i builded a ModalForm with an header with close, a cancel and a ok button.
Inside the content i render props.childrens.
Something like that
<App>
<ModalForm>
<Input>
</ModalForm>
</App>
On pressing "Ok" the component will give the input value to the parent via callback.
That value will be validated ( so i cant give the value onChange).
But it will need to know the values of childrens input.
Moreover the parent will have control of inputs ( and validation ), that is not a thing that i like.
I can let the modal choose what inputs render with an internal switch, but it cant be reused for other porpuse.
Should abandon childrens and found another way ?
Please give me some advice on how composite my components to achieve this results.
Thank you
I've found a way.
I have a parent component, that is like a wrapper or a decorator, but is lower than my final component.
interface ModalProps {
title: string,
show: boolean,
setShow: Function,
value: number | string,
onSave: Function
}
const ModalInput: React.FC<ModalProps> = (props) => {
var { show, setShow, title, value, onSave } = props;
return (
<IonPopover isOpen={show} onDidDismiss={() => { setShow(false); }}>
<IonContent class="ion-text-center modal-content">
<IonCard>
<IonCardHeader>
<IonCardTitle>{title}</IonCardTitle>
</IonCardHeader>
<IonCardContent className="text-center">
{props.children}
</IonCardContent>
</IonCard>
<IonFooter>
<IonButton color="light" onClick={() => { setShow(false); }}>Cancel</IonButton>
<IonButton color="primary" onClick={() => { onSave(value); setShow(false); }}><IonIcon slot="start" icon={save} /> Save</IonButton>
</IonFooter>
</IonContent>
</IonPopover>
);
};
export default ModalInput;
It tooks the props to open/close the modal, a title, one props to get the child value and onSave that is a callback from app.
Then i wrote a more higher component with the implementation of the children.
All the props goes to the ModalInput wrapper.
interface InputProps {
onSave: Function,
show: boolean,
setShow: Function,
defaultValue: number
}
const WeightInput: React.FC<InputProps> = ({show, setShow, defaultValue, onSave}) => {
const [value, setValue] = useState<number>(defaultValue);
return (
<ModalInput show={show} setShow={setShow} title="Starting weight" value={value} onSave={onSave}>
<IonItem>
<IonInput value={value} onIonChange={e => setValue(parseFloat(e.detail.value!))}></IonInput>
</IonItem>
</ModalInput>
);
}
export default WeightInput;
And finally how to use it :
<WeightInput show={showWeight} setShow={setShowWeight} defaultValue={weight} onSave={(w:number) => setWeight(w)}/>
So it works like that :
on input change, the input call setValue for changing the state
on state change will change also the props for the modal component
on OK click the modal component will trigger the onSave props, that come directly from the app
So the app should care only about value and the state of the modal.
The modal component should care only about his value and open state and callback on ok
The higher component contain the input logic and pass pther props to modal component.
Maybe this is not the best way, but is the best i could develop with my limited knowledge

Resources