简体   繁体   中英

Prevent re-render using React.memo and React.useCallback

For learning purpose,

I am trying prevent re-render on <InputWithLable /> component whenever i Dismiss a search result (see deploy in Full code )

I have use React.memo but it still re-render. So I think maybe its props is the culprit. I use React.useCallback to handleSearch prop, but it doesn't work.

Full code

 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script> import React from 'react'; const API_ENDPOINT = 'https://hn.algolia.com/api/v1/search?query='; const useSemiPersistentState = (key, initialState) => { const [value, setValue] = React.useState( localStorage.getItem(key) || initialState ); React.useEffect(() => { localStorage.setItem(key, value); }, [value, key]); return [value, setValue]; }; function storiesReducer(prevState, action) { switch (action.type) { case "SET": return { ...prevState, data: action.data, isLoading: false, isError: false }; case "REMOVE": return { ...prevState, data: prevState.data.filter( story => action.data.objectID !== story.objectID ) } case "ERROR": return { ...prevState, isLoading: false, isError: true }; default: throw new Error(); } } const App = () => { const [searchTerm, setSearchTerm] = useSemiPersistentState( 'search', 'Google' ); const [stories, dispatchStories] = React.useReducer(storiesReducer, { data: [], isLoading: true, isError: false }); const [url, setUrl] = React.useState(""); const handleFetchStories = React.useCallback(() => { fetch(url) .then((response) => response.json()) .then((result) => { console.log(result); dispatchStories({ type: "SET", data: result.hits }) }) .catch(err => dispatchStories({ type: "ERROR", data: err })) }, [url]) React.useEffect(() => { handleFetchStories(); }, [handleFetchStories]) const handleRemoveStory = React.useCallback( (item) => { dispatchStories({ type: "REMOVE", data: item }); }, [], // chi render 1 lan vi props khong thay doi ) const handleSearch = React.useCallback( (e) => { setSearchTerm(e.target.value); }, [], ) // Chuc nang filter la cua server (vd: database) // const searchedStories = stories.data ? stories.data.filter(story => // story.title.toLowerCase().includes(searchTerm.toLowerCase()) // ) : null; // nghich cai nay! console.log('App render'); return ( <div> <h1>My Hacker Stories</h1> <InputWithLabel id="search" value={searchTerm} isFocused onInputChange={handleSearch} > <strong>Search:</strong> </InputWithLabel> <button onClick={() => setUrl(API_ENDPOINT + searchTerm)}>Search!</button> <hr /> {stories.isError && <h4>ERROR!</h4>} {stories.isLoading ? <i>Loading...</i> : <List list={stories.data} onRemoveItem={handleRemoveStory} />} </div> ); }; const InputWithLabel = React.memo( ({ id, value, type = 'text', onInputChange, isFocused, children, }) => { const inputRef = React.useRef(); React.useEffect(() => { if (isFocused) { inputRef.current.focus(); } }, [isFocused]); console.log('Search render') return ( <> <label htmlFor={id}>{children}</label> &nbsp; <input ref={inputRef} id={id} type={type} value={value} onChange={onInputChange} /> </> ); } ); // Prevent default React render mechanism: Parent rerender -> Child rerender const List = React.memo( ({ list, onRemoveItem }) => console.log('List render') || list.map(item => ( <Item key={item.objectID} item={item} onRemoveItem={onRemoveItem} /> )) ); const Item = ({ item, onRemoveItem }) => ( <div> <span> <a href={item.url}>{item.title}</a> </span> <span>{item.author}</span> <span>{item.num_comments}</span> <span>{item.points}</span> <span> <button type="button" onClick={() => onRemoveItem(item)}> Dismiss </button> </span> </div> ); export default App;

You should not be looking at how many times a component's render function gets called; React is free to call it as many times as it likes (and indeed, in strict mode, it calls them twice to help you not make mistakes).

But to answer your question (with the actual code that uses children ):

<InputWithLabel>
   <strong>Search:</strong>
</InputWithLabel>

compiles down to

React.createElement(InputWithLabel, null,
    React.createElement("strong", null, "Search:"))

the identity of the children prop (the <strong /> element) changes for each render of the parent component since React.createElement() returns new objects for each invocation. Since that identity changes, React.memo does nothing.

If you wanted to (but please don't ), you could do

const child = React.useMemo(() => <strong>Search:</strong>);
// ...
<InputWithLabel>{child}</InputWithLabel>

but doing that for all of your markup leads to nigh-unreadable code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM