how to make useState hook act like synchronous function? - reactjs

here is my code,
const App= () => {
const [someData, setSomeData] = useState('');
const [token, setToken] = useState('');
const fetchingAPI = async () => {
try {
const response = await someAPI();
const data = response.data.data;
const tokenData = response.data.token;
setSomeData(data);
setToken(tokenData);
return true;
} catch (err) {
console.log(err)
return false;
}
};
const onSubmitHandler = async (e: any) => {
e.preventDefault();
const fetchHandler = async () => {
const data = await fetchingAPI();
return data ? e.target.submit() : '';
};
fetchHandler();
};
return (
<div>
<button onClick={toPreviousStep}>previous step</button>
<form
action="https://somewebsite.com"
method="POST"
onSubmit={onSubmitHandler}>
<button>
<input type="hidden" name="someData" value={someData} />
<input type="hidden" name="token" value={token} />
confirm
</button>
</form>
</div>
);
};
when user click confirm button, I would try to fetch some data from backend and store it in a state.
If nothing goes wrong, it would be submitted and redirect to another website.
However, the problem is state doesn't change immediately, so it just bring an empty string to the third party website.
how should I fix this code? thank you.

I solve this problem with document.querySelector, and here is my code
const App= () => {
const fetchingAPI = async () => {
const data = document.querySelector<HTMLInputElement>.
('#data');
const token = document.querySelector<HTMLInputElement>('#token');
try {
const response = await someAPI();
if (data) data.value = response.data.data;
if (token) token.value = response.data.token;
return true;
} catch (err) {
console.log(err)
return false;
}
};
const onSubmitHandler = async (e: any) => {
e.preventDefault();
const fetchHandler = async () => {
const data = await fetchingAPI();
return data ? e.target.submit() : '';
};
fetchHandler();
};
return (
<div>
<button onClick={toPreviousStep}>previous step</button>
<form
action="https://somewebsite.com"
method="POST"
onSubmit={onSubmitHandler}>
<button>
<input type="hidden" name="someData" value="data" id="data" />
<input type="hidden" name="token" value="token" id="token" />
confirm
</button>
</form>
</div>
);
};
It makes both data and token input change immediately.

Related

Unable to edit text in Draft.js editor when setting it with data from Firebase

I created a blog app using Draft.js and I'm a bit stuck on the EditPost.js file. I am able to import the data from an existing post into the editor but unable to edit the text at all. The cursor just stays in the same place and won't even go to the end of the text. Keep in mind I am saving the editor text to the database as HTML, and then converting it back to text after the post is loaded. Here is all the code, except for the imports:
export default function EditPost() {
const [user, loading, error] = useAuthState(auth);
const [formData, setFormData] = useState({
title: "",
body: "",
image: "",
})
const [file, setFile] = useState("");
const [percent, setPercent] = useState(0);
const [imageURL, setImageURL] = useState("");
const [post, setPost] = useState([]);
const [text, setText] = useState("");
const { id } = useParams();
//Draft.js code
const [editorState, setEditorState] = useState(
() => EditorState.createEmpty(),
);
const [convertedContent, setConvertedContent] = useState(null);
//function for converting post body to HTML
useEffect(() => {
let html = convertToHTML(editorState.getCurrentContent());
setConvertedContent(html);
setFormData({ ...formData, body: convertedContent });
}, []);
//function for setting the image to the image URL
useEffect(() => {
setFormData({ ...formData, image: imageURL });
}, [formData, imageURL]);
// console.log(convertedContent);
//END
//Image upload code
function handleImageChange(event) {
setFile(event.target.files[0]);
// setFormData({ ...formData, image: event.target.files[0].name });
}
const handleUpload = () => {
if (!file) {
alert("Please upload an image first!");
}
const storageRef = ref(storage, `/files/${file.name}`);
// progress can be paused and resumed. It also exposes progress updates.
// Receives the storage reference and the file to upload.
const uploadTask = uploadBytesResumable(storageRef, file);
uploadTask.on(
"state_changed",
(snapshot) => {
const percent = Math.round(
(snapshot.bytesTransferred / snapshot.totalBytes) * 100
);
// update progress
setPercent(percent);
},
(err) => console.log(err),
() => {
// download url
getDownloadURL(uploadTask.snapshot.ref).then((url) => {
console.log(url);
const urlToString = url.toString();
console.log(urlToString);
setImageURL(urlToString);
console.log(imageURL);
});
}
);
};
//END
//Get post code
//function for getting the post
useEffect(() => {
const getPost = async () => {
const docRef = doc(db, 'posts', id);
try {
const docSnap = await getDoc(docRef);
setPost(docSnap.data());
console.log(post);
const text = htmlToText(post.body, {
});
console.log(text);
setText(text);
} catch (error) {
console.log(error)
}
};
getPost();
return () => {
// this now gets called when the component unmounts
};
}, [id, post]);
//END
//Submit form code
const handleSubmit = async (e) => {
e.preventDefault()
if (formData.title === "") {
alert("Please enter a title");
return;
}
if (formData.body === "") {
alert("Please enter some body copy");
return;
}
const postDocRef = doc(db, 'posts', id);
try {
await updateDoc(postDocRef, {
title: formData.title,
body: formData.body,
image: formData.image,
created: Timestamp.now(),
})
alert("Post updated successfully!")
} catch (err) {
alert(err)
}
}
//END
return (
<div>
<Menu />
<div>
<div>
<h1>Blog App</h1>
</div>
<form onSubmit={handleSubmit}>
<input
name="title"
type="text"
placeholder="Title"
defaultValue={post.title}
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
/>
<br></br>
<br></br>
{/* <textarea
name="body"
placeholder="Enter text here"
value={formData.body}
onChange={(e) => setFormData({ ...formData, body: e.target.value })}
></textarea> */}
<Editor
editorState={EditorState.createWithContent(ContentState.createFromText(text))}
onEditorStateChange={setEditorState}
wrapperClassName="wrapper-class"
editorClassName="editor-class"
toolbarClassName="toolbar-class"
/>
<br></br>
<br></br>
<input
value=""
name="image"
type="file"
accept="image/*"
onChange={handleImageChange}
/>
<button
type="button"
value="Upload Image"
onClick={handleUpload}
>Upload Image</button>
<p>{percent} % uploaded</p>
<br></br>
<br></br>
<button
type="submit"
>Submit</button>
</form>
</div>
</div >
);
};
I was able to fix this by:
Using a separate function for converting HTML back to text.
Removing formData from the dependency array.
Adding post.body to the dependency array.
Changing editorState to editorState={editorState}.
Having formData in the dependency array was causing an infinite loop which was preventing me from typing anything new into the editor.
Here is my updated code:
export default function EditPost() {
const [formData, setFormData] = useState({
title: "",
body: "",
image: "",
})
const [file, setFile] = useState("");
const [percent, setPercent] = useState(0);
const [imageURL, setImageURL] = useState("");
const [post, setPost] = useState([]);
const { id } = useParams();
const navigate = useNavigate();
//Draft.js code
const [editorState, setEditorState] = useState(
() => EditorState.createEmpty(),
);
const [convertedContent, setConvertedContent] = useState(null);
//function for converting the HTML back to text
const htmlToDraftBlocks = (html) => {
const blocksFromHtml = htmlToDraft(html);
const { contentBlocks, entityMap } = blocksFromHtml;
const contentState = ContentState.createFromBlockArray(contentBlocks, entityMap);
const editorState = EditorState.createWithContent(contentState);
return editorState;
}
//function for converting post body to HTML
useEffect(() => {
let html = convertToHTML(editorState.getCurrentContent());
setConvertedContent(html);
setFormData({ ...formData, body: convertedContent });
},);
//function for setting the image to the image URL
useEffect(() => {
setFormData({ ...formData, image: imageURL });
}, [imageURL]);
//END
//Image upload code
function handleImageChange(event) {
setFile(event.target.files[0]);
}
const handleUpload = () => {
if (!file) {
alert("Please upload an image first!");
}
const storageRef = ref(storage, `/files/${file.name}`);
// progress can be paused and resumed. It also exposes progress updates.
// Receives the storage reference and the file to upload.
const uploadTask = uploadBytesResumable(storageRef, file);
uploadTask.on(
"state_changed",
(snapshot) => {
const percent = Math.round(
(snapshot.bytesTransferred / snapshot.totalBytes) * 100
);
// update progress
setPercent(percent);
},
(err) => console.log(err),
() => {
// download url
getDownloadURL(uploadTask.snapshot.ref).then((url) => {
console.log(url);
const urlToString = url.toString();
console.log(urlToString);
setImageURL(urlToString);
console.log(imageURL);
});
}
);
};
//END
//Get post code
//function for getting the post and setting the editor state
useEffect(() => {
const getPost = async () => {
const docRef = doc(db, 'posts', id);
try {
const docSnap = await getDoc(docRef);
setPost(docSnap.data());
setFormData({ ...formData, title: post.title });
setEditorState(htmlToDraftBlocks(post.body));
setImageURL(post.image);
} catch (error) {
console.log(error)
}
};
getPost();
return () => {
// this now gets called when the component unmounts
};
}, [id, post.body]);
//END
//Submit form code
const handleSubmit = async (e) => {
e.preventDefault()
if (formData.title === "") {
alert("Please enter a title");
return;
}
if (formData.body === "") {
alert("Please enter some body copy");
return;
}
const postDocRef = doc(db, 'posts', id);
try {
await updateDoc(postDocRef, {
title: formData.title,
body: formData.body,
image: formData.image,
created: Timestamp.now(),
})
alert("Post updated successfully!")
navigate("/dashboard");
} catch (err) {
alert(err)
}
}
//END
return (
<div>
<Menu />
<div>
<div>
<h1>Blog App</h1>
</div>
<form onSubmit={handleSubmit}>
<input
name="title"
type="text"
placeholder="Title"
defaultValue={post.title}
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
/>
<br></br>
<br></br>
<Editor
editorState={editorState}
onEditorStateChange={setEditorState}
wrapperClassName="wrapper-class"
editorClassName="editor-class"
toolbarClassName="toolbar-class"
/>
<br></br>
Featured image:
<br></br>
<br></br>
<img
className="post-image"
src={`${imageURL}`}
/>
<br></br>
<br></br>
<input
name="image"
type="file"
accept="image/*"
onChange={handleImageChange}
/>
<button
type="button"
value="Upload Image"
onClick={handleUpload}
>Upload Image</button>
<p>{percent} % uploaded</p>
<br></br>
<br></br>
<button
type="submit"
>Submit</button>
</form>
</div>
</div >
);
};

Database not updating - React.js, Moralis

I'm having issues updating my database when saving edit. I am trying to retrieve Github data via API and with that data, add it to the Moralis database. I added another button outside of the form because I presume the saveEdit function will have to run after the form has been submitted. Code is as follows:
export const Dashboard = () => {
const [userSearch, setUserSearch] = useState<string>("");
const [foundUser, setFoundUser] = useState<IGitHubUser>();
const performSearchRequest = async () => {
try {
const response = await axios.get<IGitHubUser>(
`https://api.github.com/users/${userSearch}`
);
setFoundUser(response.data);
} catch (error) {
console.log(error);
}
};
const searchForUser = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
performSearchRequest();
};
const { Moralis, isInitialized } = useMoralis();
const user = isInitialized ? Moralis.User.current() : undefined;
const saveEdits = async () => {
const User = Moralis.Object.extend("_User");
const query = new Moralis.Query(User);
const myDetails = await query.first();
if (foundUser) {
myDetails?.set("github", foundUser.name);
console.log("details saved");
}
try {
await myDetails?.save();
} catch (err) {
console.log(err);
}
window.location.reload();
};
return (
<>
<h2>Search for a user</h2>
<form className="search-user" onSubmit={searchForUser}>
<input
value={userSearch}
onChange={(e) => setUserSearch(e.target.value)}
placeholder="Enter a username..."
/>
<button>Search</button>
</form>
<button onClick={saveEdits}>Search</button>
</>
);
};

Trigger react useEffect

I'm fetching data from a firebase db it works when the component renders, but I can't make it to fetch again when there is a new entry in my db.
What I've tried
I've tried passing a state to the dependency array of useEffect and I changed that state every time my form was submitted (That's the time when there's a new entry in my db)
App
function App() {
const [showForm, setShowForm] = useState(true);
const [tasks, setTasks] = useState([]);
const [isSubmitted, setIsSubmitted] = useState(true);
//Fetch tasks from server
const fetchData = () => {
fetch(
"https://react-task-tracker-8e519-default-rtdb.firebaseio.com/tasks.json"
)
.then((response) => {
return response.json();
})
.then((data) => {
const tasks = [];
//Convert the data to an array so i can map over it
for (const key in data) {
const task = {
id: key,
...data[key],
};
tasks.push(task);
}
setTasks(tasks);
});
};
useEffect(() => {
fetchData();
}, [isSubmitted]);
//Show/Hide form
const onAddHandler = () => {
setShowForm(!showForm);
};
const formSubmitted = () => {
setIsSubmitted(!isSubmitted);
console.log(isSubmitted);
};
return (
<Container>
<Header click={onAddHandler} isShown={showForm}></Header>
{showForm ? <Form fs={formSubmitted}></Form> : ""}
<Tasks tasks={tasks}></Tasks>
</Container>
);
}
export default App;
Form
function Form(props) {
const [task, setTask] = useState();
const [dayTime, setDayTime] = useState();
const [reminder, setReminder] = useState();
//Posting Form data to firebase (DUMMY API)
const postFormData = (fullTask) => {
fetch(
"https://react-task-tracker-8e519-default-rtdb.firebaseio.com/tasks.json",
{
method: "POST",
body: JSON.stringify(fullTask),
headers: {
"Content-Type": "application/json",
},
}
);
};
//Make an object of form data
const onSubmit = (e) => {
e.preventDefault();
const fullTask = {
task: task,
dayTime: dayTime,
reminder: reminder,
};
//Post func call
postFormData(fullTask);
props.fs();
//Field clearing
setTask("");
setDayTime("");
setReminder("");
};
return (
<AddForm onSubmit={onSubmit}>
<FormControl>
<Label>Task</Label>
<Input
type="text"
placeholder="Add Task"
onChange={(e) => setTask(e.target.value)}
value={task}
required
></Input>
</FormControl>
<FormControl>
<Label>Day & Time</Label>
<Input
type="text"
placeholder="Add Task"
onChange={(e) => setDayTime(e.target.value)}
value={dayTime}
required
></Input>
</FormControl>
<FromControlCheck>
<CheckLabel>Set Reminder</CheckLabel>
<CheckInput
type="checkbox"
onChange={(e) => setReminder(e.currentTarget.checked)}
value={reminder}
></CheckInput>
</FromControlCheck>
<Submit type="submit" value="Save Task"></Submit>
</AddForm>
);
}
export default Form;
I would pass fetchData as a props to <Form>. When submitted, I would call it.
Form
const onSubmit = async (e) => {
e.preventDefault();
const fullTask = {
task: task,
dayTime: dayTime,
reminder: reminder,
};
//Post func call
await postFormData(fullTask);
await props.fetchData();
//Field clearing
setTask("");
setDayTime("");
setReminder("");
};
Then remove the isSubmitted state.
Try change the "Id" value to "id". Try make it the same name as the key for the id in "fecthData" function.
I think this solve your problem
function App() {
const [showForm, setShowForm] = useState(true);
const [tasks, setTasks] = useState([]);
const [isSubmitted, setIsSubmitted] = useState(false);
//Fetch tasks from server
const fetchData = () => {
fetch(
"https://react-task-tracker-8e519-default-rtdb.firebaseio.com/tasks.json"
)
.then((response) => {
return response.json();
})
.then((data) => {
const tasks = [];
//Convert the data to an array so i can map over it
for (const key in data) {
const task = {
id: key,
...data[key],
};
tasks.push(task);
}
setTasks(tasks);
});
};
useEffect(() => {
if (isSubmitted) {
fetchData();
setIsSubmitted(false);
}
}, [isSubmitted]);
//Show/Hide form
const onAddHandler = () => {
setShowForm(!showForm);
};
const formSubmitted = () => {
setIsSubmitted(true);
console.log(isSubmitted);
};
return (
<Container>
<Header click={onAddHandler} isShown={showForm}></Header>
{showForm ? <Form fs={formSubmitted}></Form> : ""}
<Tasks tasks={tasks}></Tasks>
</Container>
);
}
export default App;

How to make my react component rerender when I submit a form into firestore

Please I am trying to insert data into my firestore and also want the data that I stored to immediately appear on my screen after submitting. The problem I am currently having is that once I insert the data into firestore, I have to reload the component before seeing it on my screen. Here is my code below, what am I Doing wrongly.
function CreateGroup({ currentUser }) {
const [name, setName] = useState("");
const [group, setGroup] = useState([]);
const handleChange = (e) => {
const { value, name } = e.target;
setName({
[name]: value,
});
};
const handleSubmit = async (e) => {
e.preventDefault();
createGroup(currentUser, name.name);
};
let id = currentUser ? currentUser.id : "";
useEffect(() => {
const fetchData = () => {
if (id) {
firestore
.collection("users")
.doc(id)
.collection("group")
.get()
.then(function (snapshot) {
snapshot.forEach(function (doc) {
// console.log(doc.id, " => ", doc.data());
setGroup({
id: doc.id,
...doc.data(),
});
});
});
}
};
fetchData();
}, [id]);
return (
<div>
<form onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="exampleInputTitle">Group Name</label>
<input
type="text"
className="form-control"
name="name"
id="name"
aria-describedby="TitleHelp"
onChange={handleChange}
/>
</div>
<button type="submit" className="btn btn-primary">
Add group{" "}
</button>
</form>
<div>
<div key={group.id}>
{group.name} {group.admin}
</div>
</div>
</div>
);
}
I would say, create a local state, say
const [submited, setSubmited] = useState(true);
then on submit, change the state to true, and in your useEffect use "submitted" state also as a dependency along with id. OnHandleChange set "submitted" to false so we can submit again.
if (id) {}
becomes
if (id && submitted) {}
in the UseEffect.
function CreateGroup({ currentUser }) {
const [name, setName] = useState("");
const [group, setGroup] = useState([]);
const [submited, setSubmited] = useState(true);
const handleChange = (e) => {
const { value, name } = e.target;
setName({
[name]: value,
});
setSubmited(false);
};
const handleSubmit = async (e) => {
e.preventDefault();
createGroup(currentUser, name.name);
setSubmited(true);
};
let id = currentUser ? currentUser.id : "";
useEffect(() => {
const fetchData = () => {
if (id && submitted) {
firestore
.collection("users")
.doc(id)
.collection("group")
.get()
.then(function (snapshot) {
snapshot.forEach(function (doc) {
// console.log(doc.id, " => ", doc.data());
setGroup({
id: doc.id,
...doc.data(),
});
});
});
}
};
fetchData();
}, [id, submitted]);
return (
<div>
<form onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="exampleInputTitle">Group Name</label>
<input
type="text"
className="form-control"
name="name"
id="name"
aria-describedby="TitleHelp"
onChange={handleChange}
/>
</div>
<button type="submit" className="btn btn-primary">
Add group{" "}
</button>
</form>
<div>
<div key={group.id}>
{group.name} {group.admin}
</div>
</div>
</div>
);
}
Yeah you can achieve this by checking first is firestore receive only:
-getting the data along after submission
-success response
the first scenario you will use .then to take the submitted data and set it to your local state.
the second scenario is to add another flag to your useeffect whenever you receive a successful response from firestore
const handleSubmit = async (e) => {
e.preventDefault();
/*
*if this method is async you can use .then
*/
await createGroup(currentUser, name.name)
.then(res=>
//here you can set a state to trigger useeffect
)
};
useEffect(() => {
const fetchData = () => {
if (id) {
firestore
.collection("users")
.doc(id)
.collection("group")
.get()
.then(function (snapshot) {
snapshot.forEach(function (doc) {
// console.log(doc.id, " => ", doc.data());
setGroup({
id: doc.id,
...doc.data(),
});
});
});
}
};
//no need for this
fetchData();
}, [id,yourstateaftersubmit]);

How can I manipulate the search input provided by user in react?

I am building an application that fetches a player's details, using the input. But the api only allows fetching the details using player's id, hence I have to use another method to first get the id using player's name. But there is some problem getting the input. I also tried using e.target.value, but it isn't working
import React, { useEffect, useState } from 'react'
import HLTV from 'hltv';
// Getting player id using this fn.
const getPlayerIdByName = async (text) => {
return await HLTV.getPlayerByName({ name: text })
.then(res => res.id)
// .then(data => console.log(data))
.catch(err => console.log(err));
}
//Getting player stats using id obtained from above
const getPlayerStats = (playerId) => {
HLTV.getPlayerStats({ id: playerId })
.then(res => Object.entries(res))
}
const Search = () => {
const [name, setName] = useState('');
const [id, setId] = useState('');
useEffect(() => {
getPlayerIdByName(name)
.then(id => setId(id))
}, [name]);
const onChange = (e) => {
setName(e.target.value)
}
const onSubmit = (e) => {
e.preventDefault();
setName(name);
console.log(name)
}
return (
<div>
<form onSubmit={onSubmit} className="player">
<input type="text" value={name} placeholder="Enter Player's in game name" onChange={onChange} />
<button type="Submit" defaultValue="Search">Search</button>
</form>
</div>
)
}
export default Search;
I would refactor your code like this:
The main problem I see, is that you are using useEffect() to get the playerIdByName every time that name changes. Instead, just call that function inside the onSubmit handler. And instead of storing the id in state, store your stats instead.
Then, when you have stats in state, you can render them by maping the key value pairs.
import HLTV from 'hltv';
// Getting player id using this fn.
const getPlayerByName = async (text) => await HLTV.getPlayerByName({ name: text });
//Getting player stats using id obtained from above
const getPlayerStats = async (playerId) => await HLTV.getPlayerStats({ id: playerId });
const Search = () => {
const [name, setName] = useState('');
const [stats, setStats] = useState([]);
const onChange = (e) => {
setName(e.target.value);
};
const fetchStats = async () => {
const player = await getPlayerByName(name);
const stats = await getPlayerStats(player.id);
const statsEntries = Object.entries(stats);
setStats(statsEntries);
};
const onSubmit = async (e) => {
e.preventDefault();
try {
await fetchStats();
} catch (error) {
console.error(error);
}
};
return (
<div>
<form onSubmit={onSubmit} className="player">
<input
type="text"
value={name}
placeholder="Enter Player's in game name"
onChange={onChange}
/>
<button type="Submit" defaultValue="Search">
Search
</button>
</form>
{stats.length > 0 && (
<div>
{stats.map(([key, value]) => (
<p>
{key}: {value}
</p>
))}
</div>
)}
</div>
);
};
export default Search;

Resources