How to update match.params? - reactjs

The react app has search page. There are input.
The path is 'search/:query', and by default you see zero results.
If you go to 'search/star%20wars' you will see some results. In componentDidMount() I added if statement to load result if match.params.query is not null.
If I type into search input Spider Man and click submit - I trigger a search and show results. But if you reload page - you will see the result about Star Wars. So how update match.params.query? Or may be there other solution of fix this.

You need to update the history object as well.
What you are doing is altering the history object available to you and calculating the results based on that object. But when you will refresh the page it still holds the original history object.
One way of doing it, you need to push or replace a new route in the history.
Because evert search page is a new page, so if you want the previous pages to stay preserved you should use history.push otherwise history.replace
Implement it like this:
var routeObj = {
pathname: samePath,
state: sameState,
query: newQuery
}
//push it in your history using which ever routing library you are using.
//For Example:
router.history.replace(routeObj);
Note: Do not worry about rendering speed on changing the history. React is smart enough to handle that. Basically whenever you will push a route whose component is already mounted it will not unmount and remount the same component again, rather it will just change the props and will re render it.
The callback for this case will be => componentWillReceiveProps

#misha-from-lviv The way I see your problem statement is that you have two source of truth on is the query params, using which you should update your state, and the other is the default state which is populated from the default value of your filters.
As #Akash Bhandwalkar suggested, you do need to update the route in using the History API. But also you also a need a top-level orchestrator for your application state, which will allow you to read and write to the history api ( change your route ) and also do an XHR / fetch for you to get the results.
How I'd approach this is that I'd start with a Parent component, namely FiltersContainer , which actually does this orchestration to read and write to the url. This Container would have all the side-effect knowledge for fetching and updating the routes ( error handling included ). Now the all the child components ( filters and search results maybe ) will just read the state thus orchestrated and re-render.
Hope this guides your thinking. Do revert here if you need further guidance. 😇
Cheers! 🍻

Related

React -> State Management

I am a beginner with React. I want to build an application similar to the page (howmuchtomakeanapp.com). That means that for every choice on a route a user does, a price will be added to the total, but when going back with the back button the price will return to the previous one. I know it is state management but I actually have no clue how to do it even after reading the whole react docs.
The React state is stored locally within a component. When it needs to be shared with other components, it is passed through props. In practice, this means that the component that needs access to a changeable value will keep that value in its state and if it can be changed by subcomponents a callback must be passed to handle the change.
To learn more about components and props, this is the link to the React documentation itself: https://reactjs.org/docs/components-and-props.html.
I recommend you take a look at Redux (https://redux.js.org/), that is a predictable state container for JavaScript apps.
Based on your description I assume you keep your price as a number and add to it the more steps you go further?
I dont think you need to do anything special besides:
My approach would be to keep an array with all the prices, that way you can always remove the last when you click on the prev button. Add one price everytime you select one and navigate to the next route. To display the total you just add the array together:
const prices = [3.5,5,10,5.76,23];
const total = prices.reduce((p, c) => p + c, 0);
Does that help?

How does react-router persist location state over page refreshes?

I am just so very confused.
I have a workflow where someone can start to fill out a form for a product. It is a long form and I want to save progress to the server as they type (but not until they fill it out for a bit). So we start at a url for creating the form, after they've typed for a bit, we POST to create a resource on the server, and after the request finishes, we update the url to the edit route with the new id.
In other words, you start filling out the form at url /product then after you've filled it out for a bit the url shifts to /product/123. After that, loading that URL gives you your form.
So basically I have
<Route path={`/product`} exact component={CreateProduct} />
and
<Route exact={true} path="/product/:productId" render={({
match: {params: {productId}},
location: {state: {data}={}}
}) => (
<EditProduct productId={productId} initialData={data}
)} />
See that state? That's because the way I do the switch over from create to edit mode is something like this
const id = await apiFetch(`/api/product`, data, {method: `POST`})
this.props.history.push({pathname: `/product/${id}`, state: {data} })
in the constructor of my <EditProduct> component I have
constructor({productId, initialData}) {
this.super()
this.state = {}
if(initialData)
this.setState({data: initialData})
else
getProduct(productId).then(({data}) => this.setState({data}))
}
By doing that, the initial data into the <EditProduct> is seeded from the <CreateProduct> component and I don't need to reload it from the server or anything.
This works, the transition is smooth, the url updates, and everything is hunky dory.
I can now continue editing the <EditProduct> component and it saves properly. I can open a new tab to the same url and it loads everything up and I can continue. This happens because in that situation initialData is undefined so it's loaded from the server. yay!
BUT
If I instead refresh the original tab things get weird. Any changes that have accumulated since the save are lost. Drilling down in the debugger I see the issue is that initialData passed from the location.state.data object is not empty - it is the initial object from when the product was first created.
So where on earth does it come from? I just did a full page refresh (even a "hard" refresh with no cache and devtools open). That data isn't in the URL (and in fact copy pasting the url into another tab in the same window doesn't have this issue).
The only mechanism I'm aware of that can persist data across refreshes but not to new tabs like this is sessionStorage, yet when I check it in the console, I am told
> sessionStorage
< Storage {length: 0}
I've even thought that maybe react-router is manipulating session storage just before the page unloads and just after it loads, but breaking on the first line of my javascript bundle shows the exact same thing.
So how on earth is this persistence happening!?
I believe the asker already resolve this problem, the answer is buried in the comment though.
The question is actually down to this:
Where the state come from when the user reloads the page? And state refers to props.location.state provided by react-router
TLDR; the state is not a plain javascript implementation, it is bound to the browser environment.
The BroswerRouter of react-router use the underlying native broswer history API directly so the history API is bound to the platform, you can not predict its behavior based on the normal rule.
Here is the special part:
The state object can be anything that can be serialized. Because Firefox saves state objects to the user's disk so they can be restored after the user restarts the browser
Most of the users treat the state as a plain javascript, so there is a problem
I had a very similar problem, and the same confusion.
Solved it with window.history.replaceState()
I had a simple search form which redirected to a second page, and used the location state from the router to repopulate the search input on the second page.
In myse case, this happened:
Search for "foo" on the first page -> Get redirected to the second page, and see search+results for "foo".
Search for "bar" on the second page. -> See results for "bar".
Hit refresh. Expectation? Either an empty search bar, or search+results for "bar". -> Instead, see search+results for "foo" (??)
I solved this by making it so that every time the user does a search on the second page, I replace the state using window.history.replaceState with the correct search term. This way a refresh gives the user the expected search. Replacing the state with an empty object on each search from the second page worked fine as well, giving the user an empty search on each refresh.

Server-side rendering for a specific selection

Let's say my app is a list of many items. There's a lot of items so I don't want to include all the items in the redux state.
When a user visits me at myapp.com/item/:itemId, I want to display the selected item. Currently I make an api call in componentDidMount to fetch the item and store the response in myReduxState.selectedItem. However, this shows the user and unfinished page until the api call finishes.
Is there any way I can get around this?
The pattern I tend to follow is to have a state of fetching being tracked in the redux state. Once the api resolves you just make sure the state is set correctly and then in your render methods use that to determine what you are rendering.
render() {
if (this.state.fetching) {
return <div> // put whatever you want here, maybe a loading component?</div>
}
return (
// put the regular content you would put for when the api call finishes and have the data you need
)
}
I solved this problem by making the creating the state on the server side. I get the itemId from the url in my express route and get the details of the specific item. I use this to create the state and pass it to the front-end.

Prevent deep link in react-router

In my application I'd like to have certain portions of the app not be able to deep linked to. For example our users have a list of surveys and I'd like if someone tried to go directly to a particular survey directly such as /survey/1 that react router would pick up on this and immediately redirect them back to /survey and they would have to select the one they want. I've tried to write onEnter hooks but they seem to be very cumbersome since the only way I've been able to get them to behave correctly is to store some global state that says they have been to the main page and inspect that every time the route is navigated to.
Im using pushstate in my application if that makes any difference and react-router 2.0
I'd like to try to avoid having to write server rewrite rules for this since there are a lot of areas in my application where this rule is applicable.
I have a suggestion which is similar to the onEnter hook:
Wrap the component of the survey/:id route with a function which verifies if deep linking is allowed or not, let's call this function preventDeepLinking
The preventDeepLinking function checks if the location state contains a certain flag, let's say allowDeep. This flag would be set in the location state when navigating from another page of your app. Obviously, this flag will not be set when the user tries to navigate directly to the page of a survey.
The preventDeepLinking function will render the wrapped component only if deep linking is allowed, otherwise will redirect to a higher route.
I created a sample on codepen.io. You can play with it in the debug view of the Pen: http://s.codepen.io/alexchiri/debug/GZoRze.
In the debug view, click the Users link and then on a specific user from the list. Its name will be displayed below. Notice that its id is part of the url. Remove the hash including the ?_ and hit Enter. You will be redirected to /users.
The code of the Pen is here: http://codepen.io/alexchiri/pen/GZoRze
The preventDeepLinking function can be improved, but this is just to prove a point. Also, I would use the browserHistory in react-router but for some reason I couldn't get it running in codepen.
Hope this helps.

How to deal with query params in react + react-router + flux

I'm trying to replace a Backbone.Marionette App to React and am facing difficulty thinking about query params. I think I'm missing a really simple peace in understanding this pattern so I apologize if this question is totally nonsense. I would appreciate any support or just pointing me to some direction that I can google more specifically.
There's a /users page which lists users and you can filter the users via search bar. So if you want to filter the users which contain 'joe' in their username, I would make a request to the server with query params like /users?username=joe. In addition I am able to paginate by adding a page parameter, too (/users?username=joe&page=1).
If I only think about the functionality, the flow would probably be
The Client inserts joe to the input element and clicks Search.
Clicking the Search button fires an Action (like Action.getUser).
The Action makes a request to the server and receives the results
The Dispatcher dispatches a function with the results payload to whomever (usually the Store) is interested in the Action.
The Store's state changes with the new result received by the Action
The View (Component) re-renders by listening to the Store's change.
and it works as expected. However, I would like the Client to be able to bookmark the current filtered result and be able to come back to the same page some time later. This means I will need somewhere to save explicit information about the search term the Client made, which is usually the url (am I right?). So I will need to update the url with query parameters to save the search term (/users?username=joe&page=1).
What I'm confused is where and when to update the url? What I can come up with right now are the 2 options below - and they don't seem to be clean at all.
Option 1
The Client inserts joe to the input element and clicks Search.
Clicking the Search button fires a transition of the ReactRouter with the new query params (/users?username=joe&page=1).
The View (Component) receives the new params via this.props.params and this.props.query.
The View (Component) fires an Action like Action.getUser depending on the query params it receives - in this case username=joe&page=1.
after this, it is the same as above
Option 2 (only 6 is different from what I explained above)
The Client inserts joe to the input element and clicks Search.
Clicking the Search button fires an Action (like Action.getUser).
The Action makes a request to the server and receives the results
The Dispatcher dispatches a function with the results payload to whomever (usually the Store) is interested in the Action.
The Store's state changes with the new result received by the Action
The View (Component) re-renders by listening to the Store's change. And somehow (I don't know how, yet) updates its url depending on its props (like this.props.searchusername, and this.props.searchpage)
What is the best practice on handling query params? (or this may not be specific to query params)
Am I completely misunderstanding the design pattern or architecture? Thanks in advance for any support.
Some articles I've read
Any way to get current params or current query from router (outside of component)?
Async data and Flux stores
Make it easier to add query parameters
React Router and Arbitrary Query Params: Page Refreshes Unintentionally on Load?
Add default params?
I would consider best practice to be the submit button only setting the location query (username). The rest should be taken care by the main react component that is assigned as router component. By this, you can be sure that anytime one revisits or shares the url, they can get the same results. And this is very generic too.
Something like this:
let myQuery = this.props.location.query;
if (myQuery.username) {
let theUser = myQuery.username;
this.setState({
userName = myQuery.username
});
} else {
this.setState({
userName = false //Show All
});
}
And then use this state "userName" to send to the server to search with. By this way, you will not need to iterate the code of the component that takes care of listing users since server already sends the relevant data.
In my experience with using location queries in React, I have been very content with their reactivity cycles and performance. I'd highly recommend keeping every different app state in relevance with the url.
Not entirely sure what you mean by
this means I will need to update the url to save the information (/users?username=joe&page=1).
You will probably have a similar structure to this.
TopContainer.jsx
-- Users.jsx
-- a list of User.jsx
Usually TopContainer will watch all the stores and if anything changed, pass it down to users.jsx. That way in Users.jsx, you can simply render this.props.users without worrying about any reRendering.
The search users actions usually happens in TopContainer's componentWillMount event, and you the page will listen to UserStore. That's a good place to throw in any query params. Something like this would work
componentWillUnmount() {
let searchTerm = router.getCurrentQuery().searchTerm;
UserActions.searchUsers(searchTerm)
},
The page doesn't really care if the url has a query params or not, it just dumbly shows whatever in the user store.
Then when the search finishes, Users.jsx will be reloaded and show the correct results

Resources