I was trying to convert my class based component to functional style. I have this code:
const [foo, setFoo] = useState(null);
const [roomList, setRoomList] = useState([]);
useEffect(() => {
setRoomList(props.onFetchRooms(props.token));
}, [props]);
let roomLists = <Spinner />;
if (!props.loading) {
roomLists = roomList.map(room => <Room key={room._id} roomName={room.name} />);
}
Previously I had:
class Rooms extends Component {
state = {
foo: null
};
componentDidMount() {
this.props.onFetchRooms(this.props.token);
}
render() {
let roomList = <Spinner />;
if (!this.props.loading) {
roomList = this.props.rooms.map(room => <Room key={room._id} roomName={room.name} />);
}
The onFetchRooms is a function I am using from Redux in mapDispatchToProps. And rooms is also coming from the Redux store in mapStateToProps With the above new code, I get Cannot read property 'map' of undefined. What am I doing wrong please?
I have also tried without using state:
useEffect(() => {
props.onFetchRooms(props.token);
}, [props]);
let roomList = <Spinner />;
if (!props.loading) {
roomList = props.rooms.map(room => <Room key={room._id} roomName={room.name} />);
}
But that goes into infinite loop.
useEffect set dependency array to []
try this
useEffect(() => {
props.onFetchRooms(props.token);
}, []);
I think the props object will change for each rerender (that's why you get an infinite loop).
You should declare each variables individually into the array :
const {onFetchRooms, token} = props;
useEffect(() => onFetchRooms(token), [onFetchRooms, token]);
It seems like you're probably starting the "Functional" version of your component like this:
const Rooms = (props) => {
...
}
Instead, why don't you destructure props like this:
const Rooms = ({onFetchRooms, rooms, token}) => {
...
}
Now you can do this in your useEffect dependency array:
useEffect(() => {
onFetchRooms(token);
}, [onFetchRooms, token]);
This should make the infinite loop stop happening.
Note: Sometimes to get rid of the "React Hook useEffect has a missing dependency: 'props'. Either include it or remove the dependency array." error, you "can" use:
// eslint-disable-next-line react-hooks/exhaustive-deps
For example, if you only want to add token in your dependency array and not worry about onFetchRooms, a function that will never change (i assume)
Related
I have a useEffect hook that is not updating after changing the dependency.
The dependency is a state variable passed through props. Included below is what the code looks like:
const MyComponent = ({resource}) => {
// runs after changing resource state
console.log(resource)
useEffect(() => {
setLoading(true);
// doesnt run after changing resource state
console.log(resource)
setVariable(setDefaultValue());
}, [resource]);
}
MyComponent is being rendered for one of two states 'option1' or 'option2' the component renders differently depending on the state. I call the component like so:
const [resource, setResource] = useState('option1');
const handleChange = (e) => {
setResource(e.target.value);
};
return (
<MyComponent resource={resource} />
)
I don't understand why the useEffect isn't running after resource state is changed. The console.log on the outside of the useEffect show the change state, but the console.log inside of the useffect isn't run after changing state.
Oh, you can change code the following above try:
//Parent Component
const [resource, setResource] = useState('option1');
const [variable,setVariable] = useState();
const [loading,setLoading] = useState(false);
useEffech(()=>{
let fetch_api = true;
setLoading(true);
fetch(URL,options).then(res=>res.json())
.then(res=>{
if(fetch_api){
setVariable(setDefaultValue());
setLoading(false);
}
});
return ()=>{
//remove memory
fetch_api = false;
}
},[resource]);
const handleChange = (e) => {
setResource(e.target.value);
};
return (
<MyComponent variable={variable} />
)
//Child Component
const MyComponent=({variable})=>{
return <>
</>
}
I have my state and I want to display the component if the value is true but in the console I receive the error message Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state my code
import React, { useState} from "react";
import { useToasts } from "react-toast-notifications";
const Index = () => {
const [test, setTest]= useState(true);
const { addToast } = useToasts();
function RenderToast() {
return (
<div>
{ addToast('message') }
</div>
)}
return (
<div>
{test && <RenderToast /> }
</div>
)
}
You cannot set state during a render. And I'm guessing that addToast internally sets some state.
And looking at the docs for that library, you don't explicitly render the toasts. You just call addToast and then the <ToastProvider/> farther up in the tree shows them.
So to make this simple example works where a toast is shown on mount, you should use an effect to add the toast after the first render, and make sure your component is wrapped by <ToastProvider>
const Index = () => {
const { addToast } = useToasts();
useEffect(() => {
addToast('message')
}, [])
return <>Some Content here</>
}
// Example app that includes the toast provider
const MyApp = () => {
<ToastProvider>
<Index />
</ToastProvider>
}
how i can display the toast based on a variable for exemple display toast after receive error on backend?
You simply call addToast where you are handling your server communication.
For example:
const Index = () => {
const { addToast } = useToasts();
useEffect(() => {
fetchDataFromApi()
.then(data => ...)
.catch(error => addToast(`error: ${error}`))
}, [])
//...
}
I am trying to learn React hooks, and trying to convert existing codebase to use hooks, but I am confused.
Is it normal to set state inside useEffect? Would I be causing the dreaded infinite loop if I do so?
import React, { useState, useEffect } from 'react';
import App from 'next/app';
import Layout from './../components/Layout';
function MyApp({ Component, pageProps }) {
const [ cart, setCart ] = useState([]);
const addToCart = (product) => {
setCart((prevCartState) => {
return [ ...prevCartState, product ];
});
localStorage.setItem('cart', JSON.stringify(cart));
};
//mimicking componentDidMount to run some effect using useEffect
//useEffect(() => setCount((currentCount) => currentCount + 1), []);
useEffect(() => {
const cartCache = JSON.parse(localStorage.getItem('cart'));
//cart = cartCache; Good or bad?
cartCache || setCart(() =>{
});
}, []);
return <Component {...pageProps} />;
}
My original class based component:
/*
export default class MyApp extends App {
state = {
cart: []
}
componentDidMount = () => {
const cart = JSON.parse(localStorage.getItem('cart'));
if (cart) {
this.setState({
cart
});
}
};
addToCart = (product) => {
this.setState({
cart: [...this.state.cart, product]
});
localStorage.setItem('cart', JSON.stringify(this.state.cart));
}
render() {
const { Component, pageProps } = this.props
return (
<contextCart.Provider value={{ cart: this.state.cart, addToCart: this.addToCart }}>
<Layout>
<Component {...pageProps} />
</Layout>
</contextCart.Provider>
)
}
}*/
It's okey to set state inside useEffect as long as you don't listen to changes of the same field inside dependency array. In your particular case you are calling useEffect only once (since you have passed an empty dependency array).
useEffect(() => {
const cartCache = JSON.parse(localStorage.getItem('cart'));
if (cartCache) {
setCart(cartCache);
}
}, []);
Also would be cool to add the second useEffect to listen to cart changes and keep the localStorage up-to-date.
useEffect(() => {
localStorage.setItem('cart', JSON.stringify(cart));
}, [cart]);
Because localStorage.getItem() is a synchronous call thus for this scenario you can also use the function callback version of useState in order to set initial value. In this way you don't need to use useEffect. Usually if it is possible I try to avoid introducing any new side effects in my functional components.
You can try as the following instead:
const [ cart, setCart ] = useState(() => {
const cartCache = JSON.parse(localStorage.getItem('cart'));
if (cartCache) {
return cartCache;
}
localStorage.setItem('cart', JSON.stringify([]));
return [];
});
In this way if the cart element is missing from the localStorage the code will create it with a default [] empty array and set it also for your state. Other case it will set your state the value from storage.
Please note: Also I agree with the answer from kind user here in terms of listening cart state changes to keep localStorage up to date with useEffect. My suggestion is only for the initial state.
React Hook useEffect has a missing dependency: 'dispatch'. Either include it or remove the dependency array react-hooks/exhaustive-deps
I use useDispatch() Hook from React Redux on a functional component like this:
const Component = () => {
const dispatch = useDispatch();
const userName = useSelect(state => state.user.name);
useEffect(() => {
dispatch(getUserInformation());
}, [userId]);
return (
<div>Hello {userName}</div>
);
};
export default Component;
How to remove this warning without removing the dependency array react-hooks/exhaustive-deps which can be useful to avoid other errors.
To avoid that warning simply add dispatch to the dependency array. That will not invoke re-renders because dispatch value will not change.
const Component = () => {
const dispatch = useDispatch();
const userName = useSelect(state => state.user.name);
useEffect(() => {
dispatch(getUserInformation());
}, [userId, dispatch]);
return (
<div>Hello {userName}</div>
);
};
export default Component;
Simply add dispatch to your dependency array or make the dependency array empty.
First Case:
const Component = () => {
const dispatch = useDispatch();
const userName = useSelect(state => state.user.name);
useEffect(() => {
dispatch(getUserInformation());
}, [userId, dispatch]);
return (
<div>Hello {userName}</div>
);
};
export default Component;
Second Case:
const Component = () => {
const dispatch = useDispatch();
const userName = useSelect(state => state.user.name);
useEffect(() => {
dispatch(getUserInformation());
}, []);
return (
<div>Hello {userName}</div>
);
};
export default Component;
By adding these dependencies, your useEffect may cause re-rendering or not re-render at all. It all depends on your data and the context in which you use it.
Anyways, if you do not wish to follow both the above methods then //ts-ignore can work but I will not recommend this as this may create bugs in the long run.
I export a JS object called Products to this file, just to replace a real API call initially while I am building/testing. I want to set the function's state to the object, but mapped. I have the component looking like this:
function App() {
const [rooms, setRooms] = useState([]);
const [days, setDays] = useState([]);
const roomsMapped = products.data.map(room => ({
id: room.id,
title: room.title
}))
useEffect(() => {
setRooms(roomsMapped);
})
return ( etc )
This returns the following error: Error: Maximum update depth exceeded.
I feel like I'm missing something really obvious here, but am pretty new to React and Hooks. How can I set this data before the component renders?
Just declare it as initial value of rooms
const Component = () =>{
const [rooms, setRooms] = useState(products.data.map(room => ({
id: room.id,
title: room.title
})))
}
You can also use lazy initial state to avoid reprocessing the initial value on each render
const Component = () =>{
const [rooms, setRooms] = useState(() => products.data.map(room => ({
id: room.id,
title: room.title
})))
}
Change useEffect to this
useEffect(() => {
setRooms(roomsMapped);
},[])
With Lazy initialisation with function as a parameter of useState
import React, { useState } from "react";
function App() {
const [rooms, setRooms] = useState(() => {
// May be a long computation initialization
const data = products.data || [];
return data.map(({ id, title }) => ({ id, title }));
});
return (
// JSX stuffs
)
}
You can use default props for this.set initial value with empty list .
You are getting 'Error: Maximum update depth exceeded', because your useEffect function doesn't have dependency array. Best way to fix this is to pass empty array as the second argument to useEffect like this:
useEffect(() => {
setRooms(roomsMapped);
},[]) <= pass empty array here
this will prevent component to re render, it you want your component to re render on props change you can pass the props in the array like this:
useEffect(() => {
setRooms(roomsMapped);
},[props.props1,props.props2])
here you can pass as many props as you want...