diff --git a/__tests__/internals/config/checkActionsConfig.test.js b/__tests__/internals/config/checkActionsConfig.test.js
index b67057bea52c6595aa3d50e9307cf8b705c95902..c436cf9261448dbad64a975075d76fb16c8cb035 100644
--- a/__tests__/internals/config/checkActionsConfig.test.js
+++ b/__tests__/internals/config/checkActionsConfig.test.js
@@ -114,6 +114,26 @@ describe('checkActionsConfig', () => {
     expect(() => checkActionsConfig(RESOURCE_NAME, validConfig2)).not.toThrow();
   });
 
+  test('invalid cacheHint', () => {
+    const invalidConfig = {
+      eat: {
+        ...VALID_ACTION_CONFIG_BASE,
+        cacheHint: '',
+      },
+    };
+
+    expect(() => checkActionsConfig(RESOURCE_NAME, invalidConfig)).toThrow();
+
+    const validConfig = {
+      eat: {
+        ...VALID_ACTION_CONFIG_BASE,
+        cacheHint: () => ({ cache: 'hint' }),
+      },
+    };
+
+    expect(() => checkActionsConfig(RESOURCE_NAME, validConfig)).not.toThrow();
+  });
+
   test('invalid beforeHook', () => {
     const invalidConfig = {
       eat: {
@@ -193,4 +213,37 @@ describe('checkActionsConfig', () => {
 
     expect(() => checkActionsConfig(RESOURCE_NAME, validConfig)).not.toThrow();
   });
+
+  test('invalid networkHelpers', () => {
+    const invalidConfig1 = {
+      eat: {
+        ...VALID_ACTION_CONFIG_BASE,
+        networkHelpers: '',
+      },
+    };
+
+    expect(() => checkActionsConfig(RESOURCE_NAME, invalidConfig1)).toThrow();
+
+    const invalidConfig2 = {
+      eat: {
+        ...VALID_ACTION_CONFIG_BASE,
+        networkHelpers: {
+          getToken: 'customToken',
+        },
+      },
+    };
+
+    expect(() => checkActionsConfig(RESOURCE_NAME, invalidConfig2)).toThrow();
+
+    const validConfig = {
+      eat: {
+        ...VALID_ACTION_CONFIG_BASE,
+        networkHelpers: {
+          getToken: () => 'customToken',
+        },
+      },
+    };
+
+    expect(() => checkActionsConfig(RESOURCE_NAME, validConfig)).not.toThrow();
+  });
 });
diff --git a/__tests__/internals/selectors/generateResourceSelectors.test.js b/__tests__/internals/selectors/generateResourceSelectors.test.js
index bfedf5f52119721ae4ffda291152404b1d34c72a..ffd828736c7f98a695bec385866baee3b9c9af01 100644
--- a/__tests__/internals/selectors/generateResourceSelectors.test.js
+++ b/__tests__/internals/selectors/generateResourceSelectors.test.js
@@ -6,6 +6,20 @@ const {
   resource: { getResource, getResourceById },
 } = generateResourceSelectors('fruits');
 
+const denormalizer = (resourceIds, { fruits, colors } = {}) =>
+  fruits
+    ? Object.values(fruits).map(fruit => ({
+        ...fruit,
+        color: colors[fruit.color],
+      }))
+    : [];
+const {
+  resource: {
+    getResource: getResourceWithDenormalizer,
+    getResourceById: getResourceByIdWithDenormalizer,
+  },
+} = generateResourceSelectors('fruits', denormalizer);
+
 const STARTED_AT = moment();
 const ENDED_AT = moment().add(1, 'seconds');
 
@@ -155,6 +169,71 @@ const FAILED_RESOURCE_ID_STATE = {
   },
 };
 
+const RECEIVED_FULL_RESOURCE_TO_DENORMALIZE_STATE = {
+  restEasy: {
+    requests: {
+      'eat:https://api.co/fruits': {
+        resourceName: 'fruits',
+        resourceId: null,
+        startedAt: STARTED_AT,
+        endedAt: ENDED_AT,
+        hasSucceeded: true,
+        hasFailed: false,
+        payloadIds: {
+          fruits: [1, 2],
+          colors: [1, 2],
+        },
+      },
+    },
+    resources: {
+      fruits: {
+        1: {
+          name: 'banana',
+          color: '1',
+        },
+        2: {
+          name: 'cherry',
+          color: '2',
+        },
+      },
+      colors: {
+        1: 'yellow',
+        2: 'red',
+      },
+    },
+  },
+};
+
+const RECEIVED_FULL_RESOURCE_ID_TO_DENORMALIZE_STATE = {
+  restEasy: {
+    requests: {
+      'eat:https://api.co/fruits/2': {
+        resourceName: 'fruits',
+        resourceId: 2,
+        startedAt: STARTED_AT,
+        endedAt: ENDED_AT,
+        hasSucceeded: true,
+        hasFailed: false,
+        payloadIds: {
+          fruits: [2],
+          colors: [2],
+        },
+      },
+    },
+    resources: {
+      fruits: {
+        2: {
+          name: 'cherry',
+          color: '2',
+        },
+      },
+      colors: {
+        2: 'red',
+      },
+    },
+  },
+};
+
 describe('generateResourceSelectors', () => {
   describe('getResource', () => {
     const emptyCase = state => () => {
@@ -230,4 +309,82 @@ describe('generateResourceSelectors', () => {
     );
     test('failed resource id state', emptyCase(FAILED_RESOURCE_ID_STATE, 2));
   });
+
+  describe('getResource with denormalizer', () => {
+    const emptyCase = state => () => {
+      const result = getResourceWithDenormalizer(state);
+
+      expect(result.length).toBe(0);
+
+      const sameResult = getResourceWithDenormalizer(state);
+
+      expect(result).toBe(sameResult);
+    };
+
+    const fullCase = state => () => {
+      const result = getResourceWithDenormalizer(state);
+
+      expect(result.length).toBe(2);
+      expect(result[0].color).toBe(state.restEasy.resources.colors['1']);
+      expect(result[1].color).toBe(state.restEasy.resources.colors['2']);
+
+      const sameResult = getResourceWithDenormalizer(state);
+
+      expect(result).toBe(sameResult);
+    };
+
+    test('empty state', emptyCase(EMPTY_STATE));
+    test('requested resource state', emptyCase(REQUESTED_RESOURCE_STATE));
+    test(
+      'received empty resource state',
+      emptyCase(RECEIVED_EMPTY_RESOURCE_STATE),
+    );
+    test(
+      'received full resource state',
+      fullCase(RECEIVED_FULL_RESOURCE_TO_DENORMALIZE_STATE),
+    );
+    test('failed resource state', emptyCase(FAILED_RESOURCE_STATE));
+  });
+
+  describe('getResourceById with denormalizer', () => {
+    const emptyCase = (state, id) => () => {
+      expect(getResourceByIdWithDenormalizer(state, id)).toBeNull();
+    };
+
+    const fullCase = (state, id) => () => {
+      const result = getResourceByIdWithDenormalizer(state, id);
+
+      expect(result.color).toBe(
+        state.restEasy.resources.colors[
+          state.restEasy.resources.fruits[id].color
+        ],
+      );
+    };
+
+    test('empty state', emptyCase(EMPTY_STATE, 2));
+    test('requested resource state', emptyCase(REQUESTED_RESOURCE_STATE, 2));
+    test(
+      'received empty resource state',
+      emptyCase(RECEIVED_EMPTY_RESOURCE_STATE, 2),
+    );
+    test(
+      'received full resource state',
+      fullCase(RECEIVED_FULL_RESOURCE_TO_DENORMALIZE_STATE, 2),
+    );
+    test('failed resource state', emptyCase(FAILED_RESOURCE_STATE, 2));
+
+    test(
+      'requested resource id state',
+      emptyCase(REQUESTED_RESOURCE_ID_STATE, 2),
+    );
+    test(
+      'received empty resource id state',
+      emptyCase(RECEIVED_EMPTY_RESOURCE_ID_STATE, 2),
+    );
+    test(
+      'received full resource id state',
+      fullCase(RECEIVED_FULL_RESOURCE_ID_TO_DENORMALIZE_STATE, 2),
+    );
+    test('failed resource id state', emptyCase(FAILED_RESOURCE_ID_STATE, 2));
+  });
 });
diff --git a/__tests__/internals/utils/resolversHashes.test.js b/__tests__/internals/utils/resolversHashes.test.js
index 5799ad4abff0c66ad039b1341ec5da6844f8507e..f4c8aebd0b3eb891fc5de609df87f179efc3b560 100644
--- a/__tests__/internals/utils/resolversHashes.test.js
+++ b/__tests__/internals/utils/resolversHashes.test.js
@@ -58,18 +58,26 @@ const FILLED_STATE_COMPUTED_HASHES = {
     PRINCIPAL_RESOURCE_IDS,
   ),
 };
+const EMPTY_STATE_RESET_HASHES = {
+  ...EMPTY_STATE,
+  resolversHashes: resetResourceResolversHashes(
+    EMPTY_STATE_COMPUTED_HASHES,
+    RESOURCE_NAME,
+  ),
+};
 const FILLED_STATE_RESET_HASHES = {
   ...FILLED_STATE,
-  resolversHashes: resetResourceResolversHashes(FILLED_STATE, RESOURCE_NAME),
+  resolversHashes: resetResourceResolversHashes(
+    FILLED_STATE_COMPUTED_HASHES,
+    RESOURCE_NAME,
+  ),
 };
 
 describe('computeNewResolversHashes', () => {
   test('empty state', () => {
     const hashBeforeComputing = getResourcesHash(EMPTY_STATE);
 
-    const hashAfterComputing = getResourcesHash(
-      EMPTY_STATE_COMPUTED_HASHES.resolversHashes,
-    );
+    const hashAfterComputing = getResourcesHash(EMPTY_STATE_COMPUTED_HASHES);
 
     expect(hashBeforeComputing).not.toBe(hashAfterComputing);
   });
@@ -77,28 +85,40 @@ describe('computeNewResolversHashes', () => {
   test('filled state', () => {
     const hashBeforeComputing = getResourcesHash(FILLED_STATE);
 
-    const hashAfterComputing = getResourcesHash(
-      FILLED_STATE_COMPUTED_HASHES.resolversHashes,
-    );
+    const hashAfterComputing = getResourcesHash(FILLED_STATE_COMPUTED_HASHES);
 
     expect(hashBeforeComputing).not.toBe(hashAfterComputing);
   });
 });
 
 describe('resetResourceResolversHashes', () => {
-  test('only path', () => {
-    const hashBeforeComputing = getResourceHash(
-      FILLED_STATE.resolversHashes,
+  test('empty state', () => {
+    const hashBeforeComputing = getResourceHash(EMPTY_STATE, RESOURCE_NAME);
+
+    const hashAfterComputing = getResourceHash(
+      EMPTY_STATE_COMPUTED_HASHES,
+      RESOURCE_NAME,
+    );
+
+    const hashAfterResetting = getResourceHash(
+      EMPTY_STATE_RESET_HASHES,
       RESOURCE_NAME,
     );
 
+    expect(hashBeforeComputing).toBe(hashAfterComputing);
+    expect(hashBeforeComputing).toBe(hashAfterResetting);
+  });
+
+  test('filled state', () => {
+    const hashBeforeComputing = getResourceHash(FILLED_STATE, RESOURCE_NAME);
+
     const hashAfterComputing = getResourceHash(
-      FILLED_STATE_COMPUTED_HASHES.resolversHashes,
+      FILLED_STATE_COMPUTED_HASHES,
       RESOURCE_NAME,
     );
 
     const hashAfterResetting = getResourceHash(
-      FILLED_STATE_RESET_HASHES.resolversHashes,
+      FILLED_STATE_RESET_HASHES,
       RESOURCE_NAME,
     );
 
@@ -123,34 +143,27 @@ describe('getPayloadIdsHash', () => {
   });
 
   test('no normalizedURL', () => {
-    expect(getPayloadIdsHash(EMPTY_STATE_COMPUTED_HASHES.resolversHashes)).toBe(
+    expect(getPayloadIdsHash(EMPTY_STATE_COMPUTED_HASHES)).toBe(
       getEmptyResourceHash(),
     );
   });
 
   test('no resourceName', () => {
-    expect(
-      getPayloadIdsHash(
-        EMPTY_STATE_COMPUTED_HASHES.resolversHashes,
-        NORMALIZED_URL,
-      ),
-    ).toBe(getEmptyResourceHash());
+    expect(getPayloadIdsHash(EMPTY_STATE_COMPUTED_HASHES, NORMALIZED_URL)).toBe(
+      getEmptyResourceHash(),
+    );
   });
 
   test('without computing first', () => {
-    expect(
-      getPayloadIdsHash(
-        FILLED_STATE.resolversHashes,
-        NORMALIZED_URL,
-        RESOURCE_NAME,
-      ),
-    ).toBe(getEmptyResourceHash());
+    expect(getPayloadIdsHash(FILLED_STATE, NORMALIZED_URL, RESOURCE_NAME)).toBe(
+      getEmptyResourceHash(),
+    );
   });
 
   test('after computing', () => {
     expect(
       getPayloadIdsHash(
-        FILLED_STATE_COMPUTED_HASHES.resolversHashes,
+        FILLED_STATE_COMPUTED_HASHES,
         NORMALIZED_URL,
         RESOURCE_NAME,
       ),
@@ -168,15 +181,11 @@ describe('getResourcesHash', () => {
   });
 
   test('without computing first', () => {
-    expect(getResourcesHash(FILLED_STATE.resolversHashes)).toBe(
-      getEmptyResourceHash(),
-    );
+    expect(getResourcesHash(FILLED_STATE)).toBe(getEmptyResourceHash());
   });
 
   test('after computing', () => {
-    expect(
-      getResourcesHash(FILLED_STATE_COMPUTED_HASHES.resolversHashes),
-    ).toMatchSnapshot();
+    expect(getResourcesHash(FILLED_STATE_COMPUTED_HASHES)).toMatchSnapshot();
   });
 });
 
@@ -190,23 +199,20 @@ describe('getResourceHash', () => {
   });
 
   test('no resourceName', () => {
-    expect(getResourceHash(FILLED_STATE_COMPUTED_HASHES.resolversHashes)).toBe(
+    expect(getResourceHash(FILLED_STATE_COMPUTED_HASHES)).toBe(
       getEmptyResourceHash(),
     );
   });
 
   test('without computing first', () => {
-    expect(getResourceHash(FILLED_STATE.resolversHashes, RESOURCE_NAME)).toBe(
+    expect(getResourceHash(FILLED_STATE, RESOURCE_NAME)).toBe(
       getEmptyResourceHash(),
     );
   });
 
   test('after computing', () => {
     expect(
-      getResourceHash(
-        FILLED_STATE_COMPUTED_HASHES.resolversHashes,
-        RESOURCE_NAME,
-      ),
+      getResourceHash(FILLED_STATE_COMPUTED_HASHES, RESOURCE_NAME),
     ).toMatchSnapshot();
   });
 });
diff --git a/docs/api/createResource/actionsConfig.md b/docs/api/createResource/actionsConfig.md
index 0dc1b49c12a365c287b02437ff0dbfb389653694..fb99f1526816ad57d4d5bb833368e075b40bc70b 100644
--- a/docs/api/createResource/actionsConfig.md
+++ b/docs/api/createResource/actionsConfig.md
@@ -24,7 +24,8 @@ const actionsConfig = {
     method: 'GET',
     url: 'https://api.co/users/:userType/::userId/infos',
     // Optional
-    beforeHook: (body, query, otherArgs, dispatch) =>
+    cacheHint: (urlParams, query, body, otherArgs) => otherArgs.language,
+    beforeHook: (urlParams, query, body, otherArgs, dispatch) =>
       console.log(
         'User infos retrieved with query: ',
         query,
diff --git a/jest.config.js b/jest.config.js
index 9317c55f0231507e2d583b6e12c76dbd9dbbc24c..e2abf6dd66979916d130370101c3b671ed02ed2f 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -5,10 +5,10 @@ const configBase = {
   coverageDirectory: path.join(__dirname, 'coverage'),
   coverageThreshold: {
     global: {
-      branches: 80,
-      functions: 80,
-      lines: 79,
       statements: 80,
+      branches: 80,
+      functions: 85,
+      lines: 80,
     },
   },
   moduleDirectories: ['node_modules'],
diff --git a/src/internals/selectors/generateActionSelectors.js b/src/internals/selectors/generateActionSelectors.js
index 262a63f60f6df6d7f88ce11b6ae3eaeda746b6ea..85b94f687367a9c1546ca4c620d4bba2c98f9ea7 100644
--- a/src/internals/selectors/generateActionSelectors.js
+++ b/src/internals/selectors/generateActionSelectors.js
@@ -92,8 +92,6 @@ const payloadIdsSelector = (state, resourceName, normalizedURL) =>
     ? state.requests[normalizedURL].payloadIds[resourceName]
     : null;
 
-const resolversHashesSelector = state => state.resolversHashes;
-
 const applyDenormalizerSelector = (
   state,
   resourceName,
@@ -134,25 +132,24 @@ const getRequestResourceResolver = (
 ) => {
   const resource = resourceSelector(state, resourceName);
   const payloadIds = payloadIdsSelector(state, resourceName, normalizedURL);
-  const resolversHashes = resolversHashesSelector(state);
 
   if (resource && payloadIds) {
-    return !applyDenormalizer || !denormalizer
-      ? `${applyDenormalizer}-${getPayloadIdsHash(
-          resolversHashes,
+    return !(applyDenormalizer && denormalizer)
+      ? `${!!(applyDenormalizer && denormalizer)}-${getPayloadIdsHash(
+          state,
           normalizedURL,
           resourceName,
-        )}-${getResourceHash(resolversHashes, resourceName)}`
-      : `${applyDenormalizer}-${Object.keys(
+        )}-${getResourceHash(state, resourceName)}`
+      : `${!!(applyDenormalizer && denormalizer)}-${Object.keys(
           state.requests[normalizedURL].payloadIds,
         )
           .map(
             resourceKey =>
               `${getPayloadIdsHash(
-                resolversHashes,
+                state,
                 normalizedURL,
                 resourceKey,
-              )}-${getResourceHash(resolversHashes, resourceKey)}`,
+              )}-${getResourceHash(state, resourceKey)}`,
           )
           .join('--')}`;
   }
diff --git a/src/internals/selectors/generateResourceSelectors.js b/src/internals/selectors/generateResourceSelectors.js
index e25ce542def86fb00638688769f9ecdae50643b0..806e9504be794cfe7aff046237be785f695044ba 100644
--- a/src/internals/selectors/generateResourceSelectors.js
+++ b/src/internals/selectors/generateResourceSelectors.js
@@ -18,8 +18,6 @@ const resourceSelector = (state, resourceName) =>
     ? state.resources[resourceName]
     : null;
 
-const resolversHashesSelector = state => state.resolversHashes;
-
 const applyDenormalizerSelector = (state, resourceName, applyDenormalizer) =>
   applyDenormalizer;
 
@@ -52,12 +50,14 @@ const getResourceResolver = (
   denormalizer,
 ) => {
   const resource = resourceSelector(state, resourceName);
-  const resolversHashes = resolversHashesSelector(state);
 
   if (resource) {
-    return !applyDenormalizer || !denormalizer
-      ? `${applyDenormalizer}-${getResourceHash(resolversHashes, resourceName)}`
-      : `${applyDenormalizer}-${getResourcesHash(resolversHashes)}`;
+    return !(applyDenormalizer && denormalizer)
+      ? `${!!(applyDenormalizer && denormalizer)}-${getResourceHash(
+          state,
+          resourceName,
+        )}`
+      : `${!!(applyDenormalizer && denormalizer)}-${getResourcesHash(state)}`;
   }
 
   return getEmptyResourceHash();
@@ -78,15 +78,31 @@ const getResourceById = (
   applyDenormalizer,
   denormalizer,
 ) => {
-  if (!applyDenormalizer || !denormalizer) {
-    return state.resources
-      && state.resources[resourceName]
-      && state.resources[resourceName][resourceId]
+  const resource
+    = state.resources
+    && state.resources[resourceName]
+    && state.resources[resourceName][resourceId]
       ? state.resources[resourceName][resourceId]
       : EMPTY_RESOURCE_ID;
+
+  if (!applyDenormalizer || !denormalizer || !resource) {
+    return resource;
   }
 
-  return denormalizer([resourceId], state.resources)[0] || EMPTY_RESOURCE_ID;
+  const resources = Object.entries(state.resources).reduce(
+    (prev, [name, value]) => ({
+      ...prev,
+      [name]:
+        name === resourceName
+          ? {
+              [resourceId]: resource,
+            }
+          : value,
+    }),
+    {},
+  );
+
+  return denormalizer([resourceId], resources)[0] || EMPTY_RESOURCE_ID;
 };
 
 const generateResourceSelectors = (resourceName, denormalizer) => ({
diff --git a/src/internals/utils/resolversHashes.js b/src/internals/utils/resolversHashes.js
index f4a4feb0e8e242b91620b2bde25624b7389c5116..bcb9c63191a06b276781811474250e2804615a26 100644
--- a/src/internals/utils/resolversHashes.js
+++ b/src/internals/utils/resolversHashes.js
@@ -70,29 +70,24 @@ export const resetResourceResolversHashes = (
 export const getEmptyResourceHash = () => EMPTY_HASH;
 
 export const getPayloadIdsHash = (
-  resolversHashes,
+  { resolversHashes = {} } = {},
   normalizedURL,
   resourceName,
 ) =>
-  resolversHashes
-  && resolversHashes.requests
+  resolversHashes.requests
   && resolversHashes.requests[normalizedURL]
   && resolversHashes.requests[normalizedURL][resourceName]
     ? resolversHashes.requests[normalizedURL][resourceName]
     : EMPTY_HASH;
 
 /* eslint-disable no-underscore-dangle */
-export const getResourcesHash = resolversHashes =>
-  resolversHashes
-  && resolversHashes.resources
-  && resolversHashes.resources._getResourcesHash
+export const getResourcesHash = ({ resolversHashes = {} } = {}) =>
+  resolversHashes.resources && resolversHashes.resources._getResourcesHash
     ? resolversHashes.resources._getResourcesHash()
     : EMPTY_HASH;
 /* eslint-enable no-underscore-dangle */
 
-export const getResourceHash = (resolversHashes, resourceName) =>
-  resolversHashes
-  && resolversHashes.resources
-  && resolversHashes.resources[resourceName]
+export const getResourceHash = ({ resolversHashes = {} } = {}, resourceName) =>
+  resolversHashes.resources && resolversHashes.resources[resourceName]
     ? resolversHashes.resources[resourceName]
     : EMPTY_HASH;