I have a navigation component where I'm passing a parameter to another page, the parameter is getting passed, however, the data in the dropdown is not updating for the passed ID:
nav:
<Link to='/service/ServiceAppointment/${car.Make}'> { serviceAppointment } </Link>
appointment page:
const ScheduleAppointment = () => {
const { id } = useParams();
console.log (id); //I can see the ID passed to the page in the console
useEffect(() => {
console.log(id); //the ID is not there
scheduleAppointment(id);
});
const Appointment= () => {
//call to API for open dates
//the ID never gets here
}
}
Router:
<Route exact path='/service/appointment/:id' component={ ScheduleAppointment } />
how can I get the appointment page to change when a new ID is passed to it?
Dependencies argument of useEffect is useEffect(callback, dependencies)
Let's explore side effects and runs:
Not provided: the side-effect runs after every rendering.
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// Runs after EVERY rendering
});
}
An empty array []: the side-effect runs once after the initial rendering.
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// Runs ONCE after initial rendering
}, []);
}
Has props or state values [prop1, prop2, ..., state1, state2]: the side-effect runs only when any dependency value changes.
import { useEffect, useState } from 'react';
function MyComponent({ prop }) {
const [state, setState] = useState('');
useEffect(() => {
// Runs ONCE after initial rendering
// and after every rendering ONLY IF `prop` or `state` changes
}, [prop, state]);
}
in your case try this way
useEffect(() => {
console.log(id); //the ID is not there
scheduleAppointment(id);
},[id]);
Please update the below link instead of your code
<Link to=`/service/ServiceAppointment/${car.Make}`> { serviceAppointment } </Link>
I hope it will work for you! Thanks.
Related
Take this piece of code:
import React from 'react';
import { useState, useEffect } from 'react'
export function App() {
let [isAdmin, setAdmin] = useState(false)
const checkIfAdmin = async() => {
setAdmin(true)
}
useEffect(() => {
checkIfAdmin()
}, []);
console.log(isAdmin)
return (
<div className='App'>
<h1>test</h1>
</div>
);
}
When console logging isAdmin, it comes out as false initially, but when checked again (such as in an onClick() event), it comes out as true. Why does it take 2 checks to finally output the desired result? How can I make it so that in checkIfAdmin the changes immediately take place, and isAdmin comes out as true on the first time?
Passing a function to setAdmin() to toggle in between states will immediately update the state variable.
const checkIfAdmin = () => {
setAdmin(isAdmin => !isAdmin)
}
You should be getting 2 console.logs, one that returns false and another that returns true. This is due to the way useEffect, and setState work.
When useEffect is used with an empty array as the second arg, it only runs once when the component mounts. And on the first mount of your component, isAdmin is initialized to false
useEffect(() => {
checkIfAdmin()
}, []);
Once setAdmin(true) executes, it updates the dom, causing the component to render for a second time, with the isAdmin value being true
You can visualize how the dom is being updated by adding another console.log() in your useEffect.
useEffect(() => {
console.log('component is mounting...')
checkIfAdmin();
}, []);
I have a problem with using navigate in one of my context's functions. I have a function called getCurrencyFromPath that is provided to my component via useContext. This function is called in useEffect, only on component's mount, to get a currency from query parameter (example path: /HistoricalRates?currency=USD). If someone would try to pass a currency (into query parameter) that is not valid for my app I'd like to navigate user to a default currency which is USD. So in my function I have an "if" check, if query parameter is ok, if not then navigate. So when user passes this wrong query parameter my page mounts and at once navigate should be called, but it's not and instead I get this error. If i call navigate directly in useEffect it works, there is no error, but as soon as i try to do the same logic with outsourcing this code to context function I get an error.
PS. I want to keep this logic in this context function, putting this directly in useEffect is not what I want to achieve (although it would solve the problem)
// component
const Header: React.FC = () => {
const { getCurrencyFromPath, currency, setCurrency } = React.useContext(HistoricalRatesContext);
const navigate = useNavigate();
React.useEffect(() => {
getCurrencyFromPath();
}, []);
const onPickerValueChange = (currency: Currencies) => {
navigate(`/HistoricalRates?currency=${currency}`);
setCurrency(currency);
};
return (
<div className={classes.chartHeader}>
<PageHeader text={`USD historical rates`}></PageHeader>
<CurrencyPicker className={classes.pickerAdditional} value={currency}
changeValue={onPickerValueChange} blockedCurrencies={[Currencies.PLN]} />
</div>
);
};
export default Header;
// context
import { useNavigate } from 'react-router-dom';
.
.
.
const navigate = useNavigate();
const getCurrencyFromPath = () => {
let currency = query.get('currency') || '';
if (!isValidCurrency(currency)) {
currency = Currencies.USD;
navigate(`/HistoricalRates?currency=${currency}`);
}
setCurrency(currency as Currencies);
};
This part should'nt be in the component of this route /HistoricalRates?currency=${currency}
React.useEffect(() => {
getCurrencyFromPath();
}, []);
I was wondering Why this isn't an infinite loop?
I set the state inside of the useEffect hook and that should re-render the component and useEffect will be ran again and again and agian...
why this isn't what i expected?
import React, { useState, useEffect } from "react";
import axios from "axios";
const App = () => {
const [data, setData] = useState();
useEffect(() => {
axios.get("https://jsonplaceholder.typicode.com/comments").then((r) => {
console.log("Checking...");
setData(r.data[0]);
});
});
return (
<div>
<h1>{data}</h1>
</div>
);
};
export default App;
useEffect also accepts an array of dependencies i.e., useEffect will get executed when any values inside the array changes. So, if you want useEffect to be called whenever data changes, do this:
useEffect(() => {
axios.get("https://jsonplaceholder.typicode.com/comments").then((r) => {
console.log("Hello world");
setData(r.data[0]);
});
}, [data]);
The useEffect hook needs a second parameter, the dependency array.
When this dependency array is empty it will have the same behavior as the componentDidMount
If you add some dependencies to your array as data this useEffect will be executed every time that the data state changes.
So, if you want to fetch the data when the component loads you must do something like this.
useEffect(() => {
axios.get("https://jsonplaceholder.typicode.com/comments").then((r) => {
console.log("Hello world");
setData(r.data[0]);
});
}, []);
const Demo = () => {
const { name } = useContext(AppContext);
function emiterCallback(val) {
console.log('value==', name);
if (name !== val) {
setContextState({ name: val });
}
}
useEffect(() => {
window.eventEmitter.on('CHANGED', emiterCallback);
return () => {
window.eventEmitter.removeListener('CHANGED', emiterCallback);
};
}, []);
}
in class component
this.emiterCallback = this.emiterCallback.bind(this) can solve my question, but how to use it in hook ?
The problem you have here is due to the fact that useEffect with an empty array dependency only runs once - when the component mounts. This means that the emiterCallback it assigns as the event function is the very first one that's made on the first render. Since you just declare emiterCallback in the body of the function, it gets remade every single re-render, so after a single re-render, it will be a different one to the event one you assigned when the component mounted. Try something like this:
import React, { useCallback, useContext, useEffect } from 'react';
...
const Demo = () => {
const { name } = useContext(AppContext);
// Assign it to a memoized function that will recalculate as needed when the context value changes
const emiterCallback = useCallback((val) => {
console.log('value==', name);
if (name !== val) {
setContextState({ name: val });
}
}, [name]);
// Adding the function as a dependency means the .on function should be updated as needed
useEffect(() => {
window.eventEmitter.on('CHANGED', emiterCallback);
return () => {
window.eventEmitter.removeListener('CHANGED', emiterCallback);
};
}, [emiterCallback]);
}
This code isn't tested but you get the idea
use useCallback to memorize the effect no need for bind since there is no this as it is not a class,
Here read more about it -
How can I bind function with hooks in React?
A callback function sets the component state. But sometimes subscription which supplies the data need to end. Because callback is executed asynchronously, it's not aware if the subscription ends just after making the service call (which executes the callback function).
Then I see following error in the console:
Warning: Can't perform a React state update on an unmounted component.
This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in a useEffect
cleanup function.
Is there a way to access the component state, even if I am in the callback function?
This would be the steps:
subscribe with parameters
unsubscribe
component is unmounted
subscribed service executes the callback function
callback functio sets state in an unmounted component and it gives error above
You can use a ref like this:
const mounted = useRef(false);
useEffect(() => {
mounted.current = true;
return () => { mounted.current = false; };
}, []);
Then in your callback you can check if mounted.current === false and avoid setting the state
Here is some pseudo code how you can use useEffect to see if a component is mounted.
It uses useEffect to listen to someService when it receives a message it checks if the component is mounted (cleanup function is also called when component unmounts) and if it is it uses setServiceMessage that was created by useState to set messages received by the service:
import { useState, useEffect } from 'react';
import someService from 'some-service';
export default props => {
const userId = props.userId;
const [serviceMessage, setServiceMessage] = useState([]);
useEffect(
() => {
const mounted = { current: true };
someService.listen(
//listen to messages for this user
userId,
//callback when message is received
message => {
//only set message when component is mounted
if (mounted.current) {
setServiceMessage(serviceMessage.concat(message));
}
});
//returning cleanup function
return () => {
//do not listen to the service anymore
someService.stopListen(userId);
//set mounted to false if userId changed then mounted
// will immediately be set to true again and someService
// will listen to another user's messages but if the
// component is unmounted then mounted.current will
// continue to be false
mounted.current = false;
};
},//<-- the function passed to useEffects
//the function passed to useEffect will be called
//every time props.userId changes, you can pass multiple
//values here like [userId,otherValue] and then the function
//will be called whenever one of the values changes
//note that when this is an object then {hi:1} is not {hi:1}
//referential equality is checked so create this with memoization
//if this is an object created by mapStateToProps or a function
[userId]
);
};
This hook (insired from Mohamed answer) solves the problem in a more elegant maner:
function useMounted() {
const mounted = useMemo(() => ({ current: true }), []);
useEffect(() => {
return () => { mounted.current = false}
}, [mounted]);
return mounted;
}
(Updated to use useMemo instead of useRef for readability).
You can return a function from useEffect, which will be fired when a functional component unmount.
Please read this
import React, { useEffect } from 'react';
const ComponentExample = () => {
useEffect(() => {
// Anything in here is fired on component mount.
return () => {
// Anything in here is fired on component unmount.
}
}, [])
}
I found the accepted answer to this question hard to read, and React provides their own documentation on just this question. Their example is:
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Specify how to clean up after this effect:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
I've created a component I call <Fade> that will fade in/out any children its given. Note that it relies on bootstrap's .fade and .show classes, though these could easily be implemented without bootstrap.
const Fade: React.FC<{}> = ({ children }) => {
const [ className, setClassName ] = useState('fade')
const [ newChildren, setNewChildren ] = useState(children)
useEffect(() => {
setClassName('fade')
const timerId = setTimeout(() => {
setClassName('fade show')
setNewChildren(children)
}, TIMEOUT_DURATION)
return () => {
clearTimeout(timerId)
}
}, [children])
return <Container fluid className={className + ' p-0'}>{newChildren}</Container>
}
It all boils down to one rule: unsubscribe from your asynchronous tasks in the cleanup function returned from useEffect.