I've new to React (and Redux), and trying to make a simple form that will create and Item (name, type
), and redirect to the ListIems page, after creation.
I'm using react-thunk to handle asynchronous actions, and everything is in typescript.
My issue is how to I handle creation results, and redirects? As my action CreateItemRequest
is asynchronous, how do I inform my containers the Item has been successful created so it redirect to the ListIems page.
I've tried to simply use a boolean in my CreateItemSuccess
action my reducer, but then, I can't go to the create form itself, as the boolean is now true is the model, and then immediately redirects to the ListIems page.
I'm stuck here, I'm sure I'm missing something silly, but can't find anything anywhere...
import { Route, Redirect } from "react-router-dom";
import { connect, Dispatch } from 'react-redux';
import * as React from 'react';
import * as Types from '../../types';
import * as Actions from '../../actions';
interface ICreateModelDispatchProps {
LoadItems: () => void;
ChangeForm: (model: Actions.IItemCreateModel) => void;
CreateItem: (model: Actions.IItemCreateModel) => void;
}
interface ICreateItemStateProps {
Types: Types.ITypeEntity[];
ItemCreated: boolean;
Model: Actions.IItemCreateModel;
}
class CreateItem extends React.Component<ICreateItemStateProps & ICreateModelDispatchProps, Types.IAppState> {
public componentDidMount() {
this.props.LoadItems();
}
public render() {
if (this.props.ItemCreated) {
const redirect = () => <Redirect to={{ pathname: '/items' }} />;
return <Route render={redirect} />;
}
if (!this.props.Types.length) {
return <div id="create-item" className="loading" />;
}
const { Name, Type } = this.props.Model;
const onSubmit = this.onSubmit.bind(this);
const onNameChange = this.onNameChange.bind(this);
const onTypeChange = this.onTypeChange.bind(this);
return (
<div id="create-item">
<form onSubmit={onSubmit}>
<div>
<label htmlFor="name">Name</label>
<input type="text" id="name" value={Name} placeholder="Item's Name" onChange={onNameChange} />
</div>
<div>
<label htmlFor="type">Type</label>
<select id="type" onChange={onTypeChange} value={Type}>
<option value="-1">Choose a type</option>
{this.props.Types.map(type => <option key={type.Id} value={type.Id}>{type.Name}</option>)}
</select>
</div>
<div>
<input type="submit" value="CREATE" />
</div>
</form>
</div>
)
}
private onNameChange(event: React.ChangeEvent<HTMLInputElement>) {
this.props.ChangeForm(Object.assign(this.props.Model, { Name: event.target.value }));
}
private onTypeChange(event: React.ChangeEvent<HTMLSelectElement>) {
this.props.ChangeForm(Object.assign(this.props.Model, { Type: event.target.value }));
}
private onSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
this.props.CreateItem(this.props.Model);
}
}
export function mapStateToProps(state: Types.IAppState): ICreateItemStateProps {
const types = Object.keys(state.Entities.Types).map(key => state.Entities.Types[Number(key)]);
const Model = state.CreateItem;
return {
Model,
Types: types,
ItemCreated: !!state.CreateItem.Created,
};
}
export function mapDispatchToProps(dispatch: Dispatch): ICreateModelDispatchProps {
return {
LoadItems: () => dispatch(Actions.ITEM_TYPES_REQUEST()),
ChangeForm: (model) => dispatch(Actions.ITEM_CREATE_CHANGE(model)),
CreateItem: (model) => dispatch(Actions.ITEM_CREATE_REQUEST(model)),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(CreateItem);
You may write your action using Promise
or async/await
. Here is example in js (not ts)
export default function ITEM_CREATE_REQUEST(model) {
return (dispatch) => {
return new Promise((resolve, reject) => {
/* SOME STUFF */
resolve(SOME_DATA);
});
}
};
than you may use it to navigate when resolved
onSubmit() {
event.preventDefault();
this.props.CreateItem(this.props.Model).then((SOME_DATA) => {
/* PERFORM REDIRECT */
});
}
When you dispatch your async action, you update the state of the store at some unpredictable time in the future. Your component has to receive these updates somehow via mapStateToProps
.
This way you could handle that the props for creating the new item got updated - presumably best inside getSnapshotBeforeUpdate
lifecycle:
getSnapshotBeforeUpdate(prevProps, prevState) {
if (prevProps.itemCreated !== this.props.itemCreated) {
// itemCreated could be a counter or something else.
// For example, you count up after item is created.
// Whenever that prop changes, you navigate away like this.
const { history } = this.props
history.pushState(null, '/anywhere') or
history.replaceState(null, '/anywhere')
}
return null;
}
Here are the docs for getSnapshotBeforeUpdate
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With