Data-Driven Functional Components via Ajax Featuring Webhooks and Redux

By Júlia Rocha 4 min read

Introducing React Hooks: New Data-Driven Components: Once upon a time, class components were the only way to hook into lifecycle methods. For example, this allowed the component to load initial data via AJAX on mount.

The lifecycle method componentDidMount worked well for this use case. This forced the component to manage its state, and it wasn’t without faults.

One gotcha was managing state with Redux. Reloading component data without ugly hacks was challenging. This difficulty came from managing state in two places: the component itself and in Redux. As class component complexity grew, so did the hacks.

Enter Hooks, a new way to manage state without class components during initial load.

The idea is to let the Redux store manage the state without lifecycle methods. This allows code reuse because state management gets encapsulated in the store. When dispatch actions fire, the stored state has everything to know about each component.

Dive into Hooks to tackle this use case.

Loading initial component data while letting Redux manage the state has its benefits. We will skip setting up a new code project to keep the focus on relevant code. All code samples are in the GitHub repository.

Redux State to Props

At the initial load, there are three props we care about: isInit, isLoading, and isError.

The init prop signals to the component that it is time to begin loading data. While Ajax is waiting for a response, isLoading can show a user-friendly loader in the UI. If there are errors, isError puts the component in an error state.

Therefore, the initial state in Redux can be:

const initialState = {
  firstName: '',
  isInit: false,
  isLoading: false,
  isError: false
};

We appended firstName to the state since it comes back in the response. Because Redux encourages a single store, this response data can live anywhere. For example, a parent component can manage the loading state, while a child component encapsulates response data.

To keep this example simple, we put it in the same hierarchy.

Redux-Thunk

These three props act like control flags. The goal is to support every event that happens during load. To flip each flag, create these action types:

const DUMMY_LOADING_STATE_DATA = 'DUMMY_LOADING_STATE_DATA';
const DUMMY_UPDATE_STATE_DATA = 'DUMMY_UPDATE_STATE_DATA';
const DUMMY_ERROR_STATE_DATA = 'DUMMY_ERROR_STATE_DATA';

Fire dispatch messages across the store by creating action functions with a payload or error:

export const showLoadingState = () => ({type: DUMMY_LOADING_STATE_DATA});
export const updateStateData = (state) => ({type: DUMMY_UPDATE_STATE_DATA, payload: state });
export const errorStateData = (reason) => ({type: DUMMY_ERROR_STATE_DATA, payload: reason, error: true })

Then, put in place the Redux-Thunk that begins the initial load:

const loadInitStateData = () => async (dispatch, getState, axios) => {
  dispatch(showLoadingState());

  try {
    const url = '/person.json';
    const response = await axios.get(url);

    return dispatch(updateStateData(response.data));
  } catch (reason) {
    return dispatch(errorStateData(reason.message));
  }
};

Note the dispatch showLoadingState; this signals the component to wait for response data.

On success, the payload has an Ajax response. On failure, the reason is an error message with an error flag. This is a useful pattern for sending dispatches in Redux.

Dispatch actions that fire across the store have the following properties:

  • type: Required. Message event taking place during load.
  • payload: Optional. Object with response data on success or an error message on failure.
  • error: Optional. A boolean that, when true, says the payload contains an error message.

Finally, all the reducer must do is manage the loading state.

const dummyReducer = (state = initialState, action) => {
  switch (action.type) {
    case DUMMY_LOADING_STATE_DATA:
      return {...state, isLoading: true, isInit: true};

    case DUMMY_UPDATE_STATE_DATA:
      const {firstName} = action.payload;
      return {...state, firstName: firstName, isLoading: false};

    case DUMMY_ERROR_STATE_DATA:
      return {...state, isError: true, isLoading: false};

    default:
      return state;
  }
};

The initialState is set so the component can begin loading. Redux persists in the isInit state once this control flag flips to true, so it knows not to reload. This is because Redux works like a state machine.

The spread …state maintains previous state data in the store. One gotcha is to set isLoading to false on errors. This is to prevent the component from appearing frozen in the UI. Customers might get the wrong idea if components never load and don’t land in a proper error state.

Functional Component

With all the hard work done in the Redux store, the component can focus on a single concern:

const DummyComponent = ({isInit, isError, isLoading, loadInitStateData, firstName}) =>
  <>
    {useEffect(() => {!isInit && loadInitStateData()})}
    {isLoading && (<p>Loading...</p>)}
    {isError && (<p>An error occurred.</p>)}
    {!isLoading && !isError && isInit && (<p>Hello {firstName}.</p>)}
  </>;

Each control flag flips certain pieces of the UI.

The props drive behavior depending on how each is set in the object parameter. This makes the component testable because it follows a functional paradigm.

Each permutation of inputs has a one-to-one relationship with the rendered output. Note the use of useEffect. This Hook runs on re-render, but notice the dispatch is behind a control flag. This gives Redux control so it can do its job: managing state.

Be sure not to wrap Hooks around any conditional logic.

React catches this during rendering and fails to mount the component. A better idea is to put any control flags inside the callback. This is because functional components must execute Hooks at re-render.

Conclusion

Hooks are an exciting new way to work with functional components.

This is one way React components embrace the functional paradigm. With a Redux store, Hooks level up because they can tap into this state machine. By letting Redux manage state, it maintains separation of concerns and clean code.

As a closing note, pay special attention to JavaScript source code protection if you’re developing commercial or enterprise apps. You can do this by starting a free Jscrambler trial, and don’t miss our guide for protecting React apps.