Best Practices

Best Practices

·

3 min read

With the version you would navigate away immediately, before the response has arrived. You should at least get a warning that you are trying to set a state on an unmounted component.

- I've just tried it, and - as expected - with your version the new item is not added to the "All Quotes" view (until that component is mounted again by some other action).

Not a good Practice NewQuote.js

import React, { useEffect } from 'react'
import QuoteForm from '../components/quotes/QuoteForm';
import { useHistory } from 'react-router-dom';
import useHttp from '../hooks/use-http';
import { addQuote } from '../lib/api';

function NewQuote() {
    const { sendRequest, status } = useHttp(addQuote);

    const history = useHistory();

    const addQuoteHandler = (quoteData) => {
        sendRequest(quoteData);
         history.push('/quotes');
    }


    return (
        <QuoteForm isLoading={status === 'pending'} onAddQuote={addQuoteHandler} />
    )
}

export default NewQuote

Best Practice : That's how we handle the side effect , because sendRequest is an async function and of course there is a delay here . After sending the request , the response is not getting directly , so before any error announcement or something like that we can use the useEffect hook.

Best Practice NewQuote.js

import React, { useEffect } from 'react'
import QuoteForm from '../components/quotes/QuoteForm';
import { useHistory } from 'react-router-dom';
import useHttp from '../hooks/use-http';
import { addQuote } from '../lib/api';

function NewQuote() {
    const { sendRequest, status } = useHttp(addQuote);

    const history = useHistory();

    useEffect(() => {
        if (status === 'completed') {
            history.push('/quotes');

        }
    }, [status, history]);


    const addQuoteHandler = (quoteData) => {
        sendRequest(quoteData);
    }


    return (
        <QuoteForm isLoading={status === 'pending'} onAddQuote={addQuoteHandler} />
    )
}

export default NewQuote

Instead of useEffect you could also write it like this:

const addQuoteHandler = (quoteData) => {
    sendRequest(quoteData).then(() => history.push('/quotes'));
  };

... or ...

const addQuoteHandler = async (quoteData) => {
    await sendRequest(quoteData);
    history.push('/quotes');
  };

api.js:

export async function addQuote(quoteData) {
  const response = await fetch(`${FIREBASE_DOMAIN}/quotes.json`, {
    method: 'POST',
    body: JSON.stringify(quoteData),
    headers: {
      'Content-Type': 'application/json',
    },
  });
  const data = await response.json();
  // console.log(data); { name: '-NAob1i5CCgXVtgo-GCq' }

  if (!response.ok) {
    throw new Error(data.message || 'Could not create quote.');
  }

  return null;
}

Custom Hook use-http.js

import { useReducer, useCallback } from 'react';

function httpReducer(state, action) {
  if (action.type === 'SEND') {
    return {
      data: null,
      error: null,
      status: 'pending',
    };
  }

  if (action.type === 'SUCCESS') {
    return {
      data: action.responseData,
      error: null,
      status: 'completed',
    };
  }

  if (action.type === 'ERROR') {
    return {
      data: null,
      error: action.errorMessage,
      status: 'completed',
    };
  }

  return state;
}



function useHttp(requestFunction, startWithPending = false) {

  const [httpState, dispatch] = useReducer(httpReducer, {
    status: startWithPending ? 'pending' : null,
    data: null,
    error: null,
  });




  const sendRequest = useCallback(

    async function (requestData) {


      dispatch({ type: 'SEND' });

      try {
        const responseData = await requestFunction(requestData);
        // console.log(responseData);
        dispatch({ type: 'SUCCESS', responseData });
      } catch (error) {
        dispatch({
          type: 'ERROR',
          errorMessage: error.message || 'Something went wrong!',
        });
      }

    },
    [requestFunction]
  );


  return {
    sendRequest,
    ...httpState,
  };
}

export default useHttp;