无法获得 onChange 函数以使用 Grid Container 进行 React

Can't get onChange function to work on React with Grid Containers

尝试为 FormRow 函数设置 onChange 事件处理程序时出现如图所示的错误。最终需要存储来自Que Reset Time、Service Level threshold和Short Abandoned Threshold的用户数据。 Queuecontainer 是使用编辑器创建容器表单的主要 class。

QuesContainer.js

import React from 'react';
import Editor from './Editor';
import LoginForm from '../LoginForm';
import axios from 'axios';

export default class QueuesContainer extends React.Component {
       constructor(props) {
    super(props);
    this.state = {
        workspaceSID: '',
        apiKey: '',
        apiSecret: '',
        queues: [],
        name: '',
    }
    this.listQueues = this.listQueues.bind(this);
    this.setCredentials = this.setCredentials.bind(this);
    this.getCredentials = this.getCredentials.bind(this);
    this.setQueues = this.setQueues.bind(this);
    this.handleChange = this.handleChange.bind(this);
}

/*
handleChange(target) {
    this.setState({ [target.id]: target.value });
}
*/


//Added by JR for Save Function
handleChange(event){
    console.log("From handle change", event);
    this.setState({ name: event.target.value })
}

/*
handleSubmit(event) {
    event.preventDefault();
    this.props.login(this.state.workspaceSID, this.state.apiKey, this.state.apiSecret);
}
*/

handleSubmit(event) {
    event.preventDefault();
    var text = this.state.text;
    console.log("form testing value output:");
}


setCredentials(workspaceSID, apiKey, apiSecret) {
    this.setState({ workspaceSID: workspaceSID });
    this.setState({ apiKey: apiKey });
    this.setState({ apiSecret: apiSecret });
}

getCredentials() {
    return this.state;
}

setQueues(queues) {
    this.setState({ queues: queues });
}




getConfig(apiKey, apiSecret) {
    return axios.get(`https://flex-api.twilio.com/v1/Configuration`, {
        auth: {
            username: apiKey,
            password: apiSecret
        },
    });


    
}


//Added by JR used to post configuration 
postConfig(apiKey, apiSecret, params) {
    return axios.post(`https://flex-api.twilio.com/v1/Configuration`, {
        auth: {
            username: apiKey,
            password: apiSecret
        },
     
    });
}


listQueues(workspaceSID, apiKey, apiSecret) {
    this.setCredentials(workspaceSID, apiKey, apiSecret);
    const queuesPromise = axios.get(`https://taskrouter.twilio.com/v1/Workspaces/${workspaceSID}/TaskQueues?PageSize=1000&Page=0`, {
        auth: {
            username: apiKey,
            password: apiSecret
        },
    });
    const channelsPromise = axios.get(`https://taskrouter.twilio.com/v1/Workspaces/${workspaceSID}/TaskChannels?PageSize=1000&Page=0`, {
        auth: {
            username: apiKey,
            password: apiSecret
        },
    });


    const configPromise = this.getConfig(apiKey, apiSecret);
    

    Promise.all([queuesPromise, channelsPromise, configPromise])
        .then((values) => {
            console.log(values);
            const twilioQueues = values[0].data.task_queues;
            const twilioChannels = values[1].data.channels;
            // const config = values[2].data.queue_stats_configuration;

            const voice = twilioChannels.find(channel => {
                console.log(channel)
                return channel.unique_name === 'voice'
            });

            const chat = twilioChannels.find(channel => {
                console.log(channel)
                return channel.unique_name === 'chat'
            });

            const email = twilioChannels.find(channel => {
                console.log(channel)
                return channel.unique_name === 'email'
            });

            const channels = [voice, chat, email];
            const queues = twilioQueues.map(q => {
                const queue = {
                    queue_sid: q.sid,
                    friendly_name: q.friendly_name,
                    channels
                }
                return queue;
            });

            this.setQueues(queues);
        });
}
   //onChange={this.handleInputChange.bind(this)}

render() {
    const { name } = this.state
    return (
        <div>
            <p> Test Value is : {name}  </p>
            <LoginForm login={this.listQueues} buttonText='Load Queues' />
            
            <Editor onSubmit={this.handleSubmit} onChange={this.handleChange} data={this.state.queues} credentials={this.getCredentials} setData={this.setQueues} />
        </div>
    );
}
}

Editor.js

import React from 'react';
import MaterialTable from 'material-table'
import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';
import Grid from '@material-ui/core/Grid';
import axios from 'axios';
import { waitForDomChange } from '@testing-library/react';

export default class Editor extends React.Component {
constructor(props) {
    super(props);
    this.state = {
        open: false,
        queue: {},
        columns: [
            { title: 'Queue Name', field: 'friendly_name', editable: 'never' },
            { title: 'Channel', field: 'channel_friendly_name', editable: 'never' },
            { title: 'Reset Timezone', field: 'channel_reset_timezone' },
            { title: 'Reset Time', field: 'channel_reset_time' },
            { title: 'Service Level Threshold', field: 'channel_service_level_threshold', type: 'numeric' },
            { title: 'Short Abandoned Threshold', field: 'channel_service_level_threshold', type: 'numeric' },
        ]
    }
    this.handleClose = this.handleClose.bind(this);
   // this.handleSave = this.handleSave.bind(this);

}




handleClose(xxx) {
    console.log('this happening');
    console.log(xxx);
    this.setState({ open: true });
}

/*
handleSave(event) {
    console.log('Saving user input data');
    console.log(event);

    this.setState({ value: event.target.value});
}
*/




render() {
    console.log('hey ho');
    console.group(this.props.data);
    const queues = this.props.data.map(q => {
        const channel = q.channels.find(c => c.unique_name === 'voice');
        q.channel_friendly_name = channel.friendly_name;
        q.channel_reset_time = channel.reset_time;
        q.channel_reset_timezone = channel.reset_timezone;
        q.channel_service_level_threshold = channel.service_level_threshold;
        q.channel_service_level_threshold = channel.service_level_threshold;

        return q;


    })
    return (
        <div>
            <MaterialTable
                title='Queue SLA'
                columns={this.state.columns}
                data={queues}
                options={{
                    pageSize: 25,
                    pageSizeOptions: [25, 100, 500, 1000]
                }}
                detailPanel={rowData => {

                    //Modified by JR for Save Function
                    return FormContainer(rowData, this.props.credentials().apiKey, 
this.props.credentials().apiSecret, this.handleChange);
                  
                }}
                
            />
        </div>
    );
}
}



function FormRow(data, handleChange) {
const queue = data.data;
const channels = queue.channels;



return (
    channels.map((channel, index) => (
        <Grid container item sm={12} spacing={3}>
            <Grid item sm={2}>
                <TextField
                    autoFocus
                    margin="dense"
                    id="channel_name"
                    label="Channel"
                    type="text"
                    value={channel.friendly_name}
                    fullWidth
                    disabled
                />
            </Grid>
            <Grid item sm={2}>
                <TextField
                    autoFocus
                    margin="dense"
                    id="reset_timezone"
                    label="Reset Timezone"
                    type="text"
                    value="GMT"
                    fullWidth
                    disabled
                />
            </Grid>

          
         

            <Grid item sm={2}>
                <TextField
                    autoFocus
                    margin="dense"
                    id="service_level_threshold"
                    label="Service Level Threshold"
                    value={channel.service_level_threshold}
                    onChange={handleChange}
                    type="text"
                    fullWidth
                />
            </Grid>
            <Grid item sm={2}>
                <TextField
                    autoFocus
                    margin="dense"
                    id="short_abandoned_threshold"
                    label="Short Abandoned Threshold"
                    value={channel.short_abandoned_threshold}
                    onChange={handleChange}
                    type="text"
                    fullWidth
                />
            </Grid>
        </Grid>
    ))
);
}


 //Modified by JR for Save Function
    function FormContainer(data, apiKey, apiSecret, props, handleChange) {
    console.log('what');
   console.log(data);
    console.log('JSON what');
    console.log(JSON.stringify(data));
    const queue = data;




  // const queue = data.data;
   const channels = queue.channels;


const cancel = () => {
    console.log('cancel');
}



const save = () => {

    //Modified by JR for Save Function
    console.log('save');
    console.log(data);

    console.log(JSON.stringify(data));

    console.log('SaveButtonClicked');
    waitForDomChange();

    console.log(data);

    console.log(JSON.stringify(data));
    savebuttonclicked(queue, apiKey, apiSecret);

    
    console.log('Props Information');
    console.log(props);
 



    console.log('Save Sucessful');

}
return (
    <div>
        <Grid container>
            <Grid item sm={1}></Grid>
            <Grid container item sm={11} spacing={3}>
                <FormRow data={queue}
                    formOnChange={handleChange}/>
                
            </Grid>
            <Grid
                container
                direction="row"
                justify="flex-end"
                alignItems="center"
            >
                <Grid item sm={1}></Grid>
                <Grid item sm={11} spacing={3} justify="flex-end"
                    alignItems="center">


                   
                    <Grid item sm={2}>
                        <TextField 
                            autoFocus
                            margin="dense"
                            id="reset_time"
                            label="Queue Reset Time"
                            type="text"
                            value={channels.reset_time}
                            onChange={handleChange}
                            fullWidth
                            
                        />
                    </Grid>

                    <Button variant="outlined" onClick={cancel} color="secondary">Cancel</Button>
                    <Button variant="outlined" onClick={save} color="primary">Save</Button>


                </Grid>
            </Grid>
        </Grid>
    </div>
);
}

//Add by JR for Save Function
function savebuttonclicked(data, apiKey, apiSecret) {

const workspace = workspaces[0];

console.log('Test');

alert(JSON.stringify(data));
console.log(JSON.stringify(data));


var params = [];

for (const [index, value] of data.channels.entries()) {
    params.push({
        'url': data.channels[index].url,
        'unique_name': data.channels[index].unique_name,
        'account_sid': data.channels[index].account_sid,
        'channel_reset_time': data.channels[index].channel_reset_time,
        'channel_reset_timezone': data.channels[index].channel_reset_timezone,
        'channel_service_level_threshold': data.channels[index].channel_service_level_threshold
    })
}

alert(JSON.stringify(params));
console.log('Parms for API post:');
console.log(JSON.stringify(params));

/* 
 * Loop for API call for each URL in created JSON
 * 
for (const [index, value] of params.entries())
 {
    axios.post(params[index].url, params[index], {
        auth: {
            username: apiKey,
            password: apiSecret,
        }
    })
}
*/

axios.get(workspace.apiURL, {
    auth: {
        username: apiKey,
        password: apiSecret,
    },
})

    .then(function (response) {
        alert('Save Sucessful.')
        alert(JSON.stringify(response))

        console.log('success');
        console.log(response.headers);
    })
    .catch(function (error) {
        alert('Error Occured.')
        alert(error)

        console.log('Error');
        console.log(error);
    });
 }

应用修复后,这是一个新错误:

TypeError: Cannot read property 'channels' of undefined FormRow C:/Users/drago/Source/Repos/twilio-flex-editor/src/queues/Editor.js:99 96 | 97 | function FormRow({ data, handleChange }) { 98 | const queue = data.data; > 99 | const channels = queue.channels; 100 | 101 | 102 | View compiled ▶ 18 stack frames were collapsed.

问题

您已将 FormRow 定义为使用 handleChange 回调,并且还错误地定义了道具。它们应该从单个道具对象中解构。

function FormRow(data, handleChange) {...

但已经通过了 formOnChange 道具回调

<FormRow data={queue} formOnChange={handleChange} />

您也将 this.handleChange 传递给 Editor 但从未使用它

<Editor
  onSubmit={this.handleSubmit}
  onChange={this.handleChange}
  data={this.state.queues}
  credentials={this.getCredentials}
  setData={this.setQueues}
/>

并且(可能巧合地)将未定义的 this.handleChange 函数传递给 FormContainer,后者传递给 FormRow 组件。

解决方案

修复 FormRow 组件定义

function FormRow({ data, handleChange }) {...

handleChange 作为 handleChange 传递给 FormRow

<FormRow data={queue} handleChange={handleChange} />

要么在Editor中定义this.handleChange传递,要么在this.props.onChange上传递。

return FormContainer(
  rowData,
  this.props.credentials().apiKey,
  this.props.credentials().apiSecret,
  this.props.onChange,
);

这是我收到的错误图片: enter image description here