collection/index.js

import React from "react";
import useReducer from "../useReducer";
import defaultInitialState from "./initialState";
import defaultResourceInitialState from "./resource/initialState";
import * as actions from "../actions";
import * as resourceActions from "./resource/actions";
import makeContext from "../makeContext";
import makeGetInitialState from "../makeGetInitialState";
import makeReducer from "./makeReducer";
import makeUseCollection from "../makeUseCollection";
import makeUseMember from "../makeUseMember";
import makeFields from "../makeFields";
import makeUseSelector from "./makeUseSelector";
import makeResourceReducer from "./resource/makeReducer";

/** @module collectionHoax.makeCollectionProvider */

/**
 * @typedef {Object} CollectionHoax
 * @property {function} Provider - The collection context provider
 * @property {function} useMember - A react hook for
 * @property {function} useCollection - A react hook for
 * @property {collectionHoax.Selectors}
 * @property {object} Field
 */

/**
 * makeCollectionProvider factory.
 * @param {string} name - The name of the resource, will be used on the `displayName`.
 * @param {object} [options={}] - The collection options.
 * @param {object} options.getInitialState - The returned value of `getInitialState` of the collection, will be merged with the default collection hoax initialState.
 * @param {function} options.reducer - `reducer(state, action)` An extra reducer for the collection, should return nothing on actionType mismatch, after passing through the custom reducer, it will go through the default collection hoax reducer.
 * @param {object} options.actions - Extra actions, check the default hoax actions for collection and nested resources.
 * @param {object} options.resourceOptions - each nested resource's options
 * @param {object} options.resourceOptions.getInitialState - The returned value of `getInitialState` of the nested resource, will be merged with the default resource hoax initialState.
 * @param {function} options.resourceOptions.reducer - `reducer(state, action)` An extra reducer for the nested resource, should return nothing on actionType mismatch, after passing through the custom reducer, it will go through the default resource reducer.
 * @param {string} [idKey='id'] - The identifier for resource scoping, the key to be used for `ids` and `byId`.
 * @return {CollectionHoax} CollectionHoax - what is needed for a collection resource
 */

const makeCollectionProvider = (
  name,
  {
    getInitialState,
    reducer: customReducer,
    actions: customActions,
    resourceOptions = {},
    idKey = 'id'
  } = {}
) => {
  const initState = makeGetInitialState({
    getInitialState,
    defaultInitialState
  });

  const getInitialResourceState = makeGetInitialState({
    getInitialState: resourceOptions.getInitialState,
    defaultInitialState: defaultResourceInitialState
  });

  const { reducer: resourceReducer, init: initResource } = makeResourceReducer(
    getInitialResourceState,
    resourceOptions.reducer
  );
  const { reducer, init } = makeReducer({
    getInitialState: initState,
    customReducer,
    resourceReducer,
    customResourceActionTypes: resourceOptions.actionTypes,
    initResource,
    idKey
  });

  const [StateCtx, DispatchCtx] = makeContext();
  const useCollection = makeUseCollection(StateCtx, DispatchCtx);
  const useMember = makeUseMember(StateCtx, DispatchCtx);
  const Field = makeFields(useMember);
  const { useSelector, useAction, useResourceSelector } = makeUseSelector(
    StateCtx,
    DispatchCtx,
    initState,
    getInitialResourceState,
  );

  const CollectionProvider = ({ children, initialState, extraArgument }) => {
    const [state, dispatches] = useReducer(reducer, {
      initialState,
      init,
      actions: { ...actions, ...resourceActions, ...customActions },
      extraArgument
    });

    return (
      <DispatchCtx.Provider value={dispatches}>
        <StateCtx.Provider value={state}>{children}</StateCtx.Provider>
      </DispatchCtx.Provider>
    );
  };

  CollectionProvider.displayName = name;

  return {
    Provider: CollectionProvider,
    useCollection,
    useMember,
    useSelector,
    useAction,
    useResourceSelector,
    Field,
    getInitialState: initState,
    getInitialResourceState
  };
};

export default makeCollectionProvider;