Is it a good idea to use browserhostory.push in action helpers? - reactjs

In my React App I need to take decision based on data I receive from the server.
If data is expected ( Dispatch actions to update state)
If data has error tag ( browserhistory.push('/notfound'); )
If expected data is unable to parsed ( browserhistory.push('/error');)
In my app structure, I am using Redux, React-Router and React-redux-Router libraries but no middleware. I have made actionHelpers to making ajax calls and then dispatch appropriate actions using Action Creator. These actionHelper methods are exposed in Components to change state.
My Questions:
What's the best way to handle these scenarios ?
Is actionHelper the best place to take these decisions ?
I don't want to use any middleware for now but please let me know if its a good idea to use middleware to handle these scenarios.

Actions are not the place where you should do redirections. This behavior should be implemented in the component itself and actions should be left to update the store.
You may want to use the Redux-thunk middleware here which allows you to dispatch a function (which receives dispatch as an argument instead of the object actions. You can then wrap that function in a promise and use it in componentWillMount.
In your actions file:
updateReduxStore(data) {
return {
type: SOME_TYPE,
payload: data.something
};
}
fetchAndValidateData() {
...
}
checkData() {
return function(dispatch) {
return new Promise((resolve, reject) => {
fetchAndValidateData().then((data) => {
try {
if (JSON.parse(data).length > 0) {
dispatch(updateReduxStore(data));
resolve('valid data');
} else if (data.error) {
reject('error in data');
}
}
catch(err) {
reject('malformed data');
}
});
});
};
}
Then in your component:
componentWillMount() {
this.props.checkData()
.then((message) => {
console.log(message); //valid data
})
.catch((err) => {
if (err === 'error in data') {
browserHistory.push('/notfound');
} else if (err === 'malformed data') {
browserHistory.push('/error');
}
});
}
Redux-thunk middleware is made for such use cases.

Related

Combine SvelteKit's throw Redirect with Felte's onSuccess

The problem:
Nothing happends when throwing throw redirect(302, '/auth/sign-up-success') in SvelteKit's actions if onSuccess: () => {...} is set in Felte's createForm({...}).
Example:
// +page.server.ts
export const actions: Actions = {
default: async (event) => {
...
throw redirect(302, '/auth/sign-up-success');
}
}
// SignUpForm.svelte
const { form, errors } = createForm({
onSuccess: (response) => {
invalidate('app:auth')
},
...
}
If I would delete the onSuccess part, then redirect would happend.
Question:
Is there a way to reuse that redirect form success response logic from default Felte form config without writing it again myself?
Action responses are JSON objects with a type, you could read the response and redirect on the client:
async onSuccess(response) {
const { type, location } = await response.json();
if (type == 'redirect') {
goto(location); // from '$app/navigation'
return;
}
}
I would not recommend using this library though. It appears to be incompatible with SSR and one of its main actions shares the name of the form data property used by SvelteKit form actions.
Depending on why you are using this, there might be more suitable tools for SvelteKit in particular (if you even need any, SvelteKit does many things out of the box).

Display loading state and change route when API call is successfull

While working on a side project, I faced an issue with react-router-dom.
What I want to implement is: When I submit a Form, I need to save the data on my server. While the request is pending, I need to display a loading indicator. Once the server says everything is ok, I need to redirect the user on a new page
action.js
export const addNotification = value => async dispatch => {
dispatch(addNotificationPending())
try {
const response = await client.createNotification(values)
dispatch(addNotificationSuccess(response))
} catch(e) {
dispatch(addNotificationFailure())
}
}
component.js
class CreateNotificationForm extends Component {
onSubmit = (values) => {
this.props.addNotification(parameters, history)
}
render() {
const { isCreating } = this.props
const submitBtnText = isCreating ? 'Creating...' : 'Submit'
return (
<Form>
// content omitted
<Submit value={submitBtnText} />
</Form>
)
}
}
const mapStateToProps = (state) => ({
isCreating: getIsFetching(state)
})
const mapDispatchToProps = (dispatch) => ({ // omitted })
connect(mapStateToProps, mapDispatchToProps)(CreateNotificationForm)
So far so good: When I submit my form, the form's submit button shows a Creating... text.
However, how do I tell react-router to load a new path once the request is successful?
Right now, I've done that by using withRouter and using this.props.history as a second argument for this.props.addNotification.
It works great, but it seems really wrong
I've seen solutions using react-router-redux, but I don't really want to add a new middleware to my store.
Should I make the API call inside my component and use a Promise?
Any help?
Update:
After working a little on my own React project, and thinking about similar situations where I handle route changes there, I decided I want to change my original answer. I think the callback solution is OK, but the solution that you already mentioned of making the API call inside your component and using a promise is better. I realized that I've actually been doing this in my own app for a while now.
I use redux-form in my app, and it provides onSubmitSuccess/onSubmitFail functions that you can use to handle the submit result, and each of those rely on you returning a promise (usually from your action creator).
I think the fact that one of the most popular packages for form submission in React/Redux supports this pattern is an indication that it's probably a good pattern to use. Also, since react-router passes history into your component, it seems logical that they expect most people to do a lot of their programmatic route changes inside the component.
Here's an example of what the promise solution would look like with your code:
action.js
export const addNotification = value => dispatch => {
return new Promise(async (resolve, reject) => {
dispatch(addNotificationPending())
try {
const response = await client.createNotification(values)
dispatch(addNotificationSuccess(response))
resolve(response)
} catch(e) {
dispatch(addNotificationFailure())
reject(e)
}
})
}
component.js
onSubmit = async () => {
try {
await this.props.addNotification(parameters)
this.props.history.push('/new/route')
} catch(e) {
// could use a try/catch block here to display
// an error to the user here if addNotification fails,
// or go to a different route
}
}
Old Answer:
A simple solution would be to allow addNotification() to accept a callback function as an optional second argument.
export const addNotification = (value, callback=null) => async dispatch => {
dispatch(addNotificationPending())
try {
const response = await client.createNotification(values)
dispatch(addNotificationSuccess(response))
(typeof callback === 'function') && callback()
} catch(e) {
dispatch(addNotificationFailure())
}
}
Then inside your component use the router to go to the new route.
onSubmit = (values) => {
this.props.addNotification(parameters, () => {
this.props.history.push('/new/route')
})
}
You should not write your asynchronous calls in reducers or actions as the documentation clearly suggests them to be pure functions. You will have to introduce a redux-middleware like redux-thunk or redux-saga (I personally prefer sagas)
All your async calls will happen inside the middleware, and when it succeeds, you can use react-routers history .replace() or .push() methods to update your route. Let me know if it makes sense
You can use one popular package axios
See Here https://www.npmjs.com/package/axios
and you can implement your login like
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
You can write your loader login while calling api
and then you can hide your loader in .then

redirect from component level after specific dispatch - redux thunk

I have a fairly simple use case, but having a hard to find the appropriate answer. I'm using React,Redux,React Router & redux thunk middleware.
Lets say, I have two module food-tags & food. These modules have individual create,list,edit page/component. In practical use case, food-tags have no special value. Whenever a food object is created, separated tags are inserted into the food object's tags property.
General use case is that, after any item is created successfully, react router redirects it to the list page.
whenever i'm calling the createTag action from food-tag module, I can do it in a hacky way. like just after the success dispatch, i can call
browserHistory.push('/dashboard/tags')
this leads me to a problem where i can create food-tag inline from the food create component. Codes are given below
actions.js
export function createTag(tag) {
return function (dispatch) {
axios.post(API_URL + 'api/tags', tag)
.then((response) => {
// I CAN DO REDIRECT HERE,BUT THIS CAUSES THE PROBLEM
dispatch({type: 'TAG_CREATE_RESOLVED', payload:response});
toastr.success('Tag created Successfully.......!');
})
.catch((err) => {
dispatch({type: 'TAG_CREATE_REJECTED', payload: err});
toastr.warning(err.message);
})
}
}
component/container.js
createTag () {
//validatation & others....
this.props.createTag(tag)
}
react-redux connection
function mapDispatchToProps (dispatch) {
return bindActionCreators({
createTag: createTag
}, dispatch)
}
Almost same pattern in food/create.js
$('#food-tags').select2(select2settings).on('select2:selecting', function (event) {
let isNewTagCreated = event.params.args.data.newOption,
name = event.params.args.data.text;
if (isNewTagCreated && name !== '') {
reactDOM.props.createTag({name}); // reactDOM = this context here
}
});
What I want basically that, I want to get access in the component level which action type is dispatching so that i can redirect from component & show notifications as well instead of action thunk. May be i'm not thinking in the proper way. there could be a dead simple work around.
It's good to know that redux-thunk passed out return value from the function. So you can return the promise from the action creator and wait until it will be finished in you component code
export function createTag(tag) {
return function (dispatch) {
return axios.post(API_URL + 'api/tags', tag) // return value is important here
.then((response) => dispatch({type: 'TAG_CREATE_RESOLVED', payload:response}))
.catch((err) => {
dispatch({type: 'TAG_CREATE_REJECTED', payload: err})
throw err; // you need to throw again to make it possible add more error handlers in component
})
}
}
Then in your component code
createTag () {
this.props.createTag(tag)
.then(() => {
toastr.success('Tag created Successfully.......!');
this.props.router.push() // I assume that you have wrapped into `withRouter`
})
.catch(err => {
toastr.warning(err.message);
});
}
Now you have proper split up between action logic and user interface.

Passing observer into redux-sagas

I'm trying to leverage a firebase observable from within the redux-sagas framework but I'm having trouble doing this without a hack. I'm trying to use firebase's "onAuthStateChange" function as shown here
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
} else {
// No user is signed in.
}
});
essentially the observer executes whenever a user signs in or out
In my firebase utility file my method looks like this:
authChanged: () =>{
return firebaseAuth.onAuthStateChanged(callback);
}
then in my saga, for the moment, I'm simply trying to log to the console whenever the observer observes something:
export function* loginState(){
Firebaseutils.authChanged(function(user){
if(user){
console.log('User logged in!')
}else{
console.log('User logged out')
}
});
}
This fails due to 'callback' not being defined. I'm essentially trying to curry the observer to pass to sagas but it's not working. My workaround is to pass the full firebase auth object to my login/logout saga and then create the observer in there. That works but seems like a hack. Any help would be hugely appreciated.
I don't think you're actually integrating the observer into the saga in your snippet. It'll work for console.log b/c that's a sync function, but you can't yield anything from that callback, as its context is separate from that of the generator. this deprives you of lots of the sagas utility, if it works # all.
I had to get this working on my project, and the best I could do was inspired by this project and the architecture in this starter-kit.
Basically,it's a few steps.
Wrap your observer in function that takes dispatch and returns a promise.
export function initAuth(dispatch) {
return new Promise((resolve, reject) => {
myFirebaseAuthObj.onAuthStateChanged(
authUser => {
if (authUser) {
dispatch(signInFulfilled(authUser))
} else if (authUser === null) {
dispatch(signOutFulfilled())
}
resolve()
},
error => reject(error)
)
})
}
Wrap your top-level container in a function
const initialState = window.___INITIAL_STATE__
const store = createStore(initialState)
let render = () => {
const routes = require('./routes/index').default(store)
ReactDOM.render(
<AppContainer store={store} routes={routes} />,
MOUNT_NODE
)
}
wrap render() in initAuth:
initAuth(store.dispatch)
.then(() => render())
.catch(error => console.error(error))
use your sagas for anything else. for example, you can conduct route-changes from your sagas like so:
function* signIn(authProvider) {
try {
const authData = yield call([firebaseAuth, firebaseAuth.signInWithPopup], authProvider)
yield take(SIGN_IN_FULFILLED)
browserHistory.push('/dash')
}
catch (error) {
yield put(signInFailed(error))
}
}
It seems like you meant to have callback as a parameter to authChanged but authChanged currently takes no parameters. Here's what you probably meant to do:
authChanged: (callback) => {
return firebaseAuth.onAuthStateChanged(callback);
}
I'm not sure I understand what you mean by currying the observer but you could also do something like this to curry the firebase method to use as your method:
{
authChanged: ::firebaseAuth.onAuthStateChanged,
}

How to integrate redux-form's onSubmit with redux-api-middleware?

I'm writing a React / Redux app using redux-form and redux-api-middleware, and I'm having trouble integrating redux-form's onSubmit function with the RSAA lifecycle.
The redux-form documentation says that the onSubmit handler should return a Promise. Until resolve is called on the promise, the form's submitting prop will be true.
However, in this app my API calls don't currently use promises (e.g. via fetch). I make API calls by dispatching a [CALL_API] RSAA action and reducing redux-api-middleware's response actions.
Problem code
class MyReduxFormContainer extends Component {
render() {
return (
<MyReduxForm submit={this.props.submit} />
)
}
}
const mapDispatchToProps = (dispatch) => {
return {
submit: function(values, dispatch) {
dispatch({
[CALL_API]: {
method: 'PATCH',
types: [
{
type: 'REQUEST',
endpoint: '...',
body: JSON.stringify(values)
},
'SUCCESS',
'FAILURE'
]
}
});
// Problem: redux-api-middleware-style API calls normally don't leverage promises.
// Out of the box, this library doesn't offer a promise to return.
}
}
};
export default connect(
// ...
mapDispatchToProps
)(MyReduxFormContainer)
Possible Solutions
I could pass a promise through the payload RSAA callback, which could then resolve/reject the promise after the API response, but this seems to violate the rule that "action creators should't cause side-effects." Granting that redux-api-middleware seems to bend this rule.
I could in theory just use fetch inside the onSubmit handler, instead of redux-api-middleware, but this isn't just a concession which makes my API interactions inconsistent across the application, it also risks duplicating any API middleware activities I've baked in, e.g. setting default headers, de-camelizing / camelizing payloads, etc.
Does anyone have experience using redux-form and redux-api-middleware together?
If it were just redux-api-middleware, I would have expected to simply change the form's submitting prop by altering the form's state when reducing the ACTION_TYPE_[REQUEST|SUCCESS|FAILURE] action types. But it seems non-standard and potentially risky to directly modify the form's state from a reducer. Example redux-form implementations seem to emphasize that redux-form state should be transparent / only indirectly manipulated.
Any thoughts / pointers would be greatly appreciated!
Related GitHub issues
redux-api-middleware:
https://github.com/agraboso/redux-api-middleware/issues/21
https://github.com/agraboso/redux-api-middleware/issues/53
redux-form:
https://github.com/erikras/redux-form/issues/777
Recently I found quite elegant and generic way combine it. Here is article with explanation
export const formToApiAdapter = (dispatch, actionCreator, formatErrors) => (
(...args) => (
new Promise((resolve, reject) => (
dispatch(actionCreator(...args)).then(
(response) => {
if (response.error) {
return reject(formatErrors(response));
}
return resolve(response);
}
)
))
)
);
For lack of a better solution, I'm currently wrapping my dispatch({[CALL_API]}) call inside of a Promise, within the redux-form submit handler.
class MyReduxFormContainer extends Component {
render() {
return (
<MyReduxForm submit={this.props.submit} />
)
}
}
const mapDispatchToProps = (dispatch) => {
return {
submit: function(values, dispatch) {
// Solution: Wrap the [CALL_API] dispatch in a Promise
return new Promise((resolve, reject) => {
dispatch({
[CALL_API]: {
method: 'PATCH',
types: [
{
type: 'MY_PATCH_REQUEST'
endpoint: '...',
body: JSON.stringify(values)
},
{
type: 'MY_PATCH_SUCCESS',
payload: function (action, state, res) {
// Solution: resolve() the promise in the SUCCESS payload callback
// Changes `submitting` prop of MyReduxForm
resolve();
}
},
{
type: 'MY_PATCH_FAILURE',
payload: function (action, state, res) {
// Solution: reject() the promise in the FAILURE payload callback
// Changes `submitting` prop of MyReduxForm
reject();
}
}
]
}
});
});
}
}
};
export default connect(
// ...
mapDispatchToProps
)(MyReduxFormContainer)
Ultimately I'm pretty unhappy with this code architecture, and at this point I think standard fetch usage would have been preferable to redux-api-middleware.
Triggering effects after API responses is standard enough as a concern, there ought to be more elegant solutions than this kind of callback nesting, e.g. using a promise chain.

Resources