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

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

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

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.

Access the latest state from external function React JS

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>;

Why does my useEffect react function run when the page loads although I am giving it a second value array

Why is my useEffect react function running on every page load although giving it a second value array with a query variable?
useEffect( () => {
getRecipes();
}, [query]);
Shouldn't it only run when the query state variable changes? I have nothing else using the getRecipes function except of the useEffect function.
import React, {useEffect, useState} from 'react';
import './App.css';
import Recipes from './components/Recipes/Recipes';
const App = () => {
// Constants
const APP_ID = '111';
const APP_KEY = '111';
const [recipes, setRecipes] = useState([]);
const [search, setSearch] = useState('');
const [query, setQuery] = useState('');
const [showRecipesList, setShowRecipesList] = useState(false);
// Lets
let recipesList = null;
// Functions
useEffect( () => {
getRecipes();
}, [query]);
// Get the recipie list by variables
const getRecipes = async () => {
const response = await fetch(`https://api.edamam.com/search?q=${query}&app_id=${APP_ID}&app_key=${APP_KEY}&from=0&to=3&calories=591-722&health=alcohol-free`);
const data = await response.json();
console.log(data.hits);
setRecipes(data.hits);
}
// Update the search constant
const updateSearch = e => {
console.log(e.target.value);
setSearch(e.target.value);
}
const runQuery = e => {
e.preventDefault();
setQuery(search);
}
// List recipes if ready
if (recipes.length) {
console.log(recipes.length);
recipesList = <Recipes recipesList={recipes} />
}
return (
<div className="App">
<form className='search-app' onSubmit={ runQuery }>
<input
type='text'
className='search-bar'
onChange={ updateSearch }
value={search}/>
<button
type='submit'
className='search-btn' > Search </button>
</form>
<div className='recipesList'>
{recipesList}
</div>
</div>
);
}
export default App;
Following this: https://www.youtube.com/watch?v=U9T6YkEDkMo
A useEffect is the equivalent of componentDidMount, so it will run once when the component mounts, and then only re-run when one of the dependencies defined in the dependency array changes.
If you want to call getRecipes() only when the query dependency has a value, you can call it in a conditional like so:
useEffect(() => {
if(query) {
getRecipes()
}
}, [query])
Also, as your useEffect is calling a function (getRecipes) that is declared outside the use effect but inside the component, you should either move the function declaration to be inside the useEffect and add the appropriate dependencies, or wrap your function in a useCallback and add the function as a dependency of the useEffect.
See the React docs for information on why this is important.
UseEffect hook work equivalent of componentDidMount, componentDidUpdate, and componentWillUnmount combined React class component lifecycles.but there is a different in time of acting in DOM.componentDidMount and useEffect run after the mount. However useEffect runs after the paint has been committed to the screen as opposed to before. This means you would get a flicker if you needed to read from the DOM, then synchronously set state to make new UI.useLayoutEffect was designed to have the same timing as componentDidMount. So useLayoutEffect(fn, []) is a much closer match to componentDidMount() than useEffect(fn, []) -- at least from a timing standpoint.
Does that mean we should be using useLayoutEffect instead?
Probably not.
If you do want to avoid that flicker by synchronously setting state, then use useLayoutEffect. But since those are rare cases, you'll want to use useEffect most of the time.

useReducer/useState updates too slow

I'm trying to remove redux of our application. To do so I try to use the new React Hooks API.
I wrote my own hook which is an extension of the useReducer function, to persist the state each time its updating.
export default (reducer, persistKey, onRehydrate) => {
let initialState = rehydrate(persistKey)
if (onRehydrate && typeof onRehydrate === 'function') {
initialState = onRehydrate(initialState)
}
const [state, setState] = useState(initialState)
function dispatch (action) {
const nextState = reducer(state, action)
setState(nextState)
persist(nextState, persistKey)
}
return [state, dispatch]
}
My problem is that I have use case where my dispatch function is called several times within a short interval which are relaying on each other. Like an array which always gets a new item added on each dispatch. To do so I merge the last state and the new state. Since it's getting called before the state has updated the last state isn't the new state of the last call. Therefore my state only saves the last dispatch.
Is there any way to solve that (besides changing the way the updates are called since they are from a thirdparty component)
Thanks in advance.
I can't verify whether this works, but I suspect it will. You could change your setState call within dispatch to the callback form, which allows you to reference the existing state before passing it into your reducer.
export default (reducer, persistKey, onRehydrate) => {
let initialState = rehydrate(persistKey)
if (onRehydrate && typeof onRehydrate === 'function') {
initialState = onRehydrate(initialState)
}
const [state, setState] = useState(initialState)
function dispatch (action) {
setState(prevState => { // Use callback form here.
const nextState = reducer(prevState, action)
persist(nextState, persistKey)
return nextState
});
}
return [state, dispatch]
}

Categories