Updating component state inside of useEffect - reactjs

I'm playing around w/ hooks for the first time. In my component I make a request for some data inside of useEffect then want to save that data to local state. I try using setUser inside of useEffect but state never gets updated. I've tried it w/o the second argument [] but I get an infinite loop. I know setState is asynchronous and previously you could specify a function as a second argument to setState which would run when state had updated.
I'm wondering what the correct process is for updating state inside of useEffect
import React, { useEffect, useState } from 'react';
import { withRouter } from 'react-router-dom';
import { db } from '../../constants/firebase';
function Profile(props) {
const [ user, setUser ] = useState(null);
const { username } = props.match.params;
useEffect(() => {
db.collection("users").where("username", "==", username)
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(doc.data()); // data comes back fine
setUser(doc.data());
console.log(user); // user is still null
});
})
.catch((error) => {
console.log("Error getting user: ", error);
});
}, [username])
return <div>Profile</div>
}
export default withRouter(Profile);

You can use another useEffect which will run when user updates.
useEffect(() => {
console.log(user);
...
}, [user]);

Try change 2nd argument of useEffect from [username] to [props.match.params.username]

Related

Why useEffect does not behave like what i expect?

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]);
});
}, []);

Problem with asynchronous tasks in useEffect hook

I have an error like this:
index.js:1 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.
at ToDoAdder (http://localhost:3000/static/js/main.chunk.js:193:3)
here's my code:
import React, { useState } from 'react';
import { useEffect } from 'react';
import { Trash, Pen, CheckLg, XCircleFill } from 'react-bootstrap-icons';
import { Button } from 'react-bootstrap';
import store from '../../store';
function ToDoAdder({ data }) {
const [user, setUser] = useState({});
const { id, title, completed, userId } = data;
useEffect(() => {
fetchUsers();
}, [user]);
const fetchUsers = async () => {
const data = await fetch(
`https://jsonplaceholder.typicode.com/users/${userId}`
);
const users = await data.json();
setUser(users);
return user;
};
const handleTodoRemove = () => {
console.log(id);
store.dispatch({ type: 'remove/todo', payload: { id } });
};
return (
// here goes some code
)
}
export default ToDoAdder;
Please help me to fix this problem
That's not an error, that's a warning.
As it says, your component unmounted (why, we can't tell, since we don't see how it's used) before the async call finished.
In your case, it doesn't even indicate a memory leak unlike the message insinuates.
If you want to get rid of the warning, you can guard the setUser call using a hook that checks whether the component is actually still mounted at that point. One such reusable hook is react-use's useMountedState.

useState setter not updating state when called in useEffect

Pretty much what it says on the title. When I console.log(repos) it returns an empty array. Why is the repos state not updating?
import React, { useEffect, useState } from "react";
import axios from "axios";
export default () => {
const [repos, setRepos] = useState([]);
useEffect(() => {
(async () => {
try {
let repo_lists = await axios.get(
"https://api.github.com/users/Coddielam/repos"
// { params: { sort: "created" } }
);
setRepos(repo_lists.data.slice(1).slice(-10));
console.log(repo_lists.data.slice(1).slice(-10));
console.log(repos);
} catch (err) {
setRepos([
"Something went wrong while fetching GitHub Api./nPlease use the link below to view my git repos instead ",
]);
}
})();
}, []);
return (
<div className="content">
<h2>View my recent git repos:</h2>
<ul>
...
</ul>
</div>
);
};
Answer is very simple. Your useState is updating .. believe me. The reason why you don't see it when you console.log() is because SetRespos is an asynchronous function.
Basically when you declare a function to update you useState value, react will use it as an async function
EXAMPLE
const [example, setExample] = useState('');
useEffect(() => {
setExample('Hello');
console.log('I'm coming first'); // This will be executed first
console.log(example); // This will come after this
}, [])
The output will be :
I'm coming first
// Blank Line
But still your useState will update after this. If you want to see that do this :
useEffect(() => {
console.log(respose); // This will give you the value
}, [respos])
I'm using a separate useEffect to console.log() the value. In the [] (dependency array) we pass respos which simply means that the useEffect will run every time the value of respos changes.
Read more about useStates and useEffects in react's documentation
State updates are async. You will only see them reflected on the next render. If you console log the state immediately after calling setState it will always log the current state, not the future state.
You can log the state in an effect every time it changes and you will see it changing:
useEffect(() => console.log(repos), [repos]);
This effect will be called after the state update has been applied.

How do I correctly implement setState() within useEffect()?

If this useEffect() runs once (using [] as the second parameter), setTicket(response.data) does not update the value of ticket with data. If I run useEffect() with [ticket] as the parameter, it updates the value of ticket with data, but useEffect becomes an infinite loop.
I need it to run once and update the ticket data. I don't think I understand useEffect() and its second parameter.
What do I do to get the expected result?
import React from "react";
import axios from "axios";
import { useState, useEffect } from "react";
const EditTicket = (props) => {
const [ticket, setTicket] = useState("");
useEffect(() => {
axios
.get("http://localhost:4000/tickets/" + props.match.params.id)
.then((response) => {
setTicket(response.data);
console.log({ ticket });
})
.catch(function (error) {
console.log(error);
});
}, []);
return <div>edit</div>;
};
export default EditTicket;
ticket is a local const. It will never change, and that's not what setTicket is trying to do. The purpose of setTicket is to tell the component to rerender. On that next render, a new local variable will be created, with the new value.
Your code is already written the way it should be written, except that your log statement is not providing you with any useful information. If you want to see that it rerenders with the new value you could move the log statement to the body of the component.
const EditTicket = (props) => {
const [ticket, setTicket] = useState("");
console.log('rendering', ticket);
useEffect(() => {
// same as before

React: Trying to rewrite ComponentDidUpdate(prevProps) with react hook useEffect, but it fires when the app starts

I'm using a componentDidUpdate function
componentDidUpdate(prevProps){
if(prevProps.value !== this.props.users){
ipcRenderer.send('userList:store',this.props.users);
}
to this
const users = useSelector(state => state.reddit.users)
useEffect(() => {
console.log('users changed')
console.log({users})
}, [users]);
but it I get the message 'users changed' when I start the app. But the user state HAS NOT changed at all
Yep, that's how useEffect works. It runs after every render by default. If you supply an array as a second parameter, it will run on the first render, but then skip subsequent renders if the specified values have not changed. There is no built in way to skip the first render, since that's a pretty rare case.
If you need the code to have no effect on the very first render, you're going to need to do some extra work. You can use useRef to create a mutable variable, and change it to indicate once the first render is complete. For example:
const isFirstRender = useRef(true);
const users = useSelector(state => state.reddit.users);
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
} else {
console.log('users changed')
console.log({users})
}
}, [users]);
If you find yourself doing this a lot, you could create a custom hook so you can reuse it easier. Something like this:
const useUpdateEffect = (callback, dependencies) => {
const isFirstRender = useRef(true);
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
} else {
return callback();
}
}, dependencies);
}
// to be used like:
const users = useSelector(state => state.reddit.users);
useUpdateEffect(() => {
console.log('users changed')
console.log({users})
}, [users]);
If you’re familiar with React class lifecycle methods, you can think
of useEffect Hook as componentDidMount, componentDidUpdate, and
componentWillUnmount combined.
As from: Using the Effect Hook
This, it will be invoked as the component is painted in your DOM, which is likely to be closer to componentDidMount.

Resources