Should renderItem of FlatList be wrapped with react useCallback hook? - javascript

const Component = React.memo(props => {
const { url } = props;
const keyExtractor = useCallback(item => item.id, []);
const handleClick = useCallback(() => {
Linking.openURL(url);
}, [url]);
const renderItem = useCallback(({ item }) => {
return (
<TouchableOpacity onPress={handleClick}>
<Text>Test</Text>
</TouchableOpacity>
);
}, [handleClick]);
return (
<FlatList
data={data}
keyExtractor={keyExtractor}
renderItem={renderItem}
/>
);
});
Just like the code above.
If the renderItem function is wrapped with a useCallback hook. it must check if the handleClick function reference is changed because the handleClick function may be changed if url is different. and it looks a little strange.
I wonder if there is a significant difference in the performance of using useCallback to wrap renderItem.
What is the best practice of using hooks for renderItem functions? Thanks

Because you already use React.memo the Component will not be executed unless url changes, if url changes the useCallback will re create the functions anyway so you can leave them out provided that is the only prop that can change.
Here is some code demonstrating this, you can re render App as much as you want but it won't re render other components unless you change url.
const SubComponent = ({ onClick }) => {
console.log('sub component render');
return <button onClick={onClick}>log url</button>;
};
const PureComponent = React.memo(function PureComponent({
url,
}) {
console.log('pure component render', url);
//even if I do React.useCallback(fn,[url]) that would mean
// it creates onClick when url changes but it would already
// only create onClick when url changes because memo will
// memoize the component result and not execute PureComponent
// unless the url changes
const onClick = () => console.log('url is', url);
return <SubComponent onClick={onClick} />;
});
const App = () => {
const [, reRenderApp] = React.useState({});
const [url, setUrl] = React.useState(
new Date().toUTCString()
);
console.log('rendering App');
return (
<div>
<button onClick={() => reRenderApp({})}>
re render app
</button>
<button
onClick={() => setUrl(new Date().toUTCString())}
>
set url
</button>
<PureComponent url={url} />
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Related

Passing components as state in React (Tab functionality)

Is it possible to pass other components through a state? I'm trying to make a tab function like a web browser, and if the user clicks the tab, a component shows up.
In my app.js I have -
const[chosenTab, setChosenTab] = useState("")
return (
<>
<Main chosenTab = {chosenTab}/>
</>
);
In Main.js -
const Main = ({chosenTab}) => {
return (
<>
{chosenTab}
</>
)
}
With the code below, the logic works to display the name of the tab/other component, but doesn't work if I replace {chosenTab} with <{chosenTab}/> to pass it as a component rather than just html.
I don't think this would work as you've structured it - I'd be welcome to have someone prove me wrong though since that would be a neat trick.
Now if I had to solve this problem, I'd simply use a object to hold what I need:
const tabMap = {
"string1": <component1 />,
"string2": <component2 />,
"string3": <component3 />
}
const Main = ({chosenTab}) => {
return (
<>
{tabMap[chosenTab]}
</>
)
}
Even further, let's say you wanted to pass in custom props, you could make tabMap a function to do that.
You can pass component reference itself as a tab.
const TabA = () => <div>Tab A</div>
const TabB = () => <div>Tab B</div>
const Main = ({ ChosenTab }) => {
retur <ChosenTab />
}
const App = () => {
const [chosenTab, setChosenTab] = useState(() => TabA);
const changeTab = (tab) => setChosenTab(() => tab);
return <Main ChosenTab={chosenTab} />
}
export default App;
Or you can store your tabs in object, Map or Array and set state accordingly
const tabs = {
A: TabA,
B: TabB
}
const App = () => {
const [chosenTab, setChosenTab] = useState(() => tabs.A);
const changeTab = (tabKey) => setChosenTab(() => tabs[tabKey]);
return <Main ChosenTab={chosenTab} />
}
export default App;

How to stop sibling components from Re-Rendering?

I'm trying to toggle the visibility of a component using another component like this:
const CompA = React.memo(({Vis})=>{
console.log('Rendered CompA');
return(<Pressable onPress={()=>{Vis[1](!Vis[0])}} style={...}></Pressable>);
})
const CompB = React.memo(({Vis})=>{
console.log('Rendered CompB');
return(<>{Vis[0]&&(<View style={...}></View>)}</>);
})
export default function App() {
const Vis=useState(()=>true);
return (<View style={styles.body}>
<CompA Vis={Vis}/>
<CompB Vis={Vis} />
</View>);
}
but every time I toggle the visibility even CompA gets re rendered
How do I only re render CompB everytime I change the visibility?
The array you receive from useState will never be === a previous array you received from useState; React creates a new array when you call useState each time. Just like [] === [] is always false, the check being done by memo on the props will always be false and it will re-render.
There are at least two ways to solve the problem:
Pass the component parts of that array instead, since the setter function is guaranteed to be stable, and only pass CompA the setter function since it doesn't need the flag (it can use the callback form of the setter).
If you really, really want to pass the array around instead, implement a custom memo callback for CompA that only looks at the second element in the array, and don't use the first element of the array in CompA. But I'd strongly recommend not doing that, the readability/semantics of it are very misleading/surprising.
Here's #1:
const CompA = React.memo(({setVisible}) => {
console.log("Rendered CompA");
return (<Pressable onPress={() => {setVisible(visible => !visible)}} style={...}></Pressable>);
});
const CompB = React.memo(({visible}) => {
console.log("Rendered CompB");
return (<>{visible && (<View style={...}></View>)}</>);
});
export default function App() {
const [visible, setVisible] = useState(true); // No need for the callback form here
return (<View style={styles.body}>
<CompA setVisible={setVisible} />
<CompB visible={visible} />
</View>);
}
Live Example:
const {useState} = React;
const CompA = React.memo(({setVisible}) => {
console.log("Rendered CompA");
return (<button onClick={() => {setVisible(visible => !visible)}}>pressable</button>);
});
const CompB = React.memo(({visible}) => {
console.log("Rendered CompB");
// Unfortunately, the version of Babel used by Stack
// Snippets is so old it doesn't understand shorthand
// fragment syntax
return (<React.Fragment>{visible && (<div>this is the View</div>)}</React.Fragment>);
});
function App() {
const [visible, setVisible] = useState(true); // No need for the callback form here
return (<div>
<CompA setVisible={setVisible} />
<CompB visible={visible} />
</div>);
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
You need to pass particular props in components. isVisible and the function which change the value of isVisible. When passing function as p props to components and using React.memo you need to use useCallback in function hooks to prevent re-render stuff.
Read more about memo and usecallback
import React from "react";
import ReactDOM from "react-dom";
const { useState, useCallback, memo } = React;
const CompA = memo(({ handleChange }) => {
console.log("Rendered CompA");
return <Pressable onPress={handleChange}>Comp A</Pressable>;
});
const CompB = memo(({ isVisible }) => {
console.log("Rendered CompB", isVisible);
return isVisible && <View> CompB</View>;
});
function App() {
const [isVisible, setVisible] = useState(true);
const handleChange = useCallback(() => setVisible((isVisible) => !isVisible),[]);
return (
<View style={styles.body}>
<CompA handleChange={handleChange} />
<CompB isVisible={isVisible} />
</View>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
Live Example
const { useState, useCallback, memo } = React;
const CompA = memo(({ handleChange }) => {
console.log("Rendered CompA");
return <button onClick={handleChange}>Comp A</button>;
});
const CompB = memo(({ isVisible }) => {
console.log("Rendered CompB");
return isVisible && <div> CompB</div>;
});
function App() {
const [isVisible, setVisible] = useState(true);
const handleChange = useCallback(
() => setVisible((isVisible) => !isVisible),
[]
);
return (
<div>
<CompA handleChange={handleChange} />
<CompB isVisible={isVisible} />
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
<div id="root"></div>
I have a solution but it doesn't look pretty:
let UniVis;
const CompA = React.memo(({Vis})=>{
console.log('Rendered CompA');
return(<Pressable onPress={()=>{UniVis[1](!UniVis[0])}} style={...}></Pressable>);
});
const CompB = React.memo(({Vis})=>{
console.log('Rendered CompB');
return(<>{Vis[0]&&(<View style={...}></View>)}</>);
})
export default function App() {
const Vis=useState(()=>true);
UniVis=Vis;
return (<View style={styles.body}>
<CompA />
<CompB Vis={Vis} />
</View>);
}
but it does work

React Context Api and State hooks mutation

I have a modern react application using the context api and many hooks, i use the context to store global values for my application, this values or the context itself should never directly re-render other components, the context itself has a its own getter/setter in form of the UseState hook/s which is what is called from the consumer components to be used, if any component is dependent on the context data a separate state in this component itself is created and state is then being properly handled.
My concrete question in my case how bad is it to directly mutate the object i have stored in the context?
for example from any random consumer component changing the context object as follows:
const handlerFunction = () => {contextObjData.value = "Something"};
Instead of the "intended" react way:
const handlerFunction = () => {setContextObjData(...contextObjData, value: "Something")};
To me it seems overkill to each time save the entire object again but maybe someone can give me another perspective and some insights.
Side question kind of nooby but i am not sure, is there a difference between these two:
const handlerFunction = () => {setContextObjData(...contextObjData, value: "Something")};
const handlerFunction = () => {setContextObjData(prevState => ({...contextObjData, value: "Something"}));
A state change will trigger a render. When you mutate something then React won't detect that the state has changed and will not re render.
The handlerFunction examples matter only if you want to optimize it using useCallback but the way you do it it is broken either way (syntax error and not using prevState in the second example).
//handlerFunction will be re created every render
const handlerFunction = () =>
setContextObjData({
...contextObjData,
value: 'Something',
});
//handler function will only be created on mount
const optimizedHandler = React.useCallback(
() =>
setContextObjData((prevState) => ({
...prevState,
value: 'Something',
})),
[] //empty dependency, only create optimizedHandler on mount
);
//broken handler, using stale closure will give you liner warning
const brokenHandler = React.useCallback(
() =>
//callback only used on mount so has contextObjData
// as it was when mounted (stale closure)
setContextObjData({
...contextObjData, //needs contextObjData in closure scope
value: 'Something',
}),
[] //empty dependency, but contextObjData will be a stale closure
);
Pure components will only re render when props change, state change or return value from useSelector or useContext change.
When you pass a callback as a prop to a child and your component re renders without the child needing to re render you can optimize a passed callback with useCallback so the child doesn't get needlessly re rendered:
//Child is a pure component
const Child = React.memo(function Increment({ increment }) {
const r = React.useRef(0);
r.current++;
return (
<button onClick={increment}>
rendered: {r.current} times, click to increment
</button>
);
});
const Parent = () => {
const [count, setCount] = React.useState(1);
const increment = React.useCallback(
() => setCount((c) => c + 1),
[]
);
React.useEffect(() => {
const t = setInterval(() => increment(), 1000);
return () => clearInterval(t);
}, [increment]);
return (
<div>
<h4>{count}</h4>
<Child increment={increment} />
</div>
);
};
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Here is an example how mutation will break re rendering:
const CounterContext = React.createContext();
const CounterProvider = ({ children }) => {
console.log('render counter provider');
const [c, setC] = React.useState({ count: 0 });
const increment = React.useCallback(
() =>
setC((c) => {
console.log('broken:', c.count);
c.count++;
return c;
}), //broken, context users never re render
[]
);
return (
<CounterContext.Provider value={[c, increment]}>
{children}
</CounterContext.Provider>
);
};
const App = () => {
console.log('render App');
const [count, increment] = React.useContext(
CounterContext
);
return (
<div>
<h4>count: {count.count}</h4>
<button onClick={increment}>+</button>
</div>
);
};
ReactDOM.render(
<CounterProvider>
<App />
</CounterProvider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Usage of useCallback and setting new object state using previous state as argument

Consider this basic form fields component with a custom form hook to handle input changes:
import React, { useState, useCallback } from 'react';
const useFormInputs = (initialState = {})=> {
const [values, setValues] = useState(initialState);
const handleChange = useCallback(({ target: { name, value } }) => {
setValues(prev => ({ ...prev, [name]: value }));
}, []);
const resetFields = useCallback(() =>
setValues(initialState), [initialState]);
return [values, handleChange, resetFields];
};
const formFields = [
{ name: 'text', placeholder: 'Enter text...', type: 'text', text: 'Text' },
{ name: 'amount', placeholder: 'Enter Amount...', type: 'number',
text: 'Amount (negative - expense, positive - income)' }
];
export const AddTransaction = () => {
const [values, handleChange, resetFields] = useFormInputs({
text: '', amount: ''
});
return <>
<h3>Add new transaction</h3>
<form>
{formFields.map(({ text, name, ...attributes }) => {
const inputProps = { ...attributes, name };
return <div key={name} className="form-control">
<label htmlFor={name}>{text}</label>
<input {...inputProps} value={values[name]}
onChange={handleChange} />
</div>;
})}
<button className="btn">Add transaction</button>
</form>
<button className="btn" onClick={resetFields}>Reset fields</button>
</>;
};
Is there really any reason / advantage for me to use useCallback to cache the function in my custom hook? I read the docs, but I just coudln't grasp the idea behind this usage of useCallback. How exactly it memoizes the function between renders? How exactly does ti work, and should I use it?
Inside the same custom hook, you can see the new values state being updated by spreading the previous state and creating a new object like so: setValues(prev => ({ ...prev, [name]: value }));
Would there be any difference if I did this instead? setValues({ ...prev, [name]: value })
as far as I can tell, doesn't look like it has any difference right? I am simply accessing the state directly.. Am I wrong?
Your first question:
In your case it doesn't matter because everything is rendered in the same component. If you have a list of things that get an event handler then useCallback can save you some renders.
In the example below the first 2 items are rendered with an onClick that is re created every time App re renders. This will not only cause the Items to re render it will also cause virtual DOM compare to fail and React will re create the Itms in the DOM (expensive operation).
The last 2 items get an onClick that is created when App mounts and not re created when App re renders so they will never re render.
const { useState, useCallback, useRef, memo } = React;
const Item = memo(function Item({ onClick, id }) {
const rendered = useRef(0);
rendered.current++;
return (
<button _id={id} onClick={onClick}>
{id} : rendered {rendered.current} times
</button>
);
});
const App = () => {
const [message, setMessage] = useState('');
const onClick = (e) =>
setMessage(
'last clicked' + e.target.getAttribute('_id')
);
const memOnClick = useCallback(onClick, []);
return (
<div>
<h3>{message}</h3>
{[1, 2].map((id) => (
<Item key={id} id={id} onClick={onClick} />
))}
{[1, 2].map((id) => (
<Item key={id} id={id} onClick={memOnClick} />
))}
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Another example is when you want to call a function in an effect that also needs to be called outside of the effect so you can't put the function inside the effect. You only want to run the effect when a certain value changes so you can do something like this.
//fetchById is (re) created when ID changes
const fetchById = useCallback(
() => console.log('id is', ID),
[ID]
);
//effect is run when fetchById changes so basically
// when ID changes
useEffect(() => fetchById(), [fetchById]);
Your second question:
The setValues({ ...prev, [name]: value }) will give you an error because you never defined pref but if you meant: setValues({ ...values, [name]: value }) and wrap the handler in a useCallback then now your callback has a dependency on values and will be needlessly be re created whenever values change.
If you don't provide the dependency then the linter will warn you and you end up with a stale closure. Here is an example of the stale closure as counter.count will never go up because you never re create onClick after the first render thus the counter closure will always be {count:1}.
const { useState, useCallback, useRef } = React;
const App = () => {
const [counts, setCounts] = useState({ count: 1 });
const rendered = useRef(0);
rendered.current++;
const onClick = useCallback(
//this function is never re created so counts.count is always 1
// every time it'll do setCount(1+1) so after the first
// click this "stops working"
() => setCounts({ count: counts.count + 1 }),
[] //linter warns of missing dependency count
);
return (
<button onClick={onClick}>
count: {counts.count} rendered:{rendered.current}
</button>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

set state is not updating state

I am trying to use the state hook in my react app.
But setTodos below seems not updating the todos
link to my work: https://kutt.it/oE2jPJ
link to github: https://github.com/who-know-cg/Todo-react
import React, { useState } from "react";
import Main from "./component/Main";
const Application = () => {
const [todos, setTodos] = useState([]);
// add todo to state(todos)
const addTodos = message => {
const newTodos = todos.concat(message);
setTodos(newTodos);
};
return (
<>
<Main
addTodos={message => addTodos(message)}
/>
</>
);
};
export default Application;
And in my main.js
const Main = props => {
const input = createRef();
return (
<>
<input type="text" ref={input} />
<button
onClick={() => {
props.addTodo(input.current.value);
input.current.value = "";
}}
>
Add message to state
</button>
</>
);
};
I expect that every time I press the button, The setTodos() and getTodos() will be executed, and the message will be added to the todos array.
But it turns out the state is not changed. (still, stay in the default blank array)
If you want to update state of the parent component, you should pass down the function from the parent to child component.
Here is very simple example, how to update state with hook from child (Main) component.
With the help of a button from child component you update state of the parent (Application) component.
const Application = () => {
const [todos, setTodos] = useState([]);
const addTodo = message => {
let todosUpdated = [...todos, message];
setTodos(todosUpdated);
};
return (
<>
<Main addTodo={addTodo} />
<pre>{JSON.stringify(todos, null, 2)}</pre>
</>
);
};
const Main = props => {
const input = createRef();
return (
<>
<input type="text" ref={input} />
<button
onClick={() => {
props.addTodo(input.current.value);
input.current.value = "";
}}
>
Add message to state
</button>
</>
);
};
Demo is here: https://codesandbox.io/s/silent-cache-9y7dl
In Application.jsx :
You can pass just a reference to addTodos here. The name on the left can be whatever you want.
<Main addTodos={addTodos} />
In Main.jsx :
Since getTodo returns a Promise, whatever that promise resolves to will be your expected message.
You don't need to pass message as a parameter in Main, just the name of the function.
<Main addTodos={addTodos} />
You are passing addTodos as prop.
<Main
addTodos={message => addTodos(message)}
/>
However, in child component, you are accessing using
props.addTodo(input.current.value);
It should be addTodos.
props.addTodos(input.current.value);

Categories

Resources