Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Redux, how to handle form item creation and redirects?

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...


My component:

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);
like image 852
jbuiss0n Avatar asked Jun 18 '18 13:06

jbuiss0n


2 Answers

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 */
  });
}
like image 117
Sasha Kos Avatar answered Oct 23 '22 17:10

Sasha Kos


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.

like image 37
Hinrich Avatar answered Oct 23 '22 19:10

Hinrich