Access the latest state from external function React JS - javascript

I have a simple React component (Hooks):
// inside React component
import { someExternalFunction } from "functions"
const [value, setValue] = useState(0)
const handleChange = () => {
someExternalFunction(value, setValue)
}
// outside of React component
const someExternalFunction = (value, setValue) => {
console.log(value) // 0
// testing "set" method
setValue(100) // working
// "set" is async, so lets wait
// testing "get" method
setTimeout(() => console.log(value), 5000) // 0
// not working
}
PROBLEM: "value"/state is always the same, it is captured at the moment when state is passed to a function.
QUESTION: How to access the latest state in external function?
CLARIFICATION: Of course it is not working with the "value"/state, I just tried to illustrate the problem that I'm facing with preferred method for providing the latest state to the function (trouh the direct reference) .

Value is primitive type, not reference type, it was pass into function as a copied value, of cause it is never changed. And setTimeout always access a copied value.

As long as you don't trigger "handleChange" again, the function won't use the new value. Your state is a number so it will not pass a reference but rather the value itself (in this case 0). When you call setValue, the state is updated but your timeOut callback still uses the stale value that was passed when you triggered "handleChange".
If you want to react to a state change a useEffect hook would be better.
You can also simply put a console.log(value) below the line where you define the state, to check if it changes.
// Update:
const [value, setValue] = React.useState(0);
const handleChange = () => {
setValue(100);
}
React.useEffect(() => console.log(value), [value]);
Or if you want to move that logic to a reusable hook:
// useExternalFunction.js
export useExternalFunction = () => {
const [value, setValue] = React.useState(0);
const handleChange = () => {
setValue(100);
}
React.useEffect(() => console.log(value), [value]);
return {value, handleChange};
}
// component
const {value, handleChange} = useExternalFunction();
return <button onClick={handleChange}>{value}</button>;

Related

onSnapshot in firebase fires multiple times [duplicate]

I use a lot of firestore snapshots in my react native application. I am also using React hooks. The code looks something like this:
useEffect(() => {
someFirestoreAPICall().onSnapshot(snapshot => {
// When the component initially loads, add all the loaded data to state.
// When data changes on firestore, we receive that update here in this
// callback and then update the UI based on current state
});;
}, []);
At first I assumed useState would be the best hook to store and update the UI. However, based on the way my useEffect hook is set up with an empty dependency array, when the snapshot callback gets fired with updated data and I try to modify the current state with the new changes, the current state is undefined. I believe this is because of a closure. I am able to get around it using useRef with a forceUpdate() like so:
const dataRef = useRef(initialData);
const [, updateState] = React.useState();
const forceUpdate = useCallback(() => updateState({}), []);
useEffect(() => {
someFirestoreAPICall().onSnapshot(snapshot => {
// if snapshot data is added
dataRef.current.push(newData)
forceUpdate()
// if snapshot data is updated
dataRef.current.find(e => some condition) = updatedData
forceUpdate()
});;
}, []);
return(
// JSX that uses dataRef.current directly
)
My question is am I doing this correct by using useRef along with a forceUpdate instead of useState in a different way? It doesn't seem right that I'm having to update a useRef hook and call forceUpdate() all over my app. When trying useState I tried adding the state variable to the dependency array but ended up with an infinite loop. I only want the snapshot function to be initialized once and the stateful data in the component to be updated over time as things change on the backend (which fires in the onSnapshot callback).
It would be better if you combine useEffect and useState. UseEffect will setup and detach the listener, useState can just be responsible for the data you need.
const [data, setData] = useState([]);
useEffect(() => {
const unsubscribe = someFirestoreAPICall().onSnapshot(snap => {
const data = snap.docs.map(doc => doc.data())
this.setData(data)
});
//remember to unsubscribe from your realtime listener on unmount or you will create a memory leak
return () => unsubscribe()
}, []);
Then you can just reference "data" from the useState hook in your app.
A simple useEffect worked for me, i don't need to create a helper function or anything of sorts,
useEffect(() => {
const colRef = collection(db, "data")
//real time update
onSnapshot(colRef, (snapshot) => {
snapshot.docs.forEach((doc) => {
setTestData((prev) => [...prev, doc.data()])
// console.log("onsnapshot", doc.data());
})
})
}, [])
I found that inside of the onSnapshot() method I was unable to access state(e.g. if I console.log(state) I would get an empty value.
Creating a helper function worked for, but I'm not sure if this is hack-y solution or not but something like:
[state, setState] = useState([])
stateHelperFunction = () => {
//update state here
setState()
}
firestoreAPICall.onSnapshot(snapshot => {
stateHelperFunction(doc.data())
})
use can get the currentState using callback on set hook
const [state, setState] = useState([]);
firestoreAPICall.onSnapshot(snapshot => {
setState(prevState => { prevState.push(doc.data()) return prevState; })
})
prevState will have Current State Value

Does `localStorage.setItem` inside a `setState` fonction affect performance?

In React setState can take a callback fonction that return new state and take previousState as argument.
To avoid using a useCallback I try to not use the previous state inside a function.
const [state, setState] = useState();
const setValue = (callback) => {
setState(prev => {
const newValue = callback(prev);
localStorage.setItem("demo", JSON.stringify(newValue))
return newValue;
})
}
I know I can use useEffect instead, but I just want to know if this code is ok without useEffect

How to use selectors and dispatch with a custom hook?

I'm trying to dispatch a Redux action from a custom hook with useDispatch but I'm getting an "Invalid hook call" error. If I'm understanding the rules of hooks correctly, I don't think I'm breaking any of the rules as I'm calling useDispatch from a custom hook. What am I missing??
CODE
Main selector for functional component
export const getFlattenedDevicesList = createSelector(
getDevicesList,
(deviceList) => {
return (
deviceList
.map(makeOrganizationKey)
.map(makeProjectKey)
.map(makeDeviceProfileKey)
.map(DispatchLSU) // custom hook
);
}
);
Custom hook
export function DispatchLSU(resource) {
const dispatch = useDispatch();
console.log(resource.device_id);
const deviceId = resource.device_id;
// useEffect(() => {
// dispatch(getHealthLsu(deviceId));
// });
dispatch(getHealthLsu(deviceId));
const result = {
...resource,
};
return result;
};
The goal is to dispatch the action for each entry in deviceList then insert new data from API call.
Hooks rule: Only Call Hooks from React Functions
This code is breaking hooks rules, Only Call Hooks from React Functions.
You are probably calling getFlattenedDevicesList with useSelector then inside of this you call DispatchLSU who call useDispatch.
So you have a hook inside a hook that takes a callback:
useSelector -> getFlattenedDevicesList -> DispatchLSU -> useDispatch
How to call a selector and dispatch with hooks ?
If you want to call dispatch and your selector inside a hook. Here is how to achieve it
export function DispatchLSU() {
const dispatch = useDispatch();
const resource = useSelector(getFlattenedDevicesList);
const deviceId = resource.device_id;
useEffect(() => {
dispatch(getHealthLsu(deviceId));
}, []);
const result = {
...resource,
};
return result;
};
Additional notes
Convention is to name custom hooks with useXXX
Instead of dispatching inside the hook method, you can use useDispatch or useCallback to avoid making a dispatch when you render
Instead of having a selector that return all resources you can have another one that return only the deviceId
Your issue is that getFlattenedDevicesList is a normal function and a normal function can not call a hook. Hooks can only be called from the top level of either 1. a react component or 2. another hook. You can convert getFlattenedDevicesList to a hook by using another hook inside it, but then you can only do so at the top level.
i.e.
const getFlattenedDevicesList = () => {
// you can only use hooks here
if (true) {
// you can't use hooks here
// or anywhere that's not at top level of getFlattenedDevicesList
}
}
The entrypoint or root of where you start calling that hook from, along with the entire hook chain it may or may not be calling, must be either a component or a hook.
Example:
const example = () => {}
That is a normal function, not a hook.
const useExample = () => {}
That is a normal JS function, not a hook.
const useExample = () => {
const [value, setValue] = useState()
}
That's a custom hook since it's using another hook, react will figure out that it needs to be a hook as well.
const example = () => {
const [value, setValue] = useState()
}
That's also exactly the same custom hook as above and will work exactly the same as the hook above, it's just called different.
If you want to chain hooks:
const useHook1 = () => {
const [value, setValue] = useState()
return {value, setValue}
}
const useHook2 = () => {
const {value, setValue} = useHook1()
return {value}
}
That's valid because any hook can also call another hook, just like you did when using useState.
However if you now want to use useHook2 somewhere that has to be called from another hook or a functional component.
i.e.
const Component = (): JSX.Element => {
const {value} = useHook2()
}
Is valid.
However if you try to call useHook1 or useHook2 from any normal function or anything that's not a hook or a function component it will break.
To fix it you can do something like:
const useDispatch = () => {
const dispatch = (resource) => {
// do whatever here
}
return dispatch
}
const useExample = () => {
const dispatch = useDispatch()
dispatch(// resource)
}
Note: The naming of the hook is irrelevant, you can call it anything you want. React will figure out it's a hook based on the fact that it's using other hooks or something like useState. However it is strongly recommended that you do prefix it with use, i.e. useSomeHook, because 1. it makes it easy for other developers to see what is a hook and what's a normal function and 2. a lot of tools like eslint is built around a use prefix in order to apply hook rules and validations so if you don't name it with "use" those things will not work correctly.

react: functional component that ignores prop changes until callback is triggered

I have a controlled component that I want to use to trigger an optimistic update, and, while the update is being performed, it should use it's internal state and ignore all changes to it's main prop, and once a callback is executed then it can again use the provided prop as values.
I tried to encode this logic on a hook using useMemo, useRef, and different setStates but without success. I think it is because the used ref does not survive long enough (the component is re-rendered like 3 times between the update trigger and the callback execution) and at some point it gets re-created with the new value coming from props.
Here is the hook that I done so far:
function useOptimistic(incoming) {
const [state, setState] = useState('INITIAL')
const ref = useRef(incoming)
const value = useMemo(() => ref.current, [state])
const setUpdating = (value) => {
setState('UPDATING')
ref.current = value
}
const setUpdated = () => setState('UPDATED')
return { state: value, setUpdating, setUpdated }
}
The reason is because I am using graphql and a toggle component. When I click on the toggle component I trigger the graphql request and you can see the change with the local state, but while the query is being executed the component is re-rendered 3 or 4 times, and the toggle shows the state from the "old-data", when the request completes then it comes back to the correct state. What I want is to avoid this intermediary weird changes.
I think that this should do it:
const initialState = { updating: false }
function useOptimistic(incoming) {
const [innerState, setInnerState] = useState(initialState)
const setUpdating = (value) => setInnerState({ updating: true, value })
const setUpdated = () => setInnerState(initialState)
const state = innerState.updating ? innerState.value : incoming
return { state, setUpdating, setUpdated }
}
Live Example (based on T.J. Crowder's answer), just to show that it's not really necessary to have stable functions for something like this ;-).
const { useState, useRef } = React;
const initialState = { updating: false }
function useOptimistic(incoming) {
const [innerState, setInnerState] = useState(initialState)
const setUpdating = (value) => setInnerState({ updating: true, value })
const setUpdated = () => setInnerState(initialState)
const state = innerState.updating ? innerState.value : incoming
return { state, setUpdating, setUpdated }
}
function Example() {
const [value, setValue] = useState(1);
const {state, setUpdating, setUpdated} = useOptimistic(value);
const triggerUpdate = () => {
const newValue = value + 1;
setUpdating(newValue);
setTimeout(() => {
setValue(newValue);
setUpdated(); // <=== Note that despite the fact that this function
// is not stable, this still works perfectly fine ;-)
}, 1000);
};
return (
<div>
<div>value: {value}</div>
<div>state: {state}</div>
<input type="button" onClick={triggerUpdate} value="Trigger Update" />
</div>
);
}
ReactDOM.render(<Example />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
A ref will only be created once, when the component using the hook is first mounted, so you were right to try to store the pending state in a ref (although it could also be stored in state). You'd also probably be best off putting the state setters in a ref as well, since making them stable (like useState does) is useful to consumers of the hook, so I'll use the ref for the pending value as well.
The main problem with your hook, I think, is that it always uses the value from ref.current, but it only sets ref.current to incoming when the component using the hook is first mounted. So it ignores subsequent changes to incoming (whether or not it's in the updating state).
The change I'd make (other than using a boolean rather than strings for the flag) is to use your "updating" flag to choose what to return as state, returning the then-current incoming when not updating, or the optimistic pending value if updating. See comments:
function useOptimistic(incoming) {
// A flag for whether we're in the "udating" state
const [updatingFlag, setUpdatingFlag] = useState(false)
// Our instance data
const instance = useRef(null);
if (!instance.current) {
// Only happens on first call, this object is created once
instance.current = {
// The pending value
pendingValue: null,
// Set the pending value and go into the updating state
setUpdating: (value) => {
instance.current.pendingValue = value;
setUpdatingFlag(true);
},
// Exit the updating state
setUpdated: () => {
setUpdatingFlag(false);
instance.current.pendingValue = null;
},
};
}
// The state value we'll return: the input prop, or the one from our
// instance data
const state = updatingFlag ? instance.current.pendingValue : incoming;
// Grab our setters and return them with the state
const { setUpdating, setUpdated } = instance.current;
return { state, setUpdating, setUpdated };
}
Live Example:
const { useState, useRef } = React;
function useOptimistic(incoming) {
// A flag for whether we're in the "udating" state
const [updatingFlag, setUpdatingFlag] = useState(false)
// Our instance data
const instance = useRef(null);
if (!instance.current) {
// Only happens on first call, this object is created once
instance.current = {
// The pending value
pendingValue: null,
// Set the pending value and go into the updating state
setUpdating: (value) => {
instance.current.pendingValue = value;
setUpdatingFlag(true);
},
// Exit the updating state
setUpdated: () => {
setUpdatingFlag(false);
instance.current.pendingValue = null;
},
};
}
// The state value we'll return: the input prop, or the one from our
// instance data
const state = updatingFlag ? instance.current.pendingValue : incoming;
// Grab our setters and return them with the state
const { setUpdating, setUpdated } = instance.current;
return { state, setUpdating, setUpdated };
}
function Example() {
const [value, setValue] = useState(1);
const {state, setUpdating, setUpdated} = useOptimistic(value);
const triggerUpdate = () => {
const newValue = value + 1;
setUpdating(newValue);
setTimeout(() => {
setValue(newValue);
setUpdated(); // <=== Note that the stability of the setters is what
// lets me use this here, it wouldn't be correct if
// the setters weren't stable
}, 1000);
};
return (
<div>
<div>value: {value}</div>
<div>state: {state}</div>
<input type="button" onClick={triggerUpdate} value="Trigger Update" />
</div>
);
}
ReactDOM.render(<Example />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>

React useState when updating state twice, first update gets deleted

I noticed a behavior I cant understand how to solve, I never had it while I was writing react class based using this.setState but I get it while using useState hook.
for example:
const Main = () => {
const [values, setValues] = useState({val1: '', val2: '', val3: ''})
const func1 = async () => {
await setValues({
...values,
val1: '111111111'
});
await func2();
}
const func2 = async () => {
result = (fetch some data);
await setValues({
...values,
val2: result
});
}
};
now if you run func1, val1 will be changed but as soon as func2 finishes and we setValues the second time val1 will get overwritten and val2 will contain value.
what am I doing wrong and how can I fix this ?
Thanks in Advance!
Edit:
when using Hooks I cant see what is the acctual anme of the value entered in the React Chrome Dev tool.
is there a way to fix this ?
when I was having one useState containing an object I could see the titles of each object key... now its hidden -_-
You're spreading the state before updating the correspondent property, try to chunk your state
const Main = () => {
const [value1, setValue1] = useState(null)
const [value2, setValue2] = useState(null)
const func1 = async () => {
setValue1('foo')
await func2();
}
const func2 = async () => {
result = (fetch some data);
await setValue2('foo')
}
};
Here is what is happening
setValues is called changing val1 (state isn't updated yet)
setValues is called again changing val2 and spreading the rest
By the time setValues spreads values val1 still holds it's initial value, overwriting the first setValues call. Remember, changes in state are reflected asynchronously
React useState also takes a functional updater to update your component state similarly to how class-based component's setState works.
Note
Unlike the setState method found in class components, useState does not automatically merge update objects. You can replicate this behavior by combining the function updater form with object spread syntax:
setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});
So in your code you could update as follows:
const Main = () => {
const [values, setValues] = useState({val1: '', val2: '', val3: ''});
const func1 = async () => {
await setValues(prevState => ({
...prevState,
val1: '111111111'
}));
await func2();
}
const func2 = async () => {
result = (fetch some data);
await setValues(prevState => ({
...prevState,
val2: result
}));
}
};
I haven't tested it yet but maybe it could work as an alternative.
Sometimes, when we need to get the value in the state, we usually do the spread operator within the state but it doesn't guarantees that the value is the correct one. For those cases, we better call setState with a callback which takes the previous value from state. So, you can use something like this:
setValues(prev => ({...prev, val1:'11111'}));
The behaviour of the code becomes clear once we note that values variable in func2 is clousre on values at outer scope, which is really a copy of state taken at the time useState was called.
So what your spreading in func2 is is stale copy of your state.
One way of correcting this would be to use functional update
setValues((values) => ({...values, val2: result}));
This will make sure that you are using updated value of the state.

Categories