Andrew Kloos
Andrew Kloos

Reputation: 4598

How do I keep focus on my react input field once the onchange fires?

Newer to React. I have this column in a ReactTable which has an input textbox. I understand that changing the state is re-rending the form just not sure how to prevent that. Appreciate any guidance!

      <ReactTable
          key={googlecampaigndata?.length}
          data={googlecampaigndata}
          loading={googlecampaignloading}
          sortable={false}
          filterable={false}
                        
          columns={[
            {
              Header: "Account Name",
              accessor: "customer_name"
            },
            {
              Header: "Campaign Name",
              accessor: "campaign_name"
            },
            {
              Header: "Campaign Type",
              accessor: "campaign_type"
            },
            {
              Header: "Status",
              accessor: "status"
            },
            {
              Header: "Monthly Budget",
              accessor: "monthly_budget"
            },
            {
              Header: "Update Monthly Budget",
              accessor: "update_monthly_budget",                
              Cell: ({ original }) => {
                return (
                  <form
                    onSubmit={e => {
                      e.preventDefault();
                      if(submissionData !== {} && submitGoogleCampaignMonthlyBudgetForm()){
                        refetch();
                        }  
                      }
                    }
                  >
                  <input
                    id={`monthly_budget${original.id}`}
                    type="text"
                    maxLength="12" 
                    size="8"
                    style={{ float: "left" }}
                    onChange = {updateFieldHandler("monthly_budget", original.id)}
                    inputprops={{
                      type: "string",
                      required: true,
                      value: original['monthly_budget'] ? original['monthly_budget'] : ""
                    }}
                  /><Button 
                      color="primary" 
                      type="submit"
                      style={{ float: "right" }}
                      >
                    Submit
                  </Button>
              </form>
                );
              },
            },
            {
              Header: "Cycle Start Date",
              accessor: "date"
            }
          ]}
          defaultPageSize={Math.min(pageSize, googlecampaigndata?.length || 2)}
          showPaginationTop
          showPaginationBottom={false}
          onPageSizeChange={ps => setPageSize(ps)}
          className="-striped -highlight"
        />

#EDIT I tried returning my input field from outside of MyAccounts but I'm still losing focus on the input when typing. More of my updated code below.

            import React, { useState, useContext, useEffect } from "react";
            import { useQuery, useMutation } from "@apollo/react-hooks";
            import { intersection, omit } from "lodash";

            // @material-ui/core components
            import { makeStyles } from "@material-ui/core/styles";
            // @material-ui/icons
            import Assignment from "@material-ui/icons/Assignment";

            // core components
            import GridContainer from "components/Grid/GridContainer";
            import GridItem from "components/Grid/GridItem";
            import Card from "components/Card/Card";
            import CardBody from "components/Card/CardBody";
            import CardIcon from "components/Card/CardIcon";
            import CardHeader from "components/Card/CardHeader";
            import ReactTable from "components/CustomReactTable";
            import Button from "components/CustomButtons/Button";
            import { cardTitle } from "assets/jss/material-dashboard-pro-react";
            import { SUBMIT_GOOGLE_CAMPAIGN_MONTHLY_BUDGET_FORM } from "queries/formSubmission";
            import { LIST_BUDGETS } from "queries/budget";
            import { GET_GOOGLE_CAMPAIGNS } from "queries/budget";
            import { Context } from "redux/store";

            const styles = {
              cardIconTitle: {
                ...cardTitle,
                marginTop: "15px",
                marginBottom: "0px"
              }
            };

            const useStyles = makeStyles(styles);


            const GoogleCampaignMonthlyBudgetInput = ({ original, setSubmissionData, submissionData, state, onChange }) => {
              return (
                <input
                  id={`monthly_budget${original.id}`}
                  type="text"
                  maxLength="12" 
                  size="8"
                  style={{ float: "left" }}
                  key={original.id}
                  onChange={ onChange }
                  value={submissionData.id === original.id ? submissionData.monthly_budget : original.monthly_budget ? (original.monthly_budget / (1 - (state.customers?.selected?.margin / 100.0))) / 1000000 : ""}
                  inputprops={{
                    type: "string",
                    required: true,
                  }}
                />
              );
            }

            const MyAccounts = () => {
              const [state] = useContext(Context);
              const [pageSize, setPageSize] = useState(20);
              const customer_id = state.customers?.selected?.id;
              const [submissionData, setSubmissionData] = useState({});
              let { loading, data } = useQuery(LIST_BUDGETS);
              let { loading: googlecampaignloading, data: googlecampaigndata, refetch } = useQuery(GET_GOOGLE_CAMPAIGNS);
              googlecampaigndata = googlecampaigndata?.getGoogleCampaigns || [];
              data = data?.listBudgets || [];

              data = data.map((row, index) => {
                let customer_name = "";
                let product_line = "";
                if (
                  index % pageSize === 0 ||
                  row.customer_name !== data[index - 1].customer_name
                ) {
                  customer_name = row.customer_name;
                }
                if (
                  index % pageSize === 0 ||
                  row.product_line !== data[index - 1].product_line
                ) {
                  product_line = row.product_line;
                }
                return {
                  ...row,
                  customer_name,
                  product_line
                };
              });
              const [submitGoogleCampaignMonthlyBudgetForm, { loading: submitting }] = useMutation(
                SUBMIT_GOOGLE_CAMPAIGN_MONTHLY_BUDGET_FORM,
                {
                  variables: {
                    customerId: customer_id,
                    data: submissionData
                  },
                  onCompleted: () => {
                  return true;
                  }
                }
              );

              const classes = useStyles();
              return (
                <GridContainer>
                   <GridItem xs={12}>
                    <Card>
                      <CardHeader color="trackedKeywords" icon>
                        <CardIcon>
                          <Assignment />
                        </CardIcon>
                        <h4 className={classes.cardIconTitle}>Google Campaigns</h4>
                      </CardHeader>
                      <CardBody>
                        <ReactTable
                          key={googlecampaigndata?.length}
                          data={googlecampaigndata}
                          loading={googlecampaignloading}
                          sortable={false}
                          filterable={false}
                                        
                          columns={[
                            {
                              Header: "Monthly Budget",
                              accessor: "monthly_budget",
                              Cell: ({ original }) => {
                                return (
                                  <div>{original.monthly_budget ? (original.monthly_budget / (1 - (state.customers?.selected?.margin / 100.0))) / 1000000 : ""}</div>
                                  );
                              },
                            },
                            {
                              Header: "Update Monthly Budget",
                              accessor: "update_monthly_budget",                
                              Cell: ({ original }) => {                    
                                return (
                                  <form
                                    onSubmit={e => {
                                      e.preventDefault();
                                      if(submissionData !== {} && submitGoogleCampaignMonthlyBudgetForm()){
                                        refetch();
                                        }  
                                      }
                                    }
                                  >
                                  <GoogleCampaignMonthlyBudgetInput original={original} onChange={e => setSubmissionData({...submissionData, "monthly_budget": parseFloat(e.target.value), "id": original.id })} setSubmissionData={setSubmissionData} submissionData={submissionData} state={state} key={original.id}  />
                                  <Button
                                    color="primary" 
                                    type="submit"
                                    style={{ float: "right" }}
                                    >
                                  Submit
                                </Button>
                              </form>
                                );
                              },
                            },

                            {
                              Header: "Cycle Start Date",
                              accessor: "date"
                            }
                          ]}
                          defaultPageSize={Math.min(pageSize, googlecampaigndata?.length || 2)}
                          showPaginationTop
                          showPaginationBottom={false}
                          onPageSizeChange={ps => setPageSize(ps)}
                          className="-striped -highlight"
                        />
                      </CardBody>
                    </Card>
                  </GridItem>
                </GridContainer>
              );
            };

            export default MyAccounts;

Edit 2: code that I got working with inwerpsel's help!

            {
              Header: "Monthly Budget",
              accessor: "monthly_budget",
              Cell: ({ original }) => {
                return (
                  <div>{submissionData.id === original.id ? submissionData.monthly_budget : original.monthly_budget ? (original.monthly_budget / (1 - (state.customers?.selected?.margin / 100.0))) / 1000000 : ""}</div>
                  );
              },
            },
            {
              Header: "Update Monthly Budget",
              accessor: "update_monthly_budget",                
              Cell: ({ original }) => {           
                const [textInput, setTextInput] = useState(null);                    

                const [submitGoogleCampaignMonthlyBudgetForm, { loading: submitting }] = useMutation(
                  SUBMIT_GOOGLE_CAMPAIGN_MONTHLY_BUDGET_FORM,
                  {
                    variables: {
                      customerId: customer_id,
                      data: submissionData
                    },
                    onCompleted: () => {
                      refetch();
                    return true;
                    }
                  }
                );

                return (
                  <form
                    key={"form"+original.id}
                    onSubmit={e => {
                      e.preventDefault();
                      if(Number.isNaN(textInput) || textInput === "" || textInput === null){
                        setSubmissionData({});
                        return;
                      }
                      submissionData["monthly_budget"] = parseFloat(textInput);
                      submissionData["id"] = original.id;
                      
                      if(submissionData !== {} && submitGoogleCampaignMonthlyBudgetForm()){
                        refetch();
                        setSubmissionData({});
                        }  
                      }
                    }
                  >
                  <input
                    id={`monthly_budget${original.id}`}
                    type="text"
                    maxLength="12" 
                    size="8"
                    style={{ float: "left" }}
                    key={original.id}
                    onChange = {event => { setTextInput(event.target.value) }}
                    defaultValue={submissionData.id === original.id ? submissionData.monthly_budget : original.monthly_budget ? (original.monthly_budget / (1 - (state.customers?.selected?.margin / 100.0))) / 1000000 : ""}
                    inutprops={{
                      type: "string",
                      required: true,
                    }}
                  />
                  <Button
                    color="primary" 
                    type="submit"
                    style={{ float: "right" }}
                    >
                  Submit
                </Button>
              </form>
                );
              },
            },

Upvotes: 1

Views: 1726

Answers (1)

inwerpsel
inwerpsel

Reputation: 3227

This GitHub issue seems to indicate that indeed cells will remount as your data changes, even if it's a small change.

In other words, as you type it replaces the whole row with a new instance every character. The new instance has no link to the previous one, even though it should contain the data you typed. As a result it won't have focus.

It seems potentially hard to get around, though I'm not that familiar with react-table.

However, as you already have a form with a submit handler set up, I guess you can just store the input in a local state, and only set it on submit.

Cell: ({ original }) => {
  const [textInput, setTextInput] = useState(null);

  return (
    <form
      onSubmit={e => {
        e.preventDefault();
        // Call the update handler here instead.
        const handler = updateFieldHandler("monthly_budget", original.id);
        handler(textInput);

        if(submissionData !== {} && submitGoogleCampaignMonthlyBudgetForm()){
          refetch();
          }  
        }
      }
    >
    <input
      // ...
      onChange = {event => { setTextInput(event.target.value) }}
      // ...
    /><Button 
        color="primary" 
        type="submit"
        style={{ float: "right" }}
        >
      Submit
    </Button>
</form>
  );

Upvotes: 3

Related Questions