Components to navigate to another page with React Router - reactjs

is it proper to write components like this? I want to redirect user to another page after click on button in my menu? What should i change here? Maybe use Link instead of button?
import React from 'react';
import { useHistory } from 'react-router-dom';
import './MyAccountButton.scss';
const MyAccountButton = ({ path }) => {
const history = useHistory();
const handleButtonClick = () => {
history.push(path);
};
return (
<div className="my-account-button">
<button type="button" onClick={() => handleButtonClick()}>
My account
</button>
</div>
);
};
export default MyAccountButton;
PS. Any advices how to unit test this component with jest and enzyme??

Related

Button don't passing data to another component. React

I have a component which is a button. Then in another component i am looping trough concerts and using this button to redirect to booking page but after clicking my data is not passed.
This is my button component:
import React from "react";
export const BookBtn = (props) => {
return (
<div>
<button
className="bookBtn"
style={{ backgroundColor: props.color }}
// onClick={props.func}
>
{props.text}
</button>
</div>
);
};
BookBtn.defaultProps = {
text: "Unavailable",
};
export default BookBtn;
Here is the button in my main component where I try to click
<a href={"/concert/" + concert.id} data={concert}>
<BookBtn text="Book a ticket" />
</a>
Here is my component where i try to redirect to and retrive my data.
import React from "react";
import { useEffect, useState } from "react";
import axios from "axios";
export const Book = (data) => {
const [concerts, setConcerts] = useState([]);
const [tickets, setTickets] = useState([]);
useEffect(() => {
const loadConcerts = async () => {
const resConcerts = await axios.get("data/concerts");
const tickets = await axios.get("/data/tickets");
};
});
return (
<div>
Booking page
<h1>{data.name}</h1>
</div>
);
};
UPDATE:
I wrapped my button in anchor tag and now i am able to redirect but still can't pass data.
Final Update
Allright, i managed to pass my data using useLocation hook.
Problem is solved.
I'd suggest using react-router to do the redirection or routing instead of anchor tags as they cause a refresh.
Use the Link tag from react-router and pass the concert state along with it!
Have a look at this https://reactrouter.com/en/main/components/link.

React Hook "useNavigate" is called in function "welcome" that is neither a React function component nor a custom React Hook function

I am new to React and following a tutorial. I want to create a simple welcome page and have the continue button navigate to the next web page. I receive an error message of useNavigate is called in function welcome that is neither a react function component not a custom react hook function.
import React from "react";
import { AGREEMENT } from "../navigation/CONSTANTS";
import { Route, useNavigate, Routes } from "react-router-dom";
const welcome = () => {
const navigate = useNavigate();
return (
<div>
<div>Welcome!! </div>
<button onClick={() => navigate(AGREEMENT)}>Continue</button>
</div>
);
};
export default welcome;
Capitilize function name welcome to Wecome
const Welcome = () => { ... ... };
export default Welcome;

Trying to update url-parameter with onClick in React

I'm trying to setup a url that changes depending on the useState. I'm doing this because I want to be able to access both states with the url. So this is what my router file looks like:
import React from 'react';
import {BrowserRouter as Router, Switch, Route} from "react-router-dom";
function router(){
return (
<Router>
<Switch>
<Route path="/restaurant/:toggleParameter" children={<Restaurant/>}/>
</Switch>
</Router>
);
}
export default router;
An the component Restaurant looks like this:
import React, {useEffect, useState} from 'react';
import {useParams,Redirect} from "react-router-dom";
function RestaurantLandingPage(){
const {toggleParameter} = useParams();
console.log("Parameter");
console.log(toggleParameter);
const [profileToggle,setProfileToggle] = useState(toggleParameter);
const restaurantID =localStorage.getItem("restaurantId");
console.log(restaurantID);
const changeParameterToProfile =()=>{
setProfileToggle("profile");
};
const changeParameterToMenu=()=>{
setProfileToggle("menu");
}
return (
<div id="wrapper">
<div className="restaurantHover">
<button
className="switchButton"
onClick={()=>{changeParameterToProfile()}}
style={profileToggle==="profile"? {textDecoration:'underline',textDecorationThickness:'3px',textDecorationColor:'#6C5AF2'}:{}}
>
Profile
</button>
<button
className="switchButton"
onClick={changeParameterToMenu}
style={profileToggle==="menu"?{textDecoration:'underline',textDecorationThickness:'3px',textDecorationColor:'#6C5AF2'}:{}}
>
Menu
</button>
<div id="switchBottom"/>
{(profileToggle==="profile")&&(
<Contact profileToggle={profileToggle} changeParameterToMenu={changeParameterToMenu}/>
)}
{(profileToggle==="menu")&&(
<RestauarntMenuOverview/>
)}
</div>
</div>
)}
}
export default RestaurantLandingPage;
The url-param "toggleParameter" is "profile" or "menu". I'll access it with useParams(). Now if I press the button Profile the url-param "toggleParameter" should switch to Profile and if I press the button Menu the url-param "toggleParameter" should switch to Menu. I thought I could use Redirect like this:
<button
className="switchButton"
onClick={()=>{changeParameterToProfile();
<Redirect to={/restaurant/{profileToggle}/>}}
style={profileToggle==="profile"? {textDecoration:'underline',textDecorationThickness:'3px',textDecorationColor:'#6C5AF2'}:{}}>
Profile
</button>
But this doesn't work. I'm a little bit confused with all the react-router possibilities because I haven't found the right one yet.
React doesn't allow updating the state when the component is unmounted & it will cause some serious memory leak hidden before your eyes. Also setState function is an Asynchrounus function & it can get called after the history.push method. Changing the route with history.push will unmount the component & in some cases setState might get called afterwards causing state update on unmounted component. Also the value of profileToggle will only get changed after setProfileToggle is called keeping the value of profileToggle same & history.push will also use the previous value & you or the user have to click the button twice to go to /restaurant/profile or /restaurant/menu
Code:
import {useParams,useHistory } from "react-router-dom";
function RestaurantLandingPage(){
const history = useHistory()
const {toggleParameter} = useParams();
const changeParameterToProfile =()=>{
history.push(`/restaurant/profile`)
};
const changeParameterToMenu=()=>{
history.push(`/restaurant/menu`)
}
return(
......
<button
className="switchButton"
onClick={()=>changeParameterToProfile()}
style={toggleParameter==="profile"?
{ textDecoration:'underline',
textDecorationThickness:'3px',
textDecorationColor:'#6C5AF2'
}:{}
}
>
Profile
</button>
......
)
}
Try this
import {useParams,useHistory } from "react-router-dom";
function RestaurantLandingPage(){
const history = useHistory()
const {toggleParameter} = useParams();
const changeParameterToProfile =()=>{
setProfileToggle("profile");
history.push(`/restaurant/${profileToggle} `)
};
const changeParameterToMenu=()=>{
setProfileToggle("menu")
history.push(`/restaurant/${profileToggle} `)
}
return(
......
<button
className="switchButton"
onClick={()=>{changeParameterToProfile();}
style={profileToggle==="profile"? {textDecoration:'underline',textDecorationThickness:'3px',textDecorationColor:'#6
C5AF2'}:{}}>
Profile
</button>
<button
className="switchButton"
onClick={()=>{changeParameterToMenu();}
style={profileToggle==="profile"?
{textDecoration:'underline',textDecorationThickness:'3px',textDecorationColor:'#6
C5AF2'}:{}}>
Menu
</button>
......
)
}
Let me know if it works

How to properly export a component from a React custom hook and a function to control it?

What I want to do is to create a reusable and convenient way of showing an alert or a confirmation modal.
Using library modals usually require you to import a Modal component and create a state variable and pass it as a prop to the imported component to control its visibility.
What I want to do is to create a custom hook that exports a modal component with all the customization (maybe a wrapper around a Modal component from a library) and a function to toggle the visibility.
Something like below.
This is the hook code:
import React, {useState} from 'react'
import 'antd/dist/antd.css'
import {Modal as AntdModal} from 'antd'
const useModal = () => {
const [on, setOn] = useState(false)
const toggleModal = () => setOn(!on)
const Modal = ({onOK, ...rest}) => (
<AntdModal
{...rest}
visible={on}
onOk={() => {
onOK && onOK()
toggleModal()
}}
onCancel={toggleModal}
/>
)
return {
on,
toggleModal,
Modal,
}
}
export default useModal
And this is how I use it:
import React, {useState} from 'react'
import ReactDOM from 'react-dom'
import useModal from './useModal'
import {Button} from 'antd'
const App = () => {
const {toggleModal, Modal} = useModal()
return (
<div>
<Button type="primary" onClick={toggleModal}>
Open Modal
</Button>
<Modal title="Simple" onOK={() => alert('Something is not OK :(')}>
<p>Modal content...</p>
</Modal>
</div>
)
}
const rootElement = document.getElementById('root')
ReactDOM.render(<App />, rootElement)
Here is a sandbox to see it in action and test it out. There are two buttons, one which shows a Modal which is normally imported from the library (here antd) and one that is from a custom hook useModal.
The one form the hook works except it seems something is wrong with it. The appearing transition is working but when you close the modal it suddenly disappears with no transition. It seems the component is immediately destroyed before transitioning out. What am I doing wrong?
If I understand it correct, you want to render a Component and also need a function which can control it (toggle it's visibility).
Though it is not possible the way you are trying to achieve with the react hooks, because on state change you are actually updating your Modal too and that is causing an unmount of the Dialogue from DOM.
You can use below solution to achieve the same result. The Solution uses a component with forwardRef and useImperativeHandle and will achieve a decoupled function which you can use to toggle your dialogue using button click:
NOTE: You need to upgrade to react and react-dom from v-16.7.0-alpha (as in your sandbox code) to latest (16.14.0) [I have not tried other intermediate versions]
Modal Component:
import React, {useState, forwardRef, useImperativeHandle} from 'react'
import 'antd/dist/antd.css'
import {Modal as AntdModal} from 'antd'
const Modal = forwardRef(({onOK, ...rest}, ref) => {
useImperativeHandle(ref, () => ({
toggleModal: toggleModal
}));
const [on, setOn] = useState(false)
const toggleModal = () => setOn(!on)
return (
<AntdModal
{...rest}
visible={on}
onOk={() => {
onOK && onOK()
toggleModal()
}}
onCancel={toggleModal}
/>
)
});
export default Modal;
And this is how to use it:
import React, {useState, useRef} from 'react'
import ReactDOM from 'react-dom'
import Modal from './ModalWrapper'
import {Button, Modal as AntdModal} from 'antd'
const App = () => {
const [on, setOn] = useState(false)
const toggle = () => setOn(!on)
const modalRef = useRef()
return (
<div>
<Button type="warning" onClick={() => setOn(true)}>
Normal Import
</Button>
<br />
<br />
<Button type="primary" onClick={() => modalRef.current.toggleModal()}>
From Modal Component
</Button>
<AntdModal visible={on} onOk={toggle} onCancel={toggle}>
<p>I was imported directly...</p>
<p>I was imported directly...</p>
<p>I was imported directly...</p>
</AntdModal>
<Modal
title="Simple"
ref={modalRef}
onOK={() => alert('Things are now OK :)')}
>
<p>I was imported from Modal Component...</p>
<p>I was imported from Modal Component...</p>
<p>I was imported from Modal Component...</p>
</Modal>
</div>
)
}
const rootElement = document.getElementById('root')
ReactDOM.render(<App />, rootElement)
I hope it will help your use case.
Thanks.

Showing modal via method call in react app

In our react app (we use reactstrap), we've multiple pages from where a confirmation modal can be shown. We do not want to include the modal code in every page. Is there a way to do this programmatically by invoking a method?
We can use plain bootstrap modals directly in the public index.html and from the util method use dom selector and invoke the modal but want to avoid this. Any pointers on how to go about this?
If what you want is only one modal which can be used across multiple pages(instead of putting one modal in every page), you can put it in the root component usually names as App.
import Modal from "somewhere";
function App() {
const [modal, setModal] = useState(false);
return <>
<Model isOpen={modal} />
{/* If you need to toggle modal when clicking something in PageA, you can pass the prop down like this */}
<PageA onToggleModel={()=>{setModal(!modal)}} />
<PageB />
</>;
}
Just in case, doing import Modal from "somewhere" in every page wouldn't result in duplicate code in your final bundle. It's totally fine to do that.
Here's what we did.
Alert Component:
import React, { useState } from "react";
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from "reactstrap";
const Alert = props => {
const [modal, setModal] = useState(props.open ? props.open : true);
const toggle = () => {
setModal(!modal);
if (props.cb) {
props.cb();
}
if (props.reloadPage) {
window.location.reload();
}
};
return (
<div>
<Modal isOpen={modal} toggle={toggle}>
<ModalHeader toggle={toggle}>{props.title}</ModalHeader>
<ModalBody>{props.text}</ModalBody>
<ModalFooter>
<Button color="primary" onClick={toggle}>
Ok
</Button>
</ModalFooter>
</Modal>
</div>
);
};
export default Alert;
Util.js:
import React from "react";
import ReactDOM from "react-dom";
import Alert from "./Alert";
const Util = {
alert: (message, okCb, reload) => {
ReactDOM.render(
<Alert
title="Done"
text={message}
cb={() => {
ReactDOM.unmountComponentAtNode(
document.getElementById("modalHolder")
);
if (okCb) {
okCb();
}
}}
reloadPage={reload}
/>,
document.getElementById("modalHolder")
);
}
};
export default Util;
In index.html we created a dom element:
<div id="modalHolder"></div>
So to invoke the modal imperatively, call:
Util.alert("Data has been saved")

Resources