Update user profile information page - React useState - reactjs

I am trying to make an update user page with previous information to be rendered inside the input fields. Console.log returns the correct value but its not showing up as the initial value inside of the useState.
Getting previous user bio
function EditProfile(props) {
const user = useSelector(state => state.user);
const [profile, setProfile] = useState([])
const userId = props.match.params.userId
const userVariable = {
userId: userId
}
useEffect(() => {
axios.post('/api/users/getProfile', userVariable)
.then(response => {
if (response.data.success) {
console.log(response.data)
setProfile(response.data.user)
} else {
alert('Failed to get user info')
}
})
}, [])
console.log(profile.bio);
Heres what I am currently using to display the input field. (edited for brevity)
const [bio, setBio] = useState("");
const handleChangeBio = (event) => {
console.log(event.currentTarget.value);
setBio(event.currentTarget.value);
}
return (
<label>Bio</label>
<TextArea
id="bio"
onChange={handleChangeBio}
value={bio}
/>
)
Was trying to do this before but object was not showing up as the useState initial value
const [bio, setBio] = useState(User.bio);
Back-end - I know that $set overrides all information, so was trying to render the previous information inside of the input fields so it would not be overrided with blank values.
router.post('/edit', auth, (req, res)=> {
console.log(req.body.education)
User.updateMany(
{ _id: req.user._id },
[ {$set: { bio: req.body.bio}},
{$set: { industry: req.body.industry}},
{$set: { jobTitle: req.body.jobTitle}},
],
(err)=>{
if (err) return res.json({success: false, err});
return res.status(200).send({
success: true
});
});
});

Create some custom component and put User as props and you will see that you get data.
const [User, setUser] = useState([])
better to change to
const [user, setUser] = useState('')
You can get some issues because components starts with capital letter
And array as default value may error after first render
You can move it to separate component:
<Example user={user} />
const Example = (props) => {
const [bio, setBio] = useState(props.user.bio);
const handleChangeBio = (event) => {
console.log(event.currentTarget.value);
setBio(event.currentTarget.value);
}
return (
<label>Bio</label>
<TextArea
id="bio"
onChange={handleChangeBio}
value={bio}
/>
)
}

Related

How to use useNavigate outside react hook?

Gets list of emails from firestore and checks if current user is registered and then redirects them to sign up if they are new user.
The code is functional(it redirects succesfully) but get the following error:
arning: Cannot update a component (BrowserRouter) while rendering a different component You should call navigate() in a React.useEffect(), not when your component is first rendered.
const navigate = useNavigate();
let hasEmail = false;
const [emailList, setEmailList] = useState([]);
const emailRef = collection(db, "emails");
useEffect(() => {
const getEmails = async () => {
const data = await getDocs(emailRef);
setEmailList(
data.docs.map((doc) => ({
...doc.data(),
}))
);
};
getEmails();
}, []);
const emailCheck = (emails) => { //checks if email exists
hasEmail = emails.some((e) => e.email === auth.currentUser.email);
};
const direct = () => { // redirects to required page
if (hasEmail) {
navigate("/index");
} else {
navigate("/enterdetails");
}
};
emailCheck(emailList);
direct();
Move the email checking logic into a useEffect hook with a dependency on the emailList state.
const navigate = useNavigate();
const [emailList, setEmailList] = useState([]);
const emailRef = collection(db, "emails");
useEffect(() => {
const getEmails = async () => {
const data = await getDocs(emailRef);
setEmailList(
data.docs.map((doc) => ({
...doc.data(),
}))
);
};
getEmails();
}, []);
useEffect(() => {
if (emailList.length) {
const hasEmail = emailList.some((e) => e.email === auth.currentUser.email);
navigate(hasEmail ? "/index" : "/enterdetails");
}
}, [auth, emailList, navigate]);
This might not run without the proper firebase config but check it out
https://codesandbox.io/s/elated-bell-kopbmp?file=/src/App.js
Things to note:
Use useMemo for hasEmail instead of emailCheck. This will re-run only when emailList changes
const hasEmail = useMemo(() => {
//checks if email exists
return emailList.some((e) => e.email === auth.currentUser.email);
}, [emailList]);
There isn't really a point in having this in a react component if you are just redirecting away. Consider having the content of 'index' at the return (</>) part of this component. Only redirect if they aren't authorized
useEffect(() => {
if (!hasEmail) {
navigate("/enterdetails");
}
//else {
// navigate("/index");
//}
}, [hasEmail, navigate]);

React Query access fetched data with filter in a different component

In my home page I have two components:
<UserSearch />
<UserResults />
In <UserSearch />, I'm making the query with a filter stored in the state text, which is coming from a text input. Also, I'm initiating the fetch when the submit button is clicked.
const [text, setText] = useState('');
const {
data: users,
isLoading,
refetch,
} = useQuery(['users', text], () => searchUsers(text), {
enabled: false,
});
const handleSubmit = (e) => {
e.preventDefault();
refetch();
if (text === '') alert('Please enter something');
console.log(users);
};
I want to actually render the data in the other component <UserResults />.
How do I get the list of users with the filter text applied and display them in the sibling component?
I think useQueriyClient in react-query will do what you want.
for example you want to refetch query in other component, you can simply do this :
const queryClient = useQueryClient();
const handleSubmit = (e) => {
e.preventDefault();
refetch();
queryClient.invalidateQueries('query Key')
if (text === '') alert('Please enter something');
console.log(users);
};
and for filtering and setting query data you can do it in this way :
queryClient.setQueryData('query key', queryClient.getQueryData('query key').filter(item => item.name !== 'john doe'))
It sounds to me like you should manage the state in your home component and update that state / consume that state from your child components.
You can do this with the use of props (pass what you need to the child components)
Something like this:
const Home = () => {
const [text, setText] = useState("");
const {
data: users,
isLoading,
refetch,
} = useQuery(["users", text], () => searchUsers(text), {
enabled: false, // you will need to enable this for the query to run
});
return (
<>
<UserSearch text={text} setText={setText} />
<UserResults users={users} />
</>
);
};
const UserSearch = ({ text, setText }) => {
// put your logic here to set the text via inputs / buttons etc.
setText("set this to something else");
};
const UserResults = ({ users }) => {
return <>{users}</>; // do something with users
};
Also you'll need to enable your useQuery call for it to make the call.

Why useState is not accepting input received from the GET request?

I am trying to get an object from the server in the form {Name: true, Place: false, Animal: true, Thing: true} save this data into categoryDetail then extract it using categoryDetail.Name and then pass it to the useState. But somehow useState is not accepting this data.
Here is the code:
const [categoryDetail, setCategoryDetail] = useState({});
useEffect(() => {
axios.get('http://localhost:8080/feeds/category')
.then(response => {
if (JSON.stringify(categoryDetail)
!== JSON.stringify(response.data.category)) {
setCategoryDetail(response.data.category);
}
})
.catch(err => {
console.log(err);
})
})
console.log(categoryDetail.Name); // 👉 this gives ``true``
const [name, setName] = useState(categoryDetail.Name);
const [place, setPlace] = useState(categoryDetail.Place);
const [animal, setAnimal] = useState(categoryDetail.Animal);
const [thing, setThing] = useState(categoryDetail.Thing);
console.log(name); // 👉but here i am getting ``undefined``
(I have commented on the value I am getting)
Please guide me on why is this happening and what to do so that useState accepts the data receive by the server.Also let me know if more information is required.
Try make your code like this
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const App = () => {
const [categoryDetail, setCategoryDetail] = useState({});
const [name, setName] = useState('');
// const [place, setPlace] = useState('');
// const [animal, setAnimal] = useState('');
// const [thing, setThing] = useState('');
useEffect(() => {
const fetchCategories = async () => {
await axios
.get('https://mocki.io/v1/5a61740b-d272-4943-abe3-908628510020')
.then((response) => {
setCategoryDetail(response.data.categories[0]);
setName(response.data.categories[0].categoryName);
});
};
fetchCategories();
}, []);
// https://mocki.io/v1/5a61740b-d272-4943-abe3-908628510020
return (
<>
<p>{name}</p>
<p>{JSON.stringify(categoryDetail)}</p>
</>
);
};
export default App;
And as you see , I am doing call to setName after fetchCategories() inside of async/await call , put other state setters there
You set default value categoryDetail.Name for useState which will be never modified in renderings (I'm doubting that it's possibly undefined or an error if categoryDetail data is not there)
If you want to get name data from categoryDetail. You can set state after receiving response from useEffect
const [name, setName] = useState(''); //to be safe, set it empty
useEffect(()=>{
axios.get('http://localhost:8080/feeds/category')
.then(response=>{
if (JSON.stringify(categoryDetail) !== JSON.stringify(response.data.category)) {
setName(response.data.category.name); //set `name` into the state
setCategoryDetail(response.data.category);
}
})
.catch(err=>{
console.log(err);
})
})
From your logic, seemingly you're trying to access one by one field from categoryDetail state which is not preferable
If you want to get name, you just simply get it from categoryDetail.Name which is already set in the state
const[categoryDetail, setCategoryDetail]=useState({});
useEffect(()=>{
axios.get('http://localhost:8080/feeds/category')
.then(response=>{
if (JSON.stringify(categoryDetail) !== JSON.stringify(response.data.category)) {
setCategoryDetail(response.data.category);
}
})
.catch(err=>{
console.log(err);
})
})
//make sure your categoryDetail is not undefined
if(categoryDetail) {
console.log(categoryDetail.Name);
}

useEffect with only one dependency / React Hook useEffect has a missing dependency: 'props'

I want to be able to reuse my input form as an edit form, so in the form component I check if there is an id in the url with { useParams }, and if there is, set state of the input fields with the data i get from a function getContact that is passed down with props.
In UseEffect I want to track id but not props, so I just put [id] as a dependency at the end of useEffect. If I add [id, props] I'm not able to set the state of the input fields because it instantly sets it back to the values i get back from getContact() (since there IS an id if I'm editing).
I'm very new to React so I just wonder if there is a better way to do this, or if I should just put // eslint-disable-next-line in my code and get on with my life :-)
const Form = (props) => {
const { id } = useParams();
const [input, setInput] = useState({});
useEffect(() => {
setInput(id ? props.getContact(id) : {});
}, [id]);
const handleChange = (event) => {
let value = event.target.value;
let name = event.target.name;
setInput((prev) => {
return { ...prev, [name]: value };
});
};
... A few more functions and then a return that spits out som JSX
Anything that is used within the useEffect should be added to the dependency array. Why not extract the id you want to set instead like this?
const contectId = id ? props.getContact(id) : {};
useEffect(() => {
setInput(contectId);
}, [contectId]);
This only works if you store the {} somewhere where it does not change on render:
Either like this:
const emptyArray = React.useMemo(() => {}, [])
const contectId = id ? props.getContact(id) : emptyArray;
useEffect(() => {
setInput(contectId);
}, [contectId]);
or
const emtyArray = {};
const Form = (props) => {
const { id } = useParams();
const [input, setInput] = useState({});
const contectId = id ? props.getContact(id) : emtyArray;
useEffect(() => {
setInput(contectId);
}, [contectId]);
const handleChange = (event) => {
let value = event.target.value;
let name = event.target.name;
setInput((prev) => {
return { ...prev, [name]: value };
});
console.log(input);
};

Assign local storage to react state. React Hooks

I'm trying to sign a user in, and update my global context with the user data. To keep the user signed in I'm storing their data in local storage.
I'm using react-hooks to take care of the state, hence I have defined a state: let [userData, setUserData] = useState({});.
Since I wan't to keep the user signed in I store their data in local storage during sign in. This works and the data does in fact get stored in local storage.
My problem is however that I can't set the initial userData state equal to the current data from local storage. In other words the userData state gets reset to default on reload.
I thought that getting the initial data from local storage and assigning it to state inside the useEffect hook would work. But the state does not update when calling setUserData inside useEffect.
AuthContext.js:
import React, { createContext, useState, useEffect } from 'react';
export const AuthContext = createContext();
const AuthContextProvider = props => {
let [userData, setUserData] = useState({});
const loginUser = (data) => {
localStorage.setItem('userData', JSON.stringify({
key: data.key,
id: data.id,
email: data.email,
first_name: data.first_name,
last_name: data.last_name
})); // Save the user object in local storage
setUserData({
key: data.key,
id: data.id,
email: data.email,
first_name: data.first_name,
last_name: data.last_name
}); // Set user data
};
const logoutUser = () => {
localStorage.removeItem('userData');
setUserData({}); // Empty user data state
newToast('Successfully signed out');
};
useEffect(() => {
const localUser = JSON.parse(localStorage.getItem('userData'));
if (localUser && localUser.key) {
setUserData({
key: localUser.key,
id: localUser.id,
email: localUser.email,
first_name: localUser.first_name,
last_name: localUser.last_name
}); // Set user data
}
}, [])
return (
<AuthContext.Provider value={{ userData, loginUser, logoutUser, newToast }}>
{props.children}
</AuthContext.Provider>
)
}
export default AuthContextProvider;
Signin.js:
const Signin = props => {
let [loading, setLoading] = useState(false);
let [formError, setFormError] = useState(false);
const { userData, loginUser, newToast } = useContext(AuthContext);
const { register, handleSubmit, errors, setError, clearError } = useForm();
const onSubmit = e => {
setLoading(true);
setFormError(false);
clearError(); // Clear all erros on form
axios
.post('users/auth/login/', {
headers: { 'Content-Type': 'application/json' },
email: `${e.email}`,
password: `${e.password}`,
})
.then(res => {
const { data } = res
loginUser(data);
newToast('Successfully signed in');
})
.catch((error) => {
const { data } = error.response;
console.log(data);
data.email && setError("email", "", data.email);
data.password1 && setError("password", "", data.password1);
setFormError(true);
})
setLoading(false);
};
return ( ... );
}
Updated answer (Aug. 15, 2022):
Since accessing the local storage on every render is expensive, it is preferred to only access it during the initial render (see Wayne Ellery's comment).
So quoting Erol's solution:
const [user, setUser] = useState([], () => {
const localData = localStorage.getItem('userData');
return localData ? JSON.parse(localData) : [];
});
Original answer:
So I figured out a solution!
In AuthContext.js i didn't need to assign the state in useEffect.
Instead I get the initial data directly when defining the state hooks:
const localUser = JSON.parse(localStorage.getItem('userData')) || {};
let [userData, setUserData] = useState(localUser);
That way I don't need the useEffect hook at all.
I hope this is the recommended way of doing it.
If I understand your question, you could do the following:
const [user, setUser] = useState([], () => {
const localData = localStorage.getItem('userData');
return localData ? JSON.parse(localData) : [];
});
How about to use useReducer like this?
const [user, setUser] = useReducer((prev, cur) => {
localStorage.setItem('userData', JSON.stringify(cur));
return cur;
}, JSON.parse(localStorage.getItem('userData')));
You can call
setUser({ key: '1', ... });

Resources