Lifecycle - fetch data only after loading complete - reactjs

I have a component that needs to wait for the user's token to be validated first before fetching data. Right now, my component only renders when authLoading is false but I'm fetching my data in componentDidMount so sometimes the token is not validated yet and I get an unauthorized error. How should I refactor this so everything should wait for authLoading to finish?
componentDidMount() {
// if (!authLoading) { <----- Sometimes the component mounts and the fetchData function doesn't get called
this.fetchData();
// }
}
render() {
const { authLoading } = this.props;
if (!authLoading) {
return (
<Component1 />
);
}
else {
return null
}
}

You want to fetch your data only once, but also only after this.props.authLoading becomes false.
Check out componentDidUpdate(), which will allow you to update any time your props change.
Your implementation might look something like this:
componentDidUpdate(prevProps) {
if (!this.props.authLoading && prevProps.authLoading) { // tokens just loaded!
this.fetchData();
}
}

If you can convert your component to be a functional component then using hooks would me this simple.
const MyComponent = ({ authLoading }) => {
const [authLoaded, setAuthLoaded] = useState();
useEffect(
() => {
fetchData();
// process data
setAuthLoaded(/* some value based on fetched data I assume */);
},
[authLoading], // triggers effect anytime value of `authLoading` changes
);
return authLoaded ? <Component1 /> : null;
};

Related

I'm trying to pass updating data from parent to child in react, but it's not updated in function of child component

I want to pass the data from parent component to child component, and update this data in the child component every time it’s updated in the parent component.
I'm updating scrollY data every time I scroll in the parent component, and I try to pass it to the child component.
export default function Parent() {
let [scrollY, setScrollY] = useState(0);
useEffect(() => {
function onScroll(e) {
setScrollY(window.scrollY);
}
window.addEventListener("scroll", onScroll);
return () => {
window.removeEventListener("scroll", onScroll);
};
});
return (
<Child scrollY={scrollY} />
);
}
In child component, I want to use received data in animation frame function. I tried to console the received scrollY data in animation frame function and also print out it in html to check if passed data is updating properly.
export default function Child(props) {
useEffect(() => {
function onScroll() {
console.log(props.scrollY);
}
function step() {
console.log(props.scrollY);
animationFrame = window.requestAnimationFrame(step);
}
window.addEventListener("scroll", onScroll);
let animationFrame = window.requestAnimationFrame(step);
return () => {
window.removeEventListener("scroll", onScroll);
window.cancelAnimationFrame(animationFrame);
};
});
return (
<div>{props.scrollY}</div>
);
}
in html, the data is keep updating, but in console, it only prints out '0'.
what I can't understand is data in return {props.scrollY} is updated, but data in function is not updated.
what did I do wrong? how can I get updating data value also within the function?
it's the nature of console.log() that it shows the previous state/data(value) please use alert() instead to check your data like:
alert(props.scrollY)

How can I update state twice in a Parent component from a Child component?

I pass an "isLoadingToggle" to my Child component like so:
export function Parent() {
const [isLoading, setIsLoading] = useState(false)
function toggleIsLoading() {
setIsLoading(!isLoading)
}
return ( <Child toggleIsLoading={toggleIsLoading} /> )
}
and the Child:
export function Child({ toggleIsLoading }) {
...
const toggle = useCallback(() => {
// set the isLoading effect in the parent
toggleIsLoading()
axios.post(someUrl...)
.then(response =>
// do something with response
// then tell the parent it's not loading anymore
toggleIsLoading()
}).catch(error => {
// do something with error
// then tell the parent it's not loading anymore
toggleIsLoading()
})
}, []);
return ( <Button onClick={toggle}>Toggle isLoading in parent!</Button> )
Only the first isLoading state change is registered. The second call, after the axios post, is not.
How can I get isLoading to toggle in the parent whenever I call it?
It's a little bit tricky to explain what's happening here. anytime you mutate the state, the entire component re-renders. So when you get response and toggleIsLoading() is invoked. It doesn't really toggles the value of isLoading. It uses the old isLoading value from first render.
What you can do is pass the boolean param to toggleIsLoading like this
toggleIsLoading(true)
toggleIsLoading(false)
and define your function like this
function toggleIsLoading(loading) {
setIsLoading(loading)
}
Here is working demo
https://stackblitz.com/edit/react-vgrkkf?file=src/Parent.js

Initial State of Redux Store with JSON Data?

I am working on React app where the state is managed by redux. I am using actions.js file to fetch JSON data and store it directly in the store. The initial Store has just one key (data) in its obj with null as its value.
I use componentDidMount() Lifecycle to call the function which updates the store's data key with the JSON data I receive. However, whenever I load my app it gives an error because it finds the data value as null.
I get it. componentDidMount() executes after the app is loaded and the error doesn't let it execute. I tried using componentWillMount() but it also gives the same error. ( Which I use in JSX )
When I try to chanage the data's value from null to an empty obj it works for some level but after I use it's nested objects and arrays. I get error.
I wanna know what is the way around it. What should I set the vaue of inital State or should you use anyother lifecycle.
If your primary App component can't function properly unless the state has been loaded then I suggest moving the initialization logic up a level such that you only render your current component after the redux state has already been populated.
class version
class LoaderComponent extends React.Component {
componentDidMount() {
if ( ! this.props.isLoaded ) {
this.props.loadState();
}
}
render() {
if ( this.props.isLoaded ) {
return <YourCurrentComponent />;
} else {
return <Loading/>
}
}
}
export default connect(
state => ({
isLoaded: state.data === null,
}),
{loadState}
)(LoaderComponent);
Try something like this. The mapStateToProps subscribes to the store to see when the state is loaded and provides that info as an isLoaded prop. The loadState in mapDispatchToProps is whatever action creator your current componentDidMount is calling.
hooks version
export const LoaderComponent = () => {
const dispatch = useDispatch();
const isLoaded = useSelector(state => state.data === null);
useEffect(() => {
if (!isLoaded) {
dispatch(loadState());
}
}, [dispatch, isLoaded]);
if (isLoaded) {
return <YourCurrentComponent />;
} else {
return <Loading />
}
}
And of course you would remove the fetching actions from the componentDidMount of the current component.

Infinite loop because of misusing ReactJS Router, useEffect hook

In the child component, I'm calling an API then pushing to a different route, this causes App component to re-render because its location prop key (location.key) keeps changing causing the child to keep re-mounting ( I know re-rendering the parent should only re-render the child not re-mount it, but this is what's happening when I tried passing empty array to the second arg in useEffect, useEffect(() => {}, []);
I can stop App from re-rendering through shouldComponenUpdate, and update it only when location.pathname changes not when the key changes, but is there a better way?
Parent.js
constructor(props) {
super(props);
const {
....,
....,
location,
}= props;
if (props.location.pathname.includes('/x');
push('/x');
}
shouldComponentUpdate(nextProps, nextState) {
if (
this.props.location.pathname !== nextProps.location.pathname ||
this.state.match.path !== nextState.match.path
) {
return true;
}
return false;
}
render() {
<Child/>
}
Child.js
useEffect(() => {
if (Y.ID) {
fetchYDetails();
}
}, [fetchYDetails, Y.ID]);
Model.js
effects: dispatch => ({
fetchYDetails() {
const response = await request({ ...});
if (!response.error) {
dispatch(push('/z'));
};
}
});
Thank you!

Ajax request won't display in react render function

I don't know why the result of my axios promise doesn't show up in the render function. I'm using the create-react-app tools by the way.
_getPrice() {
const url = 'https://api.coinbase.com/v2/prices/BTC-USD/spot';
axios.get(url)
.then(function (response) {
//console.log(response.data.data.amount);
let prices = response.data.data.amount;
return prices;
})
}
render() {
return(<div><h3> {this._getPrice()} </h3></div>);
}
React only re-renders components when either the state or props of the component change. If data changes during the render cycle, but doesn't interact with those variables, then the changes will not show up.
You can save the result of your promise to state as follows:
getInitialState() {
return {prices: undefined}
}
componentDidMount() {
const url = 'https://api.coinbase.com/v2/prices/BTC-USD/spot';
axios.get(url)
.then(function (response) {
//console.log(response.data.data.amount);
let prices = response.data.data.amount;
this.setState({prices: prices});
}.bind(this))
}
render() {
return(<div><h3> {this.state.prices} </h3></div>);
}
first you cant call a function in return in render function and if you want update your view you must update state or props...
When requesting data to the server, the request is async, this means it will take time for the server to respond and the browser will continue the execution, than been said, in your current implementation you are returning a promise in your _getPrice function and then when the server responds you are not doing anything with the data.
The second problem is that react will only re-render the component when there are changes on the state or on the props, and in your current implementation you are not changing any of that.
Here's a sample of how you need to do it in order to make it work.
class YourComponent extends Component {
state = {
prices: 0,
};
componentDidMount() {
const url = 'https://api.coinbase.com/v2/prices/BTC-USD/spot';
axios.get(url)
.then((response) => {
let prices = response.data.data.amount;
this.setState({ prices });
});
}
render() {
const { prices } = this.state;
return(
<div>
<h3> {prices} </h3>
</div>
);
}
}
Good luck!

Resources