This article has participated in the good article call order activity, click to see: back end, big front end double track submission, 20,000 yuan prize pool for you to challenge!

Finally to the weekend, taking advantage of the rest of the day I will write some of the next project all finished. The article is a little long but also need to be patient to read to write this kind of project to have a more complete understanding of oh ~ hope to help you. In the last post we showed a simple list, so let’s continue. Project address: github.com/Anber-H/rea…

Umi and Dva combine to transfer data

Mainly go through a DVA process (just like the flow chart drawn before)

model.ts

1, first umijs.org/zh-CN/plugi… To our own project’s model.ts file

import { Effect, ImmerReducer, Reducer, Subscription } from 'umi'; export interface IndexModelState { name: string; } export interface IndexModelType { namespace: 'index'; state: IndexModelState; effects: { query: Effect; }; reducers: { save: Reducer<IndexModelState>; // Save: Reducer<IndexModelState>; // Save: Reducer<IndexModelState>; }; subscriptions: { setup: Subscription }; } const IndexModel: IndexModelType = { namespace: 'index', state: { name: '', }, effects: { *query({ payload }, { call, put }) {}, }, reducers: { save(state, action) { return { ... state, ... action.payload, }; }, // After immer is enabled // save(state, action) {// state.name = action.payload; // }, }, subscriptions: { setup({ dispatch, history }) { return history.listen(({ pathname }) => { if (pathname === '/') { dispatch({ type: 'query', }); }}); ,}}}; export default IndexModel;Copy the code

2. Delete all interfaces and add ts. And you can change the names of some of the variables and fields on the page to whatever you want

import { Effect, ImmerReducer, Reducer, Subscription } from 'umi'; const UserModel = { namespace: 'users', state: { name: '', }, effects: { *query({ payload }, { call, put }) {}, }, reducers: { save(state, action) { return { ... state, ... action.payload, }; }, // After immer is enabled // save(state, action) {// state.name = action.payload; // }, }, subscriptions: { setup({ dispatch, history }) { return history.listen(({ pathname }) => { if (pathname === '/') { dispatch({ type: 'query', }); }}); ,}}}; export default UserModel;Copy the code

3. Next, a data operation process can be made according to the flowchart I drew in the last article:

  • Currently, the data in the page list is written dead, so if we want to move it to the DVA repository, we need to move it to the reducer, since the reducer is the only outlet to return the page data.
  • So when is it? In this case we are through subscription, because we visit a page address is http://10.110.2.70:8000/users, once we enter the users page address when we call reducer to the page again by subscription.

(1) Put the data in the page data into the reducers of Model. ts, here I exist under save

// users/model.ts reducers: { save(state, action) { const data = [ { key: '1', name: 'John Brown', age: 32, address: 'New York No. 1 Lake Park', tags: ['nice', 'developer'], }, { key: '2', name: 'Jim Green', age: 42, address: 'London No. 1 Lake Park', tags: ['loser'], }, { key: '3', name: 'Joe Black', age: 32, address: 'Sidney No. 1 Lake Park', tags: ['cool', 'teacher'], }, ]; return data; }},Copy the code

2. Next, you will find:

  • As soon as I trigger this reducer, the page receives the value.
  • Then we’ll find that we need connect to get from the Model to the page.

Call procedure:

// users/index.tsx // const mapStateToProps =>{} Const mapStateToProps = ({users})=>{return {users}}Copy the code

Connect to quote:

import { connect } from 'umi';
Copy the code

Connection:

Export default connect(mapStateToProps)(index)Copy the code

3, from the code above, we can see that the page returns a Users object, so the above HTML structure in the data should be replaced by users, users is the data we want, and index needs to receive users

// Receive Users const index = ({users}) => {..... .... Return (<div className="list-table"> < table columns={columns} dataSource={users} /> </div>)Copy the code

In model.ts, we write the data that is returned to the page, but now we don’t have a trigger process. That is when the user access to the users: http://10.110.2.70:8000/users we didn’t go to trigger. So we need to revise this subscriptions.

subscriptions: { setup({ dispatch, history }) { return history.listen(({ pathname }) => { if (pathname === '/users') { dispatch({ type: 'save', }); }}); }},Copy the code

5. Refresh the page again and you’ll have the data. (The data here is from the warehouse)

Summarize the process

To summarize the overall process: First, DVA subscriptions detected that our page address was users, so it dispatched something with type save. 2. Then the save could directly find a save method in model reducers. 3. The data name returned is actually the namespace of model, which is called Users 4. Therefore, my page index.tsx is bound to the warehouse through connect. Export default connect(mapStateToProps)(index 6, the users value is then given to the component as a parameter (const index = ({users}) => {….. …. })) 7, then the component takes the value and assigns it to the table dataSource

Note: (Throughout this process, we are: ——>subscriptions– > Reducer —–connect—-> pages)

Model structure and writing method

The above is the code we directly copy and paste, in order to see a more intuitive effect, I want to specifically list the model writing, after all, we need to write in real development, so detailed explanation is more friendly to beginners.

The following code is written in model.ts (before deleting the code from model.ts)

const UserModel = {

};
export default UserModel;
Copy the code

2. Many parameters of UserModel are fixed because it is a model

Const UserModel = {// namespace:'users', // model unique identifier state: {}, // initial value reducers: {}, // {}, // there are multiple functions and * before the functions, which means generate function subscriptions: {} // there are multiple functions};Copy the code

3, next we add functions and parameters, can refer to dVA official WEBSITE API writing (dvajs.com/api/#state)

  reducers: {
    getList(state, action) {
    }
  },
  effects: {
    *function_name({ type, payload }, effects) {
    }
  },
  subscriptions: {
    function_name({ dispatch, history }){
    }
  }
Copy the code

Note: When we learn react, dispatch(action), we know that action is an object, and this object is:

{
  type:'getList',
  payload:{}
}
Copy the code

So when we look at action we can change it to an object of type and payload, which is a bit of an opportunistic concept, but I find it useful.

Reducers: {getList(state, {type, payload}) {// state: obtained data // return newState; } }, effects: {*getRemote({type, payload}, effects) {// Return a void, // effects to put // yield put()}}, subscriptions: {setup({dispatch, history}){}}Copy the code

4. Now do some ts configuration work

/ / / / in the front file to write down the code umi has been written for us some type of definition, including Reducer, Effect, Subscription import {Reducer, Effect, Subscription} from "umi"; Interface UserModelType {// we can write namespace: string, but the namespace must be constant and unique, so we can write namespace: 'users' namespace: 'users'; state: {}; reducers: { getList: Reducer }; effects: { getRemote: Effect } subscriptions: { setup: Subscription } }Copy the code

Let’s define a type for UserModel:

const UserModel: UserModelType = {.... .... }Copy the code

5. The next step is to improve subscriptions and reducers subscriptions:

subscriptions: {setup({dispatch, history}){// The complete thing isto write this // but the location has a pathname in it, so you can just write it out // the action is not there so you can delete it // dispatch is just using type, Type return history.listen((location, action) => {if(location.pathName === '/users'){dispatch({type: xxx, payload: xxx }); }}}})Copy the code

↓↓↓↓↓↓↓ Simplified version (also the current final code) ↓↓↓↓↓↓↓

subscriptions: { setup({ dispatch, history }){ return history.listen(({pathname}) => { if(pathname === '/users'){ dispatch({ type: 'getList' }); }}}})Copy the code

Reducers:

reducers: { getList(state, { type, payload }) { const data = [ { key: '1', name: 'John Brown', age: 32, address: 'New York No. 1 Lake Park', tags: ['nice', 'developer'], }, { key: '2', name: 'Jim Green', age: 42, address: 'London No. 1 Lake Park', tags: ['loser'], }, { key: '3', name: 'Joe Black', age: 32, address: 'Sidney No. 1 Lake Park', tags: ['cool', 'teacher'], }, ]; return data; }},Copy the code

This completes a model.ts.

The representation of the data obtained by the real interface

Normally we get data from the back end asynchronously, so here we’re dealing with effects.

1, the effects

First let’s move the data from reducers into Effects

effects: {// *getRemote(action, effects) {*getRemote(action, {put, call}) {// return a void, // effects to put // yield put() const data = [{key: '1', name: 'John Brown', age: 32, address: 'New York No. 1 Lake Park', tags: ['nice', 'developer'], }, { key: '2', name: 'Jim Green', age: 42, address: 'London No. 1 Lake Park', tags: ['loser'], }, { key: '3', name: 'Joe Black', age: 32, address: 'Sidney No. 1 Lake Park', tags: ['cool', 'teacher'], }, ]; }}Copy the code

2, effect — — — — — – put — — — — — > reducer

Then what we need to do is to push the data on the effect side to reducer, using put (see the flowchart from the previous article again).

// Put: emits an Action, similar to Dispatch yield put({type: 'getList', payload: data})Copy the code

3, reducer

By putting the data in the getList, the effect is transferred to the reducer action in the getList(state,action){}, which is equivalent to {type, payload}. Payload is the data that we get.

reducers: Payload getList(state, {payload}) {// state: the data returned by getRemote // return newState; return payload } }Copy the code

4. Call the real data and process the service.ts file

Async function name(params) {} const name = async (params) => {}

Export const getRemoteList = async (params) => {const data = [{key: '1', name: 'John Brown', age: 32, address: 'New York No. 1 Lake Park', tags: ['nice', 'developer'], }, { key: '2', name: 'Jim Green', age: 42, address: 'London No. 1 Lake Park', tags: ['loser'], }, { key: '3', name: 'Joe Black', age: 32, address: 'Sidney No. 1 Lake Park', tags: ['cool', 'teacher'], }, ]; return data; }Copy the code
// Model. Ts = service. Ts = service. { *getRemote(action, { put, call }) { const data = yield call(getRemoteList); yield put({ type: 'getList', payload: data }) } },Copy the code

Pay attention

Some is not a function. When you start the service, you will see TypeError: rawData. Some is not a function. So let’s deal with this error now. Two changes are needed.

// model.ts effects: { *getRemote(action, { put, call }) { const data = yield call(getRemoteList); Yield put({type: 'getList', payload: {data})}}, // Yield put({type: 'getList', payload: {data})}}, // yield put({type: 'getList', payload: {data})}},Copy the code
// index.tsx return ( <div className="list-table"> <Table columns={columns} dataSource={users.data} /> // Uesrs.data </div>)Copy the code

6. Access real back-end interfaces

Request sent: umijs.org/zh-CN/plugi… Specific writing method can refer to: github.com/umijs/umi-r… Umi – the request of the API

// service.ts

import { request } from 'umi';
export const getRemoteList = async (params) => {
    return request('https://apixxxxxxxxx/users', {
      method: 'get',
      params: { id: 1 },
    })
    .then(function(response) {
    	return response
      console.log(response);
    })
    .catch(function(error) {
      console.log(error);
    });
 }
Copy the code

Payload :{data:[],meta:{}}} = = “payload”; / / getRemote payload = “payload” Data, the data does not need to be wrapped up another layer, unlike when you mock your own data because you are sending out raw data. The index. TSX is still fetching users.data.

effects: { *getRemote(action, { put, call }) { const data = yield call(getRemoteList); Yield put({type: 'getList', payload: data //})}},Copy the code

There is to modify the columns of the page, according to the returned data to modify.

7, cross-domain

To configure an agent: umijs.org/zh-CN/confi… Place the agent’s code in a.umirc.ts file

proxy: { '/api': { //'target': 'http://jsonplaceholder.typicode.com/', / / here to own interface address, such as your address is https://apixxxxxxxx/ 'target' : 'https://apixxxxxxxx/', 'changeOrigin': true, 'pathRewrite': { '^/api' : '' }, }, },Copy the code
// service.ts import { request } from 'umi'; Export const getRemoteList = async (params) => {// After our agent is configured, Return request('/ API /users', {method: 'get', params: {id: 1 }, }) .then(function(response) { return response console.log(response); }) .catch(function(error) { console.log(error); }); }Copy the code

Then wrote after we can directly enter http://localhost:8000/api/users can access to data in the url. You can mock data when you don’t have real data, as long as you know how to write it. Now we can add some popover form components and handle the additions, deletions, and checks.

Modal popover components

Ant design: Ant. Design/Components /…

1. Add modal popover

// UserModal.tsx

import React from 'react'
import { Modal } from 'antd';

const userModal = () => {
  return (
    <div>
      <Modal title="Basic Modal" visible={true}>
      </Modal>
    </div>
  )
}
export default userModal;
Copy the code
// index.tsx import UserModal from './components/UserModal'; . ...... is omitted here return ( <div className="list-table"> <Table columns={columns} dataSource={users.data} /> <UserModal></UserModal> // Modal component references </div>)Copy the code

You’ll notice that you have a modal popover on the page. And then what we’re going to do is we’re going to deal with this modal when to say when to shut it down.

2, modal explicit and implicit

According to

The popover opens when I click the Edit button (1). First, the UserModal component referenced in the structure needs to pass a visible to tell the child component to be visible

// index.tsx
return (
    <div className="list-table">
      <Table columns={columns} dataSource={users.data} />
      <UserModal visible={modalVisible}></UserModal>
    </div>
  )
Copy the code

(2) Then use props to receive visible from the parent component

// UserModal.tsx
import React from 'react'
import { Modal } from 'antd';

const userModal = (props) => {
  return (
    <div>
      <Modal title="Basic Modal" visible={props.visible}>
      </Modal>
    </div>
  )
}
export default userModal;
Copy the code

{modalVisible} ={modalVisible} ={modalVisible} True or false Because we are using a functional component, we need to use the useState in the hook

Note: Const [state, setstate] = useState(initialState) const [state, setstate] = useState(initialState) The second argument, setState, is the function method that modifies the variable. InitialState is the default.

import React, { useState } from 'react'; . ..... is omitted here const index = ({ users }) => { const [modalVisible, setModalVisible] = useState(false); } const columns = [.....]..... {title: 'Action', key: 'Action', render: (text, record) = > (< span > < a onClick = {() = > {setModalVisible (true)}} > edit < / a > & have spent & have spent < a > delete < / a > < / span >),},]Copy the code

So you can click edit on the page and you get a popover

Another way to edit button click is as follows:

<a onClick={visibleHandler}> Edit </a>..... ..... is omitted here const visibleHandler = () => { setModalVisible(true) }Copy the code

hidden

Click OK and Cancel to close the popover (only the modified code is posted below)

// UserModal.tsx
<div>
  <Modal
    title="Basic Modal"
    visible={props.visible}
    onOk={props.closeHandler}
    onCancel={props.closeHandler}>
  </Modal>
</div>
Copy the code
// index.tsx
const closeHandler = () => {
   setModalVisible(false);
}
<UserModal visible={modalVisible} closeHandler={closeHandler}></UserModal>
Copy the code

Because props are read-only and cannot be changed, you cannot directly close the popover in usermodal.tsx by changing props. Visible =false. So we need to pass the closeHandler event from the parent component to the child component to close.

Pass a value to a popover component

After we have written the popover component, we need to handle the record value record that was passed. Click on the Edit button and insert the value into the popover. (When the edit button is clicked, our previous click event was visibleHandler, but since we need to not only implicit but also pass, I changed the name to editHandler to be more semantic.)

Const [record, setRecord] = useState(undefined); ..... {title: 'Action', key: 'Action', render: (text, record) = > (< span > / / modify < a onClick = {() = > {editHandler (record)}} > edit < / a > & have spent & have spent < a > delete < / a > < / span >),}, ] // Modify const editHandler = (record) => {setModalVisible(true); setRecord(record); } return ( <div className="list-table"> <Table columns={columns} dataSource={users.data} /> <UserModal Visible ={modalVisible} closeHandler={closeHandler} record={record}> </UserModal> </div>)Copy the code
<div> <Modal title="Basic Modal" visible={props.visible} onOk={props.closeHandler} onCancel={props.closeHandler}> {json.stringify (props. Record)} // record </Modal> </div>Copy the code

That’s when the data gets passed, so we can add the form to the popover and see what happens.

Form Form

1. Add the form and display the initial values

// UserModal.tsx
import React from 'react'
import { Modal, Button, Form, Input } from 'antd';

const userModal = (props) => {
  return (
    <div>
      <Modal
        title="Basic Modal"
        visible={props.visible}
        onOk={props.closeHandler}
        onCancel={props.closeHandler}>
          <Form
            name="basic"
            labelCol={{ span: 4 }}
            wrapperCol={{ span: 16 }}
            initialValues={props.record}>
            <Form.Item
              label="Name"
              name="name"
              rules={[{ required: true, message: 'Please input your name!' }]}
            >
              <Input />
            </Form.Item>
            <Form.Item label="Email" name="email">
              <Input />
            </Form.Item>
            <Form.Item label="Create_Time" name="create_time">
              <Input />
            </Form.Item>
            <Form.Item label="Status" name="status">
              <Input />
            </Form.Item>
          </Form>
      </Modal>
    </div>
  )
}
export default userModal;
Copy the code

This is when you’re doing modal display and the data is already here. However, you will notice that each message displays the same data when you click the Edit button, so the problem should be on initialValues. Note: initialValues cannot be dynamically updated by setState, you need to update it with setFieldsValue.

So initialValues should be deleted, setFieldsValue should be set, and notice that Form ={Form}

// UserModal.tsx const userModal = (props) => { const [form] = Form.useForm(); form.setFieldsValue(props.record); . ...... is omitted here return ( ..... ...... is omitted here <Form form={form} name="basic" labelCol={{ span: 4 }} wrapperCol={{ span: 16 }}> ..... Here the code is omitted......) }Copy the code

That’s when the modal data is correct.

To solve the error

Error 1: devscripts.js :6523 Warning: Instance created by `useForm` is not connected to any Form element. Forget to pass `form` prop? In Ant design: Ant. Design/Components /… There is information about how to handle this error.

So we know that you can just put forceRender in modal

// UserModal.tsx <Modal title="Basic Modal" visible={props.visible} onOk={props.closeHandler} onCancel={props.closeHandler} forceRender> ..... ...... is omitted here </Modal>Copy the code

Error 2: devscript.js :6523 Warning: Each child in a list should have a unique “key” prop. This is a list key problem, each list must have a unique key value. We can also see the solution to this problem by looking at Ant Design: Ant.

We add a rowKey to the table component

// index.tsx
<Table columns={columns} dataSource={users.data} rowKey="id"/>
Copy the code

Error 3: (when clicking the edit button) Warning: Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state. Solution: Assign a value to useEffect

UseEffect (()=>{},[]) useEffect()=>{},[])Copy the code

So make the following changes:

// UserModal.tsx const userModal = (props) => { const [form] = Form.useForm(); Form.setfieldsvalue (props.record) useEffect(()=>{form.setfieldsValue (props.record); },[props.visible]); . ...... is omitted here }Copy the code

Simplify the code

(const {visible, record, closeHandler} = props;) To simplify the code, we can remove all props

// userModal.tsx import React, { useEffect } from 'react' import { Modal, Button, Form, Input } from 'antd'; const userModal = (props) => { const [form] = Form.useForm(); const {visible, record, closeHandler} = props; useEffect(()=>{ form.setFieldsValue(record); },[visible]); return ( <div> <Modal title="Basic Modal" visible={visible} onOk={closeHandler} onCancel={closeHandler} forceRender> . ...... is omitted here </Modal> </div> ) } export default userModal;Copy the code

This completes the building of the form and the assignment of the internal space.

We’ve done a lot of things above, lists, popovers, forms, etc. Next we’ll deal with adding, modifying, and removing logic and optimizations for HTTP state errors, which I’ll also complete with typescript. Then we can see a more complete project.

Modify and Delete

Modify the

Requirement: After the modal popover opens, I submit the data when I click the OK button

// UserModal.tsx ...... ...... is omitted here const {visible, record, closeHandler, onFinish} = props; // Parent receives onFinish const onOk = () => {form.submit(); } const onFinishFailed = errorInfo => { console.log('Failed:', errorInfo); }; <Modal title="Basic Modal" visible={visible} onOk={onOk} // Submit data onCancel={closeHandler} forceRender> <Form Form ={Form} name="basic" labelCol={{ span: 4 }} wrapperCol={{ span: 16}} onFinish={onFinish} // Successful onFinishFailed={onFinishFailed} // Failed >...... ...... is omitted here </Form> </Modal>Copy the code
// index.tsx ...... ...... is omitted here const onFinish = values => { console.log('Success:', values); }; return ( <div className="list-table"> <Table columns={columns} dataSource={users.data} rowKey="id"/> <UserModal </UserModal> visible={modalVisible} closeHandler={closeHandler} record={record} onFinish={onFinish}> </div> ) ...... ...... is omitted hereCopy the code

The next step is to go to the warehouse and communicate with the service

Steps: data — — — — — dispatch — — — — > effect call — — — — — — — — — – > service – > effect — — — — — — put — — — — — — > reducer is — — — — — connect > page

Interface request method: github.com/umijs/umi-r…

// index.tsx const onFinish = values => { const id = record.id; Payload: {id, values}}); // Select * from record where id = 'users/edit'; // Select * from record where id = 'users/edit'; setModalVisible(false); // Close the window};Copy the code
// model.ts import {getRemoteList, editRecord} from './service'; // Insert editRecord Effects: { *edit({payload:{id, values}}, { put, call }) { const data = yield call(editRecord, {id, values}) yield put ({ type: 'getRemote' // Refresh the list by calling getRemote again})}}Copy the code
// service.ts import { message } from 'antd'; . ...... is omitted here Export const editRecord = async({id, values}) => {return request('/API /v1/user/${id} ', Method: 'put', data: values,}). Then (function(response) {message.success(' edit successfully '); }).catch(function(error) {message.error(' failed to edit '); // message prompt}); }Copy the code

delete

See antd: ant.design/ Components /…

// index.tsx import { Table, Tag, Space, Popconfirm } from 'antd'; . ...... is omitted here Const columns = [... code omitted here... {title: 'Action' key: 'Action', render: (text, Record) = > (< span > {/ * way * / 2} {/ * < a onClick = {() = > {setModalVisible (true)}} > edit < / a > * /} < a OnClick ={()=>{editHandler(record)}}> Edit </a>&nbsp; &nbsp; <Popconfirm Title =" Are you sure to delete this message?" OnConfirm = {() = > {deleteHandler (record. Id)}} okText = "Yes" cancelText = "No" > < a href = "#" > delete < / a > < / Popconfirm > < / span >). },] const deleteHandler = (id) => {console.log(id,'id')}Copy the code

The above is the code for editing and deleting operations. For more details, please refer to my Github: github.com/Anber-H/rea… , edit delete writing method actually is all the same, ten thousand change does not leave its zong.

add

Let’s look at the “add” requirement in more detail.

1. Add operations

What we need to do is:

  • Add Add button
  • Write add event
  • When the popover data is submitted, determine whether the ID exists to submit to the add interface
// index.tsx import { Table, Button, Popconfirm } from 'antd'; . ...... is omitted here const onFinish = values => { const id = record && record.id; If (id){dispatch({type: 'users/edit', payload: {id, values}}); } else { dispatch({ type: 'users/add', payload: { values } }); } setModalVisible(false); }; // Add event const addHandler = (params) => {setModalVisible(true); } return (<div className="list-table"> <Button type="primary" onClick={addHandler} columns={columns} dataSource={users.data} rowKey="id"/> <UserModal visible={modalVisible} closeHandler={closeHandler} record={record} onFinish={onFinish}> </UserModal> </div> )Copy the code

2. Add an interface

// service.ts export const addRecord = async({values}) => {return request('/API /v1/user ', {method: 'post', data: values,}).then(function(response) {message. Success (' added successfully '); }).catch(function(error) {message.error(' failed to add '); }); }Copy the code

3. Transfer data to the page through the warehouse

// model.ts import {getRemoteList, editRecord, addRecord} from './service'; effects: { *add({payload:{values}}, { put, call }) { const data = yield call(addRecord, {values}) yield put ({ type: 'getRemote' // Refresh the list by calling getRemote again})},}Copy the code

4. The added initial values are cleared

Use form.resetFields() to clear the initial values;

// index.tsx const addHandler = (params) => { setModalVisible(true); setRecord(undefined); // Set record to undefined}Copy the code

If record is undefined, the initial value is cleared, otherwise the value is assigned

// UserModal.tsx
  useEffect(() => {
    if (record === undefined){
      form.resetFields();
    } else {
      form.setFieldsValue(record);
    }
  },[visible]);
Copy the code

That’s all you need to do to add it. Follow this step to code the effect.

HTTP status error handling & page tuning

1, when processing the request interface error, the page error display is messy

We can learn github.com/umijs/umi-r… Handling by the Handling module.

// service.ts import request, { extend } from 'umi-request'; const errorHandler = function(error) { if (error.response) { // error.response.status: code 503 // error.data: body Random Error Response. if (error.response.status > 400) { message.error(error.data.message ? Message: error.data)}} else {// The request was sent but no reply messe.error ('Network error ')}}; const extendRequest = extend({ errorHandler }); // return extendRequest('/API /v1/user/${id} ', // method: 'put', // data: values, //})Copy the code

2, solve the console error

Error: devscripts.js :6523 uncaught at _callee Error: When called with an action of type “users/getList”, the slice reducer for key “users” returned undefined. To ignore an action, you must explicitly return the previous state. If you want this reducer to hold no value, you can return null instead of undefined.

T = service. Ts = service. Ts = service. Ts = service. Ts = service

Export const editRecord = async({id, values}) => { return extendRequest(`/api/v1/user/${id}`, { method: 'put', data: values, }) .then(function(response) { return true; }).catch(function(error) {return false; // add return}); }Copy the code
// model.ts *edit({payload:{id, values}}, { put, call }) { const data = yield call(editRecord, {id, values}); If (data) {// make a data judgment message.success(' edit succeeded '); Yield put ({type: 'getRemote' // call the above getRemote again to refresh the list})} else {message.error(' edit failed '); }}Copy the code

3, add loading

Const index = ({users, dispatch, UserListLoading}) ={return (<div className="list-table"> <Button type="primary" onClick={addHandler}> </Button> // Loading < table columns={columns} dataSource={users.data} rowKey="id" loading={userListLoading}/> <UserModal visible={modalVisible} closeHandler={closeHandler} record={record} onFinish={onFinish}> </UserModal> </div> ) } const MapStateToProps = ({users, loading})=>{return {users, userListLoading: load.models.users // Get loading}}Copy the code

TypeScript supplement

The project requirements are basically all about the same, and other functions you want to add can be written by following the flowchart in the first article. Finally, let’s deal with TypeScript for the project. (I will post only the modified part of the code below)

1, the model ts

The list interface returns something like this:

// service.ts
{
    "data": [
      {
        id: '111',
        name: 'baixiangguo',
        create_time: '2020-06-01',
        email: '[email protected]',
        status: 1
      },
      {
        id: '112',
        name: 'wangyibo',
        create_time: '2020-07-02',
        email: '[email protected]',
        status: 2
      },
      {
        id: '113',
        name: 'xiaozhan',
        create_time: '2020-08-03',
        email: '[email protected]',
        status: 2
      },
    ],
    "meta": {
      "total": 2,
      "per_page": 10,
      "page": 1
    }
  }
Copy the code

So let’s start with a type definition for state:

// model. Ts // interface SingleUserType {id: number; name: string; email: string; create_time: string; status: number; } // Interface UserState {data: SingleUserType[]; // Interface UserState {data: SingleUserType[]; meta: { total: number; per_page: number; page: number; } } interface UserModelType { namespace: 'users'; state: UserState; Reducers: {getList: Reducer<UserState>; / / note}. effects: { getRemote: Effect; edit: Reducer; add: Reducer; }; subscriptions: { setup: Subscription; }; }Copy the code

After the type is defined, the initial value of state in UserModel needs to be completed

// model.ts const UserModel: UserModelType = { namespace:'users', state: { data: [], meta: { total: 0, per_page: 5, page: 1 } }, ..... ...... is omitted here }Copy the code

2, the index. The TSX

I changed the name here, from index to UserListPage, just to make it more semantic. When the UserState type in model.ts is exported, the index. TSX receives it

// model.ts export interface UserState { data: SingleUserType[]; meta: { total: number; per_page: number; page: number; }}Copy the code
// index.tsx import React, { useState, FC } from 'react'; import { connect, Dispatch, Loading, UserState } from 'umi'; interface UserPageProps { users: UserState; dispatch: Dispatch; userListLoading: boolean; } const UserListPage:FC<UserPageProps> = ({ users, dispatch, userListLoading }) => { ....... ....... is omitted here } const mapStateToProps = ({users, loading}:{users: UserState, loading: Loading})=>{ return { users, userListLoading: loading.models.users } }Copy the code

The SingleUserType in model.ts will be used in many files, so it will be mentioned separately. Create a new data.d file

// data.d // Export interface SingleUserType {id: number; name: string; email: string; create_time: string; status: number; }Copy the code
// index.tsx import { SingleUserType } from './data.d'; const [record, setRecord] = useState<SingleUserType | undefined>(undefined); Render: (text: string, record: SingleUserType) => () const editHandler = (record: SingleUserType) =>Copy the code

3, UserModal TSX

Define the type for values on all pages

// data.d.ts
export interface FormValues {
  [name: string]: any
}
Copy the code
// index.tsx
import { SingleUserType, FormValues } from './data.d';

const onFinish = (values: FormValues) => {}
Copy the code
// UserModal.tsx import { SingleUserType, FormValues } from '.. /data.d'; import React, { useEffect, FC } from 'react'; interface UserModalProps { visible: boolean; record: SingleUserType | undefined; closeHandler: () => void; onFinish: (values: FormValues) => void; } const userModal: FC<UserModalProps> = (props) => { const onFinishFailed = (errorInfo:any) => { message.error(errorInfo.errorFields[0].errors[0]); }; }Copy the code

4, service. Ts

import { FormValues } from './data.d'; const errorHandler = function(error:any) {} export const editRecord = async({id, values}:{id: number; values: FormValues}) => {} export const addRecord = async({values}:{values:FormValues}) => {}Copy the code

Well, all the TS have been completed, and the whole project is over. Thank you for reading this article. If you think it’s good, please extend your hand and give me a thumbs-up. Thank you