Data fetching - Conditional Logic
Using conditional logic to deal with fetch requests.
Conditional logic#
When data is fetched from components, that might be done either directly inside the component's code (e.g. inside a lifecycle or useEffect hook), or from an external service/hook that does it and returns the data. Let's say our RestaurantSection
component calls a method getRestaurants
that looks somewhat like this:
export const getRestaurants = async () => {
const data = await fetch('https://application-url.com/endpoint')
return data.json()
}
We can add some logic there to conditionally return the data either from the real backend or from mocks, depending on where the component is running. Storybook provides a STORYBOOK
environment variable to help us check whether the code is being run in Storybook or not.
The method would look like this:
export const getRestaurants = async () => {
if (process.env.STORYBOOK) {
// Am I running on Storybook? then just return the mocked data
return mockedData
}
// if not, fetch data from the real backend
const data = await fetch('https://application-url.com/endpoint')
return data.json()
}
By doing this, our component would not request anything from the backend services when calling that method while in Storybook. Instead, it would just receive the mock data. There would be no need to change any code in the component, it would just work!

You could also use the same approach when running your tests, so the mocked data would be returned either if the component is rendering on Storybook or via your tests (for instance process.env.NODE_ENV === 'test'
).
API abstractions#
Some projects have an API layer that is framework-agnostic (the services are just javascript classes), so if they ever were to migrate the project to use any other framework like Vue or Svelte, they would be able to do so with less effort.
To achieve that, you can create a service that has the same methods and signatures as the one that calls the real backend data, but then just return mock data instead. You can use Typescript interfaces to make sure that those APIs follow the same structure. Applying this concept in the previous example, here's what it would look like:
// common interface that acts as a contract for the apis
interface BaseApi {
getRestaurants: () => Promise<Restaurant[]>
}
// real data service, implementing base api
class RestaurantsApi implements BaseApi {
async getRestaurants() {
const data = await fetch('https://application-url.com/endpoint')
return data.json()
}
}
// mocked data service, implementing base api
class MockedRestaurantsApi implements BaseApi {
async getRestaurants() {
return restaurantsMock
}
}
// we want mocked data either on Storybook or tests
const isMockedEnvironment = !!process.env.STORYBOOK || process.env.NODE_ENV === 'test'
// decide wether we want to return a real or mocked service
export const api: BaseApi = isMockedEnvironment
? new MockedRestaurantsApi()
: new RestaurantsApi()
Seeing it in action in our project#
This page is a preview of Storybook for React Apps