GithubHelp home page GithubHelp logo

aubreyzulu / star-wars-characters-react-state Goto Github PK

View Code? Open in Web Editor NEW

This project forked from stevekinney/star-wars-characters-react-state

0.0 0.0 0.0 897 KB

HTML 8.90% JavaScript 66.40% SCSS 24.70%

star-wars-characters-react-state's Introduction

Star Wars Autocomplete

Course

This project was built to teach the React State for Frontend Masters.

The Basics

If we wanted to fetch all of the characters, we could do something like this.

useEffect(() => {
  console.log('Fetching');
  fetch(`${endpoint}/characters`)
    .then(response => response.json())
    .then(response => {
      console.log({ response });
      setCharacters(Object.values(response.characters));
    })
    .catch(console.error);
});

A careful eye will see that this is actually triggering an infinite loop since we're calling this effect on every single render.

Adding a bracket will make this less bad.

useEffect(() => {
  console.log('Fetching');
  fetch(`${endpoint}/characters`)
    .then(response => response.json())
    .then(response => {
      console.log({ response });
      setCharacters(Object.values(response.characters));
    })
    .catch(console.error);
}, []);

This has its own catch since this will only run once when the component mounts and will never run again.

But we can burn that bridge later.

Okay, but we want this to run every time the search term changes.

Adding Loading and Error States

We'll need to keep track of that state.

const [loading, setLoading] = useState(true);
const [error, setError] = useState(error);

Updating the fetch effect.

useEffect(() => {
  console.log('Fetching');

  setLoading(true);
  setError(null);
  setCharacters([]);

  fetch(`${endpoint}/characters`)
    .then(response => response.json())
    .then(response => {
      setCharacters(Object.values(response.characters));
      setLoading(false);
    })
    .catch(error => {
      setError(error);
      setLoading(false);
    });
}, []);

Displaying It In The Component

<section className="sidebar">
  {loading ? (
    <p className="loading">Loading…</p>
  ) : (
    <CharacterList characters={characters} />
  )}
  {error && <p className="error">{error.message}</p>}
</section>

Creating a Custom Hook

const useFetch = url => {
  const [response, setResponse] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    console.log('Fetching');

    setLoading(true);
    setError(null);
    setResponse(null);

    fetch(url)
      .then(response => response.json())
      .then(response => {
        setResponse(response);
        setLoading(false);
      })
      .catch(error => {
        setError(error);
        setLoading(false);
      });
  }, [url]);

  return [response, loading, error];
};

Adding the Formatting in There

Let's break that out into a function.

const formatData = response => (response && response.characters) || [];

Then we can use it like this.

const [characters, loading, error] = useFetch(
  endpoint + '/characters',
  formatData,
);

We can add that to our useEffect.

useEffect(() => {
  console.log('Fetching');

  setLoading(true);
  setError(null);
  setResponse(null);

  fetch(url)
    .then(response => response.json())
    .then(response => {
      setResponse(formatData(response));
      setLoading(false);
    })
    .catch(error => {
      setError(error);
      setLoading(false);
    });
}, [url, formatData]);

Using an Async Function

You can't pass an async function directly to useEffect.

useEffect(() => {
    console.log('Fetching');

    setLoading(true);
    setError(null);
    setResponse(null);

    const get = async () => {
      try {
        const response = await fetch(url);
        const data = await response.json();
        setResponse(formatData(data));
      } catch (error) {
        setError(error);
      } finally {
        setLoading(false);
      }
    };

    get();
  }, [url, formatData]);

  return [response, loading, error];
};

I don't like this, but you might.

Refactoring to a Reducer

const fetchReducer = (state, action) => {
  if (action.type === 'FETCHING') {
    return {
      result: null,
      loading: true,
      error: null,
    };
  }

  if (action.type === 'RESPONSE_COMPLETE') {
    return {
      result: action.payload.result,
      loading: false,
      error: null,
    };
  }

  if (action.type === 'ERROR') {
    return {
      result: null,
      loading: false,
      error: action.payload.error,
    };
  }

  return state;
};

Now, we can just dispatch actions.

const useFetch = (url, dependencies = [], formatResponse = () => {}) => {
  const [state, dispatch] = useReducer(fetchReducer, initialState);

  useEffect(() => {
    dispatch({ type: 'FETCHING' });
    fetch(url)
      .then(response => response.json())
      .then(response => {
        dispatch({
          type: 'RESPONSE_COMPLETE',
          payload: { result: formatResponse(response) },
        });
      })
      .catch(error => {
        dispatch({ type: 'ERROR', payload: { error } });
      });
  }, [url, formatResponse]);

  const { result, loading, error } = state;

  return [result, loading, error];
};

Dispensing Asynchronous Actions

Important: We're going to check out the asynchronous-actions branch.

How could we right a simple thunk reducer?

const useThunkReducer = (reducer, initialState) => {
  const [state, dispatch] = useReducer(reducer, initialState);

  const enhancedDispatch = useCallback(
    action => {
      if (typeof action === 'function') {
        console.log('It is a thunk');
        action(dispatch);
      } else {
        dispatch(action);
      }
    },
    [dispatch],
  );

  return [state, enhancedDispatch];
};

Now, we just use that reducer instead.

We can have a totally separate function for fetching the data that our state management doesn't know anything about.

const fetchCharacters = dispatch => {
  dispatch({ type: 'FETCHING' });
  fetch(endpoint + '/characters')
    .then(response => response.json())
    .then(response => {
      dispatch({
        type: 'RESPONSE_COMPLETE',
        payload: {
          characters: response.characters,
        },
      });
    })
    .catch(error => dispatch({ type: error, payload: { error } }));
};

Exercise: Implementing Character Search

There is a CharacterSearch component. Can you you implement a feature where we update the list based on the search field?

import React from 'react';
import endpoint from './endpoint';

const SearchCharacters = React.memo(({ dispatch }) => {
  const [query, setQuery] = React.useState('');

  React.useEffect(() => {
    dispatch({ type: 'FETCHING' });
    fetch(endpoint + '/search/' + query)
      .then(response => response.json())
      .then(response => {
        dispatch({
          type: 'RESPONSE_COMPLETE',
          payload: {
            characters: response.characters,
          },
        });
      })
      .catch(error => dispatch({ type: error, payload: { error } }));
  }, [query, dispatch]);

  return (
    <input
      onChange={event => setQuery(event.target.value)}
      placeholder="Search Here"
      type="search"
      value={query}
    />
  );
});

export default SearchCharacters;

The Perils of useEffect and Dependencies

We're going to need to important more things.

import { BrowserRouter as Router, Route } from 'react-router-dom';

import CharacterList from './CharacterList';
import CharacterView from './CharacterView';

Now, we'll add this little tidbit.

<section className="CharacterView">
  <Route path="/characters/:id" component={CharacterView} />
</section>

In CharacterView, we'll do the following refactoring:

const CharacterView = ({ match }) => {
  const [character, setCharacter] = useState({});

  useEffect(() => {
    fetch(endpoint + '/characters/' + match.params.id)
      .then(response => response.json())
      .then(response => setCharacter(response.character))
      .catch(console.error);
  }, []);

  // …
};

I have an ESLint plugin that solves most of this for us.

const CharacterView = ({ match }) => {
  const [character, setCharacter] = useState({});

  useEffect(() => {
    fetch(endpoint + '/characters/' + match.params.id)
      .then(response => response.json())
      .then(response => setCharacter(response.character))
      .catch(console.error);
  }, []);

  // …
};

star-wars-characters-react-state's People

Contributors

stevekinney avatar dependabot[bot] avatar abhi18av avatar aubreyzulu avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.