1

Issue: I am using React with Redux store to manage my state. I want to display filters on a component. These filters will be returned from Tableau Javascript API. Based on the return from Tableau JavaScriptAPI I am updating the state in the store. The state is getting updated correctly with the right filters, which is also causing my component to update/ re-render. All good so far.

But when I am trying to access these filters (be it object using Object.keys() or array using array.length) from state.props, I am getting null array in return.

This is probably dues to incorrect use of Thunk/ Async Await.

Here is the code snippet

Step 1: Calling the Action from React. When I click a button, fetch dashboard action is called

class DashboardView extends Component {
  componentDidMount() {
    id = this.props.dashboardDetails.categoryID;
    console.log("Mounted");
    this.props.fetchDashboard(
      this.props.dashboardDetails.categoryURL,
      this.vizContainer
    );
  }

Step 2A: Here's the fetchDashboard action: within this action, I am creating filterNAmes array which I want to display on my component. I am making sing of Thunk here as my actions sends an API request to Tableau JavascriptAPI. The way I want it to work is once, we get response from the API, we will dispatch the viz to a reducer and the filterNames array to another action fetchFilter. Snippet attached.

export const fetchDashboard = (url, vizContainer) => async dispatch => {
  let filterNames = [];
  //  let titlesTemp = {};
  //const vizContainer = {};
  const options = {
    hideToolbar: true,
    onFirstInteractive: function() {
      let workbook = viz.getWorkbook();
      const activeSheet = workbook.getActiveSheet();
      activeSheet
        .getWorksheets()
        .get("Index")
        .getFiltersAsync()
        .then(function(filters) {
          for (let filter in filters) {
            let filterTitle = filters[filter].getFieldName();
            let filterName = {};
            filterName[filterTitle] = [];
            let len = filters[filter].getAppliedValues().length;
            for (let i = 0; i < len; i++) {
              filterName[filterTitle].push(
                filters[filter].getAppliedValues()[i].value
              );
            }
            filterNames.push(filterName);
          }
        });
    }
  };
  let viz = await new window.tableau.Viz(vizContainer, url, options);
  dispatch({ type: "FETCH_DASHBOARD", payload: { viz } });
  console.log(filterNames, filterNames.length);
  return dispatch(fetchFilter(filterNames));
};

When I console.log(filterNames, filterNames.length), the filterNames array has elements in it(which get evaluated later) but the filterNames.length return 0. I think this is the root cause of the issue. I want to dispatch the next action only when FilterNames is populated.

Step 2B:

export const fetchFilter = filterNames => {
  console.log("FETCH_FILTER action called", filterNames);
  return { type: "FETCH_FILTER", filterNames };
};

Assuming this is not the issue- Step 3: Reducer

const fetchFilterReducer = (fetchFilter = [], action) => {
  if (action.type === "FETCH_FILTER") {
    console.log("fetchfilterreducercalled:", action.filterNames);
    return action.filterNames;
  }
  return fetchFilter;
};

This updates the State fetchFilter in the store correctly.

Step 4: Using the connect function, I am pulling the fetchFilter array in the property of the component.

class FilterDisplay extends React.Component {
  componentDidMount() {
    console.log(this.props);
    if (this.props.selectionFilters.length !== 0) {
      console.log(this.props);
    }
  }

  componentDidUpdate() {
    console.log(this.props.selectionFilters.length);
    if (this.props.selectionFilters.length !== 0) {
      console.log("filter display updated");
      console.log(this.props);     
    }
  }

  render() {
    return <div>hey</div>; //<Tile></Tile>;
  }
}

const mapStateToProps = state => {
  console.log("first  filters in state:", state.fetchFilter);

  return {
    selectionFilters: state.fetchFilter
  };
};
export default connect(mapStateToProps)(FilterDisplay);

On my componentDidUpdate, I am getting length of selectionFilters array as 0. This is not allowing me to display the contents of the array on my screen.

This is a lot to read but I could not fit it in a smaller description. Please let me know in case I need to add more details.

Thanks for all the help in advance :)

2 Answers2

0

There is a lot going on here and probably a better way to integrate with Tableau but in order to solve you problem I think you just need to move dispatch(fetchFilter(filterNames)); inside the then() block in your fetchDashboard action.

.then(function(filters) {
  // all that other code ...
  dispatch(fetchFilter(filterNames));
});
Evan Taylor
  • 216
  • 3
  • 9
  • Thanks for the response. Its not working. Dispatch never gets executed, tried adding a console.log() as well and it did not execute at all. adding snippet in next comment – Ashay Nirgude Oct 01 '19 at 05:42
  • .then(function(filters) { for (let filter in filters) { let filterTitle = filters[filter].getFieldName(); let filterName = {}; filterName[filterTitle] = []; let len = filters[filter].getAppliedValues().length; for (let i = 0; i < len; i++) { filterName[filterTitle].push( filters[filter].getAppliedValues()[i].value ); } filterNames.push(filterName); } console.log(filterNames, filterNames.length); }); – Ashay Nirgude Oct 01 '19 at 05:43
0

In fetchDashboard, you could try to move dispatch(fetchFilter(filterNames)); at the end of the onFirstInteractive function.

new window.tableau.Viz(vizContainer, url, options) is not an async operation, which means that you are dispatching fetchFilter with an empty array (console.log does not help in this case, see here).

Pierre V.
  • 1,495
  • 1
  • 8
  • 14