Changing routes after successfully saga - reactjs

I'm trying to figure out what the proper way to go about this is.
Lets say we have a store of items. These items can be edited, deleted and created. When editing or adding an item the route changes to /item/add or /item/edit/{id}.
After an item has been added or edited successfully by saga we want to send them back to the base route. What's the proper way to go about this?
I've seen two ways, one where you inject a history object into a and then include the history object in the saga's as well. Another to keep a "status" ("", "failed", "success") in the item store using in the components and resetting that status when the component unmounts since add and edit both need to use the status.
Which is the proper way to go about this problem though?

In the past, I've used react-router v3 with react-router-redux integration to dispatch actions that change the route.
React router 4 has a react-router-redux package, which is still in alpha stage. Although it's alpha, this functionality works fine for me, but you should check it for yourself.
In your saga, you can use the put effect to dispatch the action:
import { push } from 'react-router-redux';
yield put(push('/route'));

There's no proper way, just whatever works with you. I prefer the minimal:
//history.js
...
export const history = createHistory();
//Root.js
import { history } from './history';
<Root history={history}>
//mySaga.js
import { history } from './history';
function *mySaga() {
yield call(history.push, '/myRoute');
}

Check this doc: https://reacttraining.com/react-router/core/guides/redux-integration
According to this doc, the best solution is to include the history object (provided to all route components) in the payload of the action, and your async handler can use this to navigate when appropriate.
If you have history object from the payload to the action, you can use
history.push('requiredRoute');
to requiredRoute.

Related

Using this.props.history.push("/path") is re-rendering and then returning

Edited the question after further debugging
I am having a strange issue, tried for a while to figure it out but I can't.
I have a React Component called NewGoal.jsx, after a user submits their new goal I attempt to reroute them to my "goals" page.
The problem: After they submit the browser loads in my goal page, but only for one second. It then continues and goes BACK to the NewGoal page!!
I am trying to understand why this is happening, I am beginning to feel that this might be an async issue.
Here is my code, currently it is using async-await, I also tried the same idea using a .then() but it also didn't work:
async handleSubmit(event)
{
const response = await axios.post("http://localhost:8080/addGoal",
{
goalID: null,
duration: this.state.days,
accomplishedDays: 0,
isPublic: this.state.isPublic,
description: this.state.name,
dateCreated: new Date().toISOString().substring(0,10),
}) */
// push to route
this.props.history.push("/goals");
}
While debugging, I tried taking out the functionality where I post the new message, and just did a history.push, code is below - and this completely worked.
// THIS WORKS
async handleSubmit(event)
{
// push to route
this.props.history.push("/goals");
}
But as soon as I add anything else to the function, whether before the history.push or after, it stops working.
Any advice would be very very appreciated!
Thank you
In the React Router doc's the developers talk about how the history object is mutable. Their recommendation is not to alter it directly.
https://reacttraining.com/react-router/web/api/history#history-history-is-mutable
Fortunately there are few ways to programmatically change the User's location while still working within the lifecycle events of React.
The easiest I've found is also the newest. React Router uses the React Context API to make the history object used by the router available to it's descendents. This will save you passing the history object down your component tree through props.
The only thing you need to do is make sure your AddNewGoalPage uses the history object from context instead of props.
handleSubmit(event)
...
//successful, redirect to all goals
if(res.data)
{
this.context.history.push("/goals")
}
...
})
}
I don't know if you're using a class component or a functional component for the AddNewGoalPage - but your handleSubmit method hints that it's a member of a Class, so the router's history object will be automatically available to you within your class through this.context.history.
If you are using a functional component, you'll need to make sure that the handleSubmit method is properly bound to the functional component otherwise the context the functional component parameter is given by React won't not be available to it.
Feel free to reply to me if this is the case.

When in component lifecycle should I get query params from URL?

I'm using React 16.4.1, React Router 4.3.1, and React Redux 5.0.7. I have a search route that can receive a query param like this:
https://example.com/search?q=foo
To be clear, React Router 4 discontinued support for location.query, so we're left having to manually parse query params from the location.search prop that React Router provides. We can use something like Javascript's URLSearchParams interface for this.
So I'd like a user to be able to visit the URL above and immediately begin a search for "foo". Therefore, I need to gather the q param at some point during page load. But when?
My first instinct was to have my Search component parse the query params during its componentDidMount lifecycle hook. That also happens to be the recommended hook for retrieving data from the server, something I'll do if the q param has a value.
But I've also considered moving that logic outside the component entirely to some JS file that generally runs on page load, like my app's index.js file. I have access to my Redux store there and could update the application state with the "searchText", and my Search component could then simply check for that prop (wired via Redux) during its mounting.
Gathering query params from the URL on page load - then taking action on them - is a relatively new problem for React developers, given that React Router handled it for us prior to version 4. But surely I'm not the first person to have to do this since version 4 was released. Is there an established pattern or best practice for this?
Thanks.
My approach would be to create an initialize folder along actions, reducers etc.. and create there functions like
export default (dispatch, getState) => {
dispatch(urlQueryParams());
// Some other initializers
};
const urlQueryParams = () => {
// return json to reducer with the params
}
Then on your main index file you can trigger it
import addQueryParamsInitialzer from 'redux/initialize/queryParam';
const store = configureStore(INITIAL_STATE);
addQueryParamsInitialzer(store.dispatch, store.getState);
That way you'll have it on your store no matter what component you're on

The most convenient pattern for XHR and route change in React/Redux application

I'm working on a React+Redux application, and I have search functionality in it. As for UI it is quite similar to any other search engine - there is a Home page, and a Search Result page.
So to get search results, I have an action requestSearch, which returns an object with action type and data. It is captured by redux-saga, where I make a request to the API, process the response and dispatch new action with the search results, which is captured by a reducer. But it is not the question.
The questions is, where do I initiate the whole thing? Also, what is the best place for routing here (react-router v3), where to change the route? I'm asking about any good pattern, because obviously, I'm missing something important here.
So far I tried 2 ways to implement that:
call requestSearch actions on search form submit and then change
the route with history.push();
change the route and call
requestSearch from Search Result page container.
I'm not satisfied with both of these solutions.
Will appreciate any help, suggestions or criticism.
•call requestSearch actions on search form submit and then change the
route with history.push();
OK, it is possible to approach the decision of this task on the other hand and to consider several moments. First, if you use react-router, it isn't necessary to do direct calls like history.push any more, instead it is necessary to use konsistenty and to cause push function from https://github.com/reactjs/react-router-redux there where it is necessary.
Secondly, in redux-saga there is an access to push action into store - in current case it will be react-router-redux action push with appropriate URL:
import { push } from 'react-router-redux'
yield put(push('/foo'))
Setup your store as following:
import { routerMiddleware, push } from 'react-router-redux'
// Apply the middleware to the store
const middleware = routerMiddleware(browserHistory)
const store = createStore(
reducers,
applyMiddleware(middleware)
)
// Dispatch from anywhere like normal.
store.dispatch(push('/foo'))

react router - keep the query string on route change

I would like to create routes that support query string.
When i say support i mean, passing it to the next route some how.
For example:
given this route: domain/home?lang=eng
and when moving to route domain/about i want it to keep the Query String and display domain/about?lang=eng.
I was sure there's a built in functionality for this but after reading the docs and a lot of search on the net, i couldn't find an elegant solution.
I'm using react-router#3.0.0 and react-router-redux#4.0.7
For react-router 4.x, try
const { history }
history.push('/about' + history.location.search)
To access this.props.history, make sure you have wrapped the component with withRouter HOC
import { withRouter } from 'react-router-dom'
...
export default withRouter(component)
refer https://github.com/ReactTraining/react-router/issues/2185
You will have to "forward" query param on each page transition - bothering and you can easily forgot to...
Instead, I would do this.
read stored/persisted lang preference. localStorage is good candidate here. Fallback to default language, when no preference is found
share lang via context, so that each and every component can read this value.
create some button (or whatever), which would modify this value
Since you are using redux, I would pull redux-persist to persist this preference across page reloads.

Accessing react-router from flummox action/store

I want to be able to make an API call in a Flummox action and transition differently depending on the response. I can pass the router into the action call but am looking for advice on a potentially better way.
UPDATE:
The correct answer is below but I wanted to add some detail to this.
I'm doing an isomorphic app that 1. needs to get data from an api and 2. may need to redirect based on the api response. Whatever I do needs to work through an express.js app and through react.
I made a small lib that does the api call and return some results. I pass it an object (query params object from express for the server-side or a similar object I create for the react-side). This lib makes the request, determines if a redirect is needed and passes back errors, path (string), redirect (boolean), and data (json).
In express, if the redirect boolean is true, I just redirect to it with the current query params. If it's false, I pass the data to flux through an action which updates a store. I then renderToString with react, serialize stores so the clint-side can bootstrap, and send a rendered page to the client.
In react, the redirect boolean isn't important, I get the response back from my lib, pass the data to my flux action, and just transition to whatever the path is. There's really no notion of redirection. Just go to the path no matter what.
Hopefully this is helpful to someone.
In my setup I have my own router module which just wraps the instance of react-router that I create at startup. That makes it easy for any part of the application to just require that module and do what it needs to.
But I would advise you not to have side effects like a call to the router inside your actions. Actions should concern themselves on mutating your application state, and nothing more. It should be possible to call the same action from anywhere in your application which needs to perform the mutation that the action encapsulates.
So if you're switching routes during an action, you're basically tying that action to that particular use case. Let's take an example. You have a todo list, with an input box at the bottom to add a new todo. For that use case, it might be useful to switch route after you saved the todo. Perhaps you switch to Recent Todos or something. But then a new use case comes along where you want to be able to add new todos during another workflow, perhaps the user is planning her week and should be able to add todos on different days. You want the same action that adds a todo, but you certainly don't want to switch routes because the user is still planning the week.
I haven't used Flummox a lot, but from my understanding your Flux object returns whatever the action returns when you trigger an action. So instead of putting the route change inside your action, make sure to return the response from the action and let your component decide if the route should be changed. Something like this:
// todo-action.js
class TodoActions extends Actions {
createMessage(todo) {
return TodoStore.saveToServer(todo);
}
}
// todo-list.js
const TodoList extends React.Component {
render() {
...
}
addTodo(todo) {
this.props.flux.addTodo(todo).then(response => {
if (response.some.prop === someValue) {
this.props.router.transitionTo(...);
}
});
}
}
That way, the action is still nicely decoupled from the route change. If you want to do the route switch in more than one place, you could encapsulate that in a addTodoAndSwitchRoute method in your Flux class.

Resources