How To Render Data From Firebase Without Page Refresh - javascript

I'm creating a simple note taking app. The functionality I need is the ability to make a new LABEL, and then inside that label create a TITLE, with TAGS(notes) associated to that title. Right now I have it all working nicely, except when I submit the new label, and/or new title/tags, I need to refresh to see the newly added data. I've used tutorials as well as trial and error to get to this point, and I'm a newly lamented bootcamp graduate so please be patient with me :) Any advice is greatly appreciated. How can I make it so when I "createLabel" or "createCard", the data is instantly rendered on screen instead of having to refresh my page?
Here's the code:
import "./App.css";
import { useState, useEffect } from "react";
import React from "react";
// Components
import Header from "./components/Header/Header";
// Firebase
import { db } from "./firebase-config";
import {
collection,
getDocs,
getDoc,
setDoc,
doc,
updateDoc,
addDoc,
deleteDoc,
arrayUnion,
} from "firebase/firestore";
// Clipboard
import { CopyToClipboard } from "react-copy-to-clipboard";
function App() {
const [newLabel, setNewLabel] = useState("");
const [labels, setLabels] = useState([]);
const [activeLabels, setActiveLabels] = useState("");
const [name, setName] = useState("");
const [tags, setTags] = useState("");
const labelCollectionRef = collection(db, "labels");
--- I think I need to add logic to the useEffect maybe?
useEffect(() => {
const getLabels = async () => {
const data = await getDocs(labelCollectionRef);
setLabels(data.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
};
getLabels();
}, []);
const createLabel = async () => {
await setDoc(doc(db, "labels", newLabel), { id: newLabel });
};
const clickHandler = async (title) => {
setActiveLabels(title);
};
const createCard = async () => {
await updateDoc(doc(db, "labels", activeLabels), {
info: arrayUnion({ name: name, tags: tags }),
});
};
// const deleteRef = doc(db, "labels", `${doc.data.id}`)
const deleteLabel = async (i) => {
await deleteDoc(doc(db, "labels", activeLabels));
};
return (
<div className="App">
<Header />
<main>
<input
onChange={(e) => {
setNewLabel(e.target.value);
}}
placeholder="Enter Label Name"
type="text"
></input>
<button onClick={createLabel}>Create Label</button>
{labels.map((label, i) => {
if (activeLabels == label.id) {
return (
<>
<div className="tags__section">
<h2
key={i}
className="tags__title"
onClick={() => clickHandler(label.id)}
>
Label: {label.id}
<button
onClick={() => deleteLabel(i)}
className="tags__delete"
>
DELETE
</button>
</h2>
</div>
{label.info &&
label.info.map((info, i) => {
return (
<div key={i}>
<h1 className="tags__name">Name: {info.name}</h1>
<h2 className="tags__tags" type="text">
Tags: {info.tags}
</h2>
<CopyToClipboard text={info.tags}>
<button className="tags__clipboard">COPY</button>
</CopyToClipboard>
</div>
);
})}
<div className="tags__add">
<input
onChange={(e) => setName(e.target.value)}
type="text"
name="title"
placeholder="Add title..."
></input>
<textarea
onChange={(e) => setTags(e.target.value)}
name="tags"
type="text"
placeholder="Add tags..."
></textarea>
<button onClick={createCard}>Create Card</button>
</div>
</>
);
} else {
return (
<h2
className="tags__title"
onClick={() => clickHandler(label.id)}
key={i}
>
Label: {label.id}
</h2>
);
}
})}
</main>
</div>
);
}
export default App;

There are two ways to retrieve data stored in the Cloud Firestore. What you're using is getDocs() method which is a method to get the data once. The second one is onSnapshot(); this sets a listener to receive data-change events which means Cloud Firestore will send your listener an initial snapshot of any changes to your documents, collections of documents, or the results of queries. See sample code below on how to implement the onSnapshot() method:
useEffect(() => {
const getLabels = async () => {
const colRef = query(collection(db, '<collection-name>'))
onSnapshot(colRef, (snapshot) => {
setLabels(snapshot.docs.map(doc => ({
id: doc.id,
data: doc.data()
})))
})
}
getLabels()
}, [])

Related

State does not get updated in functional component in React for the first time after axios call

Question : "detectLanguageKey" is getting updated only after selecting the language from the dropdown twice.
When I select the option from the dropdown first time, detectLanguageKey is still "", and gets updated only after selecting the option second time.
Can you please explain why ? I have tried using async await and callbacks as well.
import React, { useState, useEffect } from "react";
import axios from "axios";
function SearchBar() {
const [inputText, setInputText] = useState("");
const [detectLanguageKey, setdetectedLanguageKey] = useState("");
const [selectedLanguageKey, setLanguageKey] = useState("");
const [languagesList, setLanguagesList] = useState([]);
const [resultText, setResultText] = useState("");
const getLanguageSource = () => {
axios
.post(`https://libretranslate.de/detect`, {
q: inputText,
})
.then((response) => {
setdetectedLanguageKey(response.data[0].language);
});
};
useEffect(() => {
axios.get("https://libretranslate.de/languages").then((res) => {
setLanguagesList(res.data);
console.log("languagesList", languagesList);
});
}, [inputText]);
const languageKey = (selectedLanguage) => {
setLanguageKey(selectedLanguage.target.value);
};
const translateText = async () => {
await getLanguageSource();
let data = {
q: inputText,
source: detectLanguageKey,
target: selectedLanguageKey,
};
axios.post(`https://libretranslate.de/translate`, data).then((response) => {
setResultText(response.data.translatedText);
});
};
return (
<div>
<textarea
rows="10"
cols="80"
onChange={(e) => setInputText(e.target.value)}
placeholder="Type text to translate.."
></textarea>
<textarea
rows="10"
cols="80"
placeholder="Your translated text will be here..."
value={resultText}
disabled={true}
></textarea>
{languagesList.length > 0 && (
<select onChange={languageKey} name="selectedLanguageKey">
<option>Please Select Language..</option>
{languagesList.map((lang) => {
return <option value={lang.code}>{lang.name}</option>;
})}
</select>
)}
<button
class="submit-btn"
onClick={(e) => {
translateText();
}}
>
Submit
</button>
</div>
);
}
Change translateText function to this
const translateText = async () => {
const detectedLanguageKey = await getLanguageSource();
const data = {
q: inputText,
source: detectedLanguageKey,
target: selectedLanguageKey,
};
axios.post(`https://libretranslate.de/translate`, data).then((response) => {
setResultText(response.data.translatedText);
});
};
Change getLanguageSource function to this
const getLanguageSource = async () => {
const response = await axios
.post(`https://libretranslate.de/detect`, {
q: inputText,
})
return response.data[0].language;
};
Remove inputText from the dependency array of the useEffect.
Remove const [detectLanguageKey, setdetectedLanguageKey] = useState("");
There were a few problems in your code.
First, inside translateText, you are awaiting a function that does not return a promise i.e. getLanguageSource.
Secondly, even if getLanguageSource returned a promise, you are expecting setdetectedLanguageKey inside getLanguageSource to take effect immediately. State updates are not instantaneous.

Uncaught Error on using Axios PUT in React "Uncaught (in promise) Error status 400"

I'm having an error while updating a record in my API using Axios.Put, and i can't seem to find solution on this problem and also i'm not able to understand it fully on what does it want to tell me.
This is my update component where i use PUT to update a record.
import React, { useState, useEffect } from 'react';
import axios from 'axios';
export default function Update() {
const [ProductDescription, setProductDescription] = useState('');
const [ProductCount, setProductCount] = useState('');
const [id, setID] = useState(null);
useEffect(() => {
setID(localStorage.getItem('productID'))
setProductDescription(localStorage.getItem('productDescription'));
setProductCount(localStorage.getItem('productCount'));
}, []);
const updateAPIData = () => {
axios.put(`https://localhost:44380/api/products/`+id, {
ProductDescription,
ProductCount
}).then(response => {
if(response.data !=null) {
alert("Update Successfully")
}
})
}
return (
<div>
<Form className="create-form">
<Form.Field>
<label>Product Description </label>
<input placeholder='Product Description' value={ProductDescription} onChange={(e) => setProductDescription(e.target.value)}/>
</Form.Field>
<Form.Field>
<label>Product Count</label>
<input placeholder='Product Count' value={ProductCount} onChange={(e) => setProductCount(e.target.value)}/>
</Form.Field>
<Button type='submit' onClick={updateAPIData}>Update</Button>
</Form>
</div>
)
};
I have also used this code but still having the same error
axios.put(`https://localhost:44380/api/products/${id}'
And this is how i get the data on my other component using a button
const [ProductDescription, setProductDescription] = useState('');
const [ProductCount, setProductCount] = useState('');
const [id, setID] = useState(null);
useEffect(() => {
setID(localStorage.getItem('productID'))
setProductDescription(localStorage.getItem('productDescription'));
setProductCount(localStorage.getItem('productCount'));
}, []);
const setData = (data) => {
let { productID, productDescription, productCount } = data;
localStorage.setItem('productID', productID);
localStorage.setItem('productDescription', productDescription);
localStorage.setItem('productCount', productCount);
<IconButton><Edit onClick={() => setData(data)}/></IconButton>
I do get undefined data using the localStorage, why could that be?

Local storage not updating React

I'm btrying to save an array of objects in local storage, each time a user clicks a button, i add the username and email fron input fields
but it keeps updating the local storage instead of appending new object to the array
Below is my code
const app = () => {
const [allusers,setAllusers] = useState([JSON.parse(localStorage.getItem('users')) || '']);
const [id,setId] = useState(0);
const [newuser,setNewuser] = useState({
'id':id
'name':'David',
'email':'david#gmail.com'
})
const handleChange = () =>{
setNewuser({...newuser,[e.target.name] : e.target.value});
}
const add = ()=>{
setAllusers([newuser])
localStorage.setItem('users',JSON.stringify(allusers))
setID(id+1); // increase id by 1
}
return(
<div>
<form>
<input type="text" name="user" onChange={handleChange}>
<input type="text" name="email" onChange={handleChange}>
<button onclick={()=>save}>Save</button>
</form>
</div>
)
}
export default app;
There were a lot of syntactical errors and use of functions like save which was never declared and still used.
I rewrote the whole example and made it a bit modular so that you can comprehend it better.
Here is the working example:
Final Output:
Full Source code:
import React, { useState, useEffect } from "react";
import "./style.css";
const App = () => {
const [allusers, setAllusers] = useState([]);
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const handleName = e => {
setName(e.target.value);
};
const handleEmail = e => {
setEmail(e.target.value);
};
const save = e => {
e.preventDefault();
let newUsers = {
id: Math.floor(Math.random() * 100000),
name: name,
email: email
};
localStorage.setItem("users", JSON.stringify([...allusers, newUsers]));
setAllusers(allusers.concat(newUsers));
console.log("Localstorage:", JSON.parse(localStorage.getItem("users")));
};
useEffect(() => {
console.log("Localstorage:", JSON.parse(localStorage.getItem("users")));
if (localStorage.getItem("users")) {
setAllusers(JSON.parse(localStorage.getItem("users")));
}
}, []);
return (
<div>
<form>
<input type="text" name="user" onChange={handleName} />
<input type="text" name="email" onChange={handleEmail} />
<button onClick={save}>Save</button>
<p>{JSON.stringify(allusers)}</p>
</form>
</div>
);
};
export default App;
As You inquired in the comment section, here is how you can implement the Update functionality:
Final Output:
Full source code:
import React, { useState, useEffect } from "react";
import "./style.css";
const App = () => {
const [allusers, setAllusers] = useState([]);
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [id, setId] = useState(null);
const handleName = e => {
setName(e.target.value);
};
const handleEmail = e => {
setEmail(e.target.value);
};
const save = e => {
e.preventDefault();
let newUsers = {
id: Math.floor(Math.random() * 100000),
name: name,
email: email
};
localStorage.setItem("users", JSON.stringify([...allusers, newUsers]));
setAllusers(allusers.concat(newUsers));
console.log("Localstorage:", JSON.parse(localStorage.getItem("users")));
};
const setForUpdate = user => {
setName(user.name);
setEmail(user.email);
setId(user.id);
};
const update = e => {
e.preventDefault();
let modifiedData = allusers.map(user => {
if (user.id === id) {
return { ...user, name: name, email: email };
}
return user;
});
setAllusers(modifiedData);
localStorage.setItem("users", JSON.stringify(modifiedData));
setId(null);
};
useEffect(() => {
console.log("Localstorage:", JSON.parse(localStorage.getItem("users")));
if (localStorage.getItem("users")) {
setAllusers(JSON.parse(localStorage.getItem("users")));
}
}, []);
return (
<div>
<form>
<input value={name} type="text" name="user" onChange={handleName} />
<input value={email} type="text" name="email" onChange={handleEmail} />
<button disabled={!(id == null)} onClick={save}>
Save
</button>
<button disabled={id == null} onClick={update}>
Update
</button>
</form>
{allusers &&
allusers.map(user => (
<div className="userInfo">
<p>{user.name}</p>
<p>{user.email}</p>
<button onClick={() => setForUpdate(user)}>
select for update
</button>
</div>
))}
</div>
);
};
export default App;
You can find the working example here: Stackblitz
You are trying to save allusers to the localStorage right after setAllUsers() but setState is asynchronous. The value does not have to be updated on the next line. You can read more about it at reactjs.org, Why is setState giving me the wrong value?.
I would recommend to use useEffect.
const add=()=> {
setAllusers([... allusers ,newuser])
}
useEffect(()=>{
// this is called only if the variable `allusers` changes
// because I've specified it in second argument of useEffect
localStorage.setItem('users',JSON.stringify(allusers))
}, [allusers]);
()=>handleChange is a function that takes no arguments and returns the handleChange function. You probably want () => handleChange(), which would take no arguments and INVOKE handleChange.
you are adding only one new user while clicking on add button. You need to copy previous data also when setting all users.
Second thing setting state is async and hence your localStorage and allusers may have different value and to avoid this one you need to use useEffect to set the value.
const add = ()=>{
setAllusers([...allusers ,newuser])
setID(id+1); // increase id by 1
}
useEffect(() => {
localStorage.setItem('users',JSON.stringify(allusers))
},[allusers])

Stop react causing an infinite loop using useEffect hook

I am very new to react and node, I have managed to create an API for a simple todo list. I have fetched the data from the api and presenting it on the screen.
If I leave the dependency array empty on the useEffect() hook it will only render once and doesn't loop. But If I add a new Todo it will not update the list unless I refresh. So I put the todos state into the dependency array, this will then show the new item when I add it but if I look at the network tab in the dev tools its hitting the api in an infinite loop. What am I doing wrong ?
here is the code:
App
import React, { useState, useEffect } from "react";
import Todo from "./components/Todo";
import Heading from "./components/Heading";
import NewTodoForm from "./components/NewTodoForm";
const App = () => {
const [todos, setTodos] = useState([]);
useEffect(() => {
const getTodos = async () => {
const res = await fetch("http://localhost:3001/api/todos");
const data = await res.json();
setTodos(data);
};
getTodos();
}, []);
return (
<div className="container">
<Heading todos={todos} />
<section className="todos-container">
<ul className="todos">
{todos.map((todo) => (
<Todo key={todo._id} todo={todo} />
))}
</ul>
</section>
<section className="todo-form">
<NewTodoForm />
</section>
</div>
);
};
export default App;
Heading
import React from "react";
const Heading = ({ todos }) => (
<header>
<h1>Todos</h1>
<p>
{todos.length} {todos.length === 1 ? "Item" : "Items"}
</p>
</header>
);
export default Heading;
Todo
import React, { useState } from "react";
const Todo = ({ todo }) => (
<li>
{todo.name}
<input type="checkbox" />
</li>
);
export default Todo;
NewTodoForm
import React, { useState } from "react";
import { Plus } from "react-feather";
const NewTodoForm = () => {
const [formData, setFormData] = useState({
name: "",
completed: false,
});
const { name } = formData;
const handleOnChange = (e) => {
setFormData({
...formData,
[e.target.name]: e.target.value,
});
};
const handleSubmit = async (e) => {
e.preventDefault();
await fetch("http://localhost:3001/api/todos", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(formData),
});
setFormData({
name: "",
completed: false,
});
};
return (
<form onSubmit={handleSubmit}>
<div className="form-control">
<Plus className="plus" />
<input
name="name"
type="text"
placeholder="Add New Item"
onChange={handleOnChange}
value={name}
/>
<button>Add</button>
</div>
</form>
);
};
export default NewTodoForm;
If I comment all the components out and only have the App component it still infinite loops when I add todos to the dependency array of the useEffect() hook.
So instead of giving that as a dependency write the function outside the useEffect so that you can call that function after you add a todo
Example:
const getTodos = async () => {
const res = await fetch("http://localhost:3001/api/todos");
const data = await res.json();
setTodos(data);
};
useEffect(() => {
getTodos();
}, []);
So getTodos will only run once initially and runs again only on the onSubmit or onClick of your Todo, So, just call getTodos function onSubmit or onClick

Can't retrieve the data from this API

I have developed this live search component in react which retrieves data from an API according to the input search value. However it doesn't retrieve or display the data when pointed to this API https://api.itbook.store/1.0/search/program
But when i use an API like for example: http://hn.algolia.com/api/v1/search?query=redux it retrieves data
const [data, setData] = useState({ books: [] });
const [query, setQuery] = useState('program');
const [url, setUrl] = useState(
'https://api.itbook.store/1.0/search/program',
);
useEffect(() => {
const fetchData = async () => {
const result = await axios(url);
setData(result.data);
};
fetchData();
}, [url]);
return(
<Paper className={classes.root}>
<Container maxWidth="lg">
<form className={classes.container} encType="multipart/form-data">
<TextField
required
id="standard-required"
placeholder="Enter Book Name"
label="Search for a Book"
name="bookName"
value={query}
onChange={event => setQuery(event.target.value)}
className={classes.textField}
multiline
rowsMax="2"
margin="normal"/>
<Button onClick={() =>
setUrl(`https://api.itbook.store/1.0/search/${query}`)
}
className={classes.button} color="primary">Search</Button>
<ul>
{data.books.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
</form>
</Container>
</Paper>
I want my code to collect the data from the API Json : https://api.itbook.store/1.0/search/something
#sideshowbarker delivers an excellent solution to this problem Trying to use fetch and pass in mode: no-cors
Essentially what you can do to workaround the CORS issue is make your request via CORS Proxy URL.
Here's a working sandbox with updates to your code:
https://codesandbox.io/s/lucid-kapitsa-w1uid
import React, { useState } from "react";
import ReactDOM from "react-dom";
import axios from "axios";
import "./styles.css";
const App = () => {
const [url, setUrl] = useState("https://api.itbook.store/1.0/search/");
const [query, setQuery] = useState("");
const [results, setResults] = useState([]);
const fetchData = async () => {
const proxyURL = "https://cors-anywhere.herokuapp.com/";
const updatedURL = `${proxyURL}${url}${query}`;
const res = await axios(updatedURL);
setResults(res.data.books);
};
const createBookList = () => {
return results.map(book => {
return (
<div>
<h4>{book.title}</h4>
</div>
);
});
};
return (
<div>
<input onChange={e => setQuery(e.target.value)} value={query} />
<button onClick={fetchData}>Click</button>
<div>{createBookList()}</div>
</div>
);
};

Categories

Resources