Can the updater function in useState be redefined in React? - reactjs

I'm working on a rather complex component that uses a store object. The data is local, so it doesn't warrant being added to my Redux store, but expensive enough that it shouldn't be calculated every render. I'm using useState to store and update this object. However, I have several functions regarding the store that I'd like to break out into a new file, including the updater function. For example, I'd like to do something akin to this:
import { storeUpdater } from './ComponentStore.js';
function MyComponent(props) {
const updateStore = storeUpdater;
let storeState = useState({});
const store = storeState[0];
storeState[1] = updateStore;
...
}
Would this reliably work, and, more importantly, is it breaking any rules/anti-patterns?

This shouldn't work. You're just re-assigning storeState[1], which you defined, to a different function, other than setState provided by useState. This code shouldn't update your storeState[0] at all.
Instead, you should make your storeUpdater function take setState function as an argument and provide it in this component.
function storeUpdater(setState) {...}
const updateStore = () => storeUpdater(storeState[1])
And then, inside your updateStore, do the desirable modifications to the storeState and then pass the new state to setState. If the new state depends on the previous state, you can use setState(prevState => nextState) syntax.

Related

Is there a way to modify a value when its changed after rendering without useEffect

I have a react app that fetches the data that I need on render.
When the data arrives, I have a useEffect in place that will format the data and update the variable required.
My issue now is that the format function that we are using has been changed to a hook, which I can't use in the useEffect anymore, and I was wondering if there was a way to basically replicate the process without the useEffect
For a simplified example, originally I had something similar to this component:
const MyComponent = ({myValue}) => {
const [outputVal, setOutputVal] = useState(0);
useEffect(() => {
if (myValue != null) setOutputVal(formatCurrency(myValue));
}, [myValue]);
[...]
formatCurrency was a simple function using the Intl.NumberFormat functionality, and would just convert the value as needed. I've now swapped to using the react-i18next library, which has other functionality that I want, but it uses hooks, which means I can't use the function within the useEffect.
I've tried to change it to use the function when the page loads, ie:
const [outputVal, setOutputVal] = useState(formatCurrency(myValue));
The issue that I've found though is that the initial value of myValue is null, and the function doesn't rerun when myValue is updated.
Any help or info on this would be great.
Since formatCurrency probably just formats the numerical value into a string, you could just run it on every render and treat it like any ordinary variable you'd use during rendering, i.e.:
const MyComponent = ({myValue}) => {
const outputVal = formatCurrency(myValue);
return <p>{outputVal}</p>
};

Difference between useCallback hook and empty dependency array and just defining a function outside of the component

In react, we can memoize a function using useCallback so the function doesn't change every rerender
const fn = useCallback(someOtherFn, []);
Instead, can we define someOtherFn outside the component, and if it uses setState, we give that to it as an argument?
Something like
function someOtherFn(setState) {
setState("ok")
}
function myComponenet(){
......
}
Sorry in advance if this is a stupid question.
You can do that, but it might probably defeat the purpose, since you'd have to write another function that calls it, and that other function would be created every render. For example, perhaps you're thinking of:
function someOtherFn(setState) {
setState("ok")
}
const MyComponent = () => {
const [state, setState] = useState();
return <button onClick={() => someOtherFn(setState)}>click</button>;
};
Although someOtherFn indeed wouldn't need to be created every time - it'd only need to be created once - the handler function, the
() => someOtherFn(setState)
would be created every time the component renders, which could be a problem in certain uncommon (IMO) situations.
To avoid this issue, you would have to bind the setState argument to the function persistently somehow - which would be most easily accomplished through the use useCallback.

How to store values of a function type (e.g. arrow functions) with the React Hook useState?

In Javascript and Typescript, (arrow) functions are supposed to be first class citizens. Hence, I expect that I can have function types in my React state. However, the React Hook useState does not seem to play nicely with function types.
I have the following code:
import React, { useState } from "react";
function callApi(num: number): number {
console.log(`Api called with number ${num}. This should never happen.`);
return num;
}
type Command = () => number;
function Foo() {
const [command, setCommand] = useState<Command>();
console.log(`command is ${command}.`);
// ####################
const handleButtonClick = () => {
console.log("Button clicked.");
const myCommand = () => callApi(42);
setCommand(myCommand);
};
// ####################
return (
<div>
<button onClick={handleButtonClick}>Change state</button>
</div>
);
}
export default Foo;
in this Codesandbox
When I visit the page and click the button, I get the following console output:
command is undefined.
Button clicked.
Api called with number 42. This should never happen.
command is 42.
Hence, one can see that although in my button handler the state variable command should be set to a new arrow function, React does not do that. Instead, it executes the new arrow function and stores the result in the state variable.
Why is that and how can I store functions in React state without having to use some inconvenient wrapper object?
For context: Quite often it would be convenient to build certain command functions by means of various user inputs and store them in state for later use, e.g. when building a job queue.
Why is that and how can I store functions in React state without having to use some inconvenient wrapper object?
Why?
React's useState takes in either a function (that it'll use as callback that gets the current value of the state) or a value, so if you pass () => callApi(42) it will understand it like you want the new State to be the return value of callApi when passing in a 42.
What can you do?
If you really need to do that this way (storing a function in the state), you can do something like useCommand(() => myCommand).
However, I would suggest you don't store functions in your component's state.
If you need a new instance of a function (or a new function) when something in your code has changed, use useCallback or useMemo instead.
Either will create a new function whenever one of the values specified in the dependencies array is changed.
useCallback will create a new function when their dependencies changed, so you can use it like:
function Button() {
const [buttonAction, setButtonAction] = useState(null);
// dynamicHandler will be a new function every time buttonAction changes
const dynamicHandler = useCallback(() => {
// Logic here based on the buttonAction value
}, [buttonAction]);
const handleClick = () => {
setButtonAction(BUTTON_ACTIONS.DO_SOMETHING);
};
return (
<button onClick={handleClick} />
);
}
Check out the useCallback documentation.
You can store function as object
setCommand({ function:()=>callApi(42) })
Then when you want to call the function stored in state, you can simply do command.function()

Setting Redux state as a default state when using React Hooks

I have a redux action that fetches all data and stores it into a global Redux store.
I want to store that state in a local state using Hooks so that the actual state doesn't get changed when I change it locally.
What I am doing right now is,
const [filteredTable, setFilteredTable] = useState([])
useEffect(() => {
props.fetchDatabase();
props.fetchOptions();
setFilteredTable(props.filtered_table_data);
}, [])
In useEffect, the props.fetchDatabase() gets the props.filtered_table_data and I can see that when I console.log it out.
However, when I use Hooks to store it into a local state and check if it's in there,
console.log(filteredTable, 'filteredTable')
just gives me [].
What am I doing wrong?
I believe the props.fetchDatabase() call is asynchronous, so by the time you are attempting to setFilteredTable the props.filtered_table_data has not updated yet.
You can try something like this:
useEffect(() => {
props.fetchDatabase();
props.fetchOptions();
}, [])
useEffect(() => {
setFilteredTable(props.filtered_table_data);
}, [props.filtered_table_data]);
Note that this effect will run every time filtered_table_data changes, so you may need to wrap around the code in the callback with some sort of condition if you want to restrict setting the local state.
useEffect's callback with [] as hook's second argument is only being called once when component just mounted. Inside it fetchDatabase, and fetchOptions callbacks are called, and right after that (when data isn't yet fetched) you call setFilteredTable, that's why there are empty array occurs in filteredTable.
Not sure if this answers your question, but React-Redux provides some hooks for accessing the redux store.
The one that might be of interest to you is the useSelector() hook.
Here's an example usage:
import { useSelector } from 'react-redux';
const App = () => {
const tableData = useSelector(state => state.tableData);
...
}

Prevent passing a new function as a prop in a functional component that uses useState

I know that passing a new function every render as a prop to a react component can affect performance (for example passing an arrow function). In a class component I usually solve this problem by passing a method of the class.
But say we have a functional component that uses say useState and I want to create a closure that calls a function returned from useState. This function will be created anew every render, so whatever component it is passed to will be rerendered. Is there a way to avoid passing a new function that refers to some variable, function inside of a functional component?
import React from 'react'
import IDontWantToRerender from './heavyComputations';
export default function IneffectiveFunctionalComponent() {
const [value, setValue] = useState(1);
const valueChanger = () => {
setValue(Math.random());
}
return (
<IDontWantToRerender valueChanger={valueChanger} value={value} />
)
}
You can use the useCallback hook.
const valueChanger = useCallback(
() => {
setValue(Math.random());
},[setValue]); // you need some other dependencies for when it should change
As said in the comments
I wonder, is it really necessary to pass setValue as it will not change?
Yes, if you take a look at the docs, it says
Note
The array of dependencies is not passed as arguments to the callback. Conceptually, though, that’s what they represent: every value referenced inside the callback should also appear in the dependencies array. In the future, a sufficiently advanced compiler could create this array automatically.
We recommend using the exhaustive-deps rule as part of our eslint-plugin-react-hooks package. It warns when dependencies are specified incorrectly and suggests a fix.

Resources