While working on a project I noticed a weird behavior of the useLocation hook that I can`t find an explanation to.
I have a button that when clicked it will redirect you to an EditOrder page and will pass a state with it:
const navigate = useNavigate();
const handleClick = (data) => {
navigate("edit-order", {state: {order: data}})
};
In the EditOrder page I check with a UseEffect hook if a state was provided and if not the user will be redirected to a different page:
const { state } = useLocation();
const navigate = useNavigate();
useEffect(() => {
if (!state) {
navigate("some-page");
}
}, []);
The weird part is when I refresh the page I can still access it, and if I console.log(state.order) the data is still there, even when I reload with ctrl + shift + r the state stays the same, and this also happens with the empty cache and hard reload option (tried in both chrome and edge).
But when I copy the URL and past it in a new tab I immediately get redirected to "some-page"
and console.log(state) will show null.
I checked both the cookies and local storage and I can't find the state data there.
Can someone explain why is this happening and how the state data is being saved?
Edit:
Here is a youtube video that shows this behavior.
The code on the video can be found in this sandbox, when you run the code on sandbox it runs as it should, on refresh all the states reset, but when running locally this problem occurs (on 2 different computers).
A git repo about location.state
React's useLocation is based on the history library, which uses the BrowserHistory in web apps.
Some browsers, like Chrome, persist BrowserHistory state between sessions, while others (like Firefox) do not.
This is likely why you're seeing this behavior locally but not in a Sandbox. It appears that CodeSandbox's browser clears history state on refresh. It's also why, if you copy the URL into another tab, the redirect works. BrowserHistory is local to a single tab.
In short, this is intended behavior. Either you need to clear the history state manually or store your application state elsewhere (useContext could be a good choice if you want to persist across pages but not across a refresh).
Related
I'm using Next.js and Next Link to handle routing in my React app.
Let's say I have the 2 pages: /product-list?year=2020 and a detailed page which will be routed to /product-list/details?year=2020&month=4
In pages/product-list.js, I use React router to get the year query param to make an API call.
const ProductList = (props) => {
const router = useRouter();
const year = router.query.year;
useEffect(() => {
// Fetch API using year
}, [year]);
return (
// UI
<Link href="/product-list/details/?year=2020&month=4" />
);
}
When I navigate back from the detailed page by clicking the back button, I can see that not the whole page is rendered (verified by setting the background of the page using the Dev Console to yellow and verified it didn't change when going back).
However, it did trigger another (unnecessary) API call since the router notified my ProductList component that the year variable changed... which technically it did...
So the question is: is there any way to not trigger the year variable change notification only when coming back from another page? (think how iOS app keeps the navigation stack in memory and when you pop back the previous page isn't necessarily rendered again).
swr is the best option by vercel for better performance on data fetching and maintain state for fetched data
One way is to re-render would be to change the state variable on the submission of the Axios request causing the component to re-render automatically.
I have a redux application that requires authentication. Right now I'm trying to make sure the application ensures the user is still logged in whenever they perform an application in the app. Here is the bottom of my App.jsx file:
function mapStateToProps({ offline, user }) {
return {
// we use this to know if we should redirect to auth pages
// we don't want logged in users to be hitting these static pages
loggedIn: !!user.token,
offline,
};
}
const ConnectedApp = connect(mapStateToProps)(App);
export default ConnectedApp;
I test this functionality by clearing the site local storage through the chrome developer tools. And then performing an action on the site. I've placed many different log statements. I find that mapStateToProps is being called, but the props are not changing. The application continues to think that the user is still logged in and the state hasn't changed.
I've been following all the online resources I can find, but nothing seems to be helping. Am I missing something?
That's because mapState only re-runs when an action was dispatched to the Redux store, and a new store state was calculated as a result. Modifying local storage does not involve dispatching a Redux action, so your mapState will never run.
Don't depend on values from localStorage in a mapState function - they should only extract values from the state argument, and return those.
I have a page in my application that is an interactive chart with a bunch of settings (filters, time ranges, etc). I'd like to store them in-app state because some of the settings may be used by other components on another page, but right now if I click on any other tab and come back to the previous tab then the page shows the initial state(chart is gone, filtered data gone, date range showing the default value, dropdowns shows default ass well). And the state is also showing null.
Anything in your component state is dynamic; i.e., it is temporary. If you refresh the window, the old state is lost. A similar thing happens when you open a fresh tab—you get the state declared in your constructor. You can use any of the following if you want the state data in another tab:
Simply using redux won't solve your problem as redux store is also related to a specific browser tab. If you would like to persist your redux state across a browser refresh, it's best to do this using redux middleware. Check out the redux-persist, redux-storage middleware.
If you are using react-router you can simply pass required state through the link when you open a fresh tab. Here's an example:
<Link to={{
pathname: '/pathname',
state: { message: 'hello, im a passed message!' }
}}/>
Simply store the data in localStorage and access localStorage in other tabs.
If you are looking to use a variable across the entire application you can also use localStorage
localStorage.setItem('move', this.state.move);
also don't forget to unset it when you are done!
localStorage.removeItem('move');
The straight forward solution to this is redux-state-sync. It will just work and the store will be updated on all tabs and even windows on the browser.
I think you should implement react-redux into your app.
React-redux is a state management tool that manage the state of your application at a centralized location and you access data from store at anytime from anywhere.
React-redux:
https://redux.js.org/basics/usage-with-react
You some library for state management. The most popular one that's used with React is redux.
https://redux.js.org/introduction
I am in the process transferring my React Webapp to a React Native version.
In React Native, I have this in the login page. I am using react-navigation.
componentWillReceiveProps(nextProps) {
const { requestStatus, currentUser } = nextProps
const getUserInProgress = get(requestStatus, GET_MY_DETAILS, true)
if (!getUserInProgress) {
if (currentUser) {
currentUser.verified_mobile ? this.props.navigation.navigate('Dashboard') : this.props.navigation.navigate('VerifyMobile')
} else if (!currentUser) {
console.log('not logged in')
}
}
}
So on the login page, it gets the user details and check if mobile is verified and then if it isn't, goes to verify mobile page.
On the verify mobile page, when a call is made, this componentWillReceiveProps on the login page still runs even though it's not the current active page.
I have the same logic/code in ReactJS and it behaves totally different.
Is this normal for React Native?
Edit:
Let me clarify what my problem is.
When I am not on the login page and I am on the verify mobile page. The componentWillReceiveProps is still running despite that page is in the history. This did not happen when I built the reactjs app. It is only happening in the react native app. So I'm wondering if this is normal behavior for pages in the history stack to run its componentWillReceiveProps?
The function still executes in react-native because it is still active in the stack while in react app, only the displayed page is active so functions in other pages are 'disabled' in a sense.
OK, I know I'm going against conventional wisdom here, but here goes. I'm learning how to build a SPA using react and redux, and things are going well. I've incorporated react-router, because it seemed to be the accepted thing to do, but now I'm having issues. I want my SPA to act like a real app where the user will always come in at the landing page, and then go from there. As the user navigates from page to page, various things are fetched and stored. Some pages require authorization, for example, others don't. The problem is that when I use react router the URL changes at each route. So then, when the user comes back at a subsequent visit through say a bookmark, the app tries to jump to the page they left at, rather than at the entry point where I need them to go. This screws up a bunch of stuff underneath the hood and they get gobbledegook.
I've never really understood why it is conventional wisdom to "never break the back button" and always reflect everything the user does in the url. This seems like such a dogma, it's difficult to find ANY alternative ways of thinking. I have had times when I do a google search, for example, and go to a site where I click around for awhile, don't find what I'm looking for and then want to use the back button to go back to my google search results, but instead it goes click-click-click back through every step I made IN THAT SITE. Bleh. I eventually have to close that tab and start over. This happens to me all the time.
In mobile apps, the user is quite used to always arriving at a landing page, and clicking around from there. Why can't web apps act the same way?
So my question is: how would I implement that in a react-redux application? I have an internal "back" button that the user can click to go to a previous "page" within the app. I'm thinking I could keep the "history" in redux store, and write some goBack methods and such WHICH DON'T ALTER THE URL. Are there any examples that use this approach?
Any advice much appreciated.
As you said, it's probably not a good idea to go against the grain and create your own back button.
That said, if you don't want those features of react-router, it shouldn't be very difficult to implement your own router.
You could, for example, model your redux state with a history list,
{
history: ["home"],
home: { ... },
otherPage: { ... },
}
Then in your top-level render function just determine the page and render,
import HomeComponent from 'components/HomePage'
import OtherPageComponent from 'components/OtherPageComponent'
const pages = {
'home': HomeComponent,
'otherPage': OtherPageComponent
}
export class App extends React.Component {
...
render() {
const CurrentPage = this.props.history[this.props.history.length-1];
return (
<CurrentPage />
)
}
}
export default App;
You'll then need a reducer/action to change routes,
// reducer.js
export default function reducer(state, action) {
switch(action.type) {
case 'CHANGE_ROUTE':
return {
...state,
history: state.history.concat([action.route])
}
case 'GO_BACK':
return {
...state,
history: history.slice(0,-1)
}
default:
return state
}
}
// action.js
export const changeRoute = (route) => ({ type: 'CHANGE_ROUTE', route });
export const goBack = () => ({ type: 'GO_BACK'});
You could then change the route on any page by dispatching the relevant action,
// some-component.js
import { goBack, changeRoute } from 'action.js'
...
// in some render method
<button onClick={() => dispatch(goBack())}>
Go Back
</button>
<button onClick={() => dispatch(changeRoute('somePage'))}>
Go to some page
</button>
You would probably want to package the onClick stuff together in some kind of <Link> component like react-router.
Good luck!
There are two challenges with what you propose. First, is that without URL's, the user cannot bookmark their location. And second, the app is operating within a browser window and the browser window has a back button (and forward, and history).
I am working on a SPA that after the initial bootstrap only ever makes JSON requests through a RESTful api. This works beautifully but does require extra work to manage the page history. Without it users get frustrated that they are 'exited' from the application back to the login page when they hit the 'back' button in the browser.
The page history we maintain, however, is one we control and so it can be managed to the granularity we want. There are many api calls that don't change the URL. Unfortunately, with many frameworks they expect you to operate within a 'page' mindset, hopefully something that will change soon. (Personally, I am contemplating just disabling the back button so the user views it more as an application.)
I think the point that you are making, and with which I agree is that we are building applications and being constrained to the 'traditional' concept of pages makes the concept of "SINGLE page application" somewhat less than what it should be.