I am building a basic chat application. I have a parent component ( Console.js
) that contains three child components
This is how the basic layout looks like
const Console = () => {
// initial state for input field
const [userInput, setUserInput] = useState("");
// fetch previous conversation history
const conversationHistory = useSelector(state=> state.conversationHistory.data)
conversationHistory.map((info, idx) => (
info.type == "statement" ?
<span key={idx}>
<QuestionCard
data={info}
/>
</span>
: info.type == "answer" ?
<span key={idx}>
<AnswerCard
userInput={userInput}
setUserInput={setUserInput}
data={info}
/>
</span>
:
<span></span>
))
<InputCard
userInput={userInput}
setUserInput={setUserInput}
/>
}
Specifically in the InputCard.js child component, there resides the input
field where the user types
const InputCard = ({userInput, setUserInput}) => {
const handleTextBoxInput = e => {
setUserInput(e.target.value)
}
return (
<input
type="text"
value={userInput || ""}
onChange={handleInput}
id="userQuery"
/>
)
}
The problem here is that every time I press a key, all the child components ( QuestionCard.js , AnswerCard.js , InputCard.js ) re-renders.
I read about memo
and it is one way to ensure components don't re-render but needs something to compare against. So I understand I need to compare the userInput
state before and after and check if indeed something changed. But I just don't know where do I do this comparison or whether to even use memo
Can anybody help me with this?
Note: I understand I can put the setState
inside the InputCard
component and re-rendering will stop but as you can see, I need the setState
variables inside the AnswerCard
too for some processing.
That's how React works AFAIK. A change in props, triggers a render of the component.
That said,
Here are some suggestions for your problem:
One of the common patterns around handling user input is to debounce it. This prevents the component re-render on every key press. You can tailor the debounce timer to suit your use-case:
const Border = { RED: { border: '1px solid red', margin: '12px 0', padding: '4px' }, GREEN: { border: '1px solid green', margin: '12px 0', padding: '4px' }, BLUE: { border: '1px solid blue', margin: '12px 0', padding: '4px' }, MAGENTA: { border: '1px solid magenta', margin: '12px 0', padding: '4px' } }; const MARGIN = { margin: '12px' }; function useDebounce(value, delay) { // State and setters for debounced value const [debouncedValue, setDebouncedValue] = React.useState(value); React.useEffect( () => { // Update debounced value after delay const handler = setTimeout(() => { setDebouncedValue(value); }, delay); // Cancel the timeout if value changes (also on delay change or unmount) // This is how we prevent debounced value from updating if value is changed ... // .. within the delay period. Timeout gets cleared and restarted. return () => { clearTimeout(handler); }; }, [value, delay] // Only re-call effect if value or delay changes ); return debouncedValue; } const useRenderCounter = (thing) => { const renderCount = React.useRef(1); React.useEffect(() => { renderCount.current += 1; }); return `Render count for ${thing} ${renderCount.current}`; }; const InputCard = ({ userInput, setUserInput }) => { const renderCount = useRenderCounter('InputCard'); const [input, setInput] = React.useState(userInput); const debouncedValue = useDebounce(input, 750); React.useEffect(() => { setUserInput(debouncedValue); }, [debouncedValue]); return ( <div style={Border.MAGENTA}> <span>{renderCount}</span> <div style={MARGIN}> <input type="text" value={input} onChange={(e) => setInput(e.target.value)} id="userQuery" /> </div> </div> ); }; const QuestionCard = ({ userInput, setUserInput }) => { const renderCount = useRenderCounter('QuestionCard'); return ( <div style={Border.RED}> <span>{renderCount}</span> <div style={MARGIN}>User Input: {userInput}</div> </div> ); }; const AnswerCard = ({ userInput, setUserInput }) => { const renderCount = useRenderCounter('AnswerCard'); return ( <div style={Border.GREEN}> <span>{renderCount}</span> <div style={MARGIN}>User Input: {userInput}</div> </div> ); }; function App() { const renderCount = useRenderCounter('App'); const [userInput, setUserInput] = React.useState(''); return ( <div style={Border.BLUE}> <span>{renderCount}</span> <QuestionCard userInput={userInput} setUserInput={setUserInput} /> <AnswerCard userInput={userInput} setUserInput={setUserInput} /> <InputCard userInput={userInput} setUserInput={setUserInput} /> </div> ); } ReactDOM.render(<App />, document.getElementById("react"));
span { padding: 4px; font-style: italic; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script> <div id="react"></div>
However, if you need the other components to not re-render on entering text , you can leverage useRef
hook and keep track of the user input without having to re-render the component tree.
But bear in mind, with this approach, in order for other components to show the updated value on the DOM , there has to be a trigger to cause a re-render. In the example, the state is updated on button click which then triggers the re-render. Since you mentioned you are building a chat app, maybe you'd find this pattern useful.
const Border = { RED: { border: '1px solid red', margin: '12px 0', padding: '4px' }, GREEN: { border: '1px solid green', margin: '12px 0', padding: '4px' }, BLUE: { border: '1px solid blue', margin: '12px 0', padding: '4px' }, MAGENTA: { border: '1px solid magenta', margin: '12px 0', padding: '4px' } }; const MARGIN = { margin: '12px' }; const useRenderCounter = (thing) => { const renderCount = React.useRef(1); React.useEffect(() => { renderCount.current += 1; }); return `Render count for ${thing} ${renderCount.current}`; }; const InputCard = ({ userInput, setUserInput }) => { const renderCount = useRenderCounter('InputCard'); const [input, setInput] = React.useState(userInput); React.useEffect(() => { setUserInput(input); }, [input]); return ( <div style={Border.MAGENTA}> <span>{renderCount}</span> <div style={MARGIN}> <input type="text" value={input} onChange={(e) => setInput(e.target.value)} id="userQuery" /> </div> </div> ); }; const QuestionCard = ({ userInput, setUserInput }) => { const renderCount = useRenderCounter('QuestionCard'); return ( <div style={Border.RED}> <span>{renderCount}</span> <div style={MARGIN}>User Input: {userInput}</div> </div> ); }; const AnswerCard = ({ userInput, setUserInput }) => { const renderCount = useRenderCounter('AnswerCard'); return ( <div style={Border.GREEN}> <span>{renderCount}</span> <div style={MARGIN}>User Input: {userInput}</div> </div> ); }; function App() { const renderCount = useRenderCounter('App'); const inputRef = React.useRef(''); const setUserInput = (input) => { inputRef.current = input; }; const [submit, onSubmit] = React.useState(''); return ( <div style={Border.BLUE}> <span>{renderCount}</span> <QuestionCard userInput={inputRef.current} setUserInput={setUserInput} /> <AnswerCard userInput={inputRef.current} setUserInput={setUserInput} /> <InputCard userInput={inputRef.current} setUserInput={setUserInput} /> <button type="submit" onClick={() => onSubmit(inputRef.current)}> Trigger Render </button> </div> ); } ReactDOM.render(<App />, document.getElementById("react"));
span { padding: 4px; font-style: italic; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script> <div id="react"></div>
React.memo()
React.memo()
is used to memoize a component, based on the dependencies passed to it. It takes a component as a prop and returns a component that prevents a component from re-rendering if the props (or values within it) have not changed.
It is important to keep in mind that your code must not depend on React.memo()
just to avoid re-renders. You should be able to replace React.memo()
with direct component calls and it must not affect anything else, except performance.
This method only exists as a performance optimization. Do not rely on it to “prevent” a render, as this can lead to bugs. - Docs
I honestly do not see any benefits by using a memo for your case. Anyways, this is ideally how you can memoize a component:
const QuestionCardMemoized = React.memo(
() => QuestionCard({ userInput, setUserInput }),
[userInput, setUserInput]
);
const AnswerCardMemoized = React.memo(
() => AnswerCard({ userInput, setUserInput }),
[userInput, setUserInput]
);
return (
<div>
<QuestionCardMemoized />
<AnswerCardMemoized />
<InputCard userInput={userInput} setUserInput={setUserInput} />
</div>
);
Do keep in mind that React is VERY fast. Unless you see any performance issues by measuring using profiler tools, I'd say spend your time on something more useful than unnecessary/pre-mature optimization.
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.