Using Redux and ReactJS to Create a Custom Support Desk in NetSuite

As JavaScript applications become more and more complex, the state that must be managed by the code increases beyond anything that was expected in the past. With so many different components affecting the applications state, it is very hard to know what is going on inside the program. This makes finding bugs and enhancing existing applications with new features a difficult task, but it doesn’t have to be. I will demonstrate the value of using Redux by showing how we have been using it to create a custom support search page within NetSuite and explain its core concepts.

Principles

Store

Redux follows three principles. The first of these is that there is a single source of truth in the application. This source of truth is the redux store. The state is stored in an object tree within the store. Our initial store for the search page will look like this:

search: {
    searchResults: [],
    searching: false
  }

When the page is first loaded it will not have any search results and it will not be searching as the user has not entered a query yet.

Actions

The second principle of redux is that state should be read-only. It says that the only way to change the state is to emit an action. This means that the various inputs for an application cannot directly modify the state. They can only express intent to modify it. All changes to the state being handled by actions make it much easier to debug the program and understand how it is working. We have three actions in the custom search for handling successful, failed and attempted searches which return JavaScript objects describing what has happened.

export function searchSuccess(searchResults) {
  return {
    type: SEARCH_SUCCESS,
    searchResults
  };
}

export function searchFail(errorMessage) {
  return {
    type: SEARCH_FAIL,
    errorMessage
  };
}

export function searchAttempt() {
  return {
    type: SEARCH_ATTEMPT
  };
}

SEARCH_SUCCESS, SEARCH_FAIL and SEARCH_ATTEMPT are our action types. They are used to determine what sort of action is being returned.

Reducers

The third principle of Redux is that changes are made with pure functions. These pure functions are called reducers and they take in the previous state and an action and use them to determine the new state which is returned. The new state is a completely new state object rather than a mutation of the previous state. Here is what our searchReducer looks like:

export default function searchReducer(state = initialState.search, action) {
  switch (action.type) {
    case SEARCH_SUCCESS:
      return {
        searchResults: action.searchResults,
        searching: false
      };
    case SEARCH_FAIL:
      return {
        errorMessage: action.errorMessage,
        searchResults: [],
        searching: false
      };
    case SEARCH_ATTEMPT:
      return {
        ...state,
        searching: true
      };
    default:
      return state;
  }
}

The reducer takes in two parameters. State, which is set to initialState.search, and action. We have already seen what initialState.search looks like in the first code block of this article. The rest of the reducer is simply a switch statement that checks the type of action being passed in, creates a new state object accordingly and updates the store. If the action type is SEARCH_SUCCESS, the searchResults state of the new state object will be set to the searchResults value of the action. The searching state will be set to false as the application is no longer performing a search once the results are returned. If the action is SEARCH_FAIL, then the results will be set to an empty array. Searching will still be set to false as the search has finished but there will be another state created, errorMessage, which contains the error message from the action. A SEARCH_ATTEMPT action returns a state object with the same state as the previous state however the searching value will be set to true as the search is ongoing.  That covers the basic components of Redux. The next step is to understand the flow of data.

Data Flow

As I said before we have used Redux to create a custom search page in NetSuite. The front end of this application was created using ReactJS and consists of a search bar and a list of results. These components are fairly simple to create so I won’t go into any more detail than to say that the search bar is an input element and the search results are displayed using the card component from the Bootstrap library.

1.png

Now that we understand how Redux works and how the front end is set up, we can start to look at how user input is handled. The search bar component has the following onChange event handler:

searchQueryChanged = (event) => {
    const query = event.target.value;
    if (query && query !== '') {
      this.props.searchActions.search(query);
    }
}

This method checks that there is a value in the search bar and passes it to the search action of the searchActions class. We didn’t cover this action when we looked at the others, but we will now.

export function search(query) {
  return (dispatch) => {
    dispatch(searchAttempt());
    return UnioAPI.search(query).then((response) => {
      if (response.data) {
        dispatch(searchSuccess(response.data.data));
      } else {
        dispatch(searchFail('Unknown error occurred'));
      }
    }).catch((error) => {
      if (error.response && error.response.status === 401 && !error.response.data.result) {
        dispatch(searchFail(error.response.data.error));
      } else {
        dispatch(searchFail('Unknown error occurred'));
      }
    });
  };
}

The search action takes in a query, the user input from the search bar, and dispatches one of our actions from before to the reducer. It first dispatches a searchAttempt action to update the searching state to true. It then uses the UnioAPI to search for the query. Unio is our own in-house NetSuite integration platform which will query the NetSuite knowledge base with the user input. For this application, we have set it up to run on a Docker container along with Elasticsearch. The response from Unio is then checked and if it contains data a searchSuccess action is dispatched. The reducer takes this action and uses it to update the state of the application. The React side will use this state to determine the search results to be displayed and the format in which to display them.

createResults = () => {
    if (this.props.results) {
      return this.props.results.map(
        result => (
          <SearchResult
            title={result.title}
            type={result.type}
            description={result.description}
            internalId={ result.internalId }
            key={`${result.type}-${result.internalId}`}
          />
        )
      );
    }
    return undefined;
  }
searchPage.gif

This concludes this article on Redux and its applications to a custom search page. I hope I have demonstrated its value in creating complex JavaScript applications and making them easier to understand, debug and test. If you would like to learn more about getting started with Redux I would recommend looking at the documentation on the Redux site. If you have any questions, please feel free to use the comment section.

 

TO STAY IN THE LOOP FOR FURTHER NEWS, GUIDES, RESOURCES AND MORE, SIGN UP TO "INSIGHT" OUR WEEKLY FLAGSHIP NETSUITE AND BUSINESS SOFTWARE NEWSLETTER, CLICK HERE.