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!