Nested components losing focus on first onchange

Viewed 108

I've got 3 components as follows and the first on change on the input field makes it lose focus. I've tried giving the input fields and divs keys but it didn't solve my problem. I'm new to React so I might be doing something basic incorrectly. I'm not creating a new function inside a render method which is the reason for most losing focus issues documented in stackoverflow. Hence the reason for the new post.

Index.tsx

import * as React from "react";
import { IQuery, Queries } from "./components/Queries/Queries";

interface IManageResponseState {
  primaryQuery: IQuery;
  alternateQueries: IQuery[];
}

export class ManageResponse extends React.Component<any, IManageResponseState> {

  constructor(props: any) {
    super(props);
    this.state = {
      primaryQuery: {
        queryText: "This is my primary query text",
        id: 0
      }, alternateQueries: [{
        queryText: "this is my alternate query text 1",
        id: 1
      },
      {
        queryText: "this is my alternate query text 2",
        id: 2
      }]
    };
    this.addFunc = this.addFunc.bind(this);
    this.removeFunc = this.removeFunc.bind(this);
    this.primaryChangedFunc = this.primaryChangedFunc.bind(this);
    this.alternateChangedFunc = this.alternateChangedFunc.bind(this);
  }

  public addFunc(text: string) {
    const newQueries = this.state.alternateQueries.filter(q => true);
    newQueries.push({ id: 0, queryText: text });
    this.setState({ alternateQueries: newQueries });
  };

  public removeFunc(index: number) {
    this.setState({ alternateQueries: this.state.alternateQueries.splice(index, 1) });
    console.log("Remove called:" + index);
  };

  public primaryChangedFunc(text: string) {
    const query = {
      queryText: text,
      id: 0
    };

    this.setState({ primaryQuery: query });
    console.log("changed primary called:" + text);
  }

  public alternateChangedFunc(index: number, text: string) {
    const item = this.state.alternateQueries[index];
    const newQueries = this.state.alternateQueries.filter(q => true);
    newQueries[index] = {
      queryText: text,
      id: item.id
    };

    this.setState({ alternateQueries: newQueries })
    console.log("changed alternate called:" + text);
  }

  public render() {
    return (
      <React.Fragment>
        <Queries primaryQuery={this.state.primaryQuery} alternateQueries={this.state.alternateQueries} onAddQuery={this.addFunc} onRemoveQuery={this.removeFunc} onPrimaryChanged={this.primaryChangedFunc} onAlternateQueryChanged={this.alternateChangedFunc} />
      </React.Fragment>
    );
  }
}

Queries.tsx

import * as classnames from 'classnames';
import * as React from "react";
import { AlternateQueryItem } from "../AlternateQueryItem/AlternateQueryItem";
import './Queries.scss'

export interface IQuery {
  queryText: string;
  id: number;
}

export interface IQueryProps {
  primaryQuery: IQuery;
  alternateQueries: IQuery[];
  onPrimaryChanged: (queryText: string) => void;
  onAlternateQueryChanged: (index: number, queryText: string) => void;
  onAddQuery: (queryText: string) => void;
  onRemoveQuery: (atIndex: number) => void;
}

interface IQueryState {
  primaryQueryInvalid: boolean;
}

class Queries extends React.Component<IQueryProps, IQueryState> {
  constructor(props: IQueryProps) {
    super(props);
    this.onPrimaryQueryChanged = this.onPrimaryQueryChanged.bind(this);
    this.state = { primaryQueryInvalid: false };
  }

  public render() {
    return (
      <main className="query-main-container">
        <div className="primary-query-container">
          <div className="form-group">
            <h5 className={classnames("primary-query-header", { 'invalid': this.state.primaryQueryInvalid })}>Primary query</h5>
            <div className="primary-query">
            <div>{ "hello " +  this.state.primaryQueryInvalid }</div>
              <input type="text" value={this.props.primaryQuery.queryText} onChange={this.onPrimaryQueryChanged} className="form-control"/>
            </div>
          </div>
        </div>

        <div>
          <h5 className="query-header">Query variations</h5>
          <hr />
        </div>
        <div className="query-container">
          {this.props.alternateQueries && this.props.alternateQueries.map((q, i) => this.renderQuery(q, i))}
        </div>
      </main>
    );
  }
  public renderQuery(query: IQuery, key: number) {
    return (
      <AlternateQueryItem onChanged={this.props.onAlternateQueryChanged} query={query} key={key} index={key} />
    );
  }

  private onPrimaryQueryChanged = (ev: React.FormEvent<HTMLInputElement>) => {
    this.setState({ primaryQueryInvalid: ev.currentTarget.value === ""})
    this.props.onPrimaryChanged(ev.currentTarget.value)
  }
}

export { Queries };

AlternateQueryItem.tsx

import * as React from "react";
import { IQuery } from "../Queries/Queries";

export interface IAlternateQueryProps {
  query: IQuery;
  onChanged: (index: number, queryText: string) => void;
  index: number;
}

class AlternateQueryItem extends React.Component<IAlternateQueryProps> {
  constructor(props: IAlternateQueryProps) {
    super(props);
    this.onItemChanged = this.onItemChanged.bind(this);
  }

  public render() {
    return (
      <div className="form-group">
        <div className="alternate-query">
          <input type="text" value={this.props.query.queryText} onChange={this.onItemChanged} className="form-control" />
        </div>
      </div>
    );
  }

  public onItemChanged = (ev: any) => {
    this.props.onChanged(this.props.index, ev.currentTarget.value)
  }
}

export { AlternateQueryItem };
0 Answers
Related