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

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.

Related

How to call a react component from another component and pass required props

At the moment, I have the following routes in my App.js file:
<Switch>
<Route exact path="/new-job"
render={(props) => <NewJob jobName={jobName} setMenuSelection={handleMenuSelection} />}
/>
<Route exact path="/past-jobs"
render={(props) => <PastJobs setMenuSelection={handleMenuSelection} />}
/>
</Switch>
Now within my PastJobs component, I have the following button with onClick process:
<Button
onClick={() => {
setConfirmDialog({
isOpen: true,
title: `Copy Job ${item.id}?`,
onConfirm: () => { onCopy(item.job_info) }
})
}}
>
Copy
</Button>
that calls the following function:
const onCopy = (job_info) => {
setConfirmDialog({
...confirmDialog,
isOpen: false
})
history.push({
pathname: '/new-job',
state: { detail: job_info }
})
}
Within my <NewJob /> component, I have now setup the following as I thought I could access the state.detail but unfortunately it's null, i.e.:
import { useLocation } from 'react-router-dom';
function NewJob( { jobName, setMenuSelection } ) {
const { state } = useLocation();
if (typeof state !== 'undefined') {
const myVal = state.detail
console.log("myVal", myVal )
}
}
The issue that I am having and unsure how to approach is that within my onCopy function that is called from button onClick, how do I call the the <NewJob /> component whose path is exact path="/new-job" in App.js above and pass in the prop job_info ?
Direct calls to components actually does not exist. But what you are looking can be achieved in different ways.
Using state machine with event bus (redux, redux-saga)
Render props https://reactjs.org/docs/render-props.html
Bunch of callbacks drilled via props (HOC's)
Ref's https://reactjs.org/docs/refs-and-the-dom.html
I suggest to read more about them to actually understand if it matches your use case. Anyhow it is great experience to develop your skills also!

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.

With React-Router, How do I update the URL without triggering a re-render?

I have a parent component that should render another component when the URL is matches a certain path:
const View: React.SFC<Props> = ({
....
}) => {
return (
<div>
....
<Route path={jobPath} component={JobPanel} />} />
</div>
);
};
JobPanel.tsx will render if jobPath === /careers/:id which all works.
JobPanel.tsx has a link that will currently go back with this.props.history.push(/careers)
<BackLink
to="/company/careers"
onClick={(e: any) => { handleClose(); }}
>
<StyledChevron orientation={Orientation.Left} />
Go Back
</BackLink>
or
<BackLink
onClick={(e: any) => { this.props.history.push('/careers/); handleClose(); }}
>
<StyledChevron orientation={Orientation.Left} />
Go Back
</BackLink>
The problem is that JobPanel is supposed to have a transition in and out of the page with this Component:
class JobPanel extends Component {
render() {
const { isOpen, handleClose, job } = this.props;
return (
<StyledFlyout
active={isOpen}
Where isOpen is a boolean value in redux store.
While rendering JobPanel all works, I believe react-router is causing the page to re-render whenever the URL is changed. I'm not entirely sure on how to achieve no re-rendering.
Use the render prop instead of component in the Route. eg:
<Route path={jobPath} render={({ history }) => (
<JobPanel {...routeProps} />
)} />
From https://reacttraining.com/react-router/web/api/Route/component:
When you use component (instead of render or children, below) the router uses React.createElement to create a new React element from the given component. That means if you provide an inline function to the component prop, you would create a new component every render. This results in the existing component unmounting and the new component mounting instead of just updating the existing component. When using an inline function for inline rendering, use the render or the children prop (below).
For more details on using render see https://reacttraining.com/react-router/web/api/Route/render-func for more details.

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>
}

ReactJS: Link to go to next page dynamically

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.

Categories

Resources