Use state is not updating in change event - javascript

I am building a social media app with MERN stack. The issue is that the profile picture and cover picture is not changing the state.
The API is working fine but after debugging I found that use state is not accepting the new state only in initial state i.e. null.
The handleChange functionality is working well and yet after updating resulting empty object in the formData
import React, {use State } from 'react';
import { Modal, useMantineTheme } from '#mantine/core';
import { use Dispatch, use Selector } from 'react-redux';
import { useParams } from 'react-router-dom';
import { uploadImage } from '../../actions/uploadAction';
import {updateuser} from '../../actions/userAction';
const ProfileModal = ({modalOpen,setModalOpen,data}) => {
const theme = useMantineTheme();
const {password,...other} = data;
const [formData,setFormData] = useState(other);
const [profileImage,setProfileImage] = useState(null);
const [coverImage,setCoverImage] = useState(null);
const dispatch = useDispatch();
const params = useParams();
const {user} = useSelector((state)=>state.authReducer.authData);
const handleChange = (e) =>{
setFormData({...formData, [e.target.name]:e.target.value})
console.log(formData)
}
const onImageChange = (event)=>{
if (event.target.files && event.target.files[0]) {
let image = event.target.files[0]
console.log(image)
event.target.name==="profilePicture"?setProfileImage(image):setCoverImage(image)
console.log(profileImage)
console.log(coverImage)
}
}
const handleSubmit =(e)=>{
e.preventDefault();
let userData = formData;
console.log(userData);
if(profileImage){
const data = new FormData();
const fileName = Date.now() + profileImage.name;
data.append("name",fileName);
data.append("file",profileImage);
userData.profilePicture = fileName;
console.log("profile",data)
}
try {
dispatch(uploadImage(data))
} catch (error) {
console.log(error)
}
if (coverImage) {
const data = new FormData();
const fileName = Date.now() + coverImage.name;
data.append("name",fileName);
data.append("file",coverImage);
userData.coverPicture = fileName;
}
try {
dispatch(uploadImage(data))
} catch (error) {
console.log(error)
}
dispatch(updateuser(params.id,userData))
console.log(userData)
setModalOpen(false)
}
return (
<Modal
overlayColor={theme.colorScheme === 'dark' ? theme.colors.dark[9] : theme.colors.gray[2]}
overlayOpacity={0.55}
overlayBlur={3}
size = '55%'
opened = {modalOpen}
onClose ={()=>setModalOpen(false)}>
<form className='infoForm'>
<h3>Your Info</h3>
<div>
<input type='text' className='infoInput' name='firstName'
placeholder='First Name' onChange={handleChange} value={formData.firstName} />
<input type='text' className='infoInput' name='lastName'
placeholder='Last Name' onChange={handleChange} value={formData.lastName} />
</div>
<div>
<input type='text' className='infoInput' name='worksAt'
placeholder='Work Location' onChange={handleChange} value={formData.worksAt}/>
</div>
<div>
<input type='text' className='infoInput' name='livesIn'
placeholder='Lives In' onChange={handleChange} value={formData.livesIn} />
<input type='text' className='infoInput' name='country'
placeholder='Country' onChange={handleChange} value={formData.country} />
</div>
<div>
<input type='text' className='infoInput' name='relationship' placeholder='Relationship Status' onChange={handleChange}
value={formData.relationship} />
</div>
<div>
profilePicture
<input type='file' name="profilePicture" onChange={onImageChange} alt=""/>
cover Image
<input type='file' name="coverPicture" onChange={onImageChange} alt="" />
</div>
<button className='button infoButton' onClick={handleSubmit}>Update</button>
</form>
</Modal>
);
}
export default ProfileModal

Setting the state in React acts like an async function.
Meaning that the when you set the state and put a console.log right after it, it will likely run before the state has actually finished updating.
Which is why we have useEffect, a built-in React hook that activates a callback when one of it's dependencies have changed.
Example:
useEffect(() => {
console.log(formData)
// Whatever else we want to do after the state has been updated.
}, [formData])
This console.log will run only after the state has finished changing and a render has occurred.
Note: "formData" in the example is interchangeable with whatever other state piece you're dealing with.
Check the documentation for more info about this.

Change your first import line from {use State} to {useState}...no whitespace import React, {useState } from 'react';

Related

Failed to get state by useLocation while it has Object data in state

First.js
import { useState } from "react";
import { Link } from "react-router-dom";
const First = () => {
const [name, setName] = useState("");
const [phone, setPhone] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
}
return (
<div className="First">
<h1>This is First Input Page</h1>
<form onSubmit={handleSubmit}>
<dd>data 1</dd>
<input
type="text"
value={name}
onChange={(e) =>
setName(e.target.value)
}
required
></input>
<dd>data 2</dd>
<input
type="text"
value={phone}
onChange={(e) =>
setPhone(e.target.value)
}
required
></input><br/>
<Link to={'/second'} state={{ state: { name : name , phone : phone } }}><button>submit</button></Link>
</form>
</div>
);
}
export default First;
I try to send Object data using Link/state to another component.
Second.js
import {useLocation} from 'react-router-dom';
const Second = () => {
const location = useLocation();
console.log(location.state);
console.log(location.state.name);
return (
<div className="Second">
<h1>This is Second Input Page</h1>
<form>
<dd>data 3</dd>
<input></input>
<dd>data 4</dd>
<input></input><br/>
<button>submit</button>
</form>
</div>
);
}
export default Second;
However, while I can access to (location.state), I can not access to (location.state.name). Why is that?
Output
state: {name: 'Myname', phone: 'myphone'}
[[Prototype]]: Object
--------------------
undefined
The output shows that the code line "console.log(location.state);" works, but to the "console.log(location.state.name);", it shows undefined.
It's because you passed an object with state as the root property, i.e.
state={{ state: { name: name, phone: phone } }}
so to access it in the receiving route it is location.state.state.name.
You really don't need to nest the data you want to pass under a state property when using the Link component, it's not quite the same as when using the navigate function.
<Link to="/second" state={{ name, phone }}>
<button>submit</button>
</Link>
It may also be considered semantically incorrect HTML to nest a button element within an anchor tag (via Link) element. Use the useNavigate hook and issue an imperative navigation action from the form's submit handler. In this case the navigate function's second argument, the options object, *does* expect the state to be passed under the state` property.
Example:
import { Link, useNavigate } from "react-router-dom";
const First = () => {
const navigate = useNavigate();
const [name, setName] = useState("");
const [phone, setPhone] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
navigate("/second", { state: { name, phone } });
};
return (
<div className="First">
<h1>This is First Input Page</h1>
<form onSubmit={handleSubmit}>
<dd>data 1</dd>
<input
type="text"
value={name}
onChange={(e) =>
setName(e.target.value)
}
required
/>
<dd>data 2</dd>
<input
type="text"
value={phone}
onChange={(e) =>
setPhone(e.target.value)
}
required
/>
<br/>
<button>submit</button>
</form>
</div>
);
};
export default First;

How do I pass my Form Data which is in the child component to the parent component(which also contains a form) and on submit log all the data?

So I have a parent component which contains a form and a child component(this component also has a form , here its a dynamic form meaning when the add button is hit it creates another form below so that user can add a list of parameters of various types . Basically I am trying to achieve state lifting from child to component . I would be really grateful if someone could help me out , I have been stuck on this since a week and I tried a lot . I would appreciate if someone could refactor my code so that it works . I have also attached a gif which shows the form
GIF : https://drive.google.com/file/d/15YXGhvo0OMCh4ch44q4S88wcqOB7i2ew/view?usp=sharing
Parent Component - ParamsForm.js
import React, { useState, useEffect } from 'react'
import { Form } from 'react-bootstrap'
import styles from '../style.module.css'
import Operations from './Operations'
import OperationsFormTest from './OperationsFormTest'
const ParamsForm = () => {
const[isToggled,setIsToggled] = useState(false)
const[formData,setFormData] = useState(
{url:'',endpoint:'',name:'',description:'',type:'',required:''}
)
const handleSubmit = (e) =>{
e.preventDefault()
console.log('Form Data is :' , formData)
}
const handleChangeInput = (index,e) =>{
const values = [...formData]
values[index][e.target.name] = e.target.value
setFormData(values)
}
const handleAddFields = () =>{
setFormData([...formData,{url:'',endpoint:'',name:'',description:'',type:'',required:''}])
}
const handleRemoveFields = (index) =>{
const values = [...formData]
values.splice(index,1)
setFormData(values)
}
useEffect(()=>{
console.log(isToggled)
},[isToggled])
return (
<div className={styles.paramFormsContainer}>
{setFormData}
<form onSubmit={handleSubmit}>
<input type='text' name='url' placeholder='Url..' value={formData.url} onChange={handleChangeInput}></input>
<input type='text' name='endpoint' placeholder='Endpoint...' value={formData.endpoint} style={{flex : 1 }} onChange={handleChangeInput}></input>
<button type='button' onClick={()=>setIsToggled(!isToggled)} className={styles.pathParamFormsBtn}>Path Params</button>
{isToggled && <OperationsFormTest name={formData.name} description={formData.description} type={formData.type} required={formData.required} handleAddFields={handleAddFields} nameFunc={handleChangeInput} descriptionFunc={handleChangeInput} typeFunc={handleChangeInput} requiredFunc={handleChangeInput} handleRemoveFields={handleRemoveFields}></OperationsFormTest>}
<br></br><br></br>
<button type='submit' onClick={handleSubmit}>Submit</button>
</form>
</div>
)
}
export default ParamsForm
Child Component - OperationsFormTest.js
import React, { useEffect, useState } from 'react'
import styles from '../style.module.css'
import {FaInfo,FaFileInvoiceDollar} from 'react-icons/fa'
import ReactTooltip from "react-tooltip";
const OperationsFormTest = ({name,type,description,required,nameFunc,descriptionFunc,typeFunc,requiredFunc,handleAddFields,handleRemoveFields}) =>{
const ChildToParentName = (e) =>{
nameFunc(e)
}
const ChildToParentType = (e) =>{
typeFunc(e)
}
const ChildToParentDescription = (e) =>{
descriptionFunc(e)
}
const ChildToParentRequired = (e) =>{
requiredFunc(e)
}
return(
<>
<div>
<div className={styles.pathParamsFormParentContainer}>
<div className={styles.pathParamsFormChildContainer}>
<label>Name : </label>
<input name='name' type='text' placeholder='Name..' value={name} onChange={ChildToParentName}></input><br></br><br></br>
<label>Description : </label>
<input name='description' type='text' placeholder='Description..' value={description} onChange={ChildToParentDescription}></input><br></br><br></br>
<select name = 'type' value = {type} onChange={ChildToParentType}>
<option>Any</option>
<option>String</option>
<option>Boolean</option>
</select>
<label>Required : </label>
<input name='required' type='text' placeholder='Yes or No..' value={required} onChange={ChildToParentRequired}></input><br></br><br></br>
<button type='button' onClick={()=>handleAddFields()}>Add</button>
<button type='button' onClick={()=>handleRemoveFields()}>Remove</button><br></br><br></br>
</div>
</div>
</div>
</>
)
}
export default OperationsFormTest
I hope you will get some idea from this simple codes.
function Home() {
// Async await is up to you
const onSubmit = async (data)=> {
console.log(data);
}
return (
<Form onSubmit={onSubmit} />
)
}
function Form() {
const [text,setText] = useState("");
// async await is up to you
const handleSubmit = async (e)=> {
e.preventDefault();
await onSubmit(text);
setText("");
}
return (
<form onSubmit={handleSubmit}>
<input type={"text"} value={text} onChange={e=>setText(e.target.value)} />
<input type={"submit"} />
</form>
)
}

Axios.post keeps sending empty objects to my API

I am trying to post new information about a cow to my cow API, however, everytime i hit the submit button on my frontend, it seems to be sending an empty object rather than the name of the cow, description of the cow, and image of the cow (via url). What is causing it to send an empty object versus my desired data?
Here is the frontend code:
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import './App.css';
const baseUrl = "http://localhost:3001/api/cows"
function Display({setNameOfCow, setImageOfCow, setDescriptionOfCow, nameOfCow, imageOfCow, descriptionOfCow}) {
axios.get(baseUrl)
.then(res => res.data)
.then(res => {
setNameOfCow(res.name)
setImageOfCow(res.image)
setDescriptionOfCow(res.description)
})
return (
<div>
<p>{nameOfCow}</p>
<img src={imageOfCow}/><p>{descriptionOfCow}</p>
</div>
)
}
function Input({setNameOfCow, setImageOfCow, setDescriptionOfCow, nameOfCow, imageOfCow, descriptionOfCow}) {
function handleSubmit(e) {
e.preventDefault()
let newObject = {
name: nameOfCow,
description: descriptionOfCow,
image: imageOfCow
}
axios.post(baseUrl, newObject)
}
return (
<div>
<form>
<label htmlFor="name">name: </label>
<input type="text" id="name" onChange={(e) => {
const eTarget = e.target.value
setNameOfCow(eTarget)}}/><br></br>
<label htmlFor="description">description: </label>
<input type="text" id="description" onChange={(e) => {
const eTargetDesc = e.target.value
setDescriptionOfCow(eTargetDesc)}}/><br></br>
<label htmlFor="image">image url: </label>
<input type='text' id="image" onChange={(e) => {
const eTargetImage = e.target.value
setImageOfCow(eTargetImage)}}/><br></br>
<button type="submit" onSubmit={handleSubmit}>Add a cow!</button>
</form>
</div>
)
}
function App() {
const [nameOfCow, setNameOfCow] = useState('')
const [descriptionOfCow, setDescriptionOfCow] = useState('')
const [imageOfCow, setImageOfCow] = useState('')
return (
<div className="App">
<Input imageOfCow={imageOfCow} setNameOfCow={setNameOfCow} setDescriptionOfCow={setDescriptionOfCow} setImageOfCow={setImageOfCow} />
<Display setNameOfCow={setNameOfCow} setImageOfCow={setImageOfCow} setDescriptionOfCow={setDescriptionOfCow} nameOfCow={nameOfCow} imageOfCow={imageOfCow} descriptionOfCow={descriptionOfCow} />
</div>
);
}
export default App
and here is the image showing the empty objects being posted:
Looking into your Input component props:
function Input({setNameOfCow, setImageOfCow, setDescriptionOfCow, nameOfCow, imageOfCow, descriptionOfCow}) {...
We can see that you missing to pass this props when using this component:
<Input imageOfCow={imageOfCow} setNameOfCow={setNameOfCow} setDescriptionOfCow={setDescriptionOfCow} setImageOfCow={setImageOfCow} />
The correct way to use is something like:
<Input
imageOfCow={imageOfCow}
nameOfCow={nameOfCow}
descriptionOfCow={descriptionOfCow}
setNameOfCow={setNameOfCow}
setDescriptionOfCow={setDescriptionOfCow}
setImageOfCow={setImageOfCow}
/>
Also the correct way to prevent the form default behavior is setting the onSubmit and the handleSubmit at the form attribute (you can remove from the button):
<form onSubmit={handleSubmit}>
Otherwise a very nice change is to put your axios request inside a useEffect hook to prevent your app from making request every time it re-render.
Using something like this the app will make the request only at the first component render.
const getCow = async (baseUrl) => {
const cow = await axios.get(baseUrl);
setNameOfCow(cow.name);
setImageOfCow(cow.image);
setDescriptionOfCow(cow.description);
};
useEffect(() => {
getCow(baseUrl);
}, []);

How to access input elements from a React function

I currently have a form that is auto generated based on the amount of "Activities" a current user has. Each activity has a name and a value. My goal is to submit these values to the backend to update them. Yet I can't figure out how to reference these inputs. I was trying to read about using "ref"s, yet they come back with {current:null}
Here is the auto generated list (excuse the placeholder text)
When I inspect console here is what I find from the ref:
Here is my code:
import React, { useEffect } from "react";
import { useDispatch, useStore } from "react-redux";
import { connect } from "react-redux";
import * as actions from "../store/actions/patientSide";
export function ActivityTemplates() {
const dispatch = useDispatch();
const store = useStore();
const ref = React.createRef();
useEffect(() => {
// Update the document title using the browser API
dispatch(actions.getTodaysActivityTemplates());
}, []);
const activities = store.getState().patientSide.todays_activities;
const listedStuff = activities.map((activity) => (
<div>
{activity.activity_name}
<label for="value"> Value: </label>
<input
type="number"
id="value"
defaultValue={activity.value}
min="0"
max="10"
></input>
</div>
));
const saveActivities = () => {
var inputs = ref;
console.log(inputs);
// Insert code to run the call to the backend
};
return (
<div>
<h1> Activity Templates</h1>
<form id="form" onSubmit={saveActivities()} ref={ref}>
{listedStuff}
<input type="submit" name="save" />
</form>
</div>
);
}
export default ActivityTemplates;
I am very new to React and JS and honestly have very little idea of what I'm doing, but if someone could point me in the right direction that would be awesome!
EDIT: After sleeping on it, I've realized I've just been trying to force react into my HTML. I believe I should instead use a React Form Hook, and do it properly from the ground up.
<form onSubmit={handleOnSubmit}>
<label>User Name</label>
<input type="text" name="username" /><br/>
<label>Password</label>
<input type="password" name="password" /><br/>
<input type="submit" value="Submit" />
</form>
const handleOnSubmit = (event) => {
const formData = new FormData(event.target);
const formDetails = {};
event.preventDefault();
for (let entry of formData.entries()) {
formDetails[entry[0]] = entry[1];
};
console.log("formDetails", formDetails);
}
You are getting the input fields value from "FormData" on onSubmit.
const saveActivities = (event) => {
event.preventDefault();
const data = new FormData(event.target);
// Insert code to run the call to the backend
}
You need to store the value
const [value, setValue] = React.useState();
Then give your input a onChange={e => setValue(e.target.value)}
I would change the id though

How do I connect Firebase Storage with Cloud Firestore so my images are associated to their respective posts?

I have a CRUD React app where you can create articles. There is a 'title', 'content' and 'image'.
I have a collection in Cloud Firestore called 'articles'. The documents have the fields: 'title' and 'content' so every time I create an article in my react app, a new document is created.
The next step was to be able to add images to the articles.
I managed to connect to Firebase and upload an image to Firebase Storage from my React page. However how do I connect the images to their articles? Like, how do I make the connection between Cloud Firestore and Storage?
Do I create an extra field in my documents called 'images'? But then how do I reference it?
I also want to try and avoid duplicates.
UploadImage.js
import React, { useState, Component } from "react";
import { storage } from "../Firebase";
function UploadFile() {
const [image, setImage] = useState(null);
const [url, setUrl] = useState("");
const [progress, setProgress] = useState(0);
const handleChange = e => {
if (e.target.files[0]) {
setImage(e.target.files[0]);
}
};
const handleUpload = () => {
const uploadTask = storage.ref(`images/${image.name}`).put(image);
uploadTask.on(
"state_changed",
snapshot => {
const progress = Math.round(
(snapshot.bytesTransferred / snapshot.totalBytes) * 100
);
setProgress(progress);
},
error => {
console.log(error);
},
() => {
storage
.ref("images")
.child(image.name)
.getDownloadURL()
.then(url => {
setUrl(url);
});
}
);
};
console.log("image: ", image);
return (
<div>
<progress value={progress} max="100" />
<br />
<br />
<input type="file" onChange={handleChange} />
<button onClick={handleUpload}>Upload</button>
<br />
{url}
<br />
<img src={url || "http://via.placeholder.com/300"} alt="firebase-image" />
</div>
);
}
export default UploadFile;
This is my 'Add Article' form-->
AddArticle.js
import React, { Component } from 'react';
import firebase from '../Firebase';
import UploadFile from '../components/UploadFile';
class AddArticle extends Component {
constructor() {
super();
this.ref = firebase.firestore().collection('articles');
this.state = {
title: '',
content: ''
};
}
onChange = (e) => {
const state = this.state
state[e.target.name] = e.target.value;
this.setState(state);
}
onSubmit = (e) => {
e.preventDefault();
const { title, content } = this.state;
this.ref.add({
title,
content
}).then((docRef) => {
this.setState({
title: '',
content: ''
});
this.props.history.push("/")
})
.catch((error) => {
console.error("Error adding document: ", error);
});
}
render() {
const { title, content } = this.state;
return (
<div className="container">
<br></br><br></br><br></br>
<div className="panel panel-default">
<div className="panel-heading">
<h3 className="panel-title text-center">
Create a new article
</h3>
</div>
<br></br><br></br>
<div className="panel-body">
<form onSubmit={this.onSubmit}>
<div className="form-group">
<label for="title">Title:</label>
<input type="text" className="form-control" name="title" value={title} onChange={this.onChange} placeholder="Title" />
</div>
<div className="form-group">
<label for="content">Content:</label>
<textArea className="form-control" name="content" onChange={this.onChange} placeholder="Content" cols="80" rows="20">{content}</textArea>
</div>
<UploadFile />
<button type="submit" className="btn btn-success">Submit</button>
</form>
</div>
</div>
</div>
);
}
}
export default AddArticle;
It is actually pretty simple, to achieve this follow steps below:
After uploading your image to firebase storage you should get download url for that particular image (I linked firebase documentation link below on how to achieve that).
https://firebase.google.com/docs/storage/web/download-files
If you successfully fetched download url from firebase storage now it is time to create separate field under your document named such as "postImageUrl" (you can name whatever you want) and save download url under this field.
Now you can show images using this download url in your page using busboy library
https://www.npmjs.com/package/busboy
Note: I am an Android Developer so I tried my best to help. If you have further questions don't hesitate to ask

Categories