React+MUI+Redux项目中如何实现Firebase自动补全搜索栏?
Hey there! I see you're building a full-stack app with React, MUI, Redux, and Firestore—great stack choice! Let's walk through adding that search bar functionality to your Navbar, so you can search document names or values in your Firestore collections.
First, a quick cleanup note: I noticed you have duplicate imports for TextField in your Navbar.js—you can remove one instance to keep your code tidy.
Step 1: Set up Firestore access
First, make sure you've initialized Firebase in your project. Create a utility file (like firebase.js) to handle Firebase setup, or add this directly to your project:
import firebase from 'firebase/app'; import 'firebase/firestore'; // Replace with your Firebase config const firebaseConfig = { apiKey: "YOUR_API_KEY", authDomain: "YOUR_AUTH_DOMAIN", projectId: "YOUR_PROJECT_ID", storageBucket: "YOUR_STORAGE_BUCKET", messagingSenderId: "YOUR_SENDER_ID", appId: "YOUR_APP_ID" }; firebase.initializeApp(firebaseConfig); const db = firebase.firestore(); export default db;
Step 2: Update your Navbar component with search logic
Let's modify your existing Navbar.js to add search submission, Firestore queries, and integrate with Redux to store results.
First, import your Firestore reference and a search icon:
import db from '../../firebase'; // Adjust path to your firebase.js import SearchIcon from '@material-ui/icons/Search';
Next, add these methods inside your Navbar class to handle search queries and key presses:
// Handle Firestore search when button is clicked handleSearch = async () => { const { query } = this.state; if (!query.trim()) return; // Skip empty searches try { // Replace 'your-collection-name' with your actual Firestore collection const collectionRef = db.collection('your-collection-name'); // Option 1: Search for documents where ID matches the query (partial match) const idQuery = collectionRef .where(firebase.firestore.FieldPath.documentId(), '>=', query) .where(firebase.firestore.FieldPath.documentId(), '<=', query + '\uf8ff'); // Option 2: Search for a specific field (e.g., 'name' or 'title'—replace with your field) const fieldQuery = collectionRef .where('name', '>=', query) .where('name', '<=', query + '\uf8ff'); // Run both queries and combine results (avoid duplicates) const [idResults, fieldResults] = await Promise.all([idQuery.get(), fieldQuery.get()]); const combinedResults = []; const seenIds = new Set(); idResults.forEach(doc => { if (!seenIds.has(doc.id)) { seenIds.add(doc.id); combinedResults.push({ id: doc.id, ...doc.data() }); } }); fieldResults.forEach(doc => { if (!seenIds.has(doc.id)) { seenIds.add(doc.id); combinedResults.push({ id: doc.id, ...doc.data() }); } }); // Send results to Redux store this.props.dispatchSearchResults(combinedResults); } catch (error) { console.error('Search error:', error); // Optional: Dispatch an error action to Redux for user feedback } }; // Trigger search when Enter key is pressed handleSearchKeyPress = (e) => { if (e.key === 'Enter') this.handleSearch(); };
Now update your search bar section to include a search button and key press handler:
// Inside the authenticated Fragment, replace your existing TextField with this: <TextField name="search" label="Cerca" value={this.state.query} onChange={this.searchChange} onKeyPress={this.handleSearchKeyPress} variant="outlined" size="small" style={{ marginRight: 8 }} /> <MyButton onClick={this.handleSearch} tip="Search" > <SearchIcon /> </MyButton>
Step 3: Add Redux Actions & Reducer to manage search results
Since you're using Redux, let's create actions and a reducer to store search results so other components can access them.
Create search actions (e.g., actions/searchActions.js)
export const SET_SEARCH_RESULTS = 'SET_SEARCH_RESULTS'; export const CLEAR_SEARCH_RESULTS = 'CLEAR_SEARCH_RESULTS'; export const setSearchResults = (results) => ({ type: SET_SEARCH_RESULTS, payload: results }); export const clearSearchResults = () => ({ type: CLEAR_SEARCH_RESULTS });
Create search reducer (e.g., reducers/searchReducer.js)
import { SET_SEARCH_RESULTS, CLEAR_SEARCH_RESULTS } from '../actions/searchActions'; const initialState = { results: [], error: null }; const searchReducer = (state = initialState, action) => { switch(action.type) { case SET_SEARCH_RESULTS: return { ...state, results: action.payload, error: null }; case CLEAR_SEARCH_RESULTS: return { ...state, results: [], error: null }; default: return state; } }; export default searchReducer;
Add reducer to your root reducer
Update your root reducer to include the search state:
// In rootReducer.js import { combineReducers } from 'redux'; import userReducer from './userReducer'; import uiReducer from './uiReducer'; import searchReducer from './searchReducer'; // Import new reducer export default combineReducers({ user: userReducer, UI: uiReducer, search: searchReducer // Add to store });
Connect actions to your Navbar
Update your Navbar.js to include the search action and state:
import { setSearchResults } from '../../actions/searchActions'; // Import action // Update mapStateToProps to get search results const mapStateToProps = state =>({ authenticated: state.user.authenticated, user: state.user, game: state.UI, search: state.search // Add search state }) // Add mapDispatchToProps to send results to Redux const mapDispatchToProps = (dispatch) => ({ dispatchSearchResults: (results) => dispatch(setSearchResults(results)) }); // Update connect call export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(Navbar))
Step 4: Display search results
Add a dropdown to your Navbar to show search results using MUI's Popover:
First, update your component state:
this.state = { open: false, game:'ygo', query:'', searchPopoverOpen: false, anchorEl: null };
Add popover handlers:
handlePopoverOpen = (event) => { this.setState({ anchorEl: event.currentTarget, searchPopoverOpen: true }); }; handlePopoverClose = () => { this.setState({ searchPopoverOpen: false, anchorEl: null }); };
Add the Popover component after your search button:
<Popover open={this.state.searchPopoverOpen} anchorEl={this.state.anchorEl} onClose={this.handlePopoverClose} anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }} > <List style={{ minWidth: 300 }}> {this.props.search.results.length === 0 ? ( <ListItem> <ListItemText primary="No results found" /> </ListItem> ) : ( this.props.search.results.map((result) => ( <ListItem key={result.id} component={Link} to={`/post/${result.id}`} // Replace with your detail page route onClick={this.handlePopoverClose} > <ListItemText primary={result.name || result.title} /> {/* Add more document details here if needed */} </ListItem> )) )} </List> </Popover>
Key Optimization Tips
- Throttle Real-Time Search: If you want search to trigger as the user types, use Lodash's
_.throttleto limit Firestore requests (e.g., only run the query after 300ms of inactivity). - Firestore Indexes: If you get an index error in your console, follow the provided link to create the required index automatically.
- Loading State: Add a loading spinner while the search runs to improve UX—add a
loadingfield to your search reducer and update it before/after the query. - Security Rules: Ensure your Firestore rules allow authenticated users to read the collection:
match /your-collection-name/{document} { allow read: if request.auth != null; }
内容的提问来源于stack exchange,提问作者Loki00

