ReactJS: Link to go to next page dynamically - javascript

I have this App.jsx that has the routing and I have a component NextPage.jsx that is a simple Link that should point to the next page. My issue is:how can I tell to the link in NextPage component to point to the next page? so if I am in Homepage the link should let me go to Portfolio, if on Portfolio I should be able to go to Skills and so on.
This is my App.jsx
const App = () => (
<div className="main-container">
<Menu/>
<NextPage/>
<AnimatedSwitch
atEnter={{ offset: 100}}
atLeave={{ offset: -100}}
atActive={{offset: 0}}
mapStyles={(style) => ({
transform: `translateX(${style.offset}%)`,
})}
>
<Route exact path="/" component={Homepage} />
<Route path="/portfolio" component={Portfolio} />
<Route path="/skills" component={Skills} />
<Route path="/contacts" component={Contacts} />
<Route path='*' component={NotFound} />
</AnimatedSwitch>
</div>
);
export default App;
This is my NextPage.jsx
const NextPage = () => (
<div className="next-arrow">
<Link to='#HEREGOESTHENEXTPAGELINK#'><i className="fa fa-angle-right" aria-hidden="true"></i></Link>
</div>
);
export default NextPage;

Your approach to implementing this seems a bit odd in my opinion, but I digress.
The first thing you'd need to do is to somehow store all the links as well as their order of appearance. The biggest question I'd say is where you're going to store this; you could store it in a database, create a global variable, store it in a stage-management library (if you use one), etc etc.
It is unclear which of these you use and which you'd prefer, so I'll leave that to you and just present the concept below.
In your root React Component define an array of all the links. You could do this in the constructor:
class MyApp extends React.Component {
constructor() {
super();
window.links = ["/", "/portfolio", "/skills", "/contacts"];
}
render() ...
}
This will make window.links accessible in all components. This means you compare the active url, look it up in the array, and make the link direct you to the next one.
const NextPage = (props) => {
let idx = window.links.indexOf(props.route.path) + 1;
let link = (idx == window.links.length) ? window.links[0] : window.links[idx];
return (
<div className="next-arrow">
<Link to={link}><i className="fa fa-angle-right" aria-hidden="true"></i></Link>
</div>
);
}
export default NextPage;
Note that if the current link is not one of the ones defined in window.links, the link will take you to the first one.

Related

How can I make an asynchronous setState before following a Link?

I want to be able to 'capture' a selection of a user clicking a link that takes them to another page. I need the users selection to display a detail page of thebselected image.
I'm facing the problem that the browser follows the link before react updated the state "key". The state-Change of key is not passed to the details page. Is there an easy way to fix that without fetch?
export default class IGallery extends React.Component {
constructor(props) {
super(props);
this.state = {
countryList: [],
wallet: "",
key: "",
};
}
handleClick = (_key) => {
console.log("before setState", this.state.key);
this.setState({ key: _key }, () =>
console.log("after setState", this.state.key)
);
};
render() {
return (
<div className={styles.gallery}>
<h1>Gallery</h1>
<ImageList cols={6} rowHeight={320}>
{this.state.countryList.map((data, idx) => (
<ImageListItem key={idx} className={styles.imageItem}>
<Link to="/item" onClick={() => this.handleClick(data.key)}>
<img src={data.image} width={320} />
<ItemDetail id={this.state.key}></ItemDetail>
</Link>
<ImageListItemBar
title={"Braintube"}
subtitle={data.key}
actionIcon={<FavoriteBorder fontSize="large" color="pink" />}
actionPosition="top"
/>
</ImageListItem>
))}
</ImageList>
</div>
);
}
}
I expect that
<ItemDetail id={this.state.key}></ItemDetail>
passes the state value to the child component itemDetail.
Here is my Routing Path from index.js
<Router>
<Header childToParent={childToParent}/>
<Switch>
<Route path="/" exact={true}>
<Home></Home></Route>
<Route path="/project-space">
<ProjectSpace childToParent={wallet}></ProjectSpace>
</Route>
<Route path="/about">
<About></About></Route>
<Route path="/item"><ItemDetail></ItemDetail></Route>
</Switch>
<FooterMain></FooterMain>
</Router>
I think we need to take a step back and understand the React paradigm to answer this question.
In React, state goes only one way and is not retained when a component is unmounted. Right now, we have the following
Router > SomePage > IGallery (State = ....)
and we're trying to redirect to:
Router > ItemPage
As you can see here, moving away to ItemPage will drop state because Router will re-render and SomePage will be unmounted.
Therefore, we have two options:
Pass this item id in the url parameter which will then be handled by the next page
Move the state to the router parent and pass the state's setter + getter down to the page components (unrecommended)
For your situation, option one is more intuitive.

Page Routing with parameters or POST request

I have index.js like this
var hist = createBrowserHistory();
ReactDOM.render(
<Router history={hist}>
<Switch>
<Route path="/landing" component={LandingPage} />
<Route path="/" component={Components} />
</Switch>
</Router>,
document.getElementById("root")
);
At first, User opens the landing page and then go to / page.
in Landing page ,there is a button to move
<Button
variant="contained"
color="secondary"
style={{margin:"50px"}} href="/">
go next
</Button>
However here I want to move / page with POST parameters
id=12
name=myname
from=landing
At first I tried with GET parameter /?id=12&name=myname&from=landing
I can't use useQuery in class Component. I am not familiar with function component so mainly use 'class component`
How can I make this???
Or is there any good way to give the parameters when switching another page without POST nor GET?
I appreciate your help.
You can use window.location.search API, just put following function into a JS file and import it where you need:
src/utils/query.js
export function getQuery() {
let b = window.location.search
.slice(1)
.split("&")
.map(qStr => qStr.split("="))
.reduce((acc, inc) => {
acc[inc[0]] = inc[1];
return acc;
}, {});
return b;
}
and where you want to use it
import getQuery from './utils/query';
.
.
.
let q = getQuery();
if(q.lenght){
let name = q['name']
let id = q['id']
let from = q['from']
}
//Or directly call it
let name = getQuery()['name']
let id = getQuery()['id']
let from = getQuery()['from']

Passing data from parent to child using react.State but State reset to initial state

I am facing an issue while passing the state to the child component, so basically I am getting customer info from child1(Home) and saving in the parent state(App) and it works fine.
And then I am passing the updated state(basketItems) to child2(Basket). But when I click on the Basket button the basket page doesn't show any info in console.log(basketItems) inside the basket page and the chrome browser(console) looks refreshed too.
Any suggestion why it is happening and how can I optimize to pass the data to child2(basket) from main (APP).
update:2
i have tired to simulated the code issue in sand box with the link below, really appreciate for any advise about my code in codesandbox (to make it better) as this is the first time i have used it
codesandbox
Update:1
i have made a small clip on youtube just to understand the issue i am facing
basketItems goes back to initial state
Main (APP)___|
|_Child 1(Home)
|_Child 2 (Basket)
Snippet from Parent main(App) component
function App() {
const [basketItems, setBasketItems] = useState([]);
const addBasketitems = (product, quantity) => {
setBasketItems(prevItems => [...prevItems, { ...product, quantity }])
}
console.log(basketItems) // here i can see the updated basketItems having customer data as expected [{...}]
return (
<Router>
<div className="App">
<header className="header">
<Nav userinfo={userData} userstatus={siginalready} />
</header>
<Switch>
<Route path="/" exact render={(props) => (
<Home {...props} userData={userData} userstatus={siginalready}
addBasketitems={addBasketitems}
/>
)}
/>
<Route path="/basket" exact render={(props) =>
(<Basket {...props} basketItems={basketItems} />
)}
/>
</Switch>
</div>
</Router>
Snippet from the child(basket)
function Basket({basketItems}) {
console.log(basketItems) // here i only get the [] and not the cusotmer data from parent component
return (
<div>
{`${basketItems}`} // here output is blank
</div>
);
}
export default Basket;
Snippet from the child(Home)
... here once the button is pressed it will pass the userselected details to parent
....
<Button name={producNumber} value={quantities[productName]} variant="primary"
onClick={() => {
addBasketitems(eachproduct, quantities[productName])
}}>
Add to Basket
</Button >
Your function works fine, the reason your output in addbasketItem does not change is the when using setState it takes some time to apply the changes and if you use code below you can see the result.
useEffect(()=>{
console.log('basket:',basketItems)
},[basketItems])
Your Basket component only renders once so replace it with this code and see if it works:
function Basket({ basketItems }) {
const [items, setItems] = useState([]);
useEffect(() => {
setItems(basketItems);
}, [basketItems]);
return <div>{`${items}`}</div>;
}
but for passing data between several components, I strongly suggest that you use provided it is much better.

Passing both match param and props into react component when using routing

I have a functional react component and want to render some properties for a selected person in my component.
So I first map a simple list with links to each person
{props.persons.map((person, i) =>{
return(
<li key={i}>
<Link to={`/topics/${person.id}`}>{person.name}</Link>
</li>
)
})}
</ul>
then I make the route
<Route path={`/topics/:id`} render={() => <Topic persons={props.persons} />} />
as of now I just pass in the persons.
However I also want to be able to pass in the id, so I can find the specific person, and render information about that person. I have tried using the matching property, but that seems to prohibit me from passing in props as well.
Any tips to make a workaround?
Maybe it would be possible to just pass in the properties of the selected person?
EDIT:
Here is what i have tried so far.
const Topic = (props) =>{
console.log('props are ', props) //logs array of persons
return(
<div>
<h2>Topic</h2>
<p>I have been rendered</p>
</div>
)
}
const Topic = ({match}) =>{
console.log('match is ', match) //logs match parameter, but now i can't access persons
return(
<div>
<h2>Topic</h2>
<p>I have been rendered</p>
</div>
)
}
When you're using the render prop on a <Route> component, the render function is passed an object with the match, location, and history information.
https://reacttraining.com/react-router/web/api/Route/route-props
<Route
path={`/topics/:id`}
render={({match}) => (
<Topic id={match.params.id} persons={props.persons} />
)}
/>
You no need to pass id as a prop to Topic component of route so instead to get the id of path param you can do following in the component to get the id
In the Topic component
You can get the id using
props.match.params.id
According to https://reacttraining.com/react-router/web/api/Route:
All three render methods will be passed the same three route props
match
location
history
So in the render prop for the route, you should be able to reference the match and pass it along:
<Route
path={`/topics/:id`}
render={({match}) => (
<Topic
persons={props.persons}
id={match.params.id}
/>
)}
/>
This is because the render function is basically a functional component, and the first parameter to a functional component is the props. (However, you don't have to reference the first parameter as props, you could use renderProps, for example, or restructure it into what you need (match) like I did above)
And then in topic:
const Topic = ({id, persons}) =>{
console.log('id is ', id) //logs id parameter
console.log('persons is ', persons) //logs persons parameter
return(
<div>
<h2>Topic</h2>
<p>I have been rendered for id {id}</p>
<code>{JSON.stringify(persons)}</code>
</div>
)
}
You can use useRouteMatch
So in your example:
<Route path={`/topics/:id`} render={() => <Topic persons={props.persons} />} />
Becomes:
<Route exact path={`/topics/:id`} component={() => <Topic persons={props.persons} />}/>
And Topic.js becomes:
import { useRouteMatch } from "react-router-dom"; // Import this
const Topic = (props) =>{
let match =useRouteMatch('/topics/:id').url.split('/'); // Get url and separate with /
match=match[match.length-1]; // The id is at the end of link, so access last array index
console.log('match id is ', match);
console.log('persons is ', persons);
return(
<div>
<h2>Topic</h2>
<p>I have been rendered</p>
</div>
)
}

React: is componentDidUpdate same for 2 different instances of a component?

I am writing a React (ES6, v16) (typescript) application with react-router v4. I am observing a very strange behavior. Here is my render code (very much simplified):
render() {
<Switch>
<Route
path="/foo/add"
render={ props => {
return (
<FormEntry title="add new" />
);
}}
/>
<Route
path="/foo/edit/:id"
render={ props => {
return (
<FormEntry title="edit item" />
);
}}
/>
</Switch>
}
And here is the FormEntry component (simplified):
class FormEntry extends React.Component< { title: string }, any > {
render() {
return (
<div>
{this.props.title}
</div>
);
}
componentDidMount() {
// code goes here
}
componentDidUpdate() {
// code goes here
}
}
Now when, inside the application, I click a link "/foo/add", the handler in the first "Route" component is fired (as expected) and the component "FormEntry" is mounted. The method componentDidMount is rightfully fired.
Now I click the link "foo/edit/1". The handler of the second Route is fired.
This time, inside the "FormEntry" component, the lifecycle method "componentDidMount" is not fired, the method "componentDidUpdate" is called. But this is cleary a different "instance" of the FormEntry which is being mounted. I was expecting the see of the lifecycle methods kicked off...
It looks like there is only one instance of "FormEntry" in my application. So why in the second case (when Route handler for url "foo/edit:id") this instance does not go through the all lifecycle methods??
Is it a breaking change in the v16 version of React? ( I have not observed this behavior in previous versions of react).
Your insight will be very much appreciated
<Switch> check the JSX of the previous matched route and compare it with the new JSX of next route.
If it does match, it will use it and only update changed values without re-mounting components.
Otherwise it will create new react elements and instantiate new components.
Check here: https://github.com/ReactTraining/react-router/pull/4592
A turn around for this is to use key attributes like this:
render() {
<Switch>
<Route
path="/foo/add"
render={ props => {
return (
<FormEntry key="1" title="add new" />
);
}}
/>
<Route
path="/foo/edit/:id"
render={ props => {
return (
<FormEntry key="2" title="edit item" />
);
}}
/>
</Switch>
}

Categories

Resources