Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

SharePoint Framework で TODO管理アプリを作る (4/4) 詳細画面の作成

続きです。今回も SharePoint Online のデータへのアクセスが含まれるので、SPFxっぽいかもです。
毎度ながら内容に関して React も TypeScript もあまり上手に使えるわけではないですので、そのあたりご容赦ください。。。

完成イメージ(4/4)

Teams のタブにもいい感じにおさまります。
GIF 2020-11-06 17-03-53.gif

下は画像が荒いですね。

もちろん SharePoint Online のページとしても使えます。
image.png

関連記事

Webパーツ初期設定編 1/4
https://qiita.com/nanoka/items/834ee5216a5437dc04bc
画面レイアウトと画面遷移編 2/4
https://qiita.com/nanoka/items/35dea4791ad767497ed6
一覧画面編 3/4
https://qiita.com/nanoka/items/fbda2f09796a445dd9e2
詳細画面編 4/4
https://qiita.com/nanoka/items/92093ca05096728e2deb

手順

 1. 新規作成用関数の作成
 2. 詳細画面用コンポーネントの新規作成機能の作成
 3. 更新、削除用関数の作成
 4. 詳細画面用コンポーネントの更新、削除機能の作成

手順の説明

1. 新規作成用関数の作成

データ操作用の関数なので、api フォルダを更新します。

思い出のフォルダ構成
...src/
 └ webparts/
  └ spfxTodo/
     ├ api/ (データ操作用関数を管理)
     ├ assets/
     │ └ stylesheets/
     ├ components/
     │ ├ atoms/
     │ ├ molecules/ (中くらいの部品を管理)
     │ └ organisms/
     │ └ pages/ (画面を管理)
     │ └ templates/
     └ loc/

詳細画面用の単一アイテムを取得する関数と、リストアイテム作成用の関数を作っていきます。
Postなので作成するデータを作ってあげないといけませんが、列名を key 値を value にした Object を渡してあげるだけであとはいい感じにしてくれます。

api/index.ts
import { SPHttpClient, SPHttpClientResponse, ISPHttpClientOptions } from '@microsoft/sp-http';
import { WebPartContext } from '@microsoft/sp-webpart-base';

/********** 画面から呼び出し用関数 **********/
/********** TodoList GET **********/
import { SPHttpClient, SPHttpClientResponse, ISPHttpClientOptions } from '@microsoft/sp-http';
import { WebPartContext } from '@microsoft/sp-webpart-base';

import * as util from '../components/util';

import { ITodoItem } from '../components/molecules/ITodoItem';

/********** 画面から呼び出し用関数 **********/
/********** TodoList GET **********/
const getTodoListOptions = "?$select=ID,Title,LimitDate,Note,Modified,Status&$filter=Status eq 'Run'&$orderby=LimitDate asc";

export const GetTodoListItems =
    async (setState: any, targetListName: string, context: WebPartContext) => {
        setState({ loading: true });
        const todoListItems: Array<Object> = await GetListItems(context, targetListName, getTodoListOptions);
        setState({ loading: false, todoListItems });
    };

/********** TodoDetail GET **********/
export const GetTodoListItemByID =
    async (setState: any, targetListName: string, context: WebPartContext, ID: string) => {

        setState({ loading: true });
        const todoListItem: Object = await GetListItemByID(context, targetListName, ID);
        const todoItem: ITodoItem = util.ObjectMerge({ Title: "", LimitDate: null, Note: "", Status: "", ID: "", Created: null, Modified: null }, todoListItem);
        setState({ loading: false, iTodoItem: todoItem });
    };

/********** TodoDetail POST **********/
export const CreateTodoListItem =
    async (setState: any, targetListName: string, context: WebPartContext, todoItem: ITodoItem) => {

        setState({ loading: true });
        const createTodoItem: ITodoItem = util.ObjectMerge({ Title: "", LimitDate: null, Note: '', Status: '' }, todoItem);
        createTodoItem.Status = "Run";
        const res = await CreateListItem(context, targetListName, createTodoItem);
    };

/********** リストアイテムの操作用共通関数 **********/
const defHeaders: HeadersInit = { "Content-type": "application/json", "Accept": "application/json" };

/********** 検索 **********/
const GetListItems =
    async (context: WebPartContext, listName: string, options: string) => {

        if (!options) {
            options = "";
        }
        const restUri: string = `${context.pageContext.web.absoluteUrl}/_api/web/lists/getbytitle('${listName}')/Items${options}`;
        const res: SPHttpClientResponse = await SpRestGet(context, restUri);
        const resJson: any = await res.json();
        const resJsonArray: Array<Object> = resJson.value;
        return resJsonArray;
    };

const GetListItemByID =
    async (context: WebPartContext, listName: string, ID: string) => {

        const restUri: string = `${context.pageContext.web.absoluteUrl}/_api/web/lists/getbytitle('${listName}')/Items(${ID})`;
        const res: SPHttpClientResponse = await SpRestGet(context, restUri);
        const resJson: any = await res.json();
        return resJson;
    };

/********** 作成 **********/
const CreateListItem = async (context: WebPartContext, listName: string, dataJson: Object) => {
    const restUri: string = `${context.pageContext.web.absoluteUrl}/_api/web/lists/getbytitle('${listName}')/Items`;
    const options: ISPHttpClientOptions = {
        body: JSON.stringify(dataJson),
        headers: defHeaders
    };
    const res: SPHttpClientResponse = await SpRestPost(context, restUri, options);
    const resJson: any = await res.json();
    return resJson;
};

/********** 更新 **********/

/********** 削除 **********/


/********** Spへのアクセス用共通関数 **********/
/********** GET Request **********/
const SpRestGet =
    async (context: WebPartContext, RestUri: string): Promise<SPHttpClientResponse> => {

        const res: SPHttpClientResponse = await context.spHttpClient.get(RestUri, SPHttpClient.configurations.v1);
        //エラーチェックは他のサイトが詳しいので省きます
        return res;
    };

/********** POST Request **********/
const SpRestPost =
    async (context: WebPartContext, RestUri: string, options: ISPHttpClientOptions): Promise<SPHttpClientResponse> => {

        const res: SPHttpClientResponse = await context.spHttpClient.post(RestUri, SPHttpClient.configurations.v1, options);
        //エラーチェックは外部サイトが詳しいので省きます
        return res;
    };

2. 詳細画面用コンポーネントの新規作成機能の作成

次に先ほどの関数を画面から呼び出します。
今回は入力フォームの部分だけ、TodoItemForm として子コンポーネントに切り出しました。
関数の呼び出しは親コンポーネントで定義して、関数ごと子コンポーネントに渡す方針とします。

pages/TodoDetail.tsx
import * as React from 'react';

import { Stack, IStackTokens } from 'office-ui-fabric-react';

import Loading from '../molecules/Loading';
import * as api from '../../api';

import TodoItemForm from '../molecules/TodoItemForm';

import { IPageProps } from './IPageProps';
import { ITodoItem } from '../molecules/ITodoItem';

interface State {
  loading: boolean;
  formType: "new" | "edit";
  iTodoItem: ITodoItem;
}

class TodoDetail extends React.Component<IPageProps, State> {

  constructor(props: IPageProps) {
    super(props);
    this.state = {
      loading: false, formType: "new",
      iTodoItem: { Title: "", LimitDate: new Date().toJSON(), Note: "", Status: "", ID: "", Created: null, Modified: null }
    };
  }

  private CreateTodoListItem = async (todoItem: ITodoItem) => {
    await api.CreateTodoListItem(this.setState.bind(this), this.props.todoListName, this.props.webPartContext, todoItem);
    this.props.routeProps.history.push({ pathname: "/" });
  }

  private GetTodoListItemByID = async () =>
    await api.GetTodoListItemByID(this.setState.bind(this), this.props.todoListName, this.props.webPartContext, this.props.routeProps.match.params.ID)

  private HistoryPushTodoList = () => {
    this.props.routeProps.history.push({ pathname: "/" });
  }

  public render() {
    const stackTokens: IStackTokens = { childrenGap: 10 };
    const { routeProps } = this.props;
    const { match } = routeProps;
    const { loading, iTodoItem } = this.state;
    return (
      <div>
        <Stack tokens={stackTokens}>
          <Stack.Item align="auto">
            {loading ? (
              <Loading />
            ) : (
                <TodoItemForm itemID={match.params.ID || null} formType={match.params.ID ? "edit" : "new"} iTodoItem={iTodoItem}
                  CreateTodoListItem={this.CreateTodoListItem} GetTodoListItemByID={this.GetTodoListItemByID}
                  HistoryPushTodoList={this.HistoryPushTodoList} />
              )}
          </Stack.Item>
        </Stack>
      </div>
    );
  }
}
export default TodoDetail;
molecules/ITodoItemFormProps.ts
import { WebPartContext } from '@microsoft/sp-webpart-base';
import { ITodoItem } from './ITodoItem';

export interface ITodoItemFormProps {
  itemID: string;
  formType: "new" | "edit";
  iTodoItem: ITodoItem;
  CreateTodoListItem: Function;
  GetTodoListItemByID: Function;
  HistoryPushTodoList: Function;
}
molecules/TodoItemForm.tsx
import * as React from 'react';

import {
  PrimaryButton, DefaultButton, Stack, IStackTokens, DatePicker, TextField, Separator
} from 'office-ui-fabric-react';

import * as util from '../util';

import { ITodoItemFormProps } from './ITodoItemFormProps';
import { ITodoItem } from './ITodoItem';


class TodoItemForm extends React.Component<ITodoItemFormProps, ITodoItem> {

  constructor(props: ITodoItemFormProps) {
    super(props);
    this.state = this.props.iTodoItem;
  }

  private CreateTodoListItem = async (e, itodoItem: ITodoItem) => {
    e.preventDefault();
    await this.props.CreateTodoListItem(itodoItem);
  }
  private GetTodoListItemByID = async () => this.props.GetTodoListItemByID();

  private HistoryPushTodoList = () => this.props.HistoryPushTodoList();

  public async componentDidMount() {
    if (this.props.formType == "edit" && !(this.state.ID)) {
      await this.GetTodoListItemByID();
    }
  }

  public render() {
    const stackTokens: IStackTokens = { childrenGap: 10 };
    const { formType } = this.props;
    const { Title, LimitDate, Note, ID, Created, Modified } = this.state;
    return (
      <div>
        <form>
          <Stack horizontal tokens={stackTokens}>
            <Stack.Item align="auto" grow={4}>
              <TextField label="件名" value={Title} onChange={(e, newVal) => this.setState({ Title: newVal })} />
            </Stack.Item>
            <Stack.Item align="auto" grow={2}>
              <DatePicker label="期限" value={new Date(LimitDate)}
                formatDate={(dispDate) => util.DateFormatJa(dispDate.toJSON())}
                onSelectDate={(newDate) => this.setState({ LimitDate: newDate.toJSON() })} />
            </Stack.Item>
          </Stack>
          <Stack horizontal tokens={stackTokens} >
            <Stack.Item align="auto" grow={6}>
              <TextField label="メモ" value={Note} multiline rows={6} resizable={false}
                onChange={(e, newVal) => this.setState({ Note: newVal })} />
            </Stack.Item>
          </Stack>
          <Separator />
          <Stack horizontal tokens={stackTokens} >
            <Stack.Item align="auto" grow={2}>
              <TextField label="ID" value={ID || "New"} borderless={true} readOnly={true} />
            </Stack.Item>
            <Stack.Item align="auto" grow={2}>
              <TextField label="作成日時" value={Created ? util.DateTimeFormatJa(Created) : "-"} borderless={true} readOnly={true} />
            </Stack.Item>
            <Stack.Item align="auto" grow={2}>
              <TextField label="更新日時" value={Modified ? util.DateTimeFormatJa(Modified) : "-"} borderless={true} readOnly={true} />
            </Stack.Item>
          </Stack>
          <Separator />
          <Stack tokens={stackTokens}>
            <Stack horizontal horizontalAlign="end" tokens={stackTokens}>
              <Stack.Item align="auto">
                <PrimaryButton type="submit" value="Create" onClick={(e) => this.CreateTodoListItem(e, this.state)} disabled={formType === "edit"} text="Create" />
              </Stack.Item>
             </Stack>
            <Stack horizontal horizontalAlign="end" tokens={stackTokens}>
              <Stack.Item align="auto">
                <DefaultButton text="Back" onClick={this.HistoryPushTodoList} />
              </Stack.Item>
            </Stack>
          </Stack>
        </form>
      </div>
    );
  }
}
export default TodoItemForm;

Hosted workbench の実行で以下のように表示できていれば成功です。
Createボタンをクリックするとアイテムが作成されると思います。

image.png

3. 更新、削除用関数の作成

ほとんど同じコードですが、更新、削除も作成します。

api/index.ts
import { SPHttpClient, SPHttpClientResponse, ISPHttpClientOptions } from '@microsoft/sp-http';
import { WebPartContext } from '@microsoft/sp-webpart-base';

import * as util from '../components/util';

import { ITodoItem } from '../components/molecules/ITodoItem';

/********** 画面から呼び出し用関数 **********/
/********** TodoList GET **********/
const getTodoListOptions = "?$select=ID,Title,LimitDate,Note,Modified,Status&$filter=Status eq 'Run'&$orderby=LimitDate asc";

export const GetTodoListItems =
    async (setState: any, targetListName: string, context: WebPartContext) => {
        setState({ loading: true });
        const todoListItems: Array<Object> = await GetListItems(context, targetListName, getTodoListOptions);
        setState({ loading: false, todoListItems });
    };

/********** TodoDetail GET **********/
export const GetTodoListItemByID =
    async (setState: any, targetListName: string, context: WebPartContext, ID: string) => {

        setState({ loading: true });
        const todoListItem: Object = await GetListItemByID(context, targetListName, ID);
        const todoItem: ITodoItem = util.ObjectMerge({ Title: "", LimitDate: null, Note: "", Status: "", ID: "", Created: null, Modified: null }, todoListItem);
        setState({ loading: false, iTodoItem: todoItem });
    };

/********** TodoDetail POST **********/
export const CreateTodoListItem =
    async (setState: any, targetListName: string, context: WebPartContext, todoItem: ITodoItem) => {

        setState({ loading: true });
        const createTodoItem: ITodoItem = util.ObjectMerge({ Title: "", LimitDate: null, Note: '', Status: '' }, todoItem);
        createTodoItem.Status = "Run";
        const res = await CreateListItem(context, targetListName, createTodoItem);
    };

export const UpdateTodoListItem =
    async (setState: any, targetListName: string, context: WebPartContext, todoItem: ITodoItem) => {

        setState({ loading: true });
        const updateTodoItem: ITodoItem = util.ObjectMerge({ Title: "", LimitDate: null, Note: '', Status: '' }, todoItem);
        const res = await UpdateListItem(context, targetListName, todoItem.ID, updateTodoItem);
    };

export const CompleteTodoListItem =
    async (setState: any, targetListName: string, context: WebPartContext, todoItem: ITodoItem) => {

        setState({ loading: true });
        const updateTodoItem: ITodoItem = util.ObjectMerge({ Title: "", LimitDate: null, Note: '', Status: '' }, todoItem);
        updateTodoItem.Status = "Done";
        const res = await UpdateListItem(context, targetListName, todoItem.ID, updateTodoItem);
    };

export const DeleteTodoListItem =
    async (setState: any, targetListName: string, context: WebPartContext, todoItem: ITodoItem) => {

        setState({ loading: true });
        const res = await DeleteListItem(context, targetListName, todoItem.ID);
    };

/********** リストアイテムの操作用共通関数 **********/
const defHeaders: HeadersInit = { "Content-type": "application/json", "Accept": "application/json" };
const updHeaders: HeadersInit = { "Content-type": "application/json", "Accept": "application/json", "X-HTTP-Method": "MERGE", "If-Match": "*" };
const delHeaders: HeadersInit = { "Content-type": "application/json", "Accept": "application/json", "X-HTTP-Method": "DELETE", "If-Match": "*" };

/********** 検索 **********/
const GetListItems =
    async (context: WebPartContext, listName: string, options: string) => {

        if (!options) {
            options = "";
        }
        const restUri: string = `${context.pageContext.web.absoluteUrl}/_api/web/lists/getbytitle('${listName}')/Items${options}`;
        const res: SPHttpClientResponse = await SpRestGet(context, restUri);
        const resJson: any = await res.json();
        const resJsonArray: Array<Object> = resJson.value;
        return resJsonArray;
    };

const GetListItemByID =
    async (context: WebPartContext, listName: string, ID: string) => {

        const restUri: string = `${context.pageContext.web.absoluteUrl}/_api/web/lists/getbytitle('${listName}')/Items(${ID})`;
        const res: SPHttpClientResponse = await SpRestGet(context, restUri);
        const resJson: any = await res.json();
        return resJson;
    };

/********** 作成 **********/
const CreateListItem = async (context: WebPartContext, listName: string, dataJson: Object) => {
    const restUri: string = `${context.pageContext.web.absoluteUrl}/_api/web/lists/getbytitle('${listName}')/Items`;
    const options: ISPHttpClientOptions = {
        body: JSON.stringify(dataJson),
        headers: defHeaders
    };
    const res: SPHttpClientResponse = await SpRestPost(context, restUri, options);
    const resJson: any = await res.json();
    return resJson;
};

/********** 更新 **********/
const UpdateListItem = async (context: WebPartContext, listName: string, ID: string, dataJson: Object) => {
    const restUri: string = `${context.pageContext.web.absoluteUrl}/_api/web/lists/getbytitle('${listName}')/Items(${ID})`;
    const options: ISPHttpClientOptions = {
        body: JSON.stringify(dataJson),
        headers: updHeaders
    };
    const res: SPHttpClientResponse = await SpRestPost(context, restUri, options);
    return res;
};

/********** 削除 **********/
const DeleteListItem = async (context: WebPartContext, listName: string, ID: string) => {
    const restUri: string = `${context.pageContext.web.absoluteUrl}/_api/web/lists/getbytitle('${listName}')/Items(${ID})`;
    const options: ISPHttpClientOptions = {
        headers: delHeaders
    };
    const res: SPHttpClientResponse = await SpRestPost(context, restUri, options);
    return res;
};

/********** Spへのアクセス用共通関数 **********/
/********** GET Request **********/
const SpRestGet =
    async (context: WebPartContext, RestUri: string): Promise<SPHttpClientResponse> => {

        const res: SPHttpClientResponse = await context.spHttpClient.get(RestUri, SPHttpClient.configurations.v1);
        //エラーチェックは外部サイトが詳しいので省きます
        return res;
    };

/********** POST Request **********/
const SpRestPost =
    async (context: WebPartContext, RestUri: string, options: ISPHttpClientOptions): Promise<SPHttpClientResponse> => {

        const res: SPHttpClientResponse = await context.spHttpClient.post(RestUri, SPHttpClient.configurations.v1, options);
        //エラーチェックは外部サイトが詳しいので省きます
        return res;
    };

4. 詳細画面用コンポーネントの更新、削除機能の作成

ここもほとんど同じです。更新、削除関数の呼び出しを追加していきます。

pages/TodoDetail.tsx
import * as React from 'react';

import { Stack, IStackTokens } from 'office-ui-fabric-react';

import Loading from '../molecules/Loading';
import * as api from '../../api';

import TodoItemForm from '../molecules/TodoItemForm';

import { IPageProps } from './IPageProps';
import { ITodoItem } from '../molecules/ITodoItem';

interface State {
  loading: boolean;
  formType: "new" | "edit";
  iTodoItem: ITodoItem;
}

class TodoDetail extends React.Component<IPageProps, State> {

  constructor(props: IPageProps) {
    super(props);
    this.state = {
      loading: false, formType: "new",
      iTodoItem: { Title: "", LimitDate: new Date().toJSON(), Note: "", Status: "", ID: "", Created: null, Modified: null }
    };
  }

  private CreateTodoListItem = async (todoItem: ITodoItem) => {
    await api.CreateTodoListItem(this.setState.bind(this), this.props.todoListName, this.props.webPartContext, todoItem);
    this.props.routeProps.history.push({ pathname: "/" });
  }
  private UpdateTodoListItem = async (todoItem: ITodoItem) => {
    await api.UpdateTodoListItem(this.setState.bind(this), this.props.todoListName, this.props.webPartContext, todoItem);
    this.props.routeProps.history.push({ pathname: "/" });
  }
  private CompleteTodoListItem = async (todoItem: ITodoItem) => {
    await api.CompleteTodoListItem(this.setState.bind(this), this.props.todoListName, this.props.webPartContext, todoItem);
    this.props.routeProps.history.push({ pathname: "/" });
  }
  private DeleteTodoListItem = async (todoItem: ITodoItem) => {
    await api.DeleteTodoListItem(this.setState.bind(this), this.props.todoListName, this.props.webPartContext, todoItem);
    this.props.routeProps.history.push({ pathname: "/" });
  }

  private GetTodoListItemByID = async () =>
    await api.GetTodoListItemByID(this.setState.bind(this), this.props.todoListName, this.props.webPartContext, this.props.routeProps.match.params.ID)

  private HistoryPushTodoList = () => {
    this.props.routeProps.history.push({ pathname: "/" });
  }

  public render() {
    const stackTokens: IStackTokens = { childrenGap: 10 };
    const { routeProps } = this.props;
    const { match } = routeProps;
    const { loading, iTodoItem } = this.state;
    return (
      <div>
        <Stack tokens={stackTokens}>
          <Stack.Item align="auto">
            {loading ? (
              <Loading />
            ) : (
                <TodoItemForm itemID={match.params.ID || null} formType={match.params.ID ? "edit" : "new"} iTodoItem={iTodoItem}
                  CreateTodoListItem={this.CreateTodoListItem} UpdateTodoListItem={this.UpdateTodoListItem} 
                  CompleteTodoListItem={this.CompleteTodoListItem} DeleteTodoListItem={this.DeleteTodoListItem} 
                  GetTodoListItemByID={this.GetTodoListItemByID} HistoryPushTodoList={this.HistoryPushTodoList} />
              )}
          </Stack.Item>
        </Stack>
      </div>
    );
  }
}
export default TodoDetail;
molecules/ITodoItemFormProps.ts
import { WebPartContext } from '@microsoft/sp-webpart-base';
import { ITodoItem } from './ITodoItem';

export interface ITodoItemFormProps {
  itemID: string;
  formType: "new" | "edit";
  iTodoItem: ITodoItem;
  CreateTodoListItem: Function;
  UpdateTodoListItem: Function;
  CompleteTodoListItem: Function;
  DeleteTodoListItem: Function;
  GetTodoListItemByID: Function;
  HistoryPushTodoList: Function;
}
molecules/TodoItemForm.tsx
import * as React from 'react';

import {
  PrimaryButton, DefaultButton, Stack, IStackTokens, DatePicker, TextField, Separator
} from 'office-ui-fabric-react';

import * as util from '../util';

import { ITodoItemFormProps } from './ITodoItemFormProps';
import { ITodoItem } from './ITodoItem';


class TodoItemForm extends React.Component<ITodoItemFormProps, ITodoItem> {

  constructor(props: ITodoItemFormProps) {
    super(props);
    this.state = this.props.iTodoItem;
  }

  private CreateTodoListItem = async (e, itodoItem: ITodoItem) => {
    e.preventDefault();
    await this.props.CreateTodoListItem(itodoItem);
  }
  private UpdateTodoListItem = async (e, itodoItem: ITodoItem) => {
    e.preventDefault();
    await this.props.UpdateTodoListItem(itodoItem);
  }
  private CompleteTodoListItem = async (e, itodoItem: ITodoItem) => {
    e.preventDefault();
    await this.props.CompleteTodoListItem(itodoItem);
  }
  private DeleteTodoListItem = async (e, itodoItem: ITodoItem) => {
    e.preventDefault();
    await this.props.DeleteTodoListItem(itodoItem);
  }

  private GetTodoListItemByID = async () => this.props.GetTodoListItemByID();

  private HistoryPushTodoList = () => this.props.HistoryPushTodoList();

  public async componentDidMount() {
    if (this.props.formType == "edit" && !(this.state.ID)) {
      await this.GetTodoListItemByID();
    }
  }

  public render() {
    const stackTokens: IStackTokens = { childrenGap: 10 };
    const { formType } = this.props;
    const { Title, LimitDate, Note, ID, Created, Modified } = this.state;
    return (
      <div>
        <form>
          <Stack horizontal tokens={stackTokens}>
            <Stack.Item align="auto" grow={4}>
              <TextField label="件名" value={Title} onChange={(e, newVal) => this.setState({ Title: newVal })} />
            </Stack.Item>
            <Stack.Item align="auto" grow={2}>
              <DatePicker label="期限" value={new Date(LimitDate)}
                formatDate={(dispDate) => util.DateFormatJa(dispDate.toJSON())}
                onSelectDate={(newDate) => this.setState({ LimitDate: newDate.toJSON() })} />
            </Stack.Item>
          </Stack>
          <Stack horizontal tokens={stackTokens} >
            <Stack.Item align="auto" grow={6}>
              <TextField label="メモ" value={Note} multiline rows={6} resizable={false}
                onChange={(e, newVal) => this.setState({ Note: newVal })} />
            </Stack.Item>
          </Stack>
          <Separator />
          <Stack horizontal tokens={stackTokens} >
            <Stack.Item align="auto" grow={2}>
              <TextField label="ID" value={ID || "New"} borderless={true} readOnly={true} />
            </Stack.Item>
            <Stack.Item align="auto" grow={2}>
              <TextField label="作成日時" value={Created ? util.DateTimeFormatJa(Created) : "-"} borderless={true} readOnly={true} />
            </Stack.Item>
            <Stack.Item align="auto" grow={2}>
              <TextField label="更新日時" value={Modified ? util.DateTimeFormatJa(Modified) : "-"} borderless={true} readOnly={true} />
            </Stack.Item>
          </Stack>
          <Separator />
          <Stack tokens={stackTokens}>
            <Stack horizontal horizontalAlign="end" tokens={stackTokens}>
              <Stack.Item align="auto">
                <PrimaryButton type="submit" value="Create" onClick={(e) => this.CreateTodoListItem(e, this.state)} disabled={formType === "edit"} text="Create" />
              </Stack.Item>
              <Stack.Item align="auto">
                <PrimaryButton type="submit" value="Update" onClick={(e) => this.UpdateTodoListItem(e, this.state)} disabled={formType === "new"} text="Update" />
              </Stack.Item>
              <Stack.Item align="auto">
                <PrimaryButton type="submit" value="Complete" onClick={(e) => this.CompleteTodoListItem(e, this.state)} disabled={formType === "new"} text="Complete(Update)" />
              </Stack.Item>
              <Stack.Item align="auto">
                <PrimaryButton type="submit" value="Delete" onClick={(e) => this.DeleteTodoListItem(e, this.state)} disabled={formType === "new"} text="Delete" />
              </Stack.Item>
            </Stack>
            <Stack horizontal horizontalAlign="end" tokens={stackTokens}>
              <Stack.Item align="auto">
                <DefaultButton text="Back" onClick={this.HistoryPushTodoList} />
              </Stack.Item>
            </Stack>
          </Stack>
        </form>
      </div>
    );
  }
}
export default TodoItemForm;

Hosted workbench の実行で完成イメージ(4/4)みたいに表示されれば完成です。
お疲れさまでした!

アプリが完成したら、実際に展開したくなると思います。
その手順はまたまた毎度引用ばかりになりますが、以下を参考にされるといいと思います。

https://sharepoint.orivers.jp/article/10302

以下に実際に動作させたファイルを置いておきますので、うまくいかない場合は参考にしてください。

https://github.com/7o83/SpfxTodo4to4

アプリとして不十分な部分はたくさんありますが、基本的な操作を網羅できたかと思います。
あくまで一つの例として参考になれば幸いです。
みなさまもぜひ、いろんなアプリを作ってみてください。

nanoka
Microsoft 365 というオンラインゲームのプレイヤーです。微課金勢。
https://corporate.tech-pops.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away