How to define a fetch
The defineFetch
API has the following shape:
makeGetPerson.js
import { defineFetch } from 'resift';
const makeGetPerson = defineFetch({
displayName: /* {display name} */,
make: (/* {make params} */) => ({
request: (/* {request params} */) => ({ http }) => {
/* {request body} */
},
}),
});
export default makeGetPerson;
There is:
- the
displayName
, - the
make
function and its parameters/arguments, and - the
request
function and its parameters/arguments.
The following sections will go over how to fill out this shape.
displayName
The The display name should be a human readable string to help you debug. These display names are seen in the Redux Dev Tools and can help you get some information when each fetch is dispatched, finished, or finished with errors.
The suggested naming convention is: {CRUD operation} {Resource name}
e.g.
Get Person
Get People
Update Config
Create Book
NOTE: it's not recommended to include the word "fetch" in your display name because it's redundant in the dev tools.
make
function
The The make
function defines two things:
- how your fetch factory will make fetch instances and
- how your fetch instances will get their data (aka the
request
, discussed later).
Defining how your fetch factory will make fetch instances
You define how your fetch factory will make fetch instances by defining the parameters of the make
function. The parameters of the make function become the parameters of your fetch factory.
const makeGetPerson = defineFetch({
displayName: 'Get Person',
make: personId => () => /* ... */,
}); // π
// π
// π these arguments -> π
// π <- will populate these parameters π
// π
const getPerson = makeGetPerson('some-person-id-123');
πππ
Key idea: Thesemake
parameters are important because they function as the ID of your fetch.
Calling a fetch factory twice with the same ID will result in the same fetch instance. e.g.
const makeGetPerson = defineFetch(/* ... */);
const getPerson123 = makeGetPerson('123');
const alsoGetPerson123 = makeGetPerson('123');
console.log(getPerson123 === alsoGetPerson123); // true
Passing the same fetch into ReSift functions (such as useData
) will have the same result too. e.g.
const makeGetPerson = defineFetch(/* ... */);
const getPerson123 = makeGetPerson('123');
const alsoGetPerson123 = makeGetPerson('123');
const data = useData(getPerson123);
const sameData = useData(alsoGetPerson123);
console.log(data === sameData); // true
Special case: singleton fetches
If your make
function does not have any parameters, (i.e., has no ID), then it's considered to be a singleton fetch.
getConfiguration.js
import { defineFetch } from 'resift';
const makeGetConfiguration = defineFetch({
displayName: 'Get Configuration',
// π no parameters === singleton fetch
make: () => ({ http }) =>
http({
method: 'GET',
route: '/configuration',
}),
});
const getConfiguration = makeGetConfiguration();
export default getConfiguration;
As a good practice, singleton fetch factories should immediately call themselves to produce their singleton fetch (as demonstrated above). This singleton fetch can be exported and then used directly in useData
, useStatus
, and <Guard />
s without having to produce the fetch every time.
request
function
The The request
functionβ¦
const makeGetPerson = defineFetch({
displayName: 'Get Person',
make: (personId) => ({
// πππ that's this thing
request: () => ({ http }) => http(/* */),
// πππ
}),
});
β¦defines how to request data from ReSift's data services.
You define the request
function as a curried function that separates the application of "request arguments" from the application of "data service arguments".
What are request arguments/parameter?
The request arguments/parameters are the parameters to the outer function of the request
function.
import { defineFetch } from 'resift';
const makeUpdatePerson = defineFetch({
displayName: 'Update Person',
make: (personId) => ({
// these πππ
request: (updatedPerson) => ({ http }) =>
// πππ
// are the request parameters
http({
method: 'PUT',
route: `/people/${personId}`,
data: updatedPerson,
}),
}),
});
When you dispatch a request, you must provide the request arguments.
In the above example, updatedPerson
is the request parameter. This means that you must call the fetch instance with an updatedPerson
in order to dispatch a request.
import React from 'react';
import { useDispatch } from 'resift';
import makeUpdatePerson from './makeUpdatePerson';
const getPersonFromForm = e => /* ... */;
function ExamplePersonForm({ personId }) {
const dispatch = useDispatch();
const updatePerson = makeUpdatePerson(personId);
const handleSubmit = e => {
const updatedPerson = getPersonFromForm(e);
// In order to create a request to dispatch, `updatedPerson`
// must be passed here because it's a required request argument.
// πππ
dispatch(updatePerson(updatedPerson));
// πππ
};
return <form onSubmit={handleSubmit}>{/* ... */}</form>;
}
export default ExamplePersonForm;
What are data service parameters?
The inner set of parameters in the curried request
function is the data service parameter.
const makeUpdatePerson = defineFetch({
displayName: 'Update Person',
make: (personId) => ({
// this πππ
request: (updatedPerson) => ({ http }) =>
// πππ
// is the data service parameter
http({
method: 'PUT',
route: `/people/${personId}`,
data: updatedPerson,
}),
}),
});
The data service parameter is an object that you can destructure to "pick off" a data service. This data service can then be used to make data calls to your backend.
Hang tight for now! There is an in-depth tutorial on data services in a later doc that details this more. For now, just know what the data service argument is.
request
function body
The The request
function body is the rest of the request
. It is where you use the data services you've picked off and make requests to your backend.
const makeGetPerson = defineFetch({
displayName: 'Get Person',
make: (personId) => ({
request: () => async ({ http }) => {
// πππ this is the request body
const person = await http({
method: 'GET',
route: `/people/${personId}`,
});
return person;
// πππ
},
}),
});
The request body should return a Promise
. ReSift will await
this promise and store the result.
Placing async
before the request body allows you to await
data services. In fact, you may call these services more than once within one ReSift request.
const makeGetResource = defineFetch({
displayName: 'Get Resource',
make: (id) => ({
request: (requestArg) => async ({ http }) => {
const dataResultOne = await http({
method: 'GET',
route: '/one',
});
const dataResultTwo = await http({
method: 'GET',
route: `/two/${dataResultOne.id}`,
});
return dataResultTwo;
},
}),
});
Good practices
Suggested naming and file conventions
- The suggested convention is to create a separate file for each fetch factory.
- If the fetch factory is a singleton fetch, then immediately invoke the singleton fetch factory and then
export default
the resulting fetch. - Otherwise,
export default
the fetch factory.
Then the file name should follow these rules in order:
- If your fetch factory can produce multiple fetch instances, then prefix the name with
make
. Otherwise, if your fetch is a singleton fetch, you may omit this prefix. - Add the CRUD operation to that corresponds with your fetch. We suggest using
Create
,Get
,Update
, andDelete
. - Lastly, add the name of the resource in consideration (e.g.
Person
,Books
) considering whether or not the resource is plural or not. (e.g.makeGetPerson
vsgetPeople
)
Examples:
makeUpdatePerson.js
β non-singleton fetch,PUT
request, that updates one personmakeGetPerson.js
β non-singleton fetch,GET
request, the gets ones persongetConfiguration.js
β singleton fetch,GET
request for the single configupdatePeople.js
β singleton fetch,PUT
request for a collection of people