I'm building an App with reactjs and in some point I have a SearchForm, then I click on one of the items and in that view I have a back button.
What I want is to populate the search form with the previous data. I already achieved it with localStorage from javascript. And saving the state on componentWillUnmount, the problem is this data is always loaded even if I don't arrive from the go back.
Is there a way to add a kind of state to the goBack to tell it?
So, the best way here is to not use localStorage, and instead use history state.
When the user updates the form, you can do this (optionally debounce it for better performance):
history.replaceState(this.props.location.pathname, {
formData: JSON.stringify(formData),
});
Then when the component loads, you can access that state like this:
const {location} = this.props;
const routeState = location.state && location.state.formData;
if (routeState) {
this.setState({
data: JSON.parse(routeState),
});
}
If they refresh the page, or go to another page and go back, or go back and then go forward, they'll have that state. If they open it in a new tab, they won't have the state. This is usually the flow you want.
Related
I have an application where I want to redirect the user to a page containing the first result of a array of data once is it fetched.
For example:
The user enters on a menu
He clicks on a button that redirects them to a page /toto
My app fetch the API /toto and redirect the user to /toto/[id]
If there is no content, the user stays on the page /toto and the front tell them that there is no data yet.
What I am doing right now:
const {data, isLoading} = useGetToto();
useEffect(() => {
if(data?.results.length > 0) {
router.push(`/toto/${data?.results[0].id}`
}
}, [data])
if(data?.results.length === 0)
return <p>No content yet</p>
return <p>Loading...</p>
I wonder if there is a better way to do this. Using a useEffect is probably a bad practice.
The best solution would be to add correct links to navigation. So you will need to check if page has only one result and display link to the result instead. But this a lot depends on your backend - can you do that efficiently (without doing a lot of requests or fetching a lot of data).
The second solution is in your example, you redirect using useEffect. The only thing I recommend is using router.replace. Cause in your case you create new browser history entry, so if you click back in the browser, your page with single entry will be rendered and again useEffect is triggered. And if you use router.replace, instead of creating new browser history entry, it will replace listing page with entry page url (so back actions will go 2 pages back).
I have a Link where I want to pass certain params in the URL but I don't want the browser to display the params.
I'm using Link's as for this:
<Link href={`/link?foo=bar`} as ={`/link`}>
<a>Link</a>
</Link>
But when I click this link and I try to access the params via router, I can't access foo=bar:
const router = useRouter()
console.log(router.query)
Returns
{
slug: ["link"],
}
And not
{
slug: ["link"],
foo: "bar",
}
So how can I access the URL params in href when using as for Link?
TL;DR You can't use as like that.
This is an incorrect usage of href and as. It would be cool if we could hide state from the end users to keep our URLs nice, clean, and compact, but obviously if you do that, you'll actually lose the state when copy/pasting the URL. That's why you can't hide query parameters in anyway (except for excluding them).
Here's the docs on href and as (dynamic routes, has little to do with hiding query params):
https://nextjs.org/docs/tag/v9.5.2/api-reference/next/link#dynamic-routes
And to further bring up my point, imagine if we could hide state, and we redirect to this URL:
https://example.com/stateful/
Presumably there would be some behind-the-scenes browser action that persists the state.
Now we copy/paste the URL:
https://example.com/stateful/
Oops! We don't have the state anymore because the browser has no previous state to keep track of! That's why you use query parameters, because they keep the state in the URL itself.
I would like wisdom again.
I am making a single page application with quite a bit of AXIOS calls, as you can see here.
useEffect(() => {
axios.all([
axios.request(options),
axios.request(optionsQuote),
axios.request(optionsDaily),
axios.request(optionsNews)
],)
.then(response => {
setData(response)
})
console.log(data);
}, [tix])
My conundrum is that this runs every time I land on the page (for obvious reasons) and have to have an initial data in order for the app to run.
const [tix, setTix] = useState('AAPL')
I want to be able to land on a 'placeholder' type data, so to avoid the first initial call and only call when user searches and clicks on the company name, data. This function works fine after the page lands and initial stock (AAPL) is called. I want to not call it.
The problem is, I am trying to make a single page web app and would like to avoid using routes, if possible.
If I leave the initial state as blank (''), than, the code breaks since all of my data is derived from the fact that "data" has data in it.
const [data, setData] = useState(null)
Is the only way for me to get initial page without data showing is to make another component (such as 'landingPage')? If so, how do I go about doing that?
I have a list of employee list on EmployeesComponent and there is "Education Overview" and "Salary Overview" buttons for each records. When I click one of the overview button it goes to the OverviewComponent first and then load the correponding component (salary or education) into this OverviewComponent. There is also a "Back" button on each of these salary and education components. The structure is as shown on the following image:
components
The problem is that: When I come back to the EmployeesComponent, I need to reload the paging params e.g. the last page number before navigating to the overview pages. For this I use localStorage and check the saved value on each page load of the EmployeesComponent.
searchParams: any;
ngOnInit() {
let searchParams = JSON.parse(localStorage.getItem('routeParams'))?.searchParameters;
if(searchParams){
this.searchParams = searchParams;
window.localStorage.removeItem('routeParams'); // remove routeParams from localStorage
// load list using this.searchParams
}
But I save the page params on the OverviewComponent so that use a single place for salary and education pages. I think it is not a good approach and it may cause the localStorage items to be mixed as they use the same key (for some reason I need to use the same key sometimes).
So, should I set the paging parameters just before navigating to the overview page in the EmployeesComponent? And then check them on loading EmployeesComponent? What is a proper way for this scenario?
You can use the query-params in routing.
So now when you redirect from employess component to overViewComponent, then based on click i.e., Education Overview or Salary Overview just send the query params with the url.
Then now when you get back to employess component, just use the query params value you get in overView component and you can get the information you want back in employess component.
Q- what is the most proper place for adding and removing paging items to local storage
A- Most proper place for adding and removing localstorage item is where it get's change.
In your case, just set localstorage in overView component where you are getting params ( this.activateRoute.params() ) inside this function. And remove the localstorage on ngOnInit function of employee component.
I'm a little confused about how History.js works at page-load. I've done a few experiments but the results seem indeterministic.
My website is a search engine and the query is stored in the URL parameters: ?Query=cats. The site is written purely in javascript. History.js works great when I do a new search, the new query is updated, and the state is pushed.
My problem is how to create an initial state if the user manually enters in a URL including a Query parameter. Every way I try to do this ends up resulting in running the search query twice in some case. The two use-cases that seem to conflict are:
User manually enters URL (mydomain.com?Query=cats) into address bar and hits enter.
User navigates to an external page, and then clicks the back button
In both cases, the javascript loads, and therefore looks to the URL parameters to generate an initial state.
However, in the second case, History.js will trigger the statechange event as well.
Necessary code:
History.Adapter.bind(window,'statechange',function() { // Note: We are using statechange instead of popstate
var s = History.getState();
if(s.data["Query"]){
executeQuery(s.data);
}
});
and in $(document).ready I have
// Get history from URL
s = getQueryObjectFromUrl(location.href);
if(s["Query"]){
History.pushState(s,'',$.param(s))
}
Is there a better way to handle creating an initial state from URL parameters?
As I had a similar problem to to yours, what i did was to define the function bound to a statechange as a named function, and then all I had it running when the page load as well.
It worked better than trying to parse the URI or anything else, hope it helps.
This is the way I chose to do it (based on Fabiano's response) to store the initial state parameters
var renderHistory = function () {
var State = History.getState(), data = State.data;
if (data.rendered) {
//Your render page methods using data.renderData
} else {
History.replaceState({ rendered: true, renderData: yourInitData}, "Title You Want", null);
}
};
History.Adapter.bind(window, 'statechange', renderHistory);
History.Adapter.onDomLoad(renderHistory);
Of course if you are using a different on DOM load like jquery's you can just place renderHistory(); inside of it, but this way doesn't require any additional libraries. It causes a state change only once and it replaces the empty initial state with one containing data. In this way if you use ajax to get the initData inside the else, and it will not need to get it the next time the person returns to the page, and you can always set rendered to false to go back to initial page state / refresh content.