Save value from custom hook to state - javascript

I am trying to save a value from a custom hook, which is fetching data for the server, to functional component state with useState, because I later need to change this value and after the change it needs to rerender. So desired behaviour is:
Set State variable to value from custom hook
Render stuff with this state variable
Modify state on button click
Rerender with new state
What I tried is:
Set the inital value of useState to my hook:
const [data, setData] = useState<DataType[] | null>(useLoadData(id).data)
but then data is always empty.
Set the state in a useEffect() hook:
useEffect(()=>{
const d = useLoadData(id).data
setData(d)
}, [id])
But this is showing me the Error warning: Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
Doing this:
const [data, setData] = useState<DocumentType[]>([])
const dataFromServer = useLoadData(id).data
useEffect(()=>{
if (dataFromServer){
setData(dataFromServer)
}
}, [dataFromServer])
Leading to: ERROR: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
What would be a proper solution for my use case?

It looks like your custom hook returns a new array every time it is used.
Solution 1: change your hook to return a 'cached' instance of an array.
function useLoadData(id) {
const [data, setData] = useState([]);
useEffect(() => {
loadData(id).then(setData);
}, [id]);
// good
return data;
//bad
//return data.map(...)
//bad
//return data.filter(...)
//etc
}
codesandbox.io link
Solution 2: change your hook to accept setData as a parameter.
function useLoadData(id, setData) {
useEffect(() => {
loadData(id).then(setData);
}, [id]);
}
Here I am telling the hook where to store data so that both custom hook and a button in a component can write to a same place.
codesandbox.io link
Full example:
import React from "react";
import ReactDOM from "react-dom";
import { useState, useEffect } from "react";
// simulates async data loading
function loadData(id) {
return new Promise((resolve) => setTimeout(resolve, 1000, [id, id, id]));
}
// a specialized 'stateless' version of custom hook
function useLoadData(id, setData) {
useEffect(() => {
loadData(id).then(setData);
}, [id]);
}
function App() {
const [data, setData] = useState(null);
useLoadData(123, setData);
return (
<div>
<div>Data: {data == null ? "Loading..." : data.join()}</div>
<div>
<button onClick={() => setData([456, 456, 456])}>Change data</button>
</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("container"));

Related

useState for event handler without callback

In the code below:
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import React, {
useState,
useEffect,
useMemo,
useRef,
useCallback
} from "react";
const App = () => {
const [channel, setChannel] = useState(null);
const handleClick1 = useCallback(() => {
console.log(channel);
}, [channel]);
const handleClick2 = () => {
console.log(channel);
};
const parentClick = () => {
console.log("parent is call");
setChannel((prev) => prev + 1);
};
// useEffect(() => {
// setChannel(1);
// });
return (
<div className="App">
<button onClick={parentClick}>parent click</button>
<br />
<br />
<button onClick={handleClick1}>child click1</button>
<button onClick={handleClick2}>child click2</button>
</div>
);
};
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(<App />);
There is one child click wrap with callback (handleclick1) and one didn't (handleclick2).
After parent click, both children get the right channel state value.
My question is for handleclick2 :
will the change of channel state value trigger the re-evaluation of the function and hence rerendering of the entire UI?
If the answer for 1 is no, then how the function got the right value of channel state value?
see codesandbox here
will the change of channel state value trigger the re-evaluation of the function and hence rerendering of the entire UI?
Yes, but this isn't due to anything in handleClick2 - it's due to the state setter being called. Whenever a state setter is called, the component will re-render - which means, in this case, that the App function runs again.
useCallback is usually useful when passing down a function to another React component as a prop, to reduce that component's need to re-calculate things. It's not useful in situations like these where everything is done in a single component.

Uncaught RangeError: Maximum call stack size exceeded in React application

Please help me.
I am new in React and try to build an application to add contact in Local storage and delete contact. Following are the code of my App.js
import React, {useState, useEffect} from 'react'
import {uuid} from 'uuidv4'
import AddContact from './AddContact'
import ContactList from './ContactList'
import Header from './Header'
function App() {
//useState Hooks in React
const LOCAL_STORAGE_KEY = 'contacts'
const [contacts, setContacts] = useState([])
const addContactHandler = (contact) => {
console.log(contacts)
setContacts([...contacts, {id: uuid(), ...contact}])
}
const removeContactHandler = (id) => {
const newContactList = contacts.filter((contact) => {
return contact.id !== id
})
setContacts(newContactList)
}
useEffect(() => {
const retrieve_contact = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY))
if (retrieve_contact) {
setContacts(retrieve_contact)
}
}, [])
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts))
}, [contacts])
return (
<div className="ui container">
<Header />
<AddContact addContactHandler={addContactHandler} />
<ContactList contacts={contacts} getContactId={removeContactHandler} />
</div>
)
}
export default App
I got the error Uncaught RangeError: Maximum call stack size exceeded.
Please help me how can i remove this error.
Thank you!
You are updating react state inside useEffect, which triggers an infinite loop.
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts))
}, [contacts])
In short, you told useEffect to run when there are changes to contacts (by passing them into the dependency array as the second param to useEffect), causing it to run every time contacts changes, which triggers a state change to contacts, which triggers the useEffect loop again, infinitely. If the array was empty, it would run once on start.
For what you are trying to do here, it makes more sense to update your store when you add or remove contacts, i.e. call the storage functions from within your event handlers.
Here's a working code sandbox with the code you provided.

localStorage - get and set

I am new to React and going through a beginner course on Youtube. In one section, the trainer is explaining about using localStorage of the browser to get and set the items. However, i did not understand why should we call the useEffect with getItem first rather than useEffect with setItem. If i place the setItem code above getItem then it does not work.
Please check the code below
import React, { useState, useEffect} from 'react';
import { uuid } from 'uuidv4';
import './App.css';
import Header from './Header';
import AddContact from './Addcontact';
import ContactList from './ContactList';
function App() {
const LOCAL_STORAGE_KEY = "contacts";
const [contacts, setContacts] = useState([]);
const addContactHandler = (contact) => {
console.log(contact);
setContacts([...contacts, {id: uuid(), ...contact}]);
}
const removeContactHandler = (id) => {
const newContactList = contacts.filter((contact) => {
return contact.id !== id;
});
setContacts(newContactList);
}
useEffect(()=>{
const retrieveContacts = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts)));
if(retrieveContacts) setContacts(retrieveContacts);
}, [])
useEffect(()=>{
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts));
}, [contacts])
return (
<div className="ui container">
<Header/>
<AddContact addContactHandler={addContactHandler}/>
<ContactList contacts={contacts} getContactId={removeContactHandler} />
</div>
);
}
export default App;
The order of useEffect matters
useEffect(()=>{
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts));
}, [contacts])
useEffect(()=>{
const retrieveContacts = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts)));
if(retrieveContacts) setContacts(retrieveContacts);
}, [])
Inverting it like above sets the localStorage empty, because contacts is initialized empty, and then you will get it empty and update your state on the next useEffect
contacts is an empty array when it is initialized:
const [contacts, setContacts] = useState([]);
So changing the order is doing the following:
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts));
Empty out local storage with []
Retrieve the local storage that we just set to empty:
const retrieveContacts = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts)));
Update our state to empty:
if(retrieveContacts) setContacts(retrieveContacts);
I think that's because your below code is executing after component has been mounted.
useEffect(()=>{
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts));}, [contacts])
And because of this it is setting the initial value of contacts into the localStorage again. To avoid this you can set the initial value of contacts to the value you get from the localStorage
const [contacts,setContacts] = useState(JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY)));
useEffect(()=>{
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts));
}, [contacts])
// remove another useEffect

React JS useState initial value not updating inside JS fetch API

I am novice to React JS. I have useState and fetchAPI inside contextAPI hooks but the initial state is not updating.
// code
import React,{useState, createContext} from 'react'
export const contextApi = createContext()
export const ContextApiProvider = (props) => {
const [query, setQuery] = useState('chicken')
const [recipes, setRecipes] = useState([])
const api_props = {
APP_ID: '84cf712e',
APP_KEY:'asdcb2b8b842f3e543casjakfa710de4fb343592a64d',
APP_QUERY: query
}
fetch(`https://api.edamam.com/search?q=${api_props.APP_QUERY}&app_id=${api_props.APP_ID}&app_key=${api_props.APP_KEY}`)
.then(res => res.json()).then(data => setRecipes(data.hits))
return (
<contextApi.Provider value={{recipes}}>
{props.children}
</contextApi.Provider>
)
}
First look up the useEffect hook that is where you want to do your data fetching. From there you could set the state using the setState hook that you are running. This might create an endless loop because your are setting state which reruns the component which then trys to set state again.
Hope that helps let me know if you have questions.

React custom hooks in callback

I'm trying to use my custom hook inside the callback logic like this:
import React, { useEffect, useState } from 'react';
import useDataChange from '../../hooks/useDataChange';
const SomeComponent = () => {
return (
<Table
handleTableChange={data => useDataChange(data)}
/>
);
};
export default SomeComponent;
And my custom hooks (just to simplify) looks like that:
const useDataChange = data => {
console.log(data);
};
export default useDataChange;
In short, custom hook supposed to be fired when data from table is changed (ie. when handleTableChange in Table component is fired). Instead I'm getting:
React Hook "useDataChange" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function react-hooks/rules-of-hooks
How can I use it when table data is changed?
The key to understanding hooks is to extract pieces of react code out of components. So your first step would be to get it working inside the component
const SomeComponent = () => {
const [data, setData] = useState([])
return (
<Table
handleTableChange={setData}
/>
);
};
Based on your code, I'm not seeing where you'd need a hook or side effect. But let's pretend that you do want to run some simple side effect:
const SomeComponent = () => {
const [data, setData] = useState([])
const [modifiedData, setModifiedData] = useState([])
useEffect(() => {
//here we're just going to save the current data stream into a new state variable for simplicity
setModifiedData(data)
}, [data])
return (
<Table
handleTableChange={setData}
data={modifiedData}
/>
);
};
So now we have some logic that runs a side effect. Now you can extract it to its own hook.
const useModifiedData = (data) => {
const [modifiedData, setModifiedData] = useState(data)
useEffect(() => {
setModifiedData(data)
}, [data])
return modifiedData
}
const SomeComponent = () => {
const [data, setData] = useState([])
const modifiedData = useModifiedData(data)
return (
<Table
handleTableChange={setData}
data={modifiedData}
/>
);
};
Here you have a hook that lives outside the component logic, so it can now go in its own file and be used across your project.
Like it says React Hooks must be called in a React function component or a custom React Hook function react-hooks/rules-of-hooks. React has this limitation so that it can track the state and effects. In your case you can define you custom hook to return a function which does the desired work, instead of directly doing it in your hook.
In this case your custom hook file will look something like this-
const useDataChange = () => data => {
console.log(data);
};
export default useDataChange;
Then in your component you can use it like this -
import React, { useEffect, useState } from 'react';
import useDataChange from '../../hooks/useDataChange';
const SomeComponent = () => {
const callback = useDataChnage();
return (
<Table handleTableChange={callbackdata} />
);
};
export default SomeComponent;

Categories

Resources