7

I am trying to add column filter on the header of the table but after each input typing on the field, it is losing the focus.

My main component: Here, I have initialized a local state: searchColArr and all others state used in fetchData are the redux state, which are being initialized using useSelectror

Category.jsx

const [searchColArr, setSearchColArr] = useState({
    name: "",
});

//all these default value are either local state or redux state
function fetchData(
    newPage = page,
    newPerPage = perPage,
    newOrder = order,
    newDir = dir,
    newSearchColArr = searchColArr,
    newSearch = search
) {
    dispatch(
        listCategory(
            newPage,
            newPerPage,
            newOrder,
            newDir,
            newSearchColArr,
            newSearch
        )
    );
}

const headerRow = React.useMemo(
    () => [
        {
            header: "Name",
            name: "name",
            filter: true,
            sort: true,
        },
        {
            header: "Action",
            name: "id",
            filter: false,
            sort: false,
        },
    ],
    []
);

const TBody = () => {
        return (
            <tbody key="tbody">
                {list &&
                    list.map((row, i) => (
                        <tr key={row.id}>
                            <td>{row.name}</td>
                            <td>
                                <ActionColumn id={row.id} />
                            </td>
                        </tr>
                    ))}
            </tbody>
        );
    };

const handleColSearch = ({ currentTarget: input }) => {
    setSearchColArr({
        ...searchColArr,
        [input.name]: input.value,
    });
};
useEffect(() => {
    fetchData();
}, [searchColArr]);

return (
    <div className="row">
        <div className="col-md-12">
            {/* REDUX TABLE STARTS */}

            <ReduxTable
                key="redux-table"
                isLoading={isLoading}
                headerRow={headerRow}
                list={list}
                page={page}
                perPage={perPage}
                order={order}
                dir={dir}
                total={total}
                totalPage={totalPage}
                searchColArr={searchColArr}
                handlePageLengthChange={handlePageLengthChange}
                handlePageChange={handlePageChange}
                handleColSearch={handleColSearch}
                TBody={TBody}
            />
            {/* REDUX TABLE ENDS */}
        </div>
    </div>
);

And, inside Redux Table I am loading another component ReduxTableHeader

ReduxTable

const ReduxTable = ({
    isLoading,
    headerRow,
    list,
    page,
    perPage,
    order,
    dir,
    total,
    totalPage,
    searchColArr,
    handlePageLengthChange,
    handlePageChange,
    handleColSearch,
    TBody,
}) => {
    return (
        <div className="card-box">

            {/* TABLE HEADER AND BODY just looping thorugh list*/}
            <table
                id="basicTable"
                className="table table-bordered action-table table-striped table-hover mb-0"
            >
                {/* ReduxTableHeader */}
                <ReduxTableHeader
                    key="redux-table-header"
                    headerRow={headerRow}
                    searchColArr={searchColArr}
                    handleColSearch={handleColSearch}
                />

                <TBody />
            </table>


        </div>
    );
};

ReduxTableHeader

    return (
        <thead key="thead">
            <tr>
                //list of header name
            </tr>
            <tr key="filter-row">
                {headerRow &&
                    headerRow.map((v, i) => (
                        <th key={v.name}>
                            {v.filter ? (
                                <input
                                    type="search"
                                    className="form-control form-control-sm d-inline-block"
                                    placeholder=""
                                    aria-controls="datatable"
                                    name={v.name}
                                    value={searchColArr[v.name] || ""}
                                    onChange={handleColSearch}
                                    autoComplete="off"
                                    key={"index" + i}
                                />
                            ) : (
                                ""
                            )}
                        </th>
                    ))}
            </tr>
        </thead>
    );

This table header is the place where I am loading an input field, and whenever I type on this letter is just losing the auto focus. How could I fix it?

Aayush Dahal
  • 856
  • 1
  • 17
  • 51

3 Answers3

2

You are violating rules of React by creating nested components like Tbody. This creates new component every render (even though function name stays the same), and causes that component to remount

In 99.999% of cases your input loses focus because one of its parent components remounts, thus creating a new component tree (and new input element) every time you type something. You are probably doing the same thing as you do with Tbody above in component tree, and your whole Category component remounts every time you trigger fetchData. To confirm you can comment out this useEffect that triggers fetchData

Max
  • 4,473
  • 1
  • 16
  • 18
  • Also setting keys to index can cause remounts. There are those also – Daniel Duong May 21 '21 at 02:26
  • @Max sorry for the late reply. I understand what you are trying to say. But, if I commented that part, how could I call the data on keyup on the input field. – Aayush Dahal May 31 '21 at 04:11
  • @AayushDahal you need to rewrite entire thing adhering to rules of react.. commenting that part out is not a solution but just a way to confirm that remounting causes loss of focus – Max May 31 '21 at 12:36
  • @Max Do you have any resource link? Which I could follow and add the column filter correctly? Could you please share one if you have one such resource link? – Aayush Dahal Jun 09 '21 at 05:00
1

Since input component loses focus, it means it is rerendered. It happens only when one of input component has changed type or upper tree is changed. In this case react does complete rerender.

So I guess one of the parent components [ReduxTable, ReduxTableHeader] is defined on every render. like you do for TBody.

In this case React thinks it's new type of component and destorys previous tree and do complete rerender which causes performance lag and in your case make input lose focus.

Regarding the performance you should not define component inside function. It makes react foolish.

To prevent this you need to make sure all of input's parent components are defined outside of Render function. In this case outside of function component.

Please check this my code sandbox link which demonstrates 2 cases.

  1. When you try to edit first row of inputs it doesn't lose focus
  2. When you try to edit other inputs which is in TBody, will lose focus Maybe same issue with you.

https://codesandbox.io/s/react-playground-rvikz?file=/src/App.js:567-572

Zhang TianYu
  • 1,163
  • 5
  • 11
0

Specifying a unique key for your input will cause React to reuse it and not redraw it. So it should keep it's focus after setting the state of the view.

return (
  <thead>
    <tr>
      /list of header name
    </tr>
    <tr>
      {headerRow &&
        headerRow.map((v, i) => (
          <th key={v.name}>
            {v.filter ? (
              <input
                 type="search"
                 className="form-control form-control-sm d-inline-block"
                 placeholder=""
                 aria-controls="datatable"
                 name={v.name}
                 value={searchColArr[v.name] || ""}
                 onChange={handleColSearch}
                 autoComplete="off"
                 key={'input' + i}
               />
             ) : (
               ""
             )}
          </th>
      ))}
    </tr>
  </thead>
);

Take a look at this post

MrCodingB
  • 2,284
  • 1
  • 9
  • 22
  • adding key to the input does not fix the issue, could you take a look to the code once again, could it be because of the re-render of parent element? on each type it will make api call and whole table component might be rerendering because of the update on the list. – Aayush Dahal May 16 '21 at 10:54
  • You might have to specify a key attribute on all of the parent elements – MrCodingB May 16 '21 at 11:23
  • I have updated the code and shown there how I have added key to each parent component, but still same issue. – Aayush Dahal May 16 '21 at 11:30
  • also has to have a key. Also in category and ReduxTable – MrCodingB May 16 '21 at 11:30
  • It looks like I have alread added key on ReduxTable, could you check the code above and please mention which tr are you talking about? – Aayush Dahal May 16 '21 at 11:44
  • Just put keys in **every** element beginning at `` in `Category.jsx` which is a parent to the inputs. – MrCodingB May 16 '21 at 12:33
  • I don't know if there is any place left to add key, I have added key on each component wherever I think its necessary. :( – Aayush Dahal May 16 '21 at 12:53