Are my expectations about React navigation correct? - reactjs

It might be my lack of understanding, but would anyone explain if my expectations are wrong about React navigation?
My app is more or less a questionnaire where one screen handles all questions. Basically it navigates to itself with a new question ID. That fetches the proper data and displays it. Working fine, but when I press back I do not go to the previous question but to the home page. I use expo and their recommended navigation.
My expectation is when I go back from the last page in :
Homepage => QuestionPage(id=1) => QuestionPage(id=3)
I would go back to QuestionPage with id = 1, but it goes to Homepage. I use withNavigation on both pages to maintain the navigation props.
Is this expectation wrong or is this correct navigation behavior? If so, any clues what to do to get my expected behavior.

To achieve react navigation multi-page application from single page. You need to add "react-router-dom" package in your project.
import {
Route as Router,
Switch,
Redirect
} from 'react-router-dom';
You have to setup router for navigate page as follow.
<Switch>
<Router exact path="/questionpage/:handle" component={Questionpage} />
</Switch>
In Question page Router :
this.props.match.params.handle // will help to get page number
By using above syntax you can get page number and able to get data exact you want.

It was so simple. Instead of
this.props.navigation.navigate('Question', {id=40})}
I had to write
this.props.navigation.push('Question', {id=40})}

If you're using a StackNavigator and from QuestionPage on the back button you do
this.props.navigation.goBack();
instead of
this.props.navigation.navigate('Screen')}
it should work !
If you want to have a custom navigation on a react navigation screen, on the backAndroid I'd recommend to listen to the didFocus like this:
_didFocusSubscription;
_willBlurSubscription;
constructor(props) {
super(props);
this._didFocusSubscription = props.navigation.addListener('didFocus', payload =>
BackHandler.addEventListener('hardwareBackPress', this.onBackButtonPressAndroid)
);
}
componentDidMount() {
this._willBlurSubscription = this.props.navigation.addListener('willBlur', payload =>
BackHandler.removeEventListener('hardwareBackPress', this.onBackButtonPressAndroid)
);
}
hardwareBackPress = () => { this.props.navigation.navigate('Screen')} }
A more accurate example and the documentation regarding it is here https://reactnavigation.org/docs/en/custom-android-back-button-handling.html

Related

React router v6, Remove History

Is there a way to remove history in react-router v6, I'm creating a web app, when the user reaches to homepage and presses back it should eventually exit the app. but as of now, it's going back if history exists. if react-router doesn't have this feature is there an alternative way to overcome this issue?
I have just done a PWA app like you and the way I solved this is that I always set option {replace: true} whenever it went forward or backward.
Ex:
navigate(/${SCREEN_1}, { replace: true })
or
navigate(-1, { replace: true })
So when user click on button "Next"/"Back" on the browser, there were only the first blank page of the browser (where before I open my URL) and the current page.
My app only have 6 screens.
I'm not sure whether this way will be useful for you or not, but for me it did.
Hope this help!
I didn't understood your question excatly.
But below code maybe helpful to go back to previous page.
import {useNavigate} from 'react-router-dom'
const Component = () => {
const navigate = useNavigate()
const goBack = () => {
navigate(-1)
}
return (
<>
<button onClick={goBack}>Back</button>
</>
)
}

How to fetch data before route change?

I'm trying to have the following user flow when a user click on a link:
The user clicks on a link
A progress bar appears at the top of the page
The JS launches a network request to fetch some data from the server
When done, the progress bar finishes, and the page is switch
Note that I don't want to have any spinner or skeleton page. When the user clicks on the link, the page should not change at all (apart from the progress bar appearing) until the data has been fetched from the server, similar to how GitHub works.
I've searched a lot about this on the last few days, and it seems that it's not possible to do this:
Apparently, there used to be a onEnter hook that made it possible to achieve my described flow, but it was removed because, according to the devs, React lifecycle hooks were enough to achieve this.
React lifecycle hooks are not enough because if I use them to trigger the network request, the page will be blank between the click on the link and the response of the network request.
I could make a wrapper on top of the Link component so that when the user clicks on it, the network request is triggered and only after it's finished, router.navigate would be called. It seems nice at first, but it doesn't solve the issue of the initial visit to a page, where a Link button has not been called at all.
Any ideas on how to achieve this?
Thanks in advance!
I created a workaround for such behaviour: react-router-loading. It allows you to fetch data before switching the page.
You only need to replace Switch / Routes and Route with ones from the package, mark some (or all) routes with the loading prop and tell the router when to switch pages using the context in components:
import { Routes, Route } from "react-router-loading";
<Routes> // or <Switch> for React Router 5
<Route path="/page1" element={<MyPage1 />} loading />
<Route path="/page2" element={<MyPage2 />} loading />
...
</Routes>
// MyPage1.jsx
import { useLoadingContext } from "react-router-loading";
const loadingContext = useLoadingContext();
const loading = async () => {
// fetching data
// call method to indicate that fetching is done and we are ready to switch pages
loadingContext.done();
};
write a onClick function for your component
then function like this
import { useHistory } from "react-router-dom";
const history=useHistory();
const [loading,setLaoding]=React.useState(false);
const myfunction=async()=>{
setLoading(true);
const res= await fetch("your link here");
const data=res.json();
if(res.status===200)
{
console.log(succusfully fetch data)
setLoading(false);
history.push("/your_destination");
}
else{
setLoading(false);
console.log("error in fetch data")
}
}
write link like this
{loading?<Spin/> :
<p onClick={myfunction}>link</p>}

React Router v4 replace history when component is unmounted

I have a app which has the Following Components
|__Base - /home/Base
|__Try - /home/Base/:try
|__Report - /home/Base/:try/report
Base is the Starting screen where the user hits a button and clicks on Try and after trying some things he hits submits which generates reports which has some back end interactions and when the data is fetched it loads the Reports.
So what i want is when the user hits the back button from the Reports Page he should not land on the Try page but on the Base page .
For that to work i went through the react router documentation and was trying to use history.replace on componentWillUnmount for Reports Page
this.props.history.replace(`/home/Base`, {
pathname: `/home/Base`,
search: null,
state: {
isActive: true
}
}, null);
In case the Report Page is FullyLoaded and i press the back button it works but calls the Try Render Method too and then takes me to the Base Page , But in case of Reports Not fully Loaded and i press the back button while the loading spinner is in progress it goes to base page still but also mounts and unmounts the TRY component.
What am i missing here , what causes it to mount/unmount or render the previous component and then load the base component even though i replace the history stack ?
Reason
Related with this issue
React v16, changing routes, componentWillMount of the new route is called before componentWillUnmount of the old route
Update:
Solution (checked, update online demo later)
Use react-router-last-location to get previous pathname
import { BrowserRouter, Switch, Route, Redirect } from 'react-router-dom';
import { LastLocationProvider } from 'react-router-last-location';
<BrowserRouter>
<LastLocationProvider>
<Switch>
...
</Switch>
</LastLocationProvider>
</BrowserRouter>
Check previous pathname in componentWillMount, if it's from certain page, push a new pathname to route.
componentWillMount() {
const { history, lastLocation } = this.props;
if (lastLocation?.pathname === '/home/Base/:try/report') {
history.push({pathname: '/home/Base'});
}
}
You can use the HOC they provide or write it yourself refer to the lib's source to reduce the dependencies
import { withLastLocation } from 'react-router-last-location';
interface Props {
lastLocation: any,
history: any,
}
export const YourComponent = withLastLocation(connect(
...
))
In this way you can redirect all the routing process from certain pages without mount current page, no matter you clicked a back button or clicked the back in your browser.

How do I dynamically switch between screens and update the url during search input

I have a search input box located in the header. When a user searches and clicks 'enter', an (callback) event is sent out to all of the relevant components that need to react to the search event, including the components that display the search results. My issue is that the header's search box would be visible on other non-search-result screens, and when I search there's no "clean" way of quickly mounting the search-result screens and displaying the search results (I hope it's not too confusing).
So the question is what type of approaches did you take to solve this issue? I was thinking of relying on window location and relying on React-router to load the search-results screen. Then looking at the query parameter (or path that contains the search query) and then kicking off the search.
Update (for clarity):
Go to https://www.brainyninja.com/podcast/78b7ab84cf98735fbadb41bb634320f8 The body component name is
Now type any other search term in the header's search box and click enter
The body component that displays search results is . I need to navigate to the /search route in order to load the component. The only way I figured out how to do that is by doing a 'window.location = "/search/?query=somesearchquery"' command, which reloads the whole page and negates the point of having an SPA. I don't know of any cleaner way of changing the current body component
Any suggestions?
Found my answer here
https://tylermcginnis.com/react-router-programmatically-navigate/
Had to use withRouter since my header was not rendered by a React Router.
Now, what if the Register component wasn’t being rendered by React Router? (Meaning, we’re not passing Register as a component prop to a Route. Instead, we’re just rendering it ourselves like <Register />). If it’s not rendered by React Router, then we won’t have access to history.push. The team thought of this use case so React Router comes with a handy HOC called withRouter. Going back to our Register code above, by adding withRouter, it would look like this
import {
withRouter
} from 'react-router-dom'
class Register extends React.Component {
handleSubmit = (user) => {
saveUser(user).then(() =>
this.props.history.push('/dashboard')
))
}
render() {
return (
<div>
<h1>Register</h1>
<Form onSubmit={this.handleSubmit} />
</div>
)
}
}
export default withRouter(Register)

How to refresh a component after already visiting it?

Working on a project and when changing routes, coming back to one after already visiting it doesnt display new changes/additions. For example, if i route to my "Offers" route, it will display my offers on current items for rent. If i then leave that route and go and place another offer on an item, routing back to "Offers" will reveal no changes unless I refresh the page. I populate the state in componentdidmount() but have also tried other ways where im not using componentdidmount() to populate it. What options do I have here? I can post the component if needed.
Going back to a route which was already visited in the same BrowserRouter context, will not MOUNT the component again. It would only cause the router to perform a pop action, which causes the browser to pop the latest DOM generated for that route to pop from the router context. So what you would have to do, is to check if the user has pressed back button in his/her browser. To do so, you can check this.props.history.action === 'POP' in componentWillReceiveProps lifecycle. Something like this:
class Offers extends Component {
constuctor (props) {
super(props);
this.state = {
offers: []
}
}
componentDidMount () {
this.fetchOffersAndStoreThemInState();
}
componentWillReceiveProps () {
if (this.props.history.action === 'POP') {
this.fetchOffersAndStoreThemInState();
}
}
}
And don't forget to wrap your component with withRouter to have access to the history object.

Resources