How to parse object to another component using fetch - reactjs

I need to be able to parse obj to another component called GuestForm.
However when i try to set obj.first_name i can see in the console that the the obj.first_name value is empty.
On top of having the object empty i would like to parse it to the component.
import React, { Component, useState, useEffect } from 'react';
import GuestForm from '../../components/Guests/GuestForm.js';
import { useParams } from 'react-router-dom';
import axios from "axios";
function Edit() {
const { id } = useParams();
const [mode, setMode] = useState('edit');
const [successMessage, setsuccessMessage] = useState('The guest has been edited successfully!');
const [action, setAction] = useState('/guests/edit');
const obj = {first_name: '', last_name: '', email: '', password: ''};
const headers = {
'Content-Type': 'application/json;charset=UTF-8',
"Access-Control-Allow-Origin": "*",
"Accept": "application/json"
}
const res = fetch(process.env.REACT_APP_API_URL + action, {
method: 'POST',
headers: headers,
body: JSON.stringify({data: {id: id}}),
})
.then((response) => response.json())
.then((responseJson) => {
//return responseJson.json.guest;
obj.first_name = responseJson.json.guest.first_name;
})
.catch((error) => {
console.error(error);
});
console.log(obj); // Empty value for first name here...
return (
<>
<div className="container">
<GuestForm mode={mode} successMessage={successMessage} obj={obj} action={action} />
</div>
</>
);
}
export default Edit;
GuestForm
Here the component GuestForm which should display first name value in the field
import React, { Component, useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Link, useHistory } from 'react-router-dom';
// react-bootstrap components
import {
Button,
Card,
Form,
Row,
Col,
} from "react-bootstrap";
import axios from "axios";
import { toast } from 'react-toastify';
function GuestForm({mode, successMessage, obj, action}) {
const history = useHistory();
const [details, setDetails] = useState([]);
const [loading, setLoading] = useState(false);
const [first_name, setFirstName] = useState(obj.first_name);
const [last_name, setLastName] = useState(obj.last_name);
const [email, setEmail] = useState(obj.email);
const [password, setPassword] = useState(obj.password);
const handleSave = e => {
e.preventDefault();
setLoading(true);
axios({
method: "POST",
url: process.env.REACT_APP_API_URL + action,
headers: { 'Content-Type': 'application/json;charset=UTF-8', "Access-Control-Allow-Origin": "*", "Accept": "application/json" },
data: {
data: obj
}
}).then(result => {
if(result.data.json.error == false) {
toast(successMessage, {
position: "top-right",
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
});
history.push('/dashboard/guests');
}
setDetails(result.data.json);
setLoading(false);
});
};
return (
<>
<div className="container">
<div class="row">
<div class="col-lg-12">
<h1 className="mt-0 mb-4 green-color">{mode == 'edit'? <span>Edit</span>: 'New' } Guest</h1>
</div>
</div>
<Form onSubmit={handleSave} autoComplete="off">
<div class="row">
<div class="col-xs-12 col-sm-6 col-md-6 col-lg-6 mt-2">
<Form.Group>
<label htmlFor="exampleInputEmail1">
Email Address
</label>
<Form.Control
value={email}
onChange={e => setEmail(e.target.value)}
type="email"
autoComplete="off"
></Form.Control>
</Form.Group>
</div>
<div class="col-xs-12 col-sm-6 col-md-6 col-lg-6 mt-2">
<Form.Group>
<label>Password</label>
<Form.Control
value={password}
onChange={e => setPassword(e.target.value)}
type="password"
autoComplete="new-password"
></Form.Control>
</Form.Group>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-6 col-md-6 col-lg-6 mt-2">
<Form.Group>
<label>First Name</label>
<Form.Control
value={first_name}
onChange={e => setFirstName(e.target.value)}
type="text"
autoComplete="off"
></Form.Control>
</Form.Group>
</div>
<div class="col-xs-12 col-sm-6 col-md-6 col-lg-6 mt-2">
<Form.Group>
<label>Last Name</label>
<Form.Control
value={last_name}
onChange={e => setLastName(e.target.value)}
type="text"
autoComplete="off"
></Form.Control>
</Form.Group>
</div>
</div>
{(details.guest && details.error ) && <div className="error-message mt-4 mb-1">{details.message}</div>}
<Button
className="btn-fill pull-right mt-3"
type="submit"
variant="info"
disabled={loading}
>
{loading && <span>{mode == 'edit'? <span>SAVE CHANGES</span>: 'ADD' }...</span>}
{!loading && <span>{mode == 'edit'? <span>SAVE CHANGES</span>: 'ADD' }</span>}
</Button>
<div className="clearfix"></div>
</Form>
</div>
</>
);
}
export default GuestForm;

The reason your console.log is showing up as empty is because you are setting the value of obj.first_name in an asynchronous callback, but the actual logging line will be executed synchronously before that asynchronous callback is called. If you were to instead add another .then to the chain and do the console.log in there, you would see the updated value. Here's a snippet that demonstrates what I mean:
const obj = { a: 'b' };
Promise.resolve()
.then(() => {
obj.a = 'c';
})
.then(() => {
console.log('async:', obj);
});
console.log('sync:', obj);
If you want to send this value to GuestForm, you'll have to use a state variable that will be updated once the fetch call finishes. You also want to wrap this fetch call in a useEffect, so that calling setObj doesn't result in an endless loop (the fetch call causes the state update, which then causes the component to be re-rendered, which causes the fetch call to rerun, and so on). Something like:
import React, { Component, useState, useEffect } from 'react';
import GuestForm from '../../components/Guests/GuestForm.js';
import { useParams } from 'react-router-dom';
import axios from "axios";
function Edit() {
const { id } = useParams();
const [mode, setMode] = useState('edit');
const [successMessage, setsuccessMessage] = useState('The guest has been edited successfully!');
const [action, setAction] = useState('/guests/edit');
const [obj, setObj] = useState({first_name: '', last_name: '', email: '', password: ''});
const headers = {
'Content-Type': 'application/json;charset=UTF-8',
"Access-Control-Allow-Origin": "*",
"Accept": "application/json"
}
useEffect(() => {
const res = fetch(process.env.REACT_APP_API_URL + action, {
method: 'POST',
headers: headers,
body: JSON.stringify({data: {id: id}}),
})
.then((response) => response.json())
.then((responseJson) => {
//return responseJson.json.guest;
const newObj = { ...obj, first_name:
responseJson.json.guest.first_name };
setObj(newObj);
})
.catch((error) => {
console.error(error);
});
}, []);
console.log(obj); // This will now show the updated value (but will still have the default value on the initial render)
return (
<>
<div className="container">
<GuestForm mode={mode} successMessage={successMessage} obj={obj} action={action} />
</div>
</>
);
}
export default Edit;
To use the updated value in GuestForm, you need to make sure your state variable is updated when the passed in prop is updated. This is best achieved with a useEffect. Add this to your GuestForm component
useEffect(() => {
setFirstName(obj.first_name);
}, [obj]);
This is necessary because you're duplicating the prop value with state variables in the child component. A more common pattern would be to pass both obj and setObj as props to GuestForm so that in the child you can modify the parent's state variable directly without creating a copy

Related

How to run multiple functions at React.js?

Currently, I'm making a system that can control home electrical equipment on the web.
Backend is ready,
I'm trying to implement a function to adjust the brightness of the light with a slider.
I can set brightness_value variable is assigned a number from 0 to 100 when the slider is moved with the code below.
<input type="range" name="speed" min="0" max="100"
value={brightness_value} onChange={(e) => setBrightnessValue(e.target.value)}></input>
The problem is that I want to fire the lightOn function at the same time as I move the slider but I don't know what to do.
(I'm already using onChange, so can't I use it?)
LightDetail.js
import React, { useState, useEffect, useCallback, onClick} from 'react';
import axios from 'axios';
import ic_light from "../../images/icons/ic_light.png"
const LightDetail = () => {
const [light, setLight] = useState([]);
const [brightness_value, setBrightnessValue] = useState();
// set light strength
const lightOn = async(data) => {
await axios.post('xxx.com/light/turn_on',
{
brightness: brightness_value
},
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${cookies.get('accesstoken')}`
},
})
.then(result => {
console.log('Turn on!');
getDevices();
})
.catch(err => {
console.log('Turn on Missed!');
});
}
// get light data from backend
const getDevices = async(data) => {
await axios.get('xxx.com/device_listr',
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${cookies.get('accesstoken')}`
},
})
.then(result => {
console.log(result.data)
setLight(result.data.attributes.light);
})
.catch(err => {
console.log(err);
});
}
useEffect(() => {
getDevices();
}, []);
return (
<div className="container">
<div className="row mx-auto text-center">
<>
{light.map((item,i) =>
<div key={i} className="col-12">
<div className="box h-100">
<img className="" src={ic_light} />
<input type="range" name="speed" min="0" max="100"
value={brightness_value} onChange={(e) => setBrightnessValue(e.target.value)}></input><br></br>
<Link to={`/device_list`} className='btn btn-primary col-4'>Back</Link>
</div>
</div>
)}
</>
</div>
</div>
);
}
export default LightDetail;
You can define onChange as a custom event handler where you can do whatever.
Example snippet:
const handleSliderChange = (e) => {
setLightOn(e.target.value)
setBrightnessValue(e.target.value)
}
...
<input type="range" name="speed" min="0" max="100"
value={brightness_value} onChange={handleSliderChange} />
You should use the state to drive the view of the view to do
Just add
useEffect(() => {
lightOn()
}, [brightness_value])

Why is useEffect(()={...},[]) not being called on component load?

There are numerous questions relating to useEffect() and running on initial page render. I am experiencing the same problem even after making sure my code is correct regarding parameters passed to useEffect.
useEffect(() => {
const token = Cookies.get('token');
setRole(Cookies.get('role'));
fetch(`${process.env.API_URL}/user/user-details`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
"user_id": role,
"language": "en",
"api_token": token
})
})
.then((user) => user.json())
.then((thisUserData) => {
if (thisUserData.status_code == 200) {
setThisUser(thisUserData)
}
})
}, [])
For some reason this doesn't seem to be called after the component mounts.
Here is the full page code:
import Header from "../../../components/header"
import { useRouter } from "next/router"
import Link from 'next/link'
import Pagination from "../../../components/datatable/pagination"
import Cookies from "js-cookie"
import { parseCookies } from "nookies"
import { useState, useEffect } from "react"
import { Modal, Button } from "react-bootstrap";
import UserSidebar from "../../../components/user_sidebar"
import TabButtonUser from "../../../components/tabbuttonuser"
import Address from "../../../components/address"
const AdminUsers = ({ data }) => {
const router = useRouter()
// const limit = 3
// const lastPage = Math.ceil(totalPage / limit)
// console.log(data)
// console.log(listUsers)
/**
* Manage states
*/
const [show, setShow] = useState(false);
const [isEdit, setIsEdit] = useState(false);
const [fullNname, setFullName] = useState("");
const [emailID, setEmailID] = useState("");
const [country_code, setCountry_code] = useState("");
const [phone_number, setPhone_number] = useState("");
const [company_access, setCompany_access] = useState("");
const [isActive, setActive] = useState("");
const [userID, setUserId] = useState("");
const [role, setRole] = useState("");
const [thisUserData, setThisUser] = useState({})
useEffect(() => {
const token = Cookies.get('token');
setRole(Cookies.get('role'));
fetch(`${process.env.API_URL}/user/user-details`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
"user_id": role,
"language": "en",
"api_token": token
})
})
.then((user) => user.json())
.then((thisUserData) => {
if (thisUserData.status_code == 200) {
setThisUser(thisUserData)
}
})
}, [])
/**
* Clear values
*/
const handleClose = () => {
setShow(false)
setIsEdit(false);
setUserId("")
setFullName("");
setEmailID("");
setCountry_code("");
setPhone_number("");
};
const handleShow = () => setShow(true);
/**
* Add User
* #param {*} e
* #returns
*/
const addUser = async (e) => {
e.preventDefault();
const token = Cookies.get('token');
if (!token) {
return {
redirect: {
destination: '/',
permanent: false,
},
}
}
const resUser = await fetch(`${process.env.API_URL}/user/create-sub-admin`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
"full_name": fullNname,
"email_id": emailID,
"country_code": "+1",
"phone_number": phone_number,
"api_token": token
})
})
const res2User = await resUser.json();
console.log(res2User);
if (res2User.status_code == 200) {
handleClose();
setFullName("");
setEmailID("");
setCountry_code("");
setPhone_number("");
}
}
/**
* Get user details basis of user id for edit purpose
* #param {*} id
* #returns
*/
const getUser = async (id) => {
// e.preventDefault();
setIsEdit(true);
setShow(true);
setUserId(id)
const token = Cookies.get('token');
if (!token) {
return {
redirect: {
destination: '/',
permanent: false,
},
}
}
const userData = await fetch(`${process.env.API_URL}/user/user-details`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
"user_id": id,
"language": "en",
"api_token": token
})
})
const userData2 = await userData.json();
console.log(userData2);
if (userData2.status_code == 200) {
// handleClose();
setFullName(userData2?.data?.full_name);
setEmailID(userData2?.data?.email_id);
setCountry_code(userData2?.data?.phone_number?.country_code);
setPhone_number(userData2?.data?.phone_number?.phone_number);
}
}
/**
* Remove user
* Api is pending
* #param {*} id
*/
const removeUser = async (id) => {
}
/**
*Update User
*
* #return {*}
*/
const updateUser = async () => {
// e.preventDefault();
const token = Cookies.get('token');
if (!token) {
return {
redirect: {
destination: '/',
permanent: false,
},
}
}
const resUser = await fetch(`${process.env.API_URL}/user/update-user`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
"user_id": userID,
"full_name": fullNname,
"email_id": emailID,
"country_code": "+1",
"phone_number": phone_number,
"api_token": token,
"is_active": isActive
})
})
const res2User = await resUser.json();
console.log(res2User);
if (res2User.status_code == 200) {
setFullName("");
setEmailID("");
setCountry_code("");
setPhone_number("");
setIsEdit(false);
setShow(false);
setUserId("")
}
}
const address = {
"address_line": "",
"city_name": "",
"state_name": "",
"zip_code": ""
}
return (
<>
<Header />
<div className="container">
<div className="row">
<div className="col-3">
<UserSidebar data={thisUserData} />
</div>
<div className="col">
<div className="sidebarwrap">
{/* <TabButtonUser id={data?._id} /> */}
<h3 className="acc_title">My Company</h3>
<h2 className="login_name">Alliance Credit</h2>
<div className="acc_email">
email#company.com
</div>
<div className="acc_phone">+1234567890</div>
<Address address={address} />
<div className="ac_left acc_title">All Team Members</div>
<div className="ac_right">
{role.indexOf('admin') > -1 ?
<button className="btn btnedit" onClick={handleShow}>Add Sub-Admin</button>
: ''}
</div>
<div className="clearfix"></div>
<div className="listing">
<table id="example" className="table table-striped">
<thead>
<tr>
<th><div>Sr. Number</div></th>
<th><div>User Name</div></th>
<th><div>Date Added</div></th>
<th><div>Email</div></th>
<th><div>Actions</div></th>
</tr>
</thead>
<tbody>
{data?.map((item, index) => (
<tr key={index}>
<td>{index + 1}</td>
<td>{item.full_name}</td>
<td>{item.date_added}</td>
<td>{item.email_id}</td>
<td>
<>
<button className="btn viewmore" onClick={() => getUser(item._id)}>Edit User</button>
</>
</td>
</tr>
))}
</tbody>
</table>
{/* <Pagination page={page} totalPage={totalPage} lastPage={lastPage} /> */}
</div>
</div>
</div>
</div>
</div>
<Modal show={show} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>{isEdit == false
? "Add Sub-Admin"
: "Edit Sub-Admin"
}</Modal.Title>
</Modal.Header>
<Modal.Body>
<div className="popupform">
<form method="POST">
<div className="row">
<div className="col">
<label htmlFor="fullname" className="form-label">Full Name</label>
<input className="form-control" name="fullname" type="text" id="fullname" value={fullNname} onChange={(e) => setFullName(e.target.value)} />
</div>
</div>
<div className="row">
<div className="col">
<label htmlFor="emailID" className="form-label">Email</label>
<input className="form-control" name="emailID" type="text" id="emailID" value={emailID} onChange={(e) => setEmailID(e.target.value)} />
</div>
</div>
<div className="row">
<div className="col">
<label htmlFor="phone_number" className="form-label">Phone Number</label>
<input className="form-control" name="phone_number" type="text" id="phone_number" value={phone_number} onChange={(e) => setPhone_number(e.target.value)} />
</div>
</div>
<div className="row">
<div className="col">
{isEdit ? (
<>
<label htmlFor="phone_number" className="form-label">Active status</label>
<select className="form-control form-select" onChange={(e) => setActive(e.target.value)}>
<option value="0">Active</option>
<option value="1">Deactivate</option>
</select>
</>
) : ''
}
</div>
</div>
<div>
<input className="form-control" name="userID" type="hidden" id="company_logo_en" value={userID} />
</div>
</form>
</div>
</Modal.Body>
<Modal.Footer>
{isEdit == false
? <>
<Button variant="secondary" className="btn btnedit" onClick={handleClose}>Cancel</Button>
<Button variant="primary" onClick={addUser}>Add User</Button>
</>
:
<>
<Button variant="primary" className="btn btnremove" onClick={removeUser}>Remove User</Button>
<Button variant="primary" className="btn btnedit">Reset User</Button>
<Button variant="primary" onClick={updateUser}>Save</Button>
</>
}
</Modal.Footer>
</Modal>
</>
)
}
/**
*
*
* #export
* #param {*} { query: { page = 1, data = null, totalPage = 10 } }
* #return {*}
*/
// export async function getServerSideProps({ query: { page = 1, data = null, totalPage = 10 } }) {
export async function getServerSideProps(ctx) {
// const start = +page === 1 ? 0 : (+page + 1)
// const { locale, locales, defaultLocale, asPath } = useRouter();
const { token } = parseCookies(ctx)
if (!token) {
return {
redirect: {
destination: '/',
permanent: false,
},
}
}
const res = await fetch(`${process.env.API_URL}/user/list-sub-admin`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
"language": 'en',
"api_token": token,
})
})
const data = await res.json()
/**
* limit, start, search item
*/
return {
props: {
data: data?.data || [],
}
}
}
export default AdminUsers
I am sure it is something simple that I've missed (it usually is) but I can't seem to spot it.
Thanks.
It's not clear for me this problem, but I try to write what I see: you have defined useEffect with the fetch without deps. In that fetch you use role state which is initialized to empty string in the first time. You set the role using setRole in the same useEffect but this has no effect for the precedent reasons.
My suggestion is to remove role state and redefine useEffect like this:
useEffect(() => {
const token = Cookies.get('token');
const role = Cookies.get('role');
fetch(`${process.env.API_URL}/user/user-details`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
user_id: role,
language: 'en',
api_token: token,
}),
})
.then((user) => user.json())
.then((thisUserData) => {
if (thisUserData.status_code == 200) {
setThisUser(thisUserData);
}
});
}, []);
React's state setters are asynchronous(see here), so when you use 'role' right after 'setRole', role is still an empty string. Thus the fetch returns nothing.
The answer may not be directly related to the question itself, but it may be useful to an another developer who is searching for an answer.
In my case I had two components and change rendering them when a button was clicked on the interface and I had accidentally put [] in dependencies, I fixed it by removing it.
Before,
useEffect(() => {
// your function
},[]);
After
useEffect(() => {
// your function
});

Setting state of a 'Loading' boolean in react doesn't update

I have a page which allows a user to submit a url from which data is scraped. The user is subsequently presented with the filtered data.
Because the scraping takes some time I would like to implement a loader. While the loader class will (hopefully) be relatively straight forward, it's the state for loading which I'm having issues with. The state itself is never updated. Although other state values are such as setFilters.
Body.js
const [searchState, setSearchState] = useState({
searchCriteria: "https://en.wikipedia.org/wiki/2020_Central_Vietnam_floods",
headers:[],
references: []
});
const [filterState, setFilters] = useState({
languageFilter: ""
});
const [loadingState, setLoadingState] = useState({
loading: false
});
The above are all passed into Search with a context
<>
<SearchContext.Provider value={{searchState, setSearchState,filterState, setFilters, loadingState, setLoadingState}} >
<Search />
<DonateButton />
<WikiHeaderGroup />
</SearchContext.Provider>
</>
And then I have a handleSubmit inside the Search component.
Search.js
import React, {useContext} from "react";
import {SearchContext} from "../../contexts/SearchContext"
import "../../App.css"
export function Search (){
const {searchState, setSearchState, filterState, setFilters, loadingState, setLoadingState} = useContext(SearchContext);
const handleSubmit = (event) => {
setFilters({languageFilter:""})
setLoadingState({loading:true})
console.log("Loading State : " + loadingState.loading)
event.preventDefault();
event.persist(); //persists the event object into the function
const fetchReferences = async () => {
fetch('http://127.0.0.1:8080/search/', {
method: 'POST',
body: JSON.stringify({
url: searchState.searchCriteria
}),
headers: {"Content-type": "application/json; charset=UTF-8"}
}).then(response => {
console.log(response)
return response.json()
}).then(json => {
console.log(json)
setSearchState({
headers:json.headers,
references:json.references
})
setLoadingState({loading:false})
console.log("Loading State : " + loadingState.loading)
});}
fetchReferences();
}
return (
<div className="search container">
<div className="input-group input-group-sm mb-3 center">
<div className="input-group-prepend">
<span className="input-group-text" id="inputGroup-sizing-sm">Wikipedia URL:</span>
</div>
<form onSubmit={(event) => handleSubmit(event)}>
<input
type="text"
id="searchBox"
className="form-control center"
aria-label="Sizing example input"
aria-describedby="inputGroup-sizing-sm"
value={searchState.searchCriteria}
onChange={(event) => setSearchState({searchCriteria:event.target.value, resultId:0})}
placeholder="Add a url" />
</form>
</div>
</div>
);
}
export default Search;
don't use object for booleans, just
const [loadingState, setLoadingState] = useState(false);
....
setLoadingState(true)
btw looks like a closure problem. you see loadingState always false cause the closure.
take a look at this Be Aware of Stale Closures when Using React Hooks
A way to solve it is using refs
const loadingStateRef = useRef(loadingState);
//then inside the function u can access
latestValue.current

Default value in input field lost after click

So, i'm making this social network app and it has user profile. if user wants to update profile, eg. name, by opening modal EditUser, the old value of users name should be there, in input filed, and user needs to have opportunity to change/update that.
I used 'defaultValue', and there is it, in input field, but if i don't change anything in that field, just click 'update', it will be lost. updated value is empty string then, and not the value that is showing in that field. how can i fix this?
Also interested how to set as default user image. so, user has profile image, and on update if user changes only name, not the picture or something else, everything else should be the same, but photo is also like input text field lost.
here is what i tried:
MyProfile.tsx
import React, { useState, useEffect, useContext } from 'react'
import './myprofile.css'
import Axios from 'axios'
import SinglePost from '../single_post/SinglePost'
import { AppContext } from '../context/AppContext'
import UpdateProfile from '../modals/UpdateProfile'
function MyProfile() {
const [userInfo, setUserInfo] = useState({
firstName: '',
lastName: '',
userBio: 'Write something about yourself.',
userPhoto: ''
})
const [isEditOpen, setIsEditOpen] = useState(false)
const { userID, setUserID } = useContext(AppContext)
// open modal on click 'edit'
const editUser = () => {
setIsEditOpen(true)
}
// get user data
const storedToken = localStorage.getItem('token')
useEffect(() => {
const config = {
headers: { "x-auth-token": `${storedToken}` }
}
Axios
.get('/api/auth/user', config)
.then(res => {
console.log('response', res)
const user = res.data.user
setUserID(user._id)
setUserInfo({
firstName: user.first_name,
lastName: user.last_name,
userBio: user.user_bio,
userPhoto: user.profile_image
})
})
.catch(err => console.log(err))
}, [])
return (
<div className="profile-container">
<button className="btn-edit" onClick={editUser}>
<i className="fa fa-edit"></i>
</button>
<div className="user-info">
<div className="img-circular">
<img className="user-profile-img2" src={userInfo.userPhoto}></img>
</div>
<p className="user-name">{userInfo.firstName} {userInfo.lastName}</p>
<p className="about-user">{userInfo.userBio}</p>
</div>
<div className="user-posts">
<p className="my-posts-title">My Posts</p>
</div>
{isEditOpen && <UpdateProfile
userID={userID}
setIsEditOpen={setIsEditOpen}
isEditOpen={isEditOpen}
setUserInfo={setUserInfo}
userInfo={userInfo}
/>}
</div>
)
}
export default MyProfile
UpdateProfile.tsx
import React, { useState, useRef, useEffect } from 'react'
import { Modal, ModalHeader, ModalBody, ModalFooter, Button, FormGroup, Label, Input } from 'reactstrap'
import Axios from 'axios'
import '../user_profile/myprofile.css'
function UpdateProfile(props: any) {
const [firstNameUpdated, setFirstNameUpdated] = useState('')
const [lastNameUpdated, setLastNameUpdated] = useState('')
const [userBioUpdated, setUserBioUpdated] = useState('')
const inputNameRef = useRef<HTMLInputElement | any>(null)
useEffect(() => {
console.log(inputNameRef.current, props.userInfo.firstName)
inputNameRef.current && (inputNameRef.current.value = props.userInfo.firstName)
}, [])
// upload image
const [file, setFile] = useState('')
const [uploaded, setUploaded] = useState('')
const handleImageUpload = (e: any) => {
e.preventDefault();
setFile(e.target.files[0])
};
const onClickHandler = (e: any) => {
const formData = new FormData()
formData.append('fileImage', file)
Axios.post("/api/image", formData, {})
.then(res => {
//console.log(`UPLOADED: http://localhost:5000/${res.data.fileImage}`)
setUploaded(`http://localhost:5000/${res.data.fileImage}`)
})
.catch(err => console.log(err))
}
// update user
const updateUser = (e: any) => {
e.preventDefault()
props.setIsEditOpen(false)
const formData = new FormData()
formData.append('fileImage', file)
formData.append('first_name', firstNameUpdated)
formData.append('last_name', lastNameUpdated)
formData.append('user_bio', userBioUpdated)
const config: any = { header: { "Content-Type": "multipart/form-data" } }
Axios
.put(`/api/users/${props.userID}`, formData, config)
.then(res => {
const user = res.data
props.setUserInfo({
firstName: user.first_name,
lastName: user.last_name,
userBio: user.user_bio,
userPhoto: user.profile_image
})
})
.catch(err => console.log(err))
}
return (
<div>
{props.isEditOpen &&
<Modal isOpen={props.isEditOpen} toggle={() => props.setIsEditOpen(!props.isEditOpen)} backdrop="static">
<ModalHeader>Update your profile</ModalHeader>
<ModalBody>
<FormGroup>
<Label>Profile Image</Label>
<Input type="file" name="fileImage" onChange={handleImageUpload}></Input>
</FormGroup>
<Button onClick={onClickHandler} className="btn-upload-img">Upload file</Button>
<div className="inline">
{uploaded ? <img src={uploaded} style={{ width: "100px" }}></img> : <img src={props.userInfo.userPhoto} style={{ width: "100px" }}></img>}
</div>
<FormGroup>
<Label>First Name</Label>
<Input type="text" onChange={(e: any) => setFirstNameUpdated(e.target.value)} defaultValue={props.userInfo.firstName}></Input>
</FormGroup>
<FormGroup>
<Label>Last Name</Label>
<input type="text" onChange={(e: any) => setLastNameUpdated(e.target.value)} defaultValue={props.userInfo.lastName} ></input>
</FormGroup>
<FormGroup>
<Label>About me</Label>
<Input type="text" onChange={(e: any) => setUserBioUpdated(e.target.value)} defaultValue={props.userInfo.userBio}></Input>
</FormGroup>
</ModalBody>
<ModalFooter>
<Button color="success" onClick={updateUser} className="btn-update">Update</Button>
<Button color="danger" onClick={() => props.setIsEditOpen(false)}>Cancel</Button>
</ModalFooter>
</Modal>}
</div>
)
}
export default UpdateProfile
I'm just trying to get the old value in input field (which i did), and user can choose if wants to change that or not. if not, old value should stay in updated profile, but in my case, on click 'update' it is lost in user profile.
In UpdateProfile, you should initialise the states with the value you got in props and later they could change.
const [firstNameUpdated, setFirstNameUpdated] = useState(props.userInfo.firstName)
const [lastNameUpdated, setLastNameUpdated] = useState(props.userInfo.lastName)
const [userBioUpdated, setUserBioUpdated] = useState(props.userInfo.userBio)
Initialising the state could solve all your problem.

I can't update array on MongoDB with React and Redux

My it's super simple but I get stuck.
I need to update an array on MongoDB with fetch PUT
I tested it with postman and works perfectly but my app React + Redux doesn't work
import React, { Fragment, useEffect, useState } from "react";
import PropTypes from "prop-types";
import "materialize-css/dist/css/materialize.min.css";
import M from "materialize-css/dist/js/materialize.min.js";
import config from "react-global-configuration";
import Preloader from "../layout/Preloader";
import { connect } from "react-redux";
import { getColors, updateColors } from "../../redux/actions/settingsActions";
const Settings = ({
setting: { settings, loading },
getColors,
updateColors
}) => {
const [HighPColor, setHighPColor] = useState("");
const [NormalPColor, setNormalPColor] = useState("");
const [LowPColor, setLowPColor] = useState("");
useEffect(() => {
M.AutoInit();
getColors();
//eslint-disable-next-line
}, []);
const onSubmit = () => {
const updColors = {
id: settings[0]._id,
colors: [
{
_id: colorsArray.colors[0]._id,
HighPColor,
NormalPColor,
LowPColor
}
]
};
updateColors(updColors);
M.toast({ html: "Settings updated" });
};
if (loading || settings === null) {
return <Preloader />;
}
const colorsArray = settings[0];
return (
<Fragment>
<div id="color-settings" className="container">
<div className="">
<h4>Set Priorities Colors </h4>
<div className="row">
<div>High Priority</div>
<div className="input-field">
<input
type="text"
name="highPColor"
defaultValue={colorsArray.colors[0].HighPColor}
onChange={e => setHighPColor(e.target.value)}
/>
</div>
</div>
<div className="row">
<div>Normal Priority</div>
<div className="input-field">
<input
type="text"
name="normalPColor"
defaultValue={colorsArray.colors[0].NormalPColor}
onChange={e => setNormalPColor(e.target.value)}
/>
</div>
</div>
<div className="row">
<div>Low Priority</div>
<div className="input-field">
<input
type="text"
name="lowPColor"
defaultValue={colorsArray.colors[0].LowPColor}
onChange={e => setLowPColor(e.target.value)}
/>
</div>
</div>
</div>
<div className="">
<a
href="#!"
onClick={onSubmit}
className="modal-close waves-effect blue btn"
>
Enter
</a>
</div>
</div>
</Fragment>
);
};
Settings.propTypes = {
setting: PropTypes.object.isRequired,
getColors: PropTypes.func.isRequired,
updateColors: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
setting: state.settings
});
export default connect(mapStateToProps, { getColors, updateColors })(Settings);
I take everything from some inputs values that work perfectly
Redux action:
export const updateColors = colors => async dispatch => {
try {
setLoading();
const res = await fetch(`/api/settings/${colors.id} `, {
method: "PUT",
body: JSON.stringify(colors),
headers: {
"Content-Type": "application/json"
}
});
const data = await res.json();
dispatch({
type: UPDATE_COLORS,
payload: data
});
} catch ...
Redux reducer:
case UPDATE_COLORS:
return {
...state,
settings: state.settings.map(setting =>
setting._id === action.payload._id ? action.payload : setting
),
loading: false
};
it gives me back:
UnhandledPromiseRejectionWarning: TypeError: Cannot destructure property `NormalPColor` of 'undefined' or 'null'.
[0] at router.put (C:\Users\Marco\Desktop\React-Course\to-do-list\routes\settings.js:81:7)
This happens despite I commented the line 81
Any Idea of my mistakes?
thanks!
It sounds odd but now works I don't know what I have done but now updates

Resources