React.js - dane wejściowe tracą koncentrację podczas renderowania

105

Piszę tylko do wprowadzania tekstu i na onChangewypadek setState, gdyby zadzwoniłem , React ponownie renderuje mój interfejs użytkownika. Problem polega na tym, że wprowadzanie tekstu zawsze traci fokus, więc muszę go ponownie ustawić dla każdej litery: D.

var EditorContainer = React.createClass({

    componentDidMount: function () {
        $(this.getDOMNode()).slimScroll({height: this.props.height, distance: '4px', size: '8px'});
    },

    componentDidUpdate: function () {
        console.log("zde");
        $(this.getDOMNode()).slimScroll({destroy: true}).slimScroll({height: 'auto', distance: '4px', size: '8px'});
    },

    changeSelectedComponentName: function (e) {
        //this.props.editor.selectedComponent.name = $(e.target).val();
        this.props.editor.forceUpdate();
    },

    render: function () {

        var style = {
            height: this.props.height + 'px'
        };
        return (
            <div className="container" style={style}>
                <div className="row">
                    <div className="col-xs-6">
                    {this.props.selected ? <h3>{this.props.selected.name}</h3> : ''}
                    {this.props.selected ? <input type="text" value={this.props.selected.name} onChange={this.changeSelectedComponentName} /> : ''}
                    </div>
                    <div className="col-xs-6">
                        <ComponentTree editor={this.props.editor} components={this.props.components}/>
                    </div>
                </div>
            </div>
        );
    }

});
Krab
źródło
Jedynym powodem, dla którego mogłoby się to zdarzyć, jest to, że a) coś innego odbiera ostrość lub b) dane wejściowe migają. Czy możesz podać plik jsfiddle / jsbin pokazujący problem? Oto baza reagująca jsbin .
Brigand
1
lol ... cóż, to brzmi trochę denerwująco: P Za pomocą jquery ustawiłbym identyfikator dla nowego renderowanego pliku wejściowego, a następnie wywołałem na nim fokus. Nie jestem pewien, jak kod wyglądałby w zwykłym js. Ale jestem pewien, że możesz to zrobić :)
Medda86
1
@FakeRainBrigand: co rozumiesz przez migotanie?
Krab
@Krab, jak gdyby this.props.selected stawało się fałszywe, a potem znowu stawało się prawdziwe. To spowodowałoby odmontowanie wejścia, a następnie ponowne zamontowanie.
Brigand
@Krab, spróbuj usunąć linie slimScroll; to może być coś dziwnego, co powoduje problemy.
Brigand

Odpowiedzi:

91

Bez zobaczenia reszty kodu, to jest przypuszczenie. Podczas tworzenia EditorContainer określ unikalny klucz dla komponentu:

<EditorContainer key="editor1"/>

Kiedy nastąpi ponowne renderowanie, jeśli ten sam klucz zostanie zauważony, to powie Reactowi, aby nie blokował i nie generował widoku, zamiast tego użyć go ponownie. Następnie skupiony element powinien zachować ostrość.

z5h
źródło
2
To rozwiązało mój problem z renderowaniem komponentu podrzędnego zawierającego formularz wejściowy. Ale w moim przypadku potrzebowałem czegoś odwrotnego - forma nie była ponownie renderowana, kiedy tego chciałem. Dodanie atrybutu klucza zadziałało.
Sam Texas
2
dodanie klucza do wejścia nie pomogło
Mïchael Makaröv
6
Być może komponenty zawierające dane wejściowe również zostaną ponownie wyrenderowane. Sprawdź klucze na dołączonych elementach.
Petr Gladkikh
Głosuj za komentarzem @PetrGladkikh. Potrzebowałem kluczy na wszystkich dołączonych komponentach.
Brian Cragun
58

Wciąż tu wracam i zawsze na końcu znajduję rozwiązanie mojego gdzie indziej. Więc udokumentuję to tutaj, ponieważ wiem, że znowu o tym zapomnę!

Przyczyną inpututraty koncentracji w moim przypadku był fakt, że ponownie renderowałem inputzmianę stanu włączenia.

Kod buggy:

import React from 'react';
import styled from 'styled-components';

class SuperAwesomeComp extends React.Component {
  state = {
    email: ''
  };
  updateEmail = e => {
    e.preventDefault();
    this.setState({ email: e.target.value });
  };
  render() {
    const Container = styled.div``;
    const Input = styled.input``;
    return (
      <Container>
        <Input
          type="text"
          placeholder="Gimme your email!"
          onChange={this.updateEmail}
          value={this.state.email}
        />
      </Container>
    )
  }
}

Problem polega więc na tym, że zawsze zaczynam kodować wszystko w jednym miejscu, aby szybko przetestować, a później podzielić to wszystko na osobne moduły. Ale tutaj ta strategia działa odwrotnie, ponieważ aktualizacja stanu przy zmianie wejścia wyzwala funkcję renderowania, a fokus zostaje utracony.

Naprawa jest prosta, wykonaj modularyzację od początku, innymi słowy „Przenieś Inputkomponent z renderfunkcji”

Kod stały

import React from 'react';
import styled from 'styled-components';

const Container = styled.div``;
const Input = styled.input``;

class SuperAwesomeComp extends React.Component {
  state = {
    email: ''
  };
  updateEmail = e => {
    e.preventDefault();
    this.setState({ email: e.target.value });
  };
  render() {
    return (
      <Container>
        <Input
          type="text"
          placeholder="Gimme your email!"
          onChange={this.updateEmail}
          value={this.state.email}
        />
      </Container>
    )
  }
}

Nr ref. do rozwiązania: https://github.com/styled-components/styled-components/issues/540#issuecomment-283664947

Deepak
źródło
1
Używałem HOC wewnątrz funkcji renderowania, przeniesienie wywołania HOC do definicji komponentu załatwiło sprawę. W jakiś sposób podobny do tej odpowiedzi.
Mark E
3
Myślę, że to jest mój problem, ale mam problem z przenoszeniem komponentów na zewnątrz render()i z class ... extends Componentpowodu odwołania do this. np.onChange={this.handleInputChange}
Nateous
4
Morale historii, dla komponentów klasy React, nie definiuje komponentów wewnątrz renderfunkcji, dla komponentów funkcjonalnych Reacta nie definiuje komponentów w ciele funkcji.
Wong Jia Hau
1
Dzięki! Uratowałeś mój tyłek !!
K-Sato
1
@Nateous miałem dokładnie taką samą sytuację. Wywołania HOC wewnątrz renderfunkcji, której przekazano metodę „this.handleChange” i zwróciły komponenty, które mają być użyte. Np const TextAreaDataField = TextAreaDataFieldFactory(this.handleChange). Po prostu przeniosłem tworzenie komponentu do konstruktora i uzyskałem do nich dostęp w rendermetodzie jako this.TextAreaDataField. Działał jak urok.
Marcus Junius Brutus
36

Jeśli problem występuje w routerze reagującym, <Route/>użyj właściwości renderzamiast component.

<Route path="/user" render={() => <UserPage/>} />


Utrata skupienia ma miejsce, ponieważ componentrekwizyt używa za React.createElementkażdym razem, a nie tylko ponownie renderuje zmiany.

Szczegóły tutaj: https://reacttraining.com/react-router/web/api/Route/component

spencer.sm
źródło
2
To był zasadniczo mój problem. W szczególności przekazywałem anonimową funkcję do elementu składowego, wywołując utratę ostrości: <Route component={() => <MyComponent/>} />kiedy powinienem był to zrobić<Route component={MyComponent} />
Daniel
Uratowałeś mi dzień - takie proste!
Fabricio
12

Moja odpowiedź jest podobna do tego, co powiedział @ z5h.

W moim przypadku Math.random()generowałem unikat keydla komponentu.

Myślałem, że keyjest on używany tylko do wyzwalania ponownego renderowania dla tego konkretnego komponentu, a nie do ponownego renderowania wszystkich komponentów w tej tablicy (zwracam tablicę komponentów w moim kodzie). Nie wiedziałem, że służy do przywracania stanu po ponownym renderowaniu.

Usunięcie tego wykonało pracę za mnie.

Shankar Thyagarajan
źródło
1
Moduł taki jak npmjs.com/package/shortid jest również dobrym sposobem na obsługę czegoś takiego.
skwidbreth
5

Zastosowanie autoFocusatrybutu do inputelementu może stanowić obejście problemu w sytuacjach, w których należy skoncentrować się tylko na jednym wejściu. W takim przypadku keyatrybut byłby niepotrzebny, ponieważ jest to tylko jeden element, a ponadto nie musiałbyś się martwić o rozbicie inputelementu na własny komponent, aby uniknąć utraty koncentracji na ponownym renderowaniu głównego komponentu.

spakmad
źródło
Musiałem spróbować tego jako obejścia w ostateczności. Cieszę się, że to działa. Oczywiście w mojej aplikacji jest błąd, ponieważ aplikacja jest ponownie renderowana za każdym razem, gdy próbuję wpisać jedyną wyszukiwaną wartość na stronie. Ale dotarcie autofocusdo pola wprowadzania danych to początek. 🙌
keshavDulal
5

Mam takie samo zachowanie.

Problem w moim kodzie polegał na tym, że utworzyłem zagnieżdżoną tablicę elementów jsx w następujący sposób:

const example = [
            [
                <input value={'Test 1'}/>,
                <div>Test 2</div>,
                <div>Test 3</div>,
            ]
        ]

...

render = () => {
    return <div>{ example }</div>
}

Każdy element w tej zagnieżdżonej tablicy jest ponownie renderowany za każdym razem, gdy aktualizuję element nadrzędny. I tak wejścia tracą tam "ref" prop za każdym razem

Rozwiązałem problem z przekształceniem tablicy wewnętrznej na komponent reagujący (funkcja z funkcją renderowania)

const example = [
            <myComponentArray />
        ]

 ...

render = () => {
    return <div>{ example }</div>
}

EDYTOWAĆ:

Ten sam problem pojawia się, gdy buduję plik zagnieżdżony React.Fragment

const SomeComponent = (props) => (
    <React.Fragment>
        <label ... />
        <input ... />
    </React.Fragment>
);

const ParentComponent = (props) => (
    <React.Fragment>
        <SomeComponent ... />
        <div />
    </React.Fragment>
);
chrisheyn
źródło
3

Rozwiązałem ten sam problem, usuwając atrybut klucza w danych wejściowych i jego elementy nadrzędne

// Before
<input
    className='invoice_table-input invoice_table-input-sm'
    type='number'
    key={ Math.random }
    defaultValue={pageIndex + 1}
    onChange={e => {
        const page = e.target.value ? Number(e.target.value) - 1 : 0
        gotoPage(page)
    }}
/>
// After
<input
    className='invoice_table-input invoice_table-input-sm'
    type='number'
    defaultValue={pageIndex + 1}
    onChange={e => {
        const page = e.target.value ? Number(e.target.value) - 1 : 0
        gotoPage(page)
    }}
/>

Adrián Pedreira
źródło
2

Właśnie natknąłem się na ten problem i przyszedłem tutaj po pomoc. Sprawdź swój CSS! Pole wejściowe nie może mieć user-select: none;lub nie będzie działać na iPadzie.

David Sinclair
źródło
2

U mnie było to spowodowane tym, że pole wprowadzania wyszukiwania było renderowane w tym samym komponencie (zwanym UserList), co lista wyników wyszukiwania. Dlatego za każdym razem, gdy zmieniły się wyniki wyszukiwania, cały składnik UserList był ponownie renderowany, łącznie z polem wprowadzania.

Moim rozwiązaniem było stworzenie zupełnie nowego komponentu o nazwie UserListSearch, który jest niezależny od UserList . Nie musiałem ustawiać kluczy w polach wejściowych w UserListSearch, aby to zadziałało. Funkcja renderowania my UsersContainer wygląda teraz następująco:

class UserContainer extends React.Component {
  render() {
    return (
      <div>
        <Route
          exact
          path={this.props.match.url}
          render={() => (
            <div>
              <UserListSearch
                handleSearchChange={this.handleSearchChange}
                searchTerm={this.state.searchTerm}
              />
              <UserList
                isLoading={this.state.isLoading}
                users={this.props.users}
                user={this.state.user}
                handleNewUserClick={this.handleNewUserClick}
              />
            </div>
          )}
        />
      </div>  
    )
  }
}

Mam nadzieję, że to też komuś pomoże.

Dom Eden
źródło
2

Jeśli pole wejściowe znajduje się wewnątrz innego elementu (tj. Elementu kontenera, takiego jak <div key={"bart"}...><input key={"lisa"}...> ... </input></div>- elipsy wskazujące tutaj pominięty kod), na elemencie kontenera (a także w polu wejściowym) musi znajdować się unikalny i stały klucz . W przeciwnym razie React renderuje zupełnie nowy element kontenera, gdy stan dziecka jest aktualizowany, a nie tylko ponownie renderuje stary kontener. Logicznie rzecz biorąc, należy zaktualizować tylko element podrzędny, ale ...

Miałem ten problem podczas próby napisania komponentu, który pobierał wiele informacji adresowych. Działający kod wygląda następująco

// import react, components
import React, { Component } from 'react'

// import various functions
import uuid from "uuid";

// import styles
import "../styles/signUp.css";

export default class Address extends Component {
  constructor(props) {
    super(props);
    this.state = {
      address1: "",
      address2: "",
      address1Key: uuid.v4(),
      address2Key: uuid.v4(),
      address1HolderKey: uuid.v4(),
      address2HolderKey: uuid.v4(),
      // omitting state information for additional address fields for brevity
    };
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(event) {
    event.preventDefault();
    this.setState({ [`${event.target.id}`]: event.target.value })
  }

  render() {
    return (
      <fieldset>
        <div className="labelAndField" key={this.state.address1HolderKey} >
          <label className="labelStyle" for="address1">{"Address"}</label>
          <input className="inputStyle"
            id="address1"
            name="address1"
            type="text"
            label="address1"
            placeholder=""
            value={this.state.address1}
            onChange={this.handleChange}
            key={this.state.address1Key} ></input >
        </div> 
        <div className="labelAndField" key={this.state.address2HolderKey} >
          <label className="labelStyle" for="address2">{"Address (Cont.)"}</label>
          <input className="inputStyle"
            id="address2"
            name="address2"
            type="text"
            label="address2"
            placeholder=""
            key={this.state.address2Key} ></input >
        </div>
        {/* omitting rest of address fields for brevity */}
      </fieldset>
    )
  }
}

Uważni czytelnicy zauważą, że <fieldset>jest to element zawierający, ale nie wymaga klucza. To samo dotyczy, <>a <React.Fragment>nawet <div>dlaczego? Może tylko bezpośredni pojemnik potrzebuje klucza. Nie wiem. Jak mówią podręczniki matematyki, wyjaśnienie pozostawia się czytelnikowi jako ćwiczenie.

Charles Goodwin
źródło
1

Głównym powodem jest to, że po ponownym wyrenderowaniu Reacta poprzedni ref DOM będzie nieprawidłowy. To znaczy, że reakcja zmieniła drzewo DOM, a ty this.refs.input.focus nie będzie działać, ponieważ wejście tutaj już nie istnieje.

hlissnake
źródło
1

Udzielone odpowiedzi nie pomogły mi, oto co zrobiłem, ale miałem wyjątkową sytuację.

Aby wyczyścić kod, zwykle używam tego formatu, dopóki nie będę gotowy do przeciągnięcia komponentu do innego pliku.

render(){
   const MyInput = () => {
      return <input onChange={(e)=>this.setState({text: e.target.value}) />
   }
   return(
      <div>
         <MyInput />
      </div>
   )

Ale to spowodowało, że stracił ostrość, kiedy umieściłem kod bezpośrednio w div.

return(
   <div>
      <input onChange={(e)=>this.setState({text: e.target.value}) />
   </div>
)

Nie wiem, dlaczego tak jest, to jedyny problem, jaki miałem z pisaniem tego w ten sposób i robię to w większości plików, które mam, ale jeśli ktoś robi coś podobnego, to traci koncentrację.

Kevin Danikowski
źródło
1

zmieniłem value prop na defaultValue. To działa dla mnie.

...
// before
<input value={myVar} />

// after
<input defaultValue={myVar} />
chrisheyn
źródło
1

Miałem ten sam problem z tabelą html, w której mam linie tekstu wejściowego w kolumnie. wewnątrz pętli czytam obiekt json i tworzę wiersze w szczególności mam kolumnę z tekstem wejściowym.

http://reactkungfu.com/2015/09/react-js-loses-input-focus-on-typing/

Udało mi się to rozwiązać w następujący sposób

import { InputTextComponent } from './InputTextComponent';
//import my  inputTextComponent 
...

var trElementList = (function (list, tableComponent) {

    var trList = [],
        trElement = undefined,
        trElementCreator = trElementCreator,
        employeeElement = undefined;



    // iterating through employee list and
    // creating row for each employee
    for (var x = 0; x < list.length; x++) {

        employeeElement = list[x];

        var trNomeImpatto = React.createElement('tr', null, <td rowSpan="4"><strong>{employeeElement['NomeTipologiaImpatto'].toUpperCase()}</strong></td>);
        trList.push(trNomeImpatto);

        trList.push(trElementCreator(employeeElement, 0, x));
        trList.push(trElementCreator(employeeElement, 1, x));
        trList.push(trElementCreator(employeeElement, 2, x));

    } // end of for  

    return trList; // returns row list

    function trElementCreator(obj, field, index) {
        var tdList = [],
            tdElement = undefined;

        //my input text
        var inputTextarea = <InputTextComponent
            idImpatto={obj['TipologiaImpattoId']}//index
            value={obj[columns[field].nota]}//initial value of the input I read from my json data source
            noteType={columns[field].nota}
            impattiComposite={tableComponent.state.impattiComposite}
            //updateImpactCompositeNote={tableComponent.updateImpactCompositeNote}
        />

        tdElement = React.createElement('td', { style: null }, inputTextarea);
        tdList.push(tdElement);


        var trComponent = createClass({

            render: function () {
                return React.createElement('tr', null, tdList);
            }
        });
        return React.createElement(trComponent);
    } // end of trElementCreator

});
...    
    //my tableComponent
    var tableComponent = createClass({
        // initial component states will be here
        // initialize values
        getInitialState: function () {
            return {
                impattiComposite: [],
                serviceId: window.sessionStorage.getItem('serviceId'),
                serviceName: window.sessionStorage.getItem('serviceName'),
                form_data: [],
                successCreation: null,
            };
        },

        //read a json data soure of the web api url
        componentDidMount: function () {
            this.serverRequest =
                $.ajax({
                    url: Url,
                    type: 'GET',
                    contentType: 'application/json',
                    data: JSON.stringify({ id: this.state.serviceId }),
                    cache: false,
                    success: function (response) {
                        this.setState({ impattiComposite: response.data });
                    }.bind(this),

                    error: function (xhr, resp, text) {
                        // show error to console
                        console.error('Error', xhr, resp, text)
                        alert(xhr, resp, text);
                    }
                });
        },

        render: function () {
    ...
    React.createElement('table', {style:null}, React.createElement('tbody', null,trElementList(this.state.impattiComposite, this),))
    ...
    }



            //my input text
            var inputTextarea = <InputTextComponent
                        idImpatto={obj['TipologiaImpattoId']}//index
                        value={obj[columns[field].nota]}//initial value of the input I read //from my json data source
                        noteType={columns[field].nota}
                        impattiComposite={tableComponent.state.impattiComposite}//impattiComposite  = my json data source

                    />//end my input text

                    tdElement = React.createElement('td', { style: null }, inputTextarea);
                    tdList.push(tdElement);//add a component

        //./InputTextComponent.js
        import React from 'react';

        export class InputTextComponent extends React.Component {
          constructor(props) {
            super(props);
            this.state = {
              idImpatto: props.idImpatto,
              value: props.value,
              noteType: props.noteType,
              _impattiComposite: props.impattiComposite,
            };
            this.updateNote = this.updateNote.bind(this);
          }

        //Update a inpute text with new value insert of the user

          updateNote(event) {
            this.setState({ value: event.target.value });//update a state of the local componet inputText
            var impattiComposite = this.state._impattiComposite;
            var index = this.state.idImpatto - 1;
            var impatto = impattiComposite[index];
            impatto[this.state.noteType] = event.target.value;
            this.setState({ _impattiComposite: impattiComposite });//update of the state of the father component (tableComponet)

          }

          render() {
            return (
              <input
                className="Form-input"
                type='text'
                value={this.state.value}
                onChange={this.updateNote}>
              </input>
            );
          }
        }
Fabryka oprogramowania
źródło
1

Mój problem polegał na tym, że nazwałem kluczem dynamicznie, podając wartość elementu, w moim przypadku „nazwa”, więc kluczem był klucz = { ${item.name}-${index}}. Więc kiedy chciałem zmienić dane wejściowe z item.name jako wartością, klucz również by się zmienił i dlatego reagował nie rozpoznałby tego elementu

Amogu
źródło
0

Okazuje się, że byłam wiążąca this z komponentem, który powodował, że się wycofywał.

Pomyślałem, że opublikuję to tutaj na wypadek, gdyby ktoś inny miał ten problem.

Musiałem się zmienić

<Field
    label="Post Content"
    name="text"
    component={this.renderField.bind(this)}
/>

Do

<Field
    label="Post Content"
    name="text"
    component={this.renderField}
/>

Prosty dylemat, ponieważ w moim przypadku, ja faktycznie nie trzeba thisw renderField, ale mam nadzieję, że mi to pomoże delegowania kogoś innego.

Matt D.
źródło
0

Miałem ten problem i okazało się, że używałem komponentu funkcjonalnego i łączyłem się ze stanem komponentu nadrzędnego. Jeśli przełączyłem się na używanie komponentu klasowego, problem zniknął. Miejmy nadzieję, że jest sposób na obejście tego podczas używania funkcjonalnych komponentów, ponieważ jest to o wiele wygodniejsze dla prostych renderów elementów i innych.

Kyle Crossman
źródło
0

zawarł następny kod w tagu wejściowym:

ref={(input) => {
     if (input) {
         input.focus();
     }
 }}

Przed:

<input
      defaultValue={email}
      className="form-control"
      type="email"
      id="email"
      name="email"
      placeholder={"[email protected]"}
      maxLength="15"
      onChange={(e) => validEmail(e.target.value)}
/>

Po:

<input
     ref={(input) => {
          if (input) {
             input.focus();
          }
      }}
      defaultValue={email}
      className="form-control"
      type="email"
      id="email"
      name="email"
      placeholder={"[email protected]"}
      maxLength="15"
      onChange={(e) => validEmail(e.target.value)}
/>
Thiago Pires
źródło
zadziałało w moim przypadku.
Mr Special