I have created a state using react hook useState() at line 28 which is const [input, setInput] = useState('');. Then I updated the state by calling the setInput() at line 45 in the createTodo function at line 32. But when the function is executing, the state is not updating. There is no error on the console. I also added a console.log() after the state updating statement and the console.log() also executing but state is not updating.
My codes are given below.
App.jsx
import React, { useEffect, useState } from 'react';
import { AiOutlinePlus } from 'react-icons/ai';
import ToDo from './ToDo';
import { db } from './firebase';
import {
addDoc,
collection,
deleteDoc,
doc,
onSnapshot,
query,
updateDoc,
} from 'firebase/firestore';
const style = {
bg: `h-screen w-screen p-4 bg-gradient-to-r from-[#2F80ED] to-[#1CB5E0]`,
container: `bg-slate-100 max-w-[500px] w-full m-auto rounded-md shadow-xl p-4`,
heading: `text-3xl font-bold text-center text-gray-800 p-2`,
form: `flex justify-between`,
input: `border p-2 w-full text-xl`,
button: `border p-4 ml-2 bg-purple-500 text-slate-100`,
count: `text-center p-2`,
};
const App = () => {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState(''); // this is the state
// ************ firebase operations ************
// create
const createTodo = async event => {
event.preventDefault();
if (input === '') {
alert('Please add some text');
return;
}
// crating data to firebase
await addDoc(collection(db, 'todos'), {
text: input,
completed: false,
});
setInput(''); // here state is not updating
};
// read
useEffect(() => {
const q = query(collection(db, 'todos'));
const unsubscribe = onSnapshot(q, querySnapshot => {
let todosArr = [];
querySnapshot.forEach(doc => {
todosArr.push({ ...doc.data(), id: doc.id });
});
setTodos(todosArr);
});
return () => unsubscribe();
}, []);
// update
const toggleComplete = async todo => {
await updateDoc(doc(db, 'todos', todo.id), {
completed: !todo.completed,
});
};
// delete
const todoDelete = async id => {
await deleteDoc(doc(db, 'todos', id));
};
return (
<div className={style.bg}>
<div className={style.container}>
<h2 className={style.heading}>ToDo CRUD with Firebase</h2>
<form onSubmit={createTodo} className={style.form}>
<input
type="text"
className={style.input}
placeholder="ToDo"
onChange={event => setInput(event.target.value)}
/>
<button className={style.button}>
<AiOutlinePlus size={30} />
</button>
</form>
<ul>
{todos.map((todo, index) => (
<ToDo
key={index}
todo={todo}
toggleComplete={toggleComplete}
todoDelete={todoDelete}
/>
))}
</ul>
{todos.length === 0 ? null : (
<p className={style.count}>{`You have ${todos.length} todos`}</p>
)}
</div>
</div>
);
};
export default App;
Please have a look. How can I solve this problem?
You are missing value={input} in your input element
<input
type="text"
className={style.input}
placeholder="ToDo"
value={input} <-- this
onChange={event => setInput(event.target.value)}
/>
<input
type="text"
className={style.input}
placeholder="ToDo"
**value={input}**
onChange={event => setInput(event.target.value)}
/>
https://reactjs.org/docs/forms.html#:~:text=An%20input%20form%20element%20whose,called%20a%20%E2%80%9Ccontrolled%20component%E2%80%9D.&text=Since%20the%20value%20attribute%20is,state%20the%20source%20of%20truth.
Related
I am trying to login a page on button click and on "Enter" keydown event also, but the keydown portion of the code is not working. It return an error that the user is not found, which mean it's the catch error block that gets called. But on the button click portion, which is similar to the keydown portion code, everything works fine.
Here is the code.
import Axios from "axios";
import { useState, useRef, useEffect } from "react";
import { useNavigate } from "react-router-dom";
const LogIn = () => {
const milliseconds = 3600000;
localStorage.setItem("twoHours", JSON.stringify(milliseconds));
const userRef = useRef();
const navigate = useNavigate();
const [input, setInput] = useState("");
const [errorMessage, setErrorMessage] = useState("");
const [error, setError] = useState(false);
const [keyCode, setKeyCode] = useState("");
useEffect(() => {
userRef.current.focus();
}, []);
useEffect(() => {
document.body.addEventListener("keydown", keyDown);
}, []);
async function keyDown(event: KeyboardEvent) {
if (event.code === "Enter") {
try {
const data = await Axios.post("http://localhost:3500/examAuth", {
login: input,
});
setError(false);
setErrorMessage("");
navigate("/examSection", { state: { user: data.data } });
} catch (error: any) {
setError(true);
setErrorMessage(error.response.data.message);
console.log(error);
}
}
}
const handleSubmit = async () => {
try {
const data = await Axios.post("http://localhost:3500/examAuth", {
login: input,
});
setError(false);
navigate("/examSection", { state: { user: data.data } });
setErrorMessage("");
} catch (error: any) {
setError(true);
setErrorMessage(error.response.data.message);
console.log(error);
}
};
return (
<>
<div className="">
<div className="w-1/6 mx-auto mt-10">
<img src="images/editedConst.png" alt="" />
</div>
<div className="flex flex-col gap-2 w-3/4 mx-auto mt-10">
<label
className="text-yellow-700 text-xl w-2/4 mx-auto"
htmlFor="logIn"
>
Login
</label>
<input
ref={userRef}
className="input w-2/4 mx-auto"
autoComplete="off"
autoSave="off"
type="text"
value={input}
name="login"
placeholder="application number"
onChange={(e) => setInput(e.target.value.trim().toUpperCase())}
required
/>
{error && (
<p className="mt-1 mx-auto w-2/4 text-red-500">{errorMessage}</p>
)}
</div>
<div className="flex justify-center w-2/4 mx-auto mt-8">
<button
className="btn shadow shadow-yellow-800"
onClick={handleSubmit}
>
Log In
</button>
</div>
</div>
</>
);
};
export default LogIn;
I cant seem to find out what I am doing wrong.
There's only one answer: wrap your inputs in <form>. Then you can listen to it's event on submission and prevent default and / or place your logic. This approach has innumerable advantages (Password managers support and accessibility) to name two most obvious. Don't try to reinvent the wheel.
I am having difficulties locking the send message object at the bottom of the screen, so that the scroll effect doesn't go beyond ( downwards past )the send message object, as indicated in the below image.
I used React and tailwindcss
Chat bar scroll bug
Here is the Chat.jsx:
import React, { useState, useEffect, useRef } from 'react';
import Message from './Message';
import SendMessage from './SendMessage';
import { db } from '../firebase';
import { query, collection, orderBy, onSnapshot } from 'firebase/firestore';
const style = {
main: `flex flex-col p-[10px]`,
};
const Chat = () => {
const [messages, setMessages] = useState([]);
const scroll = useRef();
useEffect(() => {
const q = query(collection(db, 'messages'), orderBy('timestamp'));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
let messages = [];
querySnapshot.forEach((doc) => {
messages.push({ ...doc.data(), id: doc.id });
});
setMessages(messages);
});
return () => unsubscribe();
}, []);
return (
<>
<main className={style.main}>
{messages &&
messages.map((message) => (
<Message key={message.id} message={message} />
))}
</main>
{/* Send Message Compoenent */}
<SendMessage scroll={scroll} />
<span ref={scroll}></span>
</>
);
};
export default Chat;
And here is a SendMessage.jsx code:
import React, { useState } from 'react';
import {auth, db} from '../firebase'
import {addDoc, collection, serverTimestamp} from 'firebase/firestore'
const style = {
form: `h-14 w-full max-w-[728px] flex text-xl absolute bottom-0`,
input: `w-full text-xl p-3 bg-gray-900 text-white outline-none border-none`,
button: `w-[20%] bg-green-500`,
};
const SendMessage = ({scroll}) => {
const [input, setInput] = useState('');
const sendMessage = async (e) => {
e.preventDefault()
if (input === '') {
alert('Please enter a valid message')
return
}
const {uid, displayName} = auth.currentUser
await addDoc(collection(db, 'messages'), {
text: input,
name: displayName,
uid,
timestamp: serverTimestamp()
})
setInput('')
scroll.current.scrollIntoView({behavior: 'smooth'})
}
return (
<form onSubmit={sendMessage} className={style.form}>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
className={style.input}
type='text'
placeholder='Message'
/>
<button className={style.button} type='submit'>
Send
</button>
</form>
);
};
export default SendMessage;
I'm making a todo list app using react and firebase realtime database.
I want to get the todos ordered by date.
My Database:
And if I cant do this from firebase, is there a way to order it from the client side (react)?
My Code
Todos.js:
import { useState, useEffect } from "react";
import { signOut, onAuthStateChanged } from "firebase/auth";
import { uid } from "uid";
import { set, ref, onValue } from "firebase/database";
import { auth, db } from "../firebase";
import moment from "moment";
function Todos() {
const [todos, setTodos] = useState([]);
const [newTodo, setNewTodo] = useState("");
const navigate = useNavigate();
useEffect(() => {
auth.onAuthStateChanged((user) => {
if (user) {
onValue(ref(db, `/${auth.currentUser.uid}`), (snapshot) => {
setTodos([]);
const data = snapshot.val();
if (data !== null) {
Object.values(data).map((todo) => {
setTodos((currentTodos) => [todo, ...currentTodos]);
});
}
});
} else {
navigate("/");
}
});
}, []);
const handleSignOut = () => {
signOut(auth)
.then(() => navigate("/"))
.catch((error) => alert(error.message));
};
const addTodo = () => {
const uidd = uid();
set(ref(db, `${auth.currentUser.uid}/${uidd}`), {
task: newTodo,
uid: uidd,
createdAt: moment().format("YYYY-MM-DD k:m:s"),
});
setNewTodo("");
};
return (
<>
<Center>
<Button colorScheme="red" marginTop={5} onClick={handleSignOut}>
Logout
</Button>
</Center>
<Container
maxW="4xl"
marginTop={8}
display="flex"
alignItems="center"
justifyContent="center"
>
<Box
boxShadow="base"
rounded="lg"
padding={10}
background="white"
width="100%"
>
<Heading as="h1" size="md" textAlign="center">
Todo List App
</Heading>
<form onSubmit={(e) => e.preventDefault()}>
<Box
display="flex"
alignItems="center"
justifyContent="space-between"
marginTop={5}
>
<Input
placeholder="New Task"
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
size="lg"
width="80%"
/>
<Button
colorScheme="teal"
height={45}
rightIcon={<MdAdd />}
margin={0}
onClick={addTodo}
type="submit"
>
Add
</Button>
</Box>
</form>
{todos.map((todo, index) => {
return <Todo key={index} task={todo.task} uid={todo.uid} />;
})}
</Box>
</Container>
</>
);
}
export default Todos;
Since you are loading the TODOs for a single user, you can indeed order them by their createdAt property. To do this, use a query as shown in the documentation on ordering and filtering data:
const ref = ref(db, `/${auth.currentUser.uid}`);
const query = query(ref, orderByChild('createdAt'));
onValue(query, (snapshot) => {
...
Inside the code you'll then need to make sure to use snapshot.forEach to loop over the children in order, as calling .val() before that will return a JSON object and the properties in a JSON object are by definition not ordered:
snapshot.forEach((child) => {
console.log(child.key, child.val());
});
I have an large application with nested components. Each component can be individually modified but I would like to be able to have the data persist between sessions. Currently, I am having each component be in charge of saving updates using the browsers LocalStorage but I would ultimately like to be able to export all of the app data to a single JSON file and use that to save the user's progress in the app.
My best idea, as of posting, is to use a prop to trigger a callback function passed to all the children. When a prop (which I called msgPort is changed) each child component will pass their data up to the parent component. So far it works as I would expect but it feels like this method is bad practice and not "React-ful". Is this method acceptable, or are there some pitfalls of scaling this method up to a much larger application? Any advice/feedback is much appreciated.
Here is a working example:
https://codesandbox.io/s/save-nested-data-s5umb?file=/src/App.js
And here is the same code from the CodeSandbox
import "./styles.css";
import { useEffect, useState } from "react";
import "bootstrap/dist/css/bootstrap.css";
function A(props) {
const [data, setData] = useState({
id: props.id,
inputValue: ""
});
useEffect(() => {
if (props.msgPort) {
props.retrieveData(props.order, data);
}
}, [props.msgPort]);
return (
<div className="m-3 text-start p-3 border row g-0">
<div>
<span className="float-start mb-2">Component A</span>
<span className="float-end">ID: {props.id}</span>
</div>
<input
className="form-control"
type="text"
placeholder="inputValue"
value={data.inputValue}
onChange={(evt) => {
setData({ ...data, inputValue: evt.target.value });
}}
/>
</div>
);
}
const B = (props) => {
const [data, setData] = useState({
id: props.id,
checkedValue: true
});
useEffect(() => {
if (props.msgPort) {
props.retrieveData(props.order, data);
}
}, [props.msgPort]);
return (
<form className="m-3 text-start p-3 border row g-0">
<div>
<span className="float-start mb-2">Component B</span>
<span className="float-end">ID: {props.id}</span>
</div>
<div className="form-check">
<input
className="form-check-input"
type="checkbox"
checked={data.checkedValue}
onChange={() => {
setData({ ...data, checkedValue: !data.checkedValue });
}}
/>
<label className="form-check-label">Default checkbox</label>
</div>
</form>
);
};
export default function App() {
const [msgPort, setMsgPort] = useState("");
const [appData, setAppData] = useState([]);
const saveData = () => {
setAppData(["", "", ""]);
setMsgPort("save"); // this will trigger the retrieve data in the children components
};
const retrieveData = (index, componentData) => {
setAppData((prevState) => {
let newData = [...prevState];
newData[index] = componentData;
return newData;
});
setMsgPort(""); // Is there a potential for a component to not get updated before this get reset
};
return (
<div className="App m-2 p-3 border">
<h1 className="h2">Children</h1>
<div className="p-3 m-2 border bg-light">
<A id={1} order={0} msgPort={msgPort} retrieveData={retrieveData} />
<B id={2} order={1} msgPort={msgPort} retrieveData={retrieveData} />
<A id={3} order={2} msgPort={msgPort} retrieveData={retrieveData} />
</div>
<button
type="button"
className="btn btn-light btn-outline-dark"
onClick={() => {
saveData();
}}
>
Get Children Data
</button>
{Object.keys(appData).length > 0 && (
<div className="my-3">
<label className="form-label">Template Data</label>
<span className="form-control-plaintext">
{JSON.stringify(appData)}
</span>
</div>
)}
</div>
);
}
I suggest creating a React context to hold the state you want to "retrieve" from the children, exposing out only a single callback for them to call and pass their state up. This inverts control where the parent component doesn't need to be aware of its children and by using a React Context you don't need to drill all the state and callbacks as props through to the children. It's an opt-in system where children components need only use the useSaveState hook to send their state to the centralized context value.
Example:
import { createContext, useContext, useEffect, useState } from "react";
const SaveStateContext = createContext({
saveState: () => {}
});
const useSaveState = (key) => {
const { saveState } = useContext(SaveStateContext);
const saveStateWithKey = (value) => saveState(key, value);
return { saveState: saveStateWithKey };
};
const SaveStateProvider = ({ children }) => {
const [state, setState] = useState({});
useEffect(() => {
... any side-effect with the updated states
}, [state]);
const saveState = (key, value) =>
setState((state) => ({
...state,
[key]: value
}));
const value = useMemo(() => ({ saveState }), []);
return (
<SaveStateContext.Provider value={value}>
{children}
</SaveStateContext.Provider>
);
};
Usage:
const { saveState } = useSaveState(id);
const [data, setData] = useState( ... );
useEffect(() => {
saveState(data);
}, [data, saveState]);
So basically I'm trying to create a code that allows me to update the slug with the use of params.
Don't know why My code throws this error.
"TypeError: Cannot read property 'params' of undefined in react".
I tried replacing
useEffect(() => {
loadCategory();
}, []);
with
useEffect(() => {
if(match.params.slug) loadOrders()
}, [match.params.slug])
but it still didn't work.
This is the code I wrote.
import React, { useState, useEffect } from "react";
import {
HistoryContainer,
HistoryBg,
TextContainer2,
TextContainer3,
Text,
CatForm,
FormLabel,
FormControl,
ButtonPrimary,
} from "./CategoryUpdateElements";
import AdminNav from "../AdminNav/index";
import { toast } from "react-toastify";
import { useSelector } from "react-redux";
import { getCategory, updateCategory } from "../../../functions/category";
const CategoryUpdate = ({ history, match }) => {
const { user } = useSelector((state) => ({ ...state }));
const [name, setName] = useState("");
const [loading, setLoading] = useState(false);
useEffect(() => {
loadCategory();
}, []);
const loadCategory = () =>
getCategory(match.params.slug).then((c) => setName(c.data.name));
const handleSubmit = (e) => {
e.preventDefault();
// console.log(name);
setLoading(true);
updateCategory(match.params.slug, { name }, user.token)
.then((res) => {
// console.log(res)
setLoading(false);
setName("");
toast.success(`"${res.data.name}" is updated`);
history.push("/admin/category");
})
.catch((err) => {
console.log(err);
setLoading(false);
if (err.response.status === 400) toast.error(err.response.data);
});
};
return (
<>
<HistoryContainer>
<HistoryBg>
<AdminNav />
<TextContainer2>
<TextContainer3>
{loading ? <Text>Loading..</Text> : <Text>Update category</Text>}
<CatForm onSubmit={handleSubmit}>
<FormLabel>Name</FormLabel>
<FormControl
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
autoFocus
required
/>
<ButtonPrimary>Save</ButtonPrimary>
</CatForm>
</TextContainer3>
</TextContainer2>
</HistoryBg>
</HistoryContainer>
</>
);
};
export default CategoryUpdate;
UPDATE:
To add context to this problem. This code lets me update the name of the slug, but the TypeError doesn't let me follow through with this haha. I was actually following a tutorial regarding this and obviously, his code works. I was sure that I was following it properly as I wrote the code exactly like his but the only difference is my ui.
I also tried console logging match and after checking it out, what I saw was "undefined" which is not surprising.. It should have shown me the slug but instead it gave me "undefined".
This is his code which allows him to update his slug.
import React, { useState, useEffect } from "react";
import AdminNav from "../../../components/nav/AdminNav";
import { toast } from "react-toastify";
import { useSelector } from "react-redux";
import { getCategory, updateCategory } from "../../../functions/category";
const CategoryUpdate = ({ history, match }) => {
const { user } = useSelector((state) => ({ ...state }));
const [name, setName] = useState("");
const [loading, setLoading] = useState(false);
useEffect(() => {
loadCategory();
}, []);
const loadCategory = () =>
getCategory(match.params.slug).then((c) => setName(c.data.name));
const handleSubmit = (e) => {
e.preventDefault();
// console.log(name);
setLoading(true);
updateCategory(match.params.slug, { name }, user.token)
.then((res) => {
// console.log(res)
setLoading(false);
setName("");
toast.success(`"${res.data.name}" is updated`);
history.push("/admin/category");
})
.catch((err) => {
console.log(err);
setLoading(false);
if (err.response.status === 400) toast.error(err.response.data);
});
};
const categoryForm = () => (
<form onSubmit={handleSubmit}>
<div className="form-group">
<label>Name</label>
<input
type="text"
className="form-control"
onChange={(e) => setName(e.target.value)}
value={name}
autoFocus
required
/>
<br />
<button className="btn btn-outline-primary">Save</button>
</div>
</form>
);
return (
<div className="container-fluid">
<div className="row">
<div className="col-md-2">
<AdminNav />
</div>
<div className="col">
{loading ? (
<h4 className="text-danger">Loading..</h4>
) : (
<h4>Update category</h4>
)}
{categoryForm()}
<hr />
</div>
</div>
</div>
);
};
export default CategoryUpdate;
Still new to coding. Hope you guys can help me with this ^_^
I think your problem with match which is getting as the props. If you are having trouble with handle match props please try
useRouteMatch instaed.
import { useRouteMatch } from "react-router-dom";
function YourComponent() {
let match = useRouteMatch();
// Do whatever you want with the match...
return <div />;
}
I think this is more convinent to use.
For more examples