using useState and External Filters in reactJS

Viewed 260

I am struggling to get External Filters to work using useState in reactJS. I have updated your published example to use useState and the filtering is always one click behind the actual filter that has been selected i.e. first select does nothing, second select filters based on the previous selected filter.

It seems that the doesExternalFilterPass function is holding onto the previous state even though the gridApi.onFilterChanged() is being called from a useEffect that does contain the correct state.

https://plnkr.co/edit/0KID6KbOpGyIYcz7

'use strict';

import React, { useState, useEffect, useRef } from 'react';
import { render } from 'react-dom';
import { AgGridReact, AgGridColumn } from 'ag-grid-react';
import 'ag-grid-enterprise';
import 'ag-grid-community/dist/styles/ag-grid.css';
import 'ag-grid-community/dist/styles/ag-theme-alpine.css';

const GridExample = () => {
  const [gridApi, setGridApi] = useState(null);
  const [gridColumnApi, setGridColumnApi] = useState(null);
  const [rowData, setRowData] = useState(null);

  // add useState
  const [ageType, setAgeType] = useState({value:'everyone'})

  const onGridReady = (params) => {
    setGridApi(params.api);
    setGridColumnApi(params.columnApi);

    const updateData = (data) => {
      document.querySelector('#everyone').checked = true;
      setRowData(data);
    };

    fetch('https://www.ag-grid.com/example-assets/olympic-winners.json')
      .then((resp) => resp.json())
      .then((data) => updateData(data));
  };

  // modify to use useState
  const externalFilterChanged = (newValue) => {
    setAgeType({value : newValue});
  };

  const isExternalFilterPresent = () => {
    return true;
  };

  // modify to use useState
  const doesExternalFilterPass = (node) => {
    switch (ageType.value) {
      case 'below25':
        return node.data.age < 25;
      case 'between25and50':
        return node.data.age >= 25 && node.data.age <= 50;
      case 'above50':
        return node.data.age > 50;
      case 'dateAfter2008':
        return asDate(node.data.date) > new Date(2008, 1, 1);
      default:
        return true;
    }
  };
  
  // add useEffect to call onFilterChanged when filter updated
  useEffect ( () => {
    gridApi && gridApi.onFilterChanged();
  }, [ageType] )

  return (
    <div style={{ width: '100%', height: '100%' }}>
      <div className="test-container">
        <div className="test-header">
          <label>
            <input
              type="radio"
              name="filter"
              id="everyone"
              onChange={() => externalFilterChanged('everyone')}
            />
            Everyone
          </label>
          <label>
            <input
              type="radio"
              name="filter"
              id="below25"
              onChange={() => externalFilterChanged('below25')}
            />
            Below 25
          </label>
          <label>
            <input
              type="radio"
              name="filter"
              id="between25and50"
              onChange={() => externalFilterChanged('between25and50')}
            />
            Between 25 and 50
          </label>
          <label>
            <input
              type="radio"
              name="filter"
              id="above50"
              onChange={() => externalFilterChanged('above50')}
            />
            Above 50
          </label>
          <label>
            <input
              type="radio"
              name="filter"
              id="dateAfter2008"
              onChange={() => externalFilterChanged('dateAfter2008')}
            />
            After 01/01/2008
          </label>
        </div>
        <div
          id="myGrid"
          style={{
            height: '100%',
            width: '100%',
          }}
          className="ag-theme-alpine"
        >
          <AgGridReact
            defaultColDef={{
              flex: 1,
              minWidth: 120,
              filter: true,
            }}
            animateRows={true}
            isExternalFilterPresent={isExternalFilterPresent}
            doesExternalFilterPass={doesExternalFilterPass}
            onGridReady={onGridReady}
            rowData={rowData}
          >
            <AgGridColumn field="athlete" minWidth={180} />
            <AgGridColumn
              field="age"
              filter="agNumberColumnFilter"
              maxWidth={80}
            />
            <AgGridColumn field="country" />
            <AgGridColumn field="year" maxWidth={90} />
            <AgGridColumn
              field="date"
              filter="agDateColumnFilter"
              filterParams={dateFilterParams}
            />
            <AgGridColumn field="gold" filter="agNumberColumnFilter" />
            <AgGridColumn field="silver" filter="agNumberColumnFilter" />
            <AgGridColumn field="bronze" filter="agNumberColumnFilter" />
          </AgGridReact>
        </div>
      </div>
    </div>
  );
};

var dateFilterParams = {
  comparator: function (filterLocalDateAtMidnight, cellValue) {
    var cellDate = asDate(cellValue);
    if (filterLocalDateAtMidnight.getTime() === cellDate.getTime()) {
      return 0;
    }
    if (cellDate < filterLocalDateAtMidnight) {
      return -1;
    }
    if (cellDate > filterLocalDateAtMidnight) {
      return 1;
    }
  },
};

function asDate(dateAsString) {
  var splitFields = dateAsString.split('/');
  return new Date(splitFields[2], splitFields[1], splitFields[0]);
}

render(<GridExample></GridExample>, document.querySelector('#root'));

I have worked around this using useRef, setting the useRef to the useState value in the useEffect function, and then updating the doesExternalFilterPass function to use ref.current.value.

https://plnkr.co/edit/qwY0Qcp2HT59ucoN

'use strict';

import React, { useState, useEffect, useRef } from 'react';
import { render } from 'react-dom';
import { AgGridReact, AgGridColumn } from 'ag-grid-react';
import 'ag-grid-enterprise';
import 'ag-grid-community/dist/styles/ag-grid.css';
import 'ag-grid-community/dist/styles/ag-theme-alpine.css';

const GridExample = () => {
  const [gridApi, setGridApi] = useState(null);
  const [gridColumnApi, setGridColumnApi] = useState(null);
  const [rowData, setRowData] = useState(null);

  // add useState and useRef
  const [ageType, setAgeType] = useState({ value: 'everyone' });
  const ageTypeRef = useRef(ageType);

  const onGridReady = (params) => {
    setGridApi(params.api);
    setGridColumnApi(params.columnApi);

    const updateData = (data) => {
      document.querySelector('#everyone').checked = true;
      setRowData(data);
    };

    fetch('https://www.ag-grid.com/example-assets/olympic-winners.json')
      .then((resp) => resp.json())
      .then((data) => updateData(data));
  };

  const externalFilterChanged = (newValue) => {
    // update to useState
    setAgeType({ value: newValue });
  };

  const isExternalFilterPresent = () => {
    // update to useRef
    return ageTypeRef.current.value !== 'everyone';
  };

  const doesExternalFilterPass = (node) => {
    // update to useRef
    switch (ageTypeRef.current.value) {
      case 'below25':
        return node.data.age < 25;
      case 'between25and50':
        return node.data.age >= 25 && node.data.age <= 50;
      case 'above50':
        return node.data.age > 50;
      case 'dateAfter2008':
        return asDate(node.data.date) > new Date(2008, 1, 1);
      default:
        return true;
    }
  };
  // add useEffect to call onFilterChanged when filter updated
  useEffect(() => {
    // add update useRef variable to current values
    ageTypeRef.current = ageType;
    gridApi && gridApi.onFilterChanged();
  }, [ageType]);

  return (
    <div style={{ width: '100%', height: '100%' }}>
      <div className="test-container">
        <div className="test-header">
          <label>
            <input
              type="radio"
              name="filter"
              id="everyone"
              onChange={() => externalFilterChanged('everyone')}
            />
            Everyone
          </label>
          <label>
            <input
              type="radio"
              name="filter"
              id="below25"
              onChange={() => externalFilterChanged('below25')}
            />
            Below 25
          </label>
          <label>
            <input
              type="radio"
              name="filter"
              id="between25and50"
              onChange={() => externalFilterChanged('between25and50')}
            />
            Between 25 and 50
          </label>
          <label>
            <input
              type="radio"
              name="filter"
              id="above50"
              onChange={() => externalFilterChanged('above50')}
            />
            Above 50
          </label>
          <label>
            <input
              type="radio"
              name="filter"
              id="dateAfter2008"
              onChange={() => externalFilterChanged('dateAfter2008')}
            />
            After 01/01/2008
          </label>
        </div>
        <div
          id="myGrid"
          style={{
            height: '100%',
            width: '100%',
          }}
          className="ag-theme-alpine"
        >
          <AgGridReact
            defaultColDef={{
              flex: 1,
              minWidth: 120,
              filter: true,
            }}
            animateRows={true}
            isExternalFilterPresent={isExternalFilterPresent}
            doesExternalFilterPass={doesExternalFilterPass}
            onGridReady={onGridReady}
            rowData={rowData}
          >
            <AgGridColumn field="athlete" minWidth={180} />
            <AgGridColumn
              field="age"
              filter="agNumberColumnFilter"
              maxWidth={80}
            />
            <AgGridColumn field="country" />
            <AgGridColumn field="year" maxWidth={90} />
            <AgGridColumn
              field="date"
              filter="agDateColumnFilter"
              filterParams={dateFilterParams}
            />
            <AgGridColumn field="gold" filter="agNumberColumnFilter" />
            <AgGridColumn field="silver" filter="agNumberColumnFilter" />
            <AgGridColumn field="bronze" filter="agNumberColumnFilter" />
          </AgGridReact>
        </div>
      </div>
    </div>
  );
};

var dateFilterParams = {
  comparator: function (filterLocalDateAtMidnight, cellValue) {
    var cellDate = asDate(cellValue);
    if (filterLocalDateAtMidnight.getTime() === cellDate.getTime()) {
      return 0;
    }
    if (cellDate < filterLocalDateAtMidnight) {
      return -1;
    }
    if (cellDate > filterLocalDateAtMidnight) {
      return 1;
    }
  },
};

function asDate(dateAsString) {
  var splitFields = dateAsString.split('/');
  return new Date(splitFields[2], splitFields[1], splitFields[0]);
}

render(<GridExample></GridExample>, document.querySelector('#root'));

Is this the correct approach?

Thanks,

1 Answers

I have looked at your code and I am not sure why function takes previous state but I have found work around for this. If you like you can take a look at it.

I have create localActiveType and it will store same default value as activeType state and changed function to update value of it.

  let localAgeType = { value: 'everyone' }

  // modify to use useState
  const externalFilterChanged = (newValue) => {
    setAgeType({ value: newValue });
    localAgeType= { value: newValue }
  };

Now in doesExternalFilterPass, I replace localAgeType with ageType and it worked. Here is whole code

'use strict';

import React, { useState, useEffect } from 'react';
import { render } from 'react-dom';
import { AgGridReact, AgGridColumn } from 'ag-grid-react';
import 'ag-grid-enterprise';
import 'ag-grid-community/dist/styles/ag-grid.css';
import 'ag-grid-community/dist/styles/ag-theme-alpine.css';

let localAgeType = { value: 'everyone' }
const GridExample = () => {
  const [gridApi, setGridApi] = useState(null);
  const [gridColumnApi, setGridColumnApi] = useState(null);
  const [rowData, setRowData] = useState(null);

  // add useState
  const [ageType, setAgeType] = useState({ value: 'everyone' });

  const onGridReady = (params) => {
    setGridApi(params.api);
    setGridColumnApi(params.columnApi);

    const updateData = (data) => {
      document.querySelector('#everyone').checked = true;
      setRowData(data);
    };

    fetch('https://www.ag-grid.com/example-assets/olympic-winners.json')
      .then((resp) => resp.json())
      .then((data) => updateData(data));
  };

  // modify to use useState
  const externalFilterChanged = (newValue) => {
    setAgeType({ value: newValue });
    localAgeType= { value: newValue }
  };

  const isExternalFilterPresent = () => {
    return true;
  };

  // modify to use useState
  const doesExternalFilterPass = (node) => {
    switch (localAgeType.value) {
      case 'below25':
        return node.data.age < 25;
      case 'between25and50':
        return node.data.age >= 25 && node.data.age <= 50;
      case 'above50':
        return node.data.age > 50;
      case 'dateAfter2008':
        return asDate(node.data.date) > new Date(2008, 1, 1);
      default:
        return true;
    }
  };

  // add useEffect to call onFilterChanged when filter updated
  useEffect(() => {
    gridApi && gridApi.onFilterChanged();
  }, [ageType]);

  return (
    <div style={{ width: '100%', height: '100%' }}>
      <div className="test-container">
        <div className="test-header">
          <label>
            <input
              type="radio"
              name="filter"
              id="everyone"
              onChange={() => externalFilterChanged('everyone')}
            />
            Everyone
          </label>
          <label>
            <input
              type="radio"
              name="filter"
              id="below25"
              onChange={() => externalFilterChanged('below25')}
            />
            Below 25
          </label>
          <label>
            <input
              type="radio"
              name="filter"
              id="between25and50"
              onChange={() => externalFilterChanged('between25and50')}
            />
            Between 25 and 50
          </label>
          <label>
            <input
              type="radio"
              name="filter"
              id="above50"
              onChange={() => externalFilterChanged('above50')}
            />
            Above 50
          </label>
          <label>
            <input
              type="radio"
              name="filter"
              id="dateAfter2008"
              onChange={() => externalFilterChanged('dateAfter2008')}
            />
            After 01/01/2008
          </label>
        </div>
        <div
          id="myGrid"
          style={{
            height: '100%',
            width: '100%',
          }}
          className="ag-theme-alpine"
        >
          <AgGridReact
            defaultColDef={{
              flex: 1,
              minWidth: 120,
              filter: true,
            }}
            animateRows={true}
            isExternalFilterPresent={isExternalFilterPresent}
            doesExternalFilterPass={doesExternalFilterPass}
            onGridReady={onGridReady}
            rowData={rowData}
          >
            <AgGridColumn field="athlete" minWidth={180} />
            <AgGridColumn
              field="age"
              filter="agNumberColumnFilter"
              maxWidth={80}
            />
            <AgGridColumn field="country" />
            <AgGridColumn field="year" maxWidth={90} />
            <AgGridColumn
              field="date"
              filter="agDateColumnFilter"
              filterParams={dateFilterParams}
            />
            <AgGridColumn field="gold" filter="agNumberColumnFilter" />
            <AgGridColumn field="silver" filter="agNumberColumnFilter" />
            <AgGridColumn field="bronze" filter="agNumberColumnFilter" />
          </AgGridReact>
        </div>
      </div>
    </div>
  );
};

var dateFilterParams = {
  comparator: function (filterLocalDateAtMidnight, cellValue) {
    var cellDate = asDate(cellValue);
    if (filterLocalDateAtMidnight.getTime() === cellDate.getTime()) {
      return 0;
    }
    if (cellDate < filterLocalDateAtMidnight) {
      return -1;
    }
    if (cellDate > filterLocalDateAtMidnight) {
      return 1;
    }
  },
};

function asDate(dateAsString) {
  var splitFields = dateAsString.split('/');
  return new Date(splitFields[2], splitFields[1], splitFields[0]);
}

render(<GridExample></GridExample>, document.querySelector('#root'));


Related