Read / Fetch



API Redux Toolkit Query (RTK)

  • File: app\containers\SampleFirebaseApps\Contact\services\contactApi.js
  • Call fetch action in API function
                      
    fetchContacts: builder.query({
      queryFn: async () => {
        const contactRef = child(dbRef, 'contacts/');
        const querySnapshot = await get(contactRef).then((snapshot) => {
          if (snapshot.exists()) {
            return snapshot.val();
          }
          return false;
        }).catch((error) => error);
    
        const contacts = Object.keys(querySnapshot).map(key => ({
          id: key,
          ...querySnapshot[key]
        }));
        return { data: contacts.reverse() };
      },
      providesTags: ['Contact'],
    }),
                      
                    
  • Refetch data changes
                      
    refetchContact: builder.mutation({
      queryFn: async () => ({
        data: null
      }),
      invalidatesTags: ['Contact'],
    }),
    
                      
                    

Reducer/Slice

  • File: app\containers\SampleFirebaseApps\Contact\reducers\contactSlice.js
  • Set local state to load items in front-end side
                      
    fetchData: (state, action) => {
      const items = action.payload;
      state.contactList = items;
    },
                      
                    

Container

  • File: \app\containers\SampleFirebaseApps\Contact\index.js
  • Load state data to container
                      
    import { useFetchContactsQuery } from './services/contactApi';
    import { fetchData } from './reducers/contactSlice';
    
    const dataContact = useSelector(state => state.contactFb.contactList);
    
    const { data, isLoading } = useFetchContactsQuery();
    
    const dispatch = useDispatch();
    
    useEffect(() => {
      dispatch(fetchData(data));
    }, [data]);
    
    return {
      <ContactList
        addFn
        total={dataContact && dataContact.length}
        addContact={() => dispatch(add())}
        clippedRight
        itemSelected={itemSelected}
        keyword={keyword}
        search={(payload) => dispatch(search(payload))}
        dataContact={dataContact}
        loading={isLoading}
        showDetail={(payload) => dispatch(showDetail(payload))}
      />
    }
                      
                    

Components

  • File: app\components\Contact\ContactList.js
  • Populate dataContact prop to component
                      
    const getItem = dataArray => dataArray.map(data => {
      const index = dataContact.indexOf(data);
      if (data.get('name').toLowerCase().indexOf(keyword) === -1) {
        return false;
      }
      return (
        <ListItem
          button
          key={data.id}
          className={index === itemSelected ? classes.selected : ''}
          onClick={() => showDetail(data)}
        >
          <Avatar alt={data.name} src={data.avatar} className={classes.avatar} />
          <ListItemText primary={data.name} secondary={data.title'} />
        </ListItem>
      );
    });
                      
                    


Create



API Redux Toolkit Query (RTK)

  • File: app\containers\SampleFirebaseApps\Contact\services\contactApi.js
  • Call submit contact item in API function
                      
    submitContact: builder.mutation({
      queryFn: async (payload) => {
        const contactId = (+new Date() + Math.floor(Math.random() * 999999)).toString(36);
    
        await set(ref(db, 'contacts/' + contactId), payload)
          .then(() => {
            console.info('submit success');
          })
          .catch((error) => {
            console.error(error);
          });
    
        return { data: null };
      },
      invalidatesTags: ['Contact'],
    }),        
                      
                    

Reducer

  • File: app\containers\SampleFirebaseApps\Contact\reducers\contactSlice.js
  • Set local state to update UI changes
                      
    submit: (state) => {
      state.selectedIndex = 0;
      state.avatarInit = '';
      state.openFrm = false;
    },
                      
                    

Container

  • File: app\containers\SampleFirebaseApps\Contact\index.js
  • Dispatch create action in container
                      
    import { useSubmitContactMutation } from './services/contactApi';
    import { submit } from './reducers/contactSlice';
    
    
    const [submitContact] = useSubmitContactMutation();
    
    const handleSubmit = (item, avatar) => {
      let value = {};
      setUploadSubmiting(true);
    
      if (item.id === selectedId) { // Update contact
        const contact = dataContact[itemSelected];
        dispatch(update());
        if (typeof avatar === 'object') {
          uploadImg(avatar, async (url) => {
            value = { ...item, avatar: url || null };
            updateContact({ contact, change: value });
            setUploadSubmiting(false);
          });
        } else {
          updateContact({ contact, change: item });
          setUploadSubmiting(false);
        }
      } else { // Create new contact
        dispatch(submit());
        uploadImg(avatar, async (url) => {
          value = { ...item, avatar: url || null };
          submitContact(value);
          setUploadSubmiting(false);
        });
      }
    };
    
    return (
      <AddContact
        initialValues={initVal}
        addContact={() => dispatch(add())}
        openForm={open}
        closeForm={() => dispatch(closeForm())}
        submit={(value, avatar) => handleSubmit(value, avatar)}
        avatarInit={avatarInit}
        processing={uploadSubmiting}
      />
    )
                      
                    

Component

  • File: app\components\Contact\AddContactForm.js
  • Get and submit New Contact
                      
    const sleep = (ms) => new Promise((r) => { setTimeout(r, ms); });
    const formik = useFormik({
      initialValues,
      enableReinitialize: true,
      validationSchema,
      onSubmit: async (values) => {
        await sleep(500);
        sendValues(values);
        await sleep(500);
        formik.resetForm({
          values: initialValues
        });
      },
    });
    
    return (
      <form onSubmit={formik.handleSubmit} noValidate>
         ...
      </form>
    )
                      
                    


Update


API Redux Toolkit Query (RTK)

  • File: app\containers\SampleFirebaseApps\Contact\services\contactApi.js
  • Call update task action API function
                      
    updateContact: builder.mutation({
      queryFn: async (payload) => {
        const { contact, change } = payload;
        const updates = {};
    
        updates['/contacts/' + contact.id] = { ...contact, ...change };
        await update(ref(db), updates).then(() => {
          console.info('update success');
        }).catch((error) => {
          console.error(error);
        });
    
        return { data: null };
      },
      invalidatesTags: ['Contact'],
    }),
                      
                    

Reducer

  • File: app\containers\SampleFirebaseApps\Contact\reducers\contactSlice.js
  • Set local state to update UI changes
                      
    update: (state) => {
      state.avatarInit = '';
      state.openFrm = false;
    },
                      
                    

Container

  • File: app\containers\SampleFirebaseApps\Contact\index.js
  • Dispatch update action in container. Basically create and update has same value and data type, so we create condition to handle it.
                      
    import { useUpdateContactMutation } from './services/contactApi';
    import { update } from './reducers/contactSlice';
    
    const [updateContact] = useUpdateContactMutation();
    
    const handleSubmit = (item, avatar) => {
      let value = {};
      setUploadSubmiting(true);
    
      if (item.id === selectedId) { // Update contact
        const contact = dataContact[itemSelected];
        dispatch(update());
        if (typeof avatar === 'object') {
          uploadImg(avatar, async (url) => {
            value = { ...item, avatar: url || null };
            updateContact({ contact, change: value });
            setUploadSubmiting(false);
          });
        } else {
          updateContact({ contact, change: item });
          setUploadSubmiting(false);
        }
      } else { // Create new contact
        dispatch(submit());
        uploadImg(avatar, async (url) => {
          value = { ...item, avatar: url || null };
          submitContact(value);
          setUploadSubmiting(false);
        });
      }
    };
      
    return (
      <AddContact
        initialValues={initVal}
        addContact={() => dispatch(add())}
        openForm={open}
        closeForm={() => dispatch(closeForm())}
        submit={(value, avatar) => handleSubmit(value, avatar)}
        avatarInit={avatarInit}
        processing={uploadSubmiting}
      />
    )
                      
                    

Component

  • File: app\components\Contact\AddContactForm.js
  • Get and submit Updated Contact
                      
    const sleep = (ms) => new Promise((r) => { setTimeout(r, ms); });
    const formik = useFormik({
      initialValues,
      enableReinitialize: true,
      validationSchema,
      onSubmit: async (values) => {
        await sleep(500);
        sendValues(values);
        await sleep(500);
        formik.resetForm({
          values: initialValues
        });
      },
    });
    
    return (
      <form onSubmit={formik.handleSubmit} noValidate>
         ...
      </form>
    )
                      
                    


Delete



API Redux Toolkit Query (RTK)

  • File: app\containers\SampleFirebaseApps\Contact\services\contactApi.js
  • Call remove contact action in saga's function
                      
    removeContact: builder.mutation({
      queryFn: async (contact) => {
        const contactId = contact.id;
    
        await remove(ref(db, 'contacts/' + contactId))
          .then(() => {
            console.info('delete success');
          }).catch((error) => {
            console.error(error);
          });
    
        return { data: null };
      },
      invalidatesTags: ['Contact'],
    }),
                      
                    

Container

  • File: app\containers\SampleFirebaseApps\Contact\index.js
  • Dispatch remove action in container
                      
    import { useRemoveContactMutation } from './services/contactApi';
    
    const [removeContact] = useRemoveContactMutation();
    
    const handleRemove = (item) => {
      removeContact(item);
      setMessage(notif.removed);
    };
    
    return (
      <ContactDetail
        showMobileDetail={showMobileDetail}
        hideDetail={() => dispatch(hideDetail())}
        dataContact={dataContact}
        itemSelected={itemSelected}
        edit={(payload) => dispatch(edit(payload))}
        remove={(contact) => handleRemove(contact)}
        favorite={(contact) => handleFavorite(contact)}
        loading={isLoading}
      />
    )
    
                      
                    

Component

  • File: app/components/Contact/ContactDetail.js
  • Call remove function by onClick remove button
                      
    const deleteContact = (item) => {
      remove(item);
      setAnchorElOpt(null);
    };
    
    return (
      <MenuItem onClick={() => deleteContact(dataContact[itemSelected])}>
        <FormattedMessage {...messages.delete} />
      </MenuItem>
    )