Async Actions
Most complex apps rely heavily on fetching data asynchronously. In this document, we present techniques used in Redux to handle an asynchronous data flow.
Introduction to Middleware and redux-thunk
When using an API, you are probably dealing with asynchronous actions. However, the Redux store only supports synchronous actions without using middleware (more on that later). To use middleware in Redux, we use the applyMiddleware()
store enhancer from Redux. redux-thunk
middleware is the standard way to handle asynchronous actions. If you are using Redux Toolkit, you don’t need to install redux-thunk
and call applyMiddleware()
directly since Redux Toolkit provides configureStore()
which includes redux-thunk
middleware by default.
We use redux-thunk
middleware to enable asynchronous requests to work with synchronous action creators. It allows an action creator to return a function instead of an object (action) and executes that function when it is returned. This allows non-pure actions (i.e. ones that can call APIs that might have different data each time). These action creators can dispatch other actions, so, for example, you can dispatch a REQUEST_BEGIN
action, then fetch remote data asynchronously and, after it returns, dispatch the REQUEST_SUCCESS
or REQUEST_ERROR
actions.
For example, you can create an async incrementer as follows:
const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
function increment() {
return {
type: INCREMENT_COUNTER
};
}
function incrementAsync() {
return dispatch => {
setTimeout(() => {
dispatch(increment());
}, 1000);
};
}
LS2Request Example
A combination of redux-thunk
and LS2Request
allows us to fetch and display data in a React component. LS2Request
is a wrapper component for WebOSServiceBridge
and is available from @enact/webos/LS2Request
. The following example shows a simple fetch routine.
At the root level, we use <Provider />
to pass store down the component hierarchy.
import {createRoot} from 'react-dom/client';
import {Provider} from 'react-redux';
import App from './App';
import store from './store';
let appElement = () => (
<Provider store={store}>
<App />
</Provider>
);
createRoot(document.getElementById('root')).render(appElement);
Store is configured to accept thunk middleware by configureStore()
from Redux Toolkit.
import {configureStore} from '@reduxjs/toolkit';
import rootSlice from '../reducers';
const initialState = {};
const store = configureStore({
reducer: rootSlice.reducer,
initialState
});
export default store;
Here we create a thunk action creator which returns a function instead of a plain object. It is also possible to dispatch an action or request at the beginning.
import LS2Request from '@enact/webos/LS2Request';
// function returning function!
export const getSystemSettings = params => dispatch => {
// possible to dispatch an action at the start of fetching
// dispatch({type: 'FETCH_SYSETEM_SETTINGS'});
return new LS2Request().send({
service: 'luna://com.webos.settingsservice/',
method: 'getSystemSettings',
parameters: params,
onSuccess: (res) => {
// dispatches action on success callback with payload
dispatch(receiveSystemSettings(res));
}
});
};
Reducer receives a payload and creates a new state.
import {configureStore} from '@reduxjs/toolkit';
const rootSlice = createSlice({
name: 'systemReducer',
initialState: {},
reducers: {
receiveSystemSettings: (state, action) => {
return Object.assign({}, state, action.payload.settings);
},
updateSystemSettings: (state, action) => {
return Object.assign({}, state, action.payload.settings);
}
}
});
export const {receiveSystemSettings, updateSystemSettings} = rootSlice.actions;
export default rootSlice;
Component dispatches getSystemSettings
on component mount and renders a pictureMode
prop that’s been got from a redux store.
import {useDispatch, useSelector} from 'react-redux';
import {getSystemSettings} from '../reducers';
const App = () => {
const pictureMode = useSelector(store => store.pictureMode);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getSystemSettings({
category: 'picture',
key: 'pictureMode',
subscribe: true
}));
}, []);
return <p>{pictureMode}</p>;
}
export default App;