Hi i am writing a basic messaging app with react and firebase everything is working except for passing in the firebase data as props to display on the screen. Right now I am getting this error:react-dom.development.js:11340 Uncaught Error: Objects are not valid as a React child (found: object with keys {id, message, timestamp, user}). If you meant to render a collection of children, use an array instead
Here is the code for my App.js:
import "./App.css";
import { FormControl, InputLabel, Input, Button } from "#material-ui/core";
import SendIcon from "#material-ui/icons/Send";
import { useEffect, useState } from "react";
import { db } from "./firebase";
// import firebase from "firebase";
import Message from "./Message";
import firebase from "#firebase/app";
function App() {
const [userName, setUserName] = useState("");
const [input, setInput] = useState("");
const [messages, setMessages] = useState([]);
console.log(messages);
console.log(db);
const getMessages = () => {
db.collection("messages").onSnapshot(function (querySnapshot) {
setMessages(
querySnapshot.docs.map((doc) => ({
id: doc.id,
message: doc.data().message,
timestamp: doc.data().timestamp,
user: doc.data().user,
}))
);
});
};
console.log(messages);
const sendMessage = (e) => {
e.preventDefault();
db.collection("messages").add({
message: input,
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
user: userName,
});
setInput("");
};
useEffect(() => {
setUserName(prompt("Enter Your Name"));
getMessages();
}, []);
return (
<div className="App">
<h1>Welcome {userName}!</h1>
<div className="form-div">
<form className="form">
<FormControl className="form-input">
<InputLabel>Enter a Message</InputLabel>
<Input
value={input}
onChange={(e) => {
setInput(e.target.value);
}}
/>
</FormControl>
<div className="button-div">
<Button
style={{ backgroundColor: "rgb(0, 110, 255)" }}
className="send-button"
variant="contained"
color="primary"
type="submit"
onClick={sendMessage}
disabled={!input}
>
<SendIcon></SendIcon>
</Button>
</div>
</form>
</div>
{messages.map((message, id) => (
<>
<Message key={id} message={message}></Message>
</>
))}
</div>
);
}
export default App;
and here is the code for my Message component:
import React from "react";
const Message = (message) => {
return (
<div>
<p>{message.message}</p>
</div>
);
};
export default Message;.
Help would be much appreciated as i am beginner i have tried other soultions like downgrading firebase but i still get this error. i have also tried passing the props as individuls e.g message=message.message but i still get same error
There is confusion in your code as you are using message keyword everywhere, I would suggest you to use proper variable names.
Seems your messages structure looks like
[{id: 1, message: 'hi', user: 'asd', timestamp: ''}, {id: 2, message: 'hi1', user: 'asd1', timestamp: ''}]
Just change your Message component like below
const Message = ({message: msgInst}) => {
return (
<div>
<p>{msgInst.message}</p>
<p>{msgInst.user}</p>
</div>
);
};
Related
I know, there is a lot of similar questions although I could not find a solution to my problem. It is the first time I am using Next.js and TypeScrypt.
I am simulating a login with REQRES storing the token in the localStorage as shown below:
import {
FormControl,
FormLabel,
Input,
Heading,
Flex,
Button,
useToast,
} from '#chakra-ui/react';
import { useRouter } from 'next/router';
import { useState } from 'react';
import LStorage from '../utils/localStorage/index';
const Login = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleEmail = (e: any) => setEmail(e.target.value);
const handlePassword = (e: any) => setPassword(e.target.value);
const router = useRouter();
const toast = useToast();
const success = () => toast({
title: 'Login Successfull',
description: 'You will be redirected now.',
status: 'success',
duration: 1200,
isClosable: true,
});
const failure = (error: string) => toast({
title: 'Login unsuccessfull',
description: error,
status: 'error',
duration: 3000,
isClosable: true,
});
const login = async () => {
const res = await fetch('/api', {
method: 'POST',
body: JSON.stringify({
email,
password,
}),
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
});
const json = await res.json();
console.log(json);
if (json.error) {
failure(json.error);
setEmail('');
setPassword('');
} else {
LStorage.set('userToken', json.token);
LStorage.set('userInfo', email);
success();
setTimeout(() => {
router.push('/users');
}, 1500);
}
};
return (<div>
<Flex justifyContent="center">
<Heading my="5">Login</Heading>
</Flex>
<FormControl>
<FormLabel htmlFor="email">Email:</FormLabel>
<Input id="email" type="email" onChange={handleEmail} value={email}/>
<FormLabel htmlFor="password">Password:</FormLabel>
<Input id="password" type="password" onChange={handlePassword} value={password}/>
</FormControl>
<br />
<Button onClick={login}>Login</Button>
</div>);
};
export default Login;
which seem to work fine. Although when trying to get the userInfo from localStorage at the _app.tsx component I get the localStorage not defined, looking for the error I found out the solution below inside the useEffect.
import '../styles/globals.sass';
import { ChakraProvider } from '#chakra-ui/react';
import type { AppProps } from 'next/app';
import { useState, useEffect } from 'react';
import NavBar from '../components/NavBar';
import MainLayout from '../layouts/mainLayout';
import theme from '../styles/theme';
import LStorage from '../utils/localStorage/index';
function MyApp({ Component, pageProps }: AppProps) {
const [userInfo, setUserInfo] = useState<string | null>(null);
const logout = () => {
LStorage.remove('userToken');
LStorage.remove('userInfo');
setUserInfo(null);
};
useEffect(() => {
if (typeof window !== 'undefined') {
if (LStorage.get('userInfo')) {
setUserInfo(LStorage.get('userInfo'));
}
}
console.log('i am here');
}, []);
return (
<ChakraProvider theme={theme}>
<NavBar user={userInfo} logout={logout} />
<MainLayout>
<Component {...pageProps}/>
</MainLayout>
</ChakraProvider>
);
}
export default MyApp;
I understood that the first run will be on the server-side and that is why I got the error, nevertheless, using the useEffect should fix it. The thing is the useEffect does not even run unless I refresh the page... What am I missing??!??
The Login.js is a page inside page folder and the NavBar is a component inside components folder in the root.
import {
Flex, Spacer, Box, Heading, Button,
} from '#chakra-ui/react';
import Link from 'next/link';
import { FC } from 'react';
interface NavBarProps {
user: string | null;
logout: () => void;
}
const NavBar: FC<NavBarProps> = ({ user, logout }: NavBarProps) => (
<Flex bg="black" color="white" p="4">
<Box p="2">
<Heading size="md">
<Link href="/">My Sanjow App</Link>
</Heading>
</Box>
<Spacer />
{user && (
<Box pt="2" pr="4">
<Heading size="md">
<Link href="/users">Users</Link>
</Heading>
</Box>
)}
{user ? (
<Button
variant="ghost"
pr="4"
onClick={logout}
>
<Heading size="md">
<Link href="/">Logout</Link>
</Heading>
</Button>
) : (
<Box pt="2" pr="4">
<Heading size="md">
<Link href="/login">Login</Link>
</Heading>
</Box>
)}
</Flex>
);
export default NavBar;
The utils/localStorage/index
const lsType = {
set: 'setItem',
get: 'getItem',
remove: 'removeItem',
};
const ls = (type: string, itemName: string, itemData?: string): void | string => {
if (typeof window !== 'undefined') {
// eslint-disable-next-line no-undef
const LS = window.localStorage;
if (type === lsType.set && itemData) {
LS[type](itemName, itemData);
return;
}
return LS[type](itemName);
}
};
export default {
set(itemName: string, itemData: string): void {
ls(lsType.set, itemName, itemData);
},
get(itemName: string): string {
return ls(lsType.get, itemName) as string;
},
remove(itemName: string): void {
ls(lsType.remove, itemName);
},
};
You are running the effect only once by passing the [] empty array, pass the props that you expect to change instead of a blank array.
via the docs:
If you want to run an effect and clean it up only once (on mount and >unmount), you can pass an empty array ([]) as a second argument. This tells >React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. This isn’t handled as a special case — it follows directly from how the dependencies array always works.
Generally speaking, managing userInfo only via localStorage is not a good idea, since you might want to re-render the application when user logs in or logs out, or any other change to the user data (i.e. change the username), and React is not subscribed to changes done to localStorage.
Instead, React has an instrument for runtime data management like that, it's called React Context. That context (let's call it UserContext) could be initializing from localStorage, so that the case when you refresh the page for example. But after that initial bootstrapping all state management should go thru the context. Just don't forget to update both context and localStorage every time you login/logout.
I hope this is just enough to give you the right direction.
I have a custom hook(useData) that takes query as an argument and then returns data and runtime(time to fetch the data from the API). But I need access to the runtime to my Editor component when I click on the run button. Right now what is happening is when I click on run button(inside Editor.js), it sets the query to the App component using the setter function and then it passes that query to the Table component and then calls the custom hook using that query and then table make use of that data. but I want the runtime in the Editor component, not in the Table component. I know I can call useData hook in the Editor component but my editor component gets rerender every time when we write on the editor, so It calls the useData() hook on each change.
If I create a context using this hook then I can able to access the runtime and data wherever I want.
Anyone, please help me how to convert that to context!
App.js code
import React, { useState } from "react";
import "./assets/output.css";
import Footer from "./components/layouts/Footer";
import Navbar from "./components/layouts/Navbar";
import Sidebar from "./components/layouts/Sidebar";
import TableSection from "./components/table/TableSection";
import Editor from "./components/editor/Editor";
const App = () => {
const [query, setQuery] = useState("");
const [value, setValue] = useState("select * from customers");
return (
<>
<div className="grid grid-cols-layout-desktop grid-rows-layout-desktop bg-gray-600 h-screen">
<Navbar />
<Sidebar setQuery={setQuery} setValue={setValue} />
<Editor setQuery={setQuery} value={value} setValue={setValue} />
{query ? <TableSection query={query} /> : null}
<Footer />
</div>
</>
);
};
export default App;
Editor.js
import React from "react";
import AceEditor from "react-ace";
import "ace-builds/src-min-noconflict/ext-language_tools";
import "ace-builds/src-min-noconflict/mode-mysql";
import "ace-builds/src-noconflict/theme-github";
import useData from "../../hooks/useData";
const Editor = ({ setQuery, value, setValue }) => {
const { runtime } = useData();
const onChange = (newValue) => {
setValue(newValue);
};
const onSubmit = () => {
var Z = value.toLowerCase().slice(value.indexOf("from") + "from".length);
setQuery(Z.split(" ")[1]);
};
return (
<div className="col-start-2 col-end-3 row-start-2 row-end-3 m-6">
<AceEditor
aria-label="query editor input"
mode="mysql"
theme="github"
name={Math.floor(Math.random() * 100000).toString()}
fontSize={16}
minLines={15}
maxLines={10}
width="100%"
showPrintMargin={false}
showGutter
placeholder="Write your Query here..."
editorProps={{ $blockScrolling: true }}
setOptions={{
enableBasicAutocompletion: true,
enableLiveAutocompletion: true,
enableSnippets: true,
}}
value={value}
onChange={onChange}
showLineNumbers
/>
<div className="">
<button
className="bg-white text-gray-800 rounded-md font-semibold px-4 py-2 my-4"
onClick={onSubmit}
>
<i className="fas fa-play"></i> Run SQL
</button>
</div>
</div>
);
};
export default Editor;
Hook code:
import { useEffect, useState } from "react";
import alasql from "alasql";
import toast from "react-hot-toast";
import TABLE_NAMES from "../utils/tableNames";
const getURL = (name) =>
`https://raw.githubusercontent.com/graphql-compose/graphql-compose-examples/master/examples/northwind/data/csv/${name}.csv`;
const useData = (tableName) => {
const [data, setData] = useState([]);
const [error, setError] = useState(false);
const [runtime, setRuntime] = useState("");
const convertToJson = (data) => {
alasql
.promise("SELECT * FROM CSV(?, {headers: false, separator:','})", [data])
.then((data) => {
setData(data);
toast.success("Query run successfully");
})
.catch((e) => {
toast.error(e.message);
});
};
const fetchData = (tableName) => {
setData([]);
const name = TABLE_NAMES.find((name) => name === tableName);
if (name) {
setError(false);
fetch(getURL(tableName))
.then((res) => res.text())
.then((data) => convertToJson(data));
} else {
setError(true);
toast.error("Please enter a valid query");
}
};
useEffect(() => {
let t0 = performance.now(); //start time
fetchData(tableName);
let t1 = performance.now(); //end time
setRuntime(t1 - t0);
console.log(
"Time taken to execute add function:" + (t1 - t0) + " milliseconds"
);
}, [tableName]);
return { data, runtime, error };
};
export default useData;
If you want to create a context and use it wherever you want, you can create a context, and add the state in this component and pass it to the value prop in the Provider component.
See the sample code.
import React, { createContext, useState } from "react";
export const UserContext = createContext({});
export interface User {
uid: string;
email: string;
}
export const UserProvider = ({ children }: any) => {
const [user, setUser] = useState<User>();
// you can defined more hooks at here
return (
// Pass the data to the value prop for sharing data
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
};
Then wrap components with the provider function like this
<UserProvider>
<MyComponment1>
</MyComponment1>
<MyComponment2>
</MyComponment2>
<MyComponment3>
</MyComponment3>
</UserProvider>
At This time, Whatever Component in the UserProvider can access the context right now and you can use useContext hook to access the data that you pass in the value props
export const MyComponment1 = () => {
const { user, setUser } = useContext<any>(UserContext);
...
}
I am working on a Todo list, and I have created the functionality for the todo to be added. My list, which renders the todo is located in a sepearate component from where I created the addTodo function. Everything seems to be fine, however, when I click to add a todo, the input that I added doesn't render in the paragraph that I created within the Todo component. I also consoled the data that I am adding in the input, and it shows up in the console. I believe my problem lies with how I am setting my props in the paragraph elemement in my Todo component. This is my code, if it helps.
import { useState, useEffect } from "react";
import classes from "./addlink.module.css";
import firebase from "firebase/app";
import initFirebase from "../../config";
import "firebase/firestore";
import Todo from "../Todo/Todo";
import { v4 as uuidv4 } from "uuid";
initFirebase();
const db = firebase.firestore();
function AddLink(props) {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState("");
useEffect(() => {
db.collection("links")
.orderBy("timestamp", "desc")
.onSnapshot((snapshot) => {
setTodos(
snapshot.docs.map((doc) => ({
id: doc.id,
todo: doc.data().todo,
}))
);
});
}, []);
const addTodo = (event) => {
event.preventDefault();
db.collection("links").add({
id: uuidv4(),
todo: input,
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
});
console.log(input);
setInput("");
};
return (
<div className={classes.addlink}>
<form>
<div className={classes.adminlink}>
<input
type="text"
value={input}
onChange={(event) => setInput(event.target.value)}
/>
<button
className={classes.adminbutton}
type="submit"
onClick={addTodo}
>
Add new link
</button>
</div>
</form>
{todos.map((todo, id) => (
<Todo todo={todo} key={id} />
))}
</div>
);
}
export default AddLink;
And the Todo.js
import React from "react";
import { AiOutlinePicture } from "react-icons/ai";
import { AiOutlineStar } from "react-icons/ai";
import { GoGraph } from "react-icons/go";
import DeleteForeverIcon from "#material-ui/icons/DeleteForever";
import classes from "./todo.module.css";
import firebase from "firebase/app";
import initFirebase from "../../config";
import "firebase/firestore";
initFirebase();
const db = firebase.firestore();
function Todo(props) {
const deleteHandler = (event) => {
db.collection("links").doc(props.todo.id).delete();
};
return (
<li className={classes.adminsection}>
<div className={classes.linkCards}>
<h3>Todo</h3>
<p>{props.value}</p>
<div>
<AiOutlinePicture />
<AiOutlineStar />
<GoGraph />
<DeleteForeverIcon onClick={deleteHandler} />
</div>
</div>
</li>
);
}
export default Todo;
From what I can see your todo objects have id, todo, and timestamp properties.
const addTodo = (event) => {
event.preventDefault();
db.collection("links").add({
id: uuidv4(),
todo: input,
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
});
setInput("");
};
And when the links collection updates you map it all into a todo property in state.
useEffect(() => {
db.collection("links")
.orderBy("timestamp", "desc")
.onSnapshot((snapshot) => {
setTodos(
snapshot.docs.map((doc) => ({
id: doc.id,
todo: doc.data().todo,
}))
);
});
}, []);
You map it and pass the entire todo property as a prop.
{todos.map((todo, id) => (
<Todo todo={todo} key={id} />
))}
And finally in Todo you access a value prop.
<p>{props.value}</p>
This isn't a defined value.
It seems you should access a props.todo.todo prop.
<p>{props.todo.todo}</p>
I could be mistaken on the level of nesting and what is unpacked by the onSnapshot subscription, so it could possibly be just props.todo.
<p>{props.todo}</p>
But given that in the deleteHandler you appear to reference correctly props.todo.id I am certain you need to access props.todo.todo in the paragraph.
Word of Caution
When mapping arrays if you are mutating the array (inserting in the middle, deleting elements, etc...) then don't use the array index as the React key as once you mutate the array the keys will shift to different elements. Use the doc.id since that will be an intrinsic value to each element.
{todos.map((todo) => (
<Todo todo={todo} key={todo.id} />
))}
See Lists & Keys
I am creating a Todo list using React and Firebase. So far, I have already created the AddToDo functionality, however, now I am having trouble with the delete functionality. I believe this is where my problem lies. For example, when I try and click the delete icon that I set up, I get an error:
Unhandled Runtime Error
TypeError: Cannot read properties of undefined (reading 'id')
This is the code if it helps. AddLink.js
import { useState, useEffect } from "react";
import classes from "./addlink.module.css";
import firebase from "firebase/app";
import initFirebase from "../../config";
import "firebase/firestore";
import Todo from "../Todo/Todo";
import { v4 as uuidv4 } from "uuid";
initFirebase();
const db = firebase.firestore();
function AddLink(props) {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState("");
useEffect(() => {
db.collection("links")
.orderBy("timestamp", "desc")
.onSnapshot((snapshot) => {
// this gives back an array
setTodos(
snapshot.docs.map((doc) => ({
id: doc.id,
todo: doc.data().todo,
}))
);
});
}, []);
const addTodo = (event) => {
event.preventDefault();
console.log("clicked");
db.collection("links").add({
id: uuidv4(),
todo: input,
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
});
setInput("");
};
return (
<div className={classes.addlink}>
<form>
<div className={classes.adminlink}>
<input
type="text"
value={input}
onChange={(event) => setInput(event.target.value)}
/>
<button
className={classes.adminbutton}
type="submit"
onClick={addTodo}
>
Add new link
</button>
</div>
</form>
{todos.map((todo, id) => (
<Todo value={todo} key={id} />
))}
{/* {modalIsOpen && (
<Modal onCancel={closeModalHandler} onConfirm={closeModalHandler} />
)}
{modalIsOpen && <Backdrop onCancel={closeModalHandler} />} */}
</div>
);
}
export default AddLink;
And Todo.js
import React from "react";
import { AiOutlinePicture } from "react-icons/ai";
import { AiOutlineStar } from "react-icons/ai";
import { GoGraph } from "react-icons/go";
import DeleteForeverIcon from "#material-ui/icons/DeleteForever";
import classes from "./todo.module.css";
import firebase from "firebase/app";
import initFirebase from "../../config";
import "firebase/firestore";
initFirebase();
const db = firebase.firestore();
function Todo(props) {
const deleteHandler = () => {
db.collection("todos").doc(props.todo.id).delete();
};
return (
<li className={classes.adminsection}>
<div className={classes.linkCards}>
<h3>{props.text}</h3>
<p>This is a new link</p>
<div>
<AiOutlinePicture />
<AiOutlineStar />
<GoGraph />
<DeleteForeverIcon onClick={deleteHandler} />
</div>
</div>
</li>
);
}
export default Todo;
Any help would be greatly appreciated.
const deleteHandler = () => {
db.collection("todos").doc(props.todo.id).delete();
};
You should replace props.todo.id with props.value.id.
const deleteHandler = () => {
db.collection("todos").doc(props.value.id).delete();
};
Alternatively you can change:
<Todo value={todo} key={id} />
To
<Todo todo={todo} key={id} />
The key you use to access props.value should be the same as the one declared in the jsx template. Using proptypes can help you avoid those mistakes.
After deleting from the database you should update the state, UI with
Todos.filter(d=>d.id !== id of deleted list item)
I am making a small blog application using React JS. I am using the context api to store the user's responses globally (in InputContext.js), so that it can be used across different components.
What I want to achieve is, when the user inputs a new blog entry on a separate input page (WriteBlogPost.js) display all the blog entries on a separate page (AllBlogs.js). The page changes are being handled with react router. I have a problem where I am unable to add the new blog objects into the array defined in the context api component (allBlogPosts). I am unsure what is causing this, any explanations and guidance towards the right direction would greatly be appreciated.
InputContext.js
import React, { useState, createContext, useMemo } from 'react'
//create context
export const InputContext = createContext();
const InputContextProvider = (props) => {
const [blogPost, setBlogPost] = useState({
id: '',
title: '',
author: '',
text: ''
});
//create an array to push all the blogPosts
const [allBlogPosts, setAllBlogPosts] = useState([]);
console.log(allBlogPosts)
//put value inside useMemo so that the component only rerenders when there is change in the value
const value = useMemo(() => ({ blogPost, setBlogPost, allBlogPosts, setAllBlogPosts }), [blogPost, allBlogPosts])
return (
<InputContext.Provider value={value}>
{props.children}
</InputContext.Provider>
)
}
export default InputContextProvider;
WriteBlogPost.js
import React, { useState, useContext } from 'react'
import { useHistory } from 'react-router-dom'
import { InputContext } from '../Contexts/InputContext'
import { TextareaAutosize } from '#material-ui/core'
import { v4 as uuidv4 } from 'uuid';
export const WriteBlogPost = () => {
const [blog, setBlog] = useState({
id: '',
title: '',
author: '',
text: ''
});
const history = useHistory();
const { setBlogPost } = useContext(InputContext);
const { allBlogPosts, setAllBlogPosts } = useContext(InputContext)
const handleBlogPost = () => {
setAllBlogPosts(setBlogPost(blog))
history.push("/blogs")
console.log({ blog })
console.log({ allBlogPosts })
}
const handleChange = (e) => {
const value = e.target.value
setBlog({
...blog,
id: uuidv4(),
[e.target.name]: value
})
}
return (
<div>
<label>
Title:
<input type="text" onChange={handleChange} value={blog.title} name="title" />
</label>
<label>
Author:
<input type="text" onChange={handleChange} value={blog.author} name="author" />
</label>
<TextareaAutosize aria-label="minimum height" minRows={20} style={{ width: '70%' }} placeholder="Your blog post"
onChange={handleChange}
value={blog.text}
name="text" />
<div>
<button onClick={handleBlogPost}>Submit</button>
</div>
</div>
)
}
AllBlogs.js(currently unable to map through the array as the array is empty)
import React, { useContext } from 'react'
import { InputContext } from '../Contexts/InputContext'
export const AllBlogs = () => {
const { allBlogPosts } = useContext(InputContext)
console.log(allBlogPosts)
return (
<div>
<h1>All blogs</h1>
{allBlogPosts.map((post) =>
<div>
<p>{post.title}</p>
<p>{post.author}</p>
<p>{post.text}</p>
</div>
)}
</div>
)
}
Just update handleBlogPost
const handleBlogPost = () => {
setBlogPost(blog);
setAllBlogPosts([...allBlogPosts, blog]);
history.push("/blogs");
};