Easily integrate Algolia into native apps with FlutterFlow
Algolia's advanced search capabilities pair seamlessly with iOS or Android Apps when using FlutterFlow. App development and search design ...
Sr. Developer Relations Engineer
Algolia's advanced search capabilities pair seamlessly with iOS or Android Apps when using FlutterFlow. App development and search design ...
Sr. Developer Relations Engineer
In the midst of the Black Friday shopping frenzy, Algolia soared to new heights, setting new records and delivering an ...
Chief Executive Officer and Board Member at Algolia
When was your last online shopping trip, and how did it go? For consumers, it’s becoming arguably tougher to ...
Senior Digital Marketing Manager, SEO
Have you put your blood, sweat, and tears into perfecting your online store, only to see your conversion rates stuck ...
Senior Digital Marketing Manager, SEO
“Hello, how can I help you today?” This has to be the most tired, but nevertheless tried-and-true ...
Search and Discovery writer
We are proud to announce that Algolia was named a leader in the IDC Marketscape in the Worldwide General-Purpose ...
VP Corporate Marketing
Twice a year, B2B Online brings together America’s leading manufacturers and distributors to uncover learnings and industry trends. This ...
Director, Sales Enablement & B2B Practice Leader
Generative AI and large language models (LLMs). These two cutting-edge AI technologies sound like totally different, incomparable things. One ...
Search and Discovery writer
ChatGPT, Bing, Bard, YouChat, DALL-E, Jasper…chances are good you’re leveraging some version of generative artificial intelligence on ...
Search and Discovery writer
Your users are spoiled. They’re used to Google’s refined and convenient search interface, so they have high expectations ...
Technical Writer
Imagine if, as your final exam for a computer science class, you had to create a real-world large language ...
Sr. SEO Web Digital Marketing Manager
What do you think of the OpenAI ChatGPT app and AI language models? There’s lots going on: GPT-3 ...
Search and Discovery writer
In the fast-paced and dynamic realm of digital merchandising, being reactive to customer trends has been the norm. In ...
Staff User Researcher
You’re at a dinner party when the conversation takes a computer-science-y turn. Have you tried ChatGPT? What ...
Sr. SEO Web Digital Marketing Manager
It’s the era of Big Data, and super-sized language models are the latest stars. When it comes to ...
Search and Discovery writer
Did you know that 86% of the global population uses a smartphone? The 7 billion devices connected to the Internet ...
Staff SME Business & Optimization - UI/UX
The Cloud Native Foundation is known for being the organization behind Kubernetes and many other Cloud Native tools. To foster ...
TL;DR Revamp your technical documentation search experience with DocSearch! Previously only available to open-source projects, we're excited ...
Senior Engineering Manager
At Algolia, we do a lot of front-end JavaScript, because for us UX and UI are key components of a great search – we want the search experience to be perfect across the entire stack.
Recently, we’ve been playing quite a lot with React in our projects, especially in Instantsearch.js, an open source library for building complex search UI widgets. So far our experience has been very positive; it was so good that we eventually decided to introduce React onto our dashboard, along with Redux to handle shared state across components.
We’re really happy with the benefits that both have brought to our stack; in particular, we found that Redux brought quite a few cool things to the table:
If you are not familiar with React or Redux as yet, there are many great resources available online for both.
React and Redux give you great powers, but also great responsibilities ! You are free to handle many things exactly the way you want. However, to really harness their combined potential, we’ve found that setting up and enforcing conventions is crucial.
We’re using the following fractal directory structure. Each page is an almost self contained app, asynchronously loading its own subpages.
- routes
- page1
- actions
- components
- containers
- routes
- subpage1
...
There’s nothing particularly controversial here, with the exception of one convention we’ve chosen to impose: To collocate Redux action creators and reducers in the same files, following the Ducks proposal. Our “actions” files look something like this:
export default function reducer(state, action) { ... };
export function actionCreator1() { ... }
export function actionCreator2() { ... }
This allows us to use default imports for the purpose of creating the combined reducer when creating the store, while still being able to used named imports in containers to import just the actions that are needed.
We found that whenever we were writing reducers and action creators, we were often writing duplicate action creators and reducers, doing little more than updating a subset of the reducer’s state, for instance:
const initialState = {value: false};
export default function reducer(state = initialState, action) {
switch(action.type) {
case TOGGLE_VALUE:
return {...state, value: payload};
break;
}
};
export function toggleValue(value) {
return {type: TOGGLE_VALUE, payload: value};
}
Strictly speaking, action creators are not required since components can directly dispatch actions by themselves. However, we found that working with the more abstract action creators, allowed us to write more modular components, which would literally need no knowledge of how the reducers work or which action types are used. Instead, we simply need to pass them data and (wrapped) action creators.
Therefore, we looked into how we could simplify the reducer side of things, instead of the action creators. After a few iterations, we ended up with redux-updeep, a strongly opinionated createReducer implementation which assumes that the majority of the actions will result in deep updates of the reducer’s state. It allowed us to write the following code:
const initialState = {value: false};
const namespace = 'REDUCER_NAME';
export default function createReducer(namespace, initialState);
// That's all you need !
export function toggleValue(value) {
return {type: `${NAMESPACE}/TOGGLE`, payload: {value}};
}
How does it work ? As mentioned before, it handles all unspecified action types in the same way, using updeep to immutably merge the action’s payload into the reducer’s current state. While it is still possible to explicitly specify action types and reducers, we still haven’t felt the need to do so!
Granted, when using redux-updeep, it becomes more complicated to compose reducers, which is idiomatic Redux. However, we’ve made it such that it is very easy to write factories that allow us to parameterize the action type’s namespaces as well as the path at which the update is merged:
export function createToggleValue(namespace, path = []) {
return {
toggleValue(value) {
return {
type: `${namespace}/TOGGLE`,
payload: {value},
path
};
}
};
}
Then, it becomes possible to use the initial action creator deeper into a reducer state:
const initialState = {
my: {
deep: {
value: false
}
}
}
const toggleValue = createToggleValue('OTHER_REDUCER', ['my', 'deep']);
export function toggleDeepValue(value) {
return toggleValue(value);
}
We’re pretty happy with our current set up, so we thought that we would share it with the world. In fact, we’ve just open sourced it ! Find it on GitHub.
By default, Redux does not care how you handle asynchronous actions. The redux-thunk middleware is available but enforces no rules: it simply enables you to dispatch multiple actions from a single action creator. Using it, you can for instance easily dispatch loading/success/error actions in response to a Promise:
const initialState = {
data: {},
isPending: false,
isError: false
};
export default createReducer('REDUCER_NAME', initialState);
export default loadData() {
return (dispatch) => {
dispatch({
type: 'REDUCER_NAME/LOADING',
payload: {isPending: true, isError: false}
});
get('/data').then(
(data) => dispatch({
type: 'REDUCER_NAME/SUCCESS',
payload: {data, isPending: false}
}),
(err) => dispatch({
type: 'REDUCER_NAME/ERROR',
payload: {isPending: false, isError: true}
});
);
}
}
This is a great start, but the code still relies on a lot of boilerplate. If you have as many simultaneous asynchronous actions as we do, and want to handle them all in the same way (e.g. how to keep track of the pending state, how to handle errors), it rapidly becomes a tedious task.
There is a lot of middlewares in the Redux ecosystem designed to handle asynchronous actions and/or promises, but we couldn’t find one that would handle a few conventions that we had initially defined for handling asynchronous actions and asynchronous data in the components:
So we decided to create one, the redux-magic-async-middleware, and we’ve just open sourced it! It is optimised to work with redux-updeep, and enables dispatching promises in payloads like synchronous values:
export function loadData() {
return {
type: 'REDUCER_NAME',
payload: {
value: get('/data')
}
}
}
The middleware will automatically extract the promises from the payload, and replaces them with eventual values. When a promise is resolved, it will trigger a success action which will update the resolved data into the reducer states, thus resolving the eventual value.
In combination with this, we have created the eventual-values library, very much inspired by another eventual values library. This library is extremely simple, but allows us to encapsulate and abstract the behaviour that we desired around asynchronous values. It allows to you write code like this:
function isReady(value) { ... };
function MyComponent({reducerState}) {
if (isReady(reducerState.data)) {
return <h1>Loading</h1>;
} else {
return <span>data.name</span>;
}
}
We’re still experimenting with those concepts and tools, and are gradually introducing flow typing in our codebase. Watch this space for more updates on how we use React, Redux and JavaScript in general!
Powered by Algolia Recommend