Search code examples
javascriptreactjsgridonchange

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


Got the pictured error while trying to set up onChange event handler for the FormRow function. Ultimately need to store the user data from Que Reset Time, Service Level threshold and Short Abandoned Threshold. The Queuecontainer is the main class that uses the editor to create the container forms.

enter image description here

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);
    });
 }

With applied fix this is new 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.

enter image description here


Solution

  • Issue

    You have defined FormRow to consume a handleChange callback and also incorrectly defined the props. They should be destructured from a single props object.

    function FormRow(data, handleChange) {...
    

    but have passed an formOnChange prop callback

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

    You also pass this.handleChange to Editor but never consume it

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

    and (possibly coincidentally) pass an undefined this.handleChange function on to FormContainer which is passed to FormRow component.

    Solution

    Fix the FormRow component definition

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

    Pass handleChange on to FormRow as handleChange.

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

    Either define this.handleChange in Editor to be passed, or pass this.props.onChange on.

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