Search code examples
reactjsfirebasereact-redux-firebase

Where should I put my logic for writing new data to Firestore?


I'm currently working on a project with React and Firebase and at the moment my logic to write new data to Firestore is directly in my components. I'm now also integrating Redux, for this I am using react-redux-firebase. My question is now, where is the best place to put this logic, because it makes my component quiet bloated.

In the documentation of react-redux-firebase, they've also put it directly in the component, but their logic is quit simple, mine is a little bit more complex, like this handleSubmit:

const handleSubmit = async (e) => {
    e.preventDefault();
    setDisabled(true); //disable button to prevent multiple sbumits
    setLoading(true);

    try {
      const docRef = await firestore.collection("goodies").add(newGoodieData); // create new event
      await firestore.collection("goodies").doc(docRef.id).update({
        // add eventId to event doc
        id: docRef.id,
      });

      if (image) {
        const imageName = docRef.id + "." + image.name.split(".").pop(); //create image name with eventID and file extions

        const imageRef = storage.ref().child("goodie_images/").child(imageName); //create imageRef

        const imageSnapshot = await imageRef.put(image); //upload image to storage

        const downloadUrl = await imageSnapshot.ref.getDownloadURL(); //get image download-url

        const imagePath = imageRef.fullPath; // get image storage path

        await firestore.collection("goodies").doc(docRef.id).update({
          //update event with imageUrl and imagePath
          image: downloadUrl,
          imagePath: imagePath,
        });
      }
      setDisabled(false);
      setLoading(false);

      dispatch(showSuccessToast(`Created new goodie "${newGoodieData.name}"!`));
      history.push("/goodies");
    } catch (error) {
      dispatch(showErrorToast("Ops, an error occurred!"));
      console.error(error.message);
    }
  };

Solution

  • This is entirely my idiosyncrasy, but I use a structure like the below. In general, component and page (i.e react component) names are initial-capital:

    src
    |-blocks
    |-components
    |-css
    |-navigation
    |-pages
    |-services
    |-App.js
    |-constants.js
    |-index.js
    

    BLOCKS

    These are my application-specific components, and some larger application-specific functions.

    |-blocks
      |-Block1
      | |-index.js
      | |-aSubModule.js
      |-Block2
      | |-index.js
      | |-aSubModule.js
    etc, etc
    

    COMPONENTS

    These are "generalized" components - i.e could be released as modules, or are customized from external modules. These are NOT application-specific. Similar structure to "blocks"

    |-components
      |-ComponentThatDoesThisThing
      |-ComponentThatDoesThatThing
    etc, etc
    

    CSS

    As the name says...

    NAVIGATION

    Headers, Footers, navigation menus. Also navigation helper functions (such as wrapping links to pages, etc)

    |-navigation
      |-Header
      |-Menu
      |-Footer
    etc, etc
    

    PAGES

    All my react pages and sub-pages, to whatever depth are needed. Everything below pages is specific to the application

    |-pages
      |-Users
      | |-Home
      | | |index.js
      | | |-etc,etc
      | |-Account
      | |-Bio
      | |-etc,etc
      |-Owners
      | |-Home
      | |-Account
      | |-Bio
      | |-etc,etc
      |-index.js  
    

    And where your question is more directly addressed:

    SERVICES

    |-services
      |-reduxEnhancers
      |-reduxMiddleware
      |-reduxRootReducers
      |-slices
      |-configureStore.js
      |-firestore.js
    

    Not yet much different than the tutorials. But this is what I have in my slices - which more-or-less-ish correspond to redux state slices:

    SLICES

    |-services
      |-slices
        |-UserSlice
        | |-actions
        | | |-index.js
        | |-reducer
        | | |-index.js
        | |-business
        | | |-index.js
        | |-index.js
        |-OwnerSlice
        | |-actions
        | | |-index.js
        | |-reducer
        | | |-index.js
        | |-business
        | | |-index.js
        | |-index.js
        |-KitchCupboardSlice
        | |-actions
        | | |-index.js
        | |-reducer
        | | |-index.js
        | |-business
        | | |-index.js
        | |-index.js
        |-etc,etc slices
        |-index.js
    

    IMPORTANT NOTE: most Redux tutorials show the Redux slices having the same branching structure as the React components. In general THIS IS NOT AT ALL THE CASE, and I have yet to find a case where it is even helpful. The tree-like structure of your data is not necessarily at all like the tree-like structure of your pages and react components - divide your Redux store the way the data wants you to, not the way the user-facing React wants you to. Even more to the point for this forum: let the FIRESTORE structure of your data guide you.

    In use:

    |-services
      |-slices
        |-UserSlice
        |-index.js
    

    ...for example, imports and then exports all the public/exported symbols from actions, reducer and business.

    |-services
      |-slices
        |-UserSlice
         |-reducer
    

    ...reducer uses store.injectReducer to add itself to the Redux store, using symbols and constants defined in

    |-services
      |-slices
        |-UserSlice
         |-actions
    

    ...actions, which also creates the specific Redux actions and helper boiler plate (actions, selectors, etc).

    To help separate responsibility:

    |-services
      |-slices
        |-UserSlice
         |-business
    

    ...business sub-directory holds the BUSINESS logic you mentioned - any logic that a React component might need to call that is NOT specifically local to the display & state aspects of the React modules. This is ALSO where I define any access to firestore, or any other external service or fetches - the React components should NOT care how this is done.

    To make accessing all of these various exports more convenient, I use

    |-services
      |-slices
        |-index
    

    ...which imports and then exports all the public/exported symbols from all the slices. Makes it easier to import into various React comopnents without worrying about changes to the underlying structure of slices

    As I said, COMPLETELY my idiosyncrasy, but it helps to separate out responsibility.