Simple question.
When I'm in a for instance /dashboard router and I click on <Link to="/users/:userID" > router and try to go back to /dashboard it works wine , but when from /users/:userID router I navigate to another /users/:userID router and try to go back I need to click the back button twice , any idea why?
e.g.
/dashboard -> /users/1 and back ( 1 click needed )
/dashboard -> /users/1 - > /users/2 and back to /users/1 ( 2 clicks
needed )
Here is my Route in App.js
<Route path='/users/:userId' render={()=><User/>} />
Here is my User.jsx render()
render() {
let movie = this.props.thisUserIdData;
const { match } = this.props;
console.log(match);
return (
<div> .... </div>
)
}
and the componentDidMount()
componentDidMount() {
this.loadData(this.props.match.params.userId);
//using redux and axios to get data
}
I had similar situation with nested onClick action.
For example:
<div onClick={() => push(`/user/${id}`)}>
<button onClick={() => push(`/user/${id}`)}>to user</button>
</div>
Related
I have the following code for a board component that is the main display on a website page.
A modal pop up window is defined such that when the url is matched with history.push(url) somewhere else in the code base, a corresponding modal window shall pop up and display on top of the current page. Clicking different buttons will open the modal window for corresponding items.
I'm stuck with the following situation:
Say there are two ways to trigger the same modal window of an item to open:
Clicking a button will open the modal window for the item;
drap and drop the button to some box will also open the modal window for the item.
The code is as below:
import React, { useState, Fragment } from "react";
import { Route, useRouteMatch, useHistory, Switch } from 'react-router-dom';
const Board = () => {
const match = useRouteMatch();
const history = useHistory();
... other code ...
return (
<Fragment>
... some other components ...
<Route
path={`${match.path}/items/:itemId`}
render={routeProps => (
<Modal
isOpen
onClose={()=>history.push(match.url)}
renderContent={modal => (
<PopupWindow
itemId={routeProps.match.params.itemId}
test={routeProps.match.state.trigger}
/>
)}
/>
)}
/>
</Fragment>
);
}
In other scripts, I have defined button components like this:
<ItemLink
to={{
pathname: `${match.url}/items/${item.id}`,
state: { trigger: 'fromClick'}
}}
ref={provided.innerRef}
>
<Item>
<Title>{item.title}</Title>
<Bottom>
<div>
... some code ...
</div>
</Bottom>
</Item>
</ItemLink>
such that clicking different buttons will open the modal window for corresponding items.
and for method 2, drap and drop the button to some box will also open the modal window for the item:
const match = useRouteMatch();
const history = useHistory();
const handleItemDrop = () => {
... some code ...
history.push({
pathname: `${match.url}/items/${itemId}`,
state: {trigger: 'fromClick'}
})
}
such that when item is dropped, history.push(url) is triggered and the modal window should pop up.
I'm passing a hidden parameter state.trigger to record how the url is triggered.
However, I got the following error:
TypeError: Cannot read property 'trigger' of undefined
renderContent
src/components/Board.js:99
96 | renderContent={modal => (
97 | <PopupWindow
98 | itemId={routeProps.match.params.itemId}
> 99 | test={routeProps.match.state.trigger}
| ^ 100
How can I get the hidden parameter?
I got the answer myself 5 minutes after posting this question:
change routeProps.match.state.trigger
to
routeProps.location.state.trigger
I have been working on an inventory management system built in ReactJS with a back-end MariaDB database. The architecture includes user registration and logon modules with JWT authentication, a simple dashboard using D3, an inventory display screen using the React-Data-Table-Component, and an add inventory module. I will use the add-inventory module in the edit-inventory functionality by passing in a parameter to differentiate between add or edit mode (I have not yet implemented that parameter). A user can click on a row on the inventory display screen to pull that record up in edit mode, but currently I am not getting to the page to allow for inventory edit.
My code (inventorylist.component.jsx) looks like this:
updateRecord = row => {
this.setState({
...this.state,
selectedRow: row,
editSelectedRow: true
}, () => {
console.log('UPDATED STATE:',this.state.selectedRow)
})
}
editRecord = (event) => {
event.preventDefault();
return <Link to='/add' />
//return <Redirect to='/add' />
// return (
// <>
// <Router>
// <Switch>
// <Route path='/add' component={AddInventory} />
// </Switch>
// </Router>
// </>
// )
}
render() {
const inventoryTitle = this.props.jwt.username + "'s Inventory"
return (
<>
<DataTable
title={inventoryTitle}
striped={true}
highlightOnHover={true}
fixedHeader={true}
pagination={true}
paginationPerPage={10}
onRowClicked={this.updateRecord}
columns={columns}
data={this.state.inventory}
/>
<button type='button' onClick={this.editRecord}>Edit</button>
</>
)
}
This is not navigating to the add-inventory component. I have put a console.log into the editRecord function to verify that it is getting into that function after clicking on the button, but that's as far as it seems to be going.
I'm sure I'm just missing something fairly obvious here, but does anyone have any suggestions?
You can use history object to navigate imperatively:
history.push(<path>);
Read more about history
The problem is that you are return a Link which is a JSX element. What you need to do is one of the following:
If you want to keep the button element, you can push a new route into the history like this: history.push('/edit')
If you want to use Link, you need to replace you button with it and, when you click the link, it will automatically navigate there
With Hooks
If you were using hooks, you could use the useHistory hook provided by react-router to navigate to a different route doing something like this:
const history = useHistory();
const editRecord = () => {
history.push('/app');
};
I am having a Next JS app where there are very simple two pages.
-> Home page
import Header from "../components/header";
const handleForm = () => {
console.log("trigger");
};
export default () => (
<>
<Header />
<h1>Home</h1>
<form onSubmit={handleForm}>
<input type="text" placeholder="Username" />
<input type="password" placeholder="Password" />
<button type="submit"> Login </button>
</form>
</>
);
-> About page
import Header from "../components/header";
export default () => (
<>
<Header />
<h1>About us</h1>
</>
);
Requirement:
-> Home page has a login form
-> If user started typing in any of the fields then without submitting the form, if he tries to move to About us page then a warning needs to be displayed something similar like beforeunload_event.
I am not sure how we can handle it in react as I am new to it.. Kindly please help me to handle a alert if user trying to navigate to other url while editing the form fields..
From my understanding, you can achieve your goal by listen the event routeChangeStart as then throws exception in case of rejecting to move the target url.
I forked above codesandbox and created a simple demo based on your idea which doesn't allow to switch page in case of username having value (form is dirty).
Here is the general idea:
import router from "next/router";
export default () => {
// Assume this value holds the status of your form
const [dirty, setDirty] = React.useState();
// We need to ref to it then we can access to it properly in callback properly
const ref = React.useRef(dirty);
ref.current = dirty;
React.useEffect(() => {
// We listen to this event to determine whether to redirect or not
router.events.on("routeChangeStart", handleRouteChange);
return () => {
router.events.off("routeChangeStart", handleRouteChange);
};
}, []);
const handleRouteChange = (url) => {
console.log("App is changing to: ", url, ref.current);
// In this case we don't allow to go target path like this
// we can show modal to tell user here as well
if (ref.current) {
throw Error("stop redirect since form is dirty");
}
};
return (
// ...
)
}
The link codesandbox is here https://codesandbox.io/s/react-spring-nextjs-routes-forked-sq7uj
i am building a project, in which when user click the buyNow button in Basket (child 2) it will pass the props back to parent where it further pass it to another child in Signin(child 3) where we call an API call(inside useEffect) to update the mysql database but it seems that the API call is called twice as in database two records are being created and in front end i got two identical invoices record but different file names.
Any suggestion guys why i am facing this, please note if i remove the useEffect statement from the Signin it will then continuously non-stop call the API call so i think i cant remove the useEffect, other then this i cant see why it is happening. any suggestion please.
Main (APP)___|
|_Child 1(Home)
|_Child 2 (Basket)
|_Child 3 (signin)(API triggers here)---Sub Child 3-1 (useraccount)
Update-1: After removing the strictMode it does solve the issue, but does it means if i temporarily fixed the issue or if i have to use the stricMode and find the real problem
Child 2- Basket- Customer press the buyNow button and it triggers resetBasket function
const buyNow = (basketItems) => {
resetBasket(basketItems);
window.location.href = "http://localhost:3000/signin";
};
<ButtonGroup aria-label="quantityofproduct">
<Button variant="secondary" name="subtract" value="subtract" onClick={() => buyNow(basketItems)}>
Buy Now
</Button>
</ButtonGroup>
Main App resetBasket takes the basketitems and pass to parent element
const [finalBuy, setfinalBuy] = useState(finalbuyitems());
const resetBasket = (basketItems) => {
setfinalBuy(basketItems);
window.localStorage.setItem("user-final", JSON.stringify(basketItems));
}
<Route
path="/basket"
exact
render={(props) => (
<Basket {...props} userData={userData} userstatus={siginalready} basketItems={basketItems} updatedBasket={updatedBasket} resetBasket={resetBasket} />
)}
/>
<Route
path="/signin"
exact
render={(props) => <Signin {...props} buyNow={buyNow} resetBuynow={resetBuynow} userData={userData} finalBuy={finalBuy} userstatus={siginalready} />}
/>
Child 3 - Signin ,here we call the API call(using useEffect) and update the Mysql server and recieve the invoice in PDf format from backend
const [allInvoices, setallInvoices] = useState([]);
// The API call in the useEffect is triggering twice and thats why i am getting two invoices and two record at backend
useEffect(() => {
const headers = new Headers();
headers.append("content-type", "application/json");
const options = {
method: "POST",
headers,
credentials: "include",
body: JSON.stringify(),
};
const newRequest = new Request("http://localhost:5000/api/invoice-all", options);
(async () => {
const invoiceFetch = await fetch(newRequest)
.then((data) => {
return data.json();
})
.then((data1) => {
setallInvoices(data1);
})
.catch();
})();
// }, []);
return <div>{userstatus ? <Useraccount userinfo={userData} userstatus={userstatus} finalBuy={finalBuy} allInvoices={allInvoices} /> : <SigninOptions />}</div>;
Sub Child-Useraccount then it display the items recieved from the backend-mysql nodejs
// here the return is showing two different invoices of same items bought i.e two times the API is being called
return (
allInvoices.map((eachInvoice, index) => {
........................................})
I think the <React.StrictMode> is causing double execution. Please take a look after removing it from your top level component.
Please refer This Link for more details on StrictMode double execution.
To keep it simple, the detail page fetches data on mount based on the movie ID in the URL, this coming from path='movie/:id' in the Route.
It's child is called Recommended, which shows you recommended movies based again on the current URL.
class MovieDetailPage extends React.Component {
// Fetch movies and cast based on the ID in the url
componentDidMount() {
this.props.getMovieDetails(this.props.match.params.id)
this.props.getMovieCast(this.props.match.params.id)
}
render() {
<div>
Movies here
</div>
<Recommended id={this.props.match.params.id}/>
}
}
The Recommended component fetches data based on the current movie as well and generates another tag pointing to another movie.
class Recommended extends React.Component {
componentDidMount() {
this.props.getRecommended(this.props.id)
}
render() {
return (
<>
<Category title={'Recommended'}></Category>
<div className="movies">
{
this.props.recommended.map((movie) => {
return (
<Link key={movie.id} to={`movie/${movie.id}`} className="movies__item">
<img
key={movie.id}
src={`https://image.tmdb.org/t/p/w342${movie.poster_path}`}
className="movies__item-img"
alt={`A poster of ${movie.title}`}
>
</img>
</Link>
)
})
}
</div>
</>
)
}
}
Now how can I trigger another render of the parent component when clicking the Link generated in the Recommended component? The URL is changing but this won't trigger a render like I intent to do.
UPDATE:
<Route
path="/movie/:id"
render={(props) => (
<MovieDetailPage key={props.match.params.id}
{...props}
)}
/>
I passed in a unique key this time that triggered the re-render of the page. I tried this before but I might've screwed up the syntax.
This post got me in the right direction: Force remount component when click on the same react router Link multiple times
Add a key to the page
If you change route but your page is not getting its "mount" data then you should add a key to the page. This will cause your page to rerender and mount with the new id and get the data again.
You can read more about react keys here
A key tells react that this is a particular component, this is why you see them in on lists. By changing the key on your page you tell react that this is a new instantiation of the component and has changed. This will cause a remount.
Class component example
class MyPage extends React.Component {
componentDidMound() {
// this will fire each time the key changes since it triggers a mount
}
render() {
return (
<div key={props.pageId}>
{/* component stuff */}
</div>
)
}
}
Functional component example
const MyPage = (props) => {
React.useEffect(() => {
// this will fire each time the key changes
}, []);
return (
<div key={props.pageId}>
{/* component stuff */}
</div>
)
}
You can add another React lifecycle method that triggers on receiving new props (UNSAFE_componentWillReceiveProps, componentDidUpdate, getDerivedStateFromProps) in your Recommended component like this:
UNSAFE_componentWillReceiveProps(nextProps) {
if (nextProps.id !== this.props.id) {
nextProps.getRecommended(nextProps.id);
};
}
You can also add key to your component (which forces it to re-render completely if key changed) like this:
<Recommended key={this.props.match.params.id} id={this.props.match.params.id}/>
You can also use React Hooks to handle this more easily with useEffect:
const Recommended = (props) => {
const { id, getRecommended, recommended } = props;
useEffect(() => {
id && getRecommended(id);
}, [id]);
return (
<>
<Category title={'Recommended'}></Category>
<div className="movies">
{recommended.map((movie) => {
return (
<Link key={movie.id} to={`movie/${movie.id}`} className="movies__item">
<img
key={movie.id}
src={`https://image.tmdb.org/t/p/w342${movie.poster_path}`}
className="movies__item-img"
alt={`A poster of ${movie.title}`}
></img>
</Link>
);
})}
</div>
</>
);
};
Note: adding key to component and complete its re-render is not best practice and you should be using Component's lifecycles to avoid it if possible