The React framework is results-oriented for those who have just learned the Front Three and want to learn the React framework quickly (history and unnecessary introductions are omitted).

  • Quick Start
  • The road to React

State of the advanced

Reducer is a Reducer in JavaScript/React/Redux. Reducer is a Reducer in JavaScript/React/Redux

We can use useReducer hooks to replace a large number of useState hooks to simplify [state management]. First introduce a reducer function outside the component and return a new state by receiving two parameters state and action.

// (state, action) => newState
const storiesReducer = (state, action) = > {
  if (action.type === "SET_STORIES") {
    return action.payload;
  } else {
    throw new Error();
  }
};
Copy the code

The useReducer hook takes a reducer function and an initial state as arguments, similar to useState, which returns an array of two items:

  • The first term is the current state
  • The second item is the function used to update state (also known as the dispatch function)
// Replace const [stories, setStories] = react.usestate ([]);
const [stories, dispatchStories] = React.useReducer(storiesReducer, []);
Copy the code

Replace the setStories function with the new Dispatch function:

const App = () = >{...const [stories, dispatchStories] = React.useReducer(storiesReducer, []);

  React.useEffect(() = > {
    setIsLoading(true);
    getAsyncStories()
      .then((res) = > {
        // Pass an action object
        dispatchStories({
          type: "SET_STORIES".payload: res.data.stories,
        });
        setIsLoading(false);
      })
      .catch(() = > setIsError(true)); } []);const handleRemoveStory = (item) = > {
    const newStories = stories.filter(
      (story) = >item.objectID ! == story.objectID ); dispatchStories({type: "SET_STORIES".payload: newStories, }); }; . };Copy the code

Unlike the useState update function, the Dispatch function needs to be passed to the Reducer an action object containing type and optional payload for the reducer to match.

We can also package functions in reducer and manage multiple states with actions:

const storiesReducer = (state, action) = > {
  // Use switch statements to increase code readability
  switch (action.type) {
    case "SET_STORIES":
      return action.payload;
    case "REMOVE_STORY":
      // Return a new state
      return state.filter(
        (story) = >action.payload.objectID ! == story.objectID );default:
      throw new Error();
  }
};

const App = () = >{...const handleRemoveStory = (item) = > {
    dispatchStories({
      type: "REMOVE_STORY".payload: item, }); }; . };Copy the code

When we continuously use the state update function, it is possible to [result in unreasonable state] and cause UI problems.

If we get the data wrong:

// The simulation did not get data
const getAsyncStories = () = >
  new Promise((resolve, reject) = > setTimeout(reject, 2000));

getAsyncStories().catch(() = > setIsError(true));
Copy the code

You’ll see both an error message and an endless load message on the screen, meaning that isError is updated but isLoading is not, which is obviously unreasonable.

To avoid this, we can merge these states into the same useReducer hook, and then all operations related to asynchronous data are updated via the dispatch function:

const storiesReducer = (state, action) = > {
  switch (action.type) {
    case "STORIES_FETCH_INIT":
      return {
        ...state,
        isLoading: true.isError: false};case "STORIES_FETCH_SUCCESS":
      return {
        ...state,
        isLoading: false.isError: false.data: action.payload,
      };
    case "STORIES_FETCH_FAILURE":
      return {
        ...state,
        isLoading: false.isError: true};case "REMOVE_STORY":
      return {
        ...state,
        // You need to manipulate state.data instead of state
        data: state.data.filter(
          (story) = >action.payload.objectID ! == story.objectID ), };default:
      throw new Error();
  }
};

const App = () = >{...const [stories, dispatchStories] = React.useReducer(
    storiesReducer, 
    { 
      data: [].isLoading: false.isError: false}); React.useEffect(() = > {
    dispatchStories({ type: "STORIES_FETCH_INIT" });
    getAsyncStories()
      .then((result) = > {
        dispatchStories({
          type: "STORIES_FETCH_SUCCESS".payload: result.data.stories,
        });
      })
      .catch(() = > dispatchStories({ type: "STORIES_FETCH_FAILURE"})); } []);const handleRemoveStory = (item) = > {
    dispatchStories({
      type: "REMOVE_STORY".payload: item, }); }; .// stories.data
  const searchedStories = stories.data.filter((story) = >
    story.title.toLowerCase().includes(searchTerm.toLowerCase())
  );

  return (
    <>. {stories.isError &&<p>Something went wrong ...</p>}
      {stories.isLoading ? (
        <p>Loading...</p>
      ) : (
        <List list={searchedStories} onRemoveItem={handleRemoveStory} />
      )}
    </>
  );
};
Copy the code

Each conversion of state returns a new state object containing all key-value pairs (expansion operators) of the current state overridden by the new attribute value.

To get the data

Use the real third-party API Hacker News API to get the data, and drop the original initialStories and getAsyncStories functions:


const API_ENDPOINT = 'https://hn.algolia.com/api/v1/search?query=';

const App = () = >{... React.useEffect(() = > {
    dispatchStories({ type: "STORIES_FETCH_INIT" });
    
    // query=react
    fetch(`${API_ENDPOINT}react`)
      .then(response= > response.json())
      .then((result) = > {
        dispatchStories({
          type: "STORIES_FETCH_SUCCESS".payload: result.hits,	 / / API data
        });
      })
      .catch(() = > dispatchStories({ type: "STORIES_FETCH_FAILURE"})); } []); . };Copy the code

Fetch API obtains React related news data, converts the data to JSON and sends it to component state.

We can upgrade the original search function from the client side to the server side, and use searchTerm as the dynamic query condition request API to obtain a set of lists filtered by the server:

const App = () = > {
  const [searchTerm, setSearchTerm] = useSemiPersistentState(
    "search"."React"   // The initial value is React); . React.useEffect(() = > {
    // Can also be written as if (! searchTerm)
    if (searchTerm === "") return;

    dispatchStories({ type: "STORIES_FETCH_INIT" });
    
    // query={searchTerm}
    fetch(`${API_ENDPOINT}${searchTerm}`)
      .then((response) = > response.json())
      .then((result) = > {
        dispatchStories({
          type: "STORIES_FETCH_SUCCESS".payload: result.hits,
        });
      })
      .catch(() = > dispatchStories({ type: "STORIES_FETCH_FAILURE" }));
  }, [searchTerm]);   // Dependent array changes

  // Delete the functions associated with searchedStories.return (
    <>. {stories.isLoading ? (<p>Loading...</p>) : ({/* pass regular data */}<List list={stories.data} onRemoveItem={handleRemoveStory} />
      )}
    </>
  );
};
Copy the code

SearchTerm is the value entered in the input box:

  • The initial value is React when the component is loaded
  • If the value is null, do nothing, okay
  • Each time the value changes, the side effect function is executed to get the relevant data

This function is completely completed on the server side, but such frequent calls to the API may lead to the speed limit of the third party, and the interface will return errors.

A simple solution is to [add a confirm button] and retrieve the data only when the button is clicked:

const App = () = > {
  const [searchTerm, setSearchTerm] = useSemiPersistentState("search"."React");
  
  const [url, setUrl] = React.useState(`${API_ENDPOINT}${searchTerm}`); . React.useEffect(() = > {
    // Delete if (searchTerm === "") return;
    dispatchStories({ type: "STORIES_FETCH_INIT"}); fetch(url) ... }, [url]); .const handleSearch = (e) = > {
    setSearchTerm(e.target.value);
  };
	
  // Click the button handler
  const handleSearchSubmit = () = > {
    setUrl(`${API_ENDPOINT}${searchTerm}`);
  };

  return (
    <>. {/* Add a button */}<button disabled={! searchTerm} onClick={handleSearchSubmit}>
        Submit
      </button>

      <hr />.</>
  );
};

Copy the code

Now the searchTerm is only used to update the value of the input field, and instead of retrieving data, the URL updates and calls the side effect function to retrieve new data when the user clicks the submit button.

We can also optimize with useCallback hook (skip) :

const App = () = >{...const handleFetchStories = React.useCallback(() = > {
    dispatchStories({ type: "STORIES_FETCH_INIT" });
    fetch(url)
      .then((response) = > response.json())
      .then((result) = > {
        dispatchStories({
          type: "STORIES_FETCH_SUCCESS".payload: result.hits,
        });
      })
      .catch(() = > dispatchStories({ type: "STORIES_FETCH_FAILURE" }));
  }, [url]);

  React.useEffect(() = >{ handleFetchStories(); }, [handleFetchStories]); . };Copy the code

axios

The FETCH API provided by native browsers is not suitable for all cases, especially for older browsers, so we decided to use a stable library, AXIos, to replace the FETCH API for asynchronous data fetching.

  • Install first via NPM:
npm install axios
Copy the code
  • Import to App file:
import axios from 'axios';
Copy the code
  • Use axios instead of fetch:
const handleFetchStories = React.useCallback(() = > {
  dispatchStories({ type: "STORIES_FETCH_INIT" });
  axios.get(url)
    .then((result) = > {
    dispatchStories({
      type: "STORIES_FETCH_SUCCESS".payload: result.data.hits,    / / note
    });
  })
    .catch(() = > dispatchStories({ type: "STORIES_FETCH_FAILURE" }));
}, [url]);
Copy the code

Similar to fetch, it takes the URL as an argument and returns a Promise object, and because it wraps the result as a JS data object, there is no need to convert the returned result to JSON.

The form

Forms in React and HTML aren’t that different. We just bind handleSearchSubmit() to the form element and set the button’s type attribute to Submit:

const App = () = >{...return (
    <>
      <form onsubmit={handleSearchSubmit}>
        <InputWithLabel
          id="search"
          value={searchTerm}
          onInputChange={handleSearch}
          isFocused
        >
          <strong>Search:</strong>
        </InputWithLabel>

        <button disabled={! searchTerm} type="submit">
          Submit
        </button>
      </form>

      <hr />.</>
  );
};
Copy the code

So we can use the Enter key to search, and don’t forget to prevent the browser refresh:

const handleSearchSubmit = (e) = > {
  e.preventDefault();
  setUrl(`${API_ENDPOINT}${searchTerm}`);
};
Copy the code

Continue to extract the Form as a separate SearchForm component and also into the App component:

const App = () = >{...return (
    <>
      <h1>Hacker Stories</h1>

      <SearchForm
        searchTerm={searchTerm}
        onSearch={handleSearch}
        onSearchSubmit={handleSearchSubmit}
      />
      <hr />.</>
  );
};

const SearchForm = ({ searchTerm, onSearch, onSearchSubmit }) = > (
  <form onsubmit={onSearchSubmit}>
    <InputWithLabel
      id="search"
      value={searchTerm}
      onInputChange={onSearch}
      isFocused
    >
      <strong>Search:</strong>
    </InputWithLabel>

    <button disabled={! searchTerm} type="submit" className="btn">
      Submit
    </button>
  </form>
);
Copy the code

style

There are many ways to write styles in React, but we’ll only discuss the most common CSS styles here. Like the class attribute of standard CSS, React provides a className attribute for each element, which you can use to set styles in your CSS file.

.return (
    <div className="container">
      <h1 className="headline">Hacker Stories</h1>

      <SearchForm
        searchTerm={searchTerm}
        onSearch={handleSearch}
        onSearchSubmit={handleSearchSubmit}
      />
			
      {stories.isError && <p>Something went wrong ...</p>}
      {stories.isLoading ? (
        <p>Loading...</p>
      ) : (
        <List list={stories.data} onRemoveItem={handleRemoveStory} />
      )}
    </div>
  );
};
Copy the code

Since we used create-react-app to create the app, you should see the SRC/app.css file and its import statement:

import './App.css';
Copy the code

You can also copy the code I wrote by styling them like this:

body {
  background: linear-gradient(to left, #b6fbff.#83a4d4);
  color: # 171212;
}

.container {
  padding: 20px;
}

.headline {
  font-size: 48px;
  letter-spacing: 2px;
}
Copy the code

That’s about the end of our tutorial, but it’s up to you to explore 😎

There are several directions you can learn:

  • Configuration Sass
  • CSS Modules
  • CSS in JS
  • The deployment of application
  • The React performance
  • TypeScript
  • test
  • .

column

Because clocking in is daily, it can be short, so check out the React Primer.

After the update, it will be integrated into a whole article, thanks for your attention and thumbs up!