I keep on getting this error when trying to update a product in my project:
react_devtools_backend.js:4012 Warning: Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it.
Index.js:
import React from 'react';
import { Provider } from 'react-redux';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import store from './store';
ReactDOM.render(
<Provider store={store}>
<React.StrictMode>
<App />
</React.StrictMode>
</Provider>,
document.getElementById('root')
);
serviceWorker.unregister();
ProductList:
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Link, useParams, useNavigate, useLocation } from 'react-router-dom';
import {
createProduct,
deleteProduct,
listProducts,
} from '../actions/productActions';
import LoadingBox from '../components/LoadingBox';
import MessageBox from '../components/MessageBox';
import {
PRODUCT_CREATE_RESET,
PRODUCT_DELETE_RESET,
} from '../constants/productConstants';
export default function ProductListScreen(props) {
const navigate = useNavigate();
const { pageNumber = 1 } = useParams();
const { pathname } = useLocation();
const sellerMode = pathname.indexOf('/seller') >= 0;
const productList = useSelector((state) => state.productList);
const { loading, error, products, page, pages } = productList;
const productCreate = useSelector((state) => state.productCreate);
const {
loading: loadingCreate,
error: errorCreate,
success: successCreate,
product: createdProduct,
} = productCreate;
const productDelete = useSelector((state) => state.productDelete);
const {
loading: loadingDelete,
error: errorDelete,
success: successDelete,
} = productDelete;
const userSignin = useSelector((state) => state.userSignin);
const { userInfo } = userSignin;
const dispatch = useDispatch();
useEffect(() => {
if (successCreate) {
dispatch({ type: PRODUCT_CREATE_RESET });
navigate(`/product/${createdProduct._id}/edit`);
}
if (successDelete) {
dispatch({ type: PRODUCT_DELETE_RESET });
}
dispatch(
listProducts({ seller: sellerMode ? userInfo._id : '', pageNumber })
);
}, [
createdProduct,
dispatch,
navigate,
sellerMode,
successCreate,
successDelete,
userInfo._id,
pageNumber,
]);
const deleteHandler = (product) => {
if (window.confirm('Are you sure to delete?')) {
dispatch(deleteProduct(product._id));
}
};
const createHandler = () => {
dispatch(createProduct());
};
return (
<div>
<div className="row">
<h1>Products</h1>
<button type="button" className="primary" onClick={createHandler}>
Create Product
</button>
</div>
{loadingDelete && <LoadingBox></LoadingBox>}
{errorDelete && <MessageBox variant="danger">{errorDelete}</MessageBox>}
{loadingCreate && <LoadingBox></LoadingBox>}
{errorCreate && <MessageBox variant="danger">{errorCreate}</MessageBox>}
{loading ? (
<LoadingBox></LoadingBox>
) : error ? (
<MessageBox variant="danger">{error}</MessageBox>
) : (
<>
<table className="table">
<thead>
<tr>
<th>ID</th>
<th>NAME</th>
<th>PRICE</th>
<th>CATEGORY</th>
<th>BRAND</th>
<th>ACTIONS</th>
</tr>
</thead>
<tbody>
{products.map((product) => (
<tr key={product._id}>
<td>{product._id}</td>
<td>{product.name}</td>
<td>{product.price}</td>
<td>{product.category}</td>
<td>{product.brand}</td>
<td>
<button
type="button"
className="small"
onClick={() => navigate(`/product/${product._id}/edit`)}
>
Edit
</button>
<button
type="button"
className="small"
onClick={() => deleteHandler(product)}
>
Delete
</button>
</td>
</tr>
))}
</tbody>
</table>
<div className="row center pagination">
{[...Array(pages).keys()].map((x) => (
<Link
className={x + 1 === page ? 'active' : ''}
key={x + 1}
to={`/productlist/pageNumber/${x + 1}`}
>
{x + 1}
</Link>
))}
</div>
</>
)}
</div>
);
}
Product Edit Page:
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import Axios from 'axios';
import { useNavigate, useParams } from 'react-router-dom';
import { detailsProduct, updateProduct } from '../actions/productActions';
import LoadingBox from '../components/LoadingBox';
import MessageBox from '../components/MessageBox';
import { PRODUCT_UPDATE_RESET } from '../constants/productConstants';
export default function ProductEditScreen(props) {
const navigate = useNavigate();
const params = useParams();
const { id: productId } = params;
const [name, setName] = useState('');
const [price, setPrice] = useState('');
const [image, setImage] = useState('');
const [category, setCategory] = useState('');
const [countInStock, setCountInStock] = useState('');
const [brand, setBrand] = useState('');
const [description, setDescription] = useState('');
const productDetails = useSelector((state) => state.productDetails);
const { loading, error, product } = productDetails;
const productUpdate = useSelector((state) => state.productUpdate);
const {
loading: loadingUpdate,
error: errorUpdate,
success: successUpdate,
} = productUpdate;
const dispatch = useDispatch();
useEffect(() => {
if (successUpdate) {
navigate('/productlist');
}
if (!product || product._id !== productId || successUpdate) {
dispatch({ type: PRODUCT_UPDATE_RESET });
dispatch(detailsProduct(productId));
} else {
setName(product.name);
setPrice(product.price);
setImage(product.image);
setCategory(product.category);
setCountInStock(product.countInStock);
setBrand(product.brand);
setDescription(product.description);
}
}, [product, dispatch, productId, successUpdate, navigate]);
const submitHandler = (e) => {
e.preventDefault();
// TODO: dispatch update product
dispatch(
updateProduct({
_id: productId,
name,
price,
image,
category,
brand,
countInStock,
description,
})
);
};
const [loadingUpload, setLoadingUpload] = useState(false);
const [errorUpload, setErrorUpload] = useState('');
const userSignin = useSelector((state) => state.userSignin);
const { userInfo } = userSignin;
const uploadFileHandler = async (e) => {
const file = e.target.files[0];
const bodyFormData = new FormData();
bodyFormData.append('image', file);
setLoadingUpload(true);
try {
const { data } = await Axios.post('/api/uploads', bodyFormData, {
headers: {
'Content-Type': 'multipart/form-data',
Authorization: `Bearer ${userInfo.token}`,
},
});
setImage(data);
setLoadingUpload(false);
} catch (error) {
setErrorUpload(error.message);
setLoadingUpload(false);
}
};
return (
<div>
<form className="form" onSubmit={submitHandler}>
<div>
<h1>Edit Product {productId}</h1>
</div>
{loadingUpdate && <LoadingBox></LoadingBox>}
{errorUpdate && <MessageBox variant="danger">{errorUpdate}</MessageBox>}
{loading ? (
<LoadingBox></LoadingBox>
) : error ? (
<MessageBox variant="danger">{error}</MessageBox>
) : (
<>
<div>
<label htmlFor="name">Name</label>
<input
id="name"
type="text"
placeholder="Enter name"
value={name}
onChange={(e) => setName(e.target.value)}
></input>
</div>
<div>
<label htmlFor="price">Price</label>
<input
id="price"
type="text"
placeholder="Enter price"
value={price}
onChange={(e) => setPrice(e.target.value)}
></input>
</div>
<div>
<label htmlFor="image">Image</label>
<input
id="image"
type="text"
placeholder="Enter image"
value={image}
onChange={(e) => setImage(e.target.value)}
></input>
</div>
<div>
<label htmlFor="imageFile">Image File</label>
<input
type="file"
id="imageFile"
label="Choose Image"
onChange={uploadFileHandler}
></input>
{loadingUpload && <LoadingBox></LoadingBox>}
{errorUpload && (
<MessageBox variant="danger">{errorUpload}</MessageBox>
)}
</div>
<div>
<label htmlFor="category">Category</label>
<input
id="category"
type="text"
placeholder="Enter category"
value={category}
onChange={(e) => setCategory(e.target.value)}
></input>
</div>
<div>
<label htmlFor="brand">Brand</label>
<input
id="brand"
type="text"
placeholder="Enter brand"
value={brand}
onChange={(e) => setBrand(e.target.value)}
></input>
</div>
<div>
<label htmlFor="countInStock">Count In Stock</label>
<input
id="countInStock"
type="text"
placeholder="Enter countInStock"
value={countInStock}
onChange={(e) => setCountInStock(e.target.value)}
></input>
</div>
<div>
<label htmlFor="description">Description</label>
<textarea
id="description"
rows="3"
type="text"
placeholder="Enter description"
value={description}
onChange={(e) => setDescription(e.target.value)}
></textarea>
</div>
<div>
<label></label>
<button className="primary" type="submit">
Update
</button>
</div>
</>
)}
</form>
</div>
);
}
what could be the problem?
The code should redirect me to a new page in which i can create a new product and add it to my database
Related
I am having error with the Login.js code these are the error shown I am also having problem with the navigation in header please help me out this is a CRUD operation website.
Warning: React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
Check your code at login.js:36.
at Login (http://localhost:3000/static/js/bundle.js:482:75)
at RenderedRoute (http://localhost:3000/static/js/bundle.js:42128:5)
at Routes (http://localhost:3000/static/js/bundle.js:42571:5)
at Router (http://localhost:3000/static/js/bundle.js:42509:15)
at BrowserRouter (http://localhost:3000/static/js/bundle.js:40827:5)
at div
at div
at App
I am also having problem with the navigation in header please help me out this is a CRUD operation website.
App.js
import './App.css';
import Create from './components/create';
import Read from './components/read';
import Update from './components/update';
import Login from './components/login';
import { Routes, Route } from 'react-router-dom';
import { BrowserRouter } from 'react-router-dom';
// import { useNavigate } from 'react-router-dom';
// import { Link } from 'react-router-dom';
// import Header from './components/header';
function App() {
// let Navigate = useNavigate();
return (
<div>
<header>
<h1 class='header1'>Aman Enterprises</h1>
<nav class ='nav'>
<ul>
{/* <li><Link to = '/login'>Login</Link></li> */}
{/* <li><Link to = '/create'>Registration</Link></li> */}
</ul>
</nav>
</header>
<div className="main">
<BrowserRouter>
<Routes>
<Route path="/" element={<Create />} />
<Route path="login" element={<Login />} />
<Route path="read" element={<Read />} />
<Route path="update" element={<Update />} />
</Routes>
</BrowserRouter>
</div>
</div>
);
}
export default App;
Login.js
import React, { useState } from 'react'
import { Button, Form } from 'semantic-ui-react'
import axios from 'axios'
import { useNavigate } from 'react-router'
export default function Login() {
let navigate = useNavigate()
const[username, setusername] = useState('');
const[Employee_password, setEmployee_password] = useState('');
const GetData = (e) =>{
e.preventDefault();
console.log(username, Employee_password)
if(username !== username && Employee_password !== Employee_password)
{
alert('Username or Password does not match!')
return false
}
axios.post('http://localhost:5000/emp/login',{
username,
Employee_password
}).then((res)=>{
console.log('Login Successfull')
navigate('/read')
}).catch(err=>{
console.log(err)
})
}
return(
<div>
<Form onCLick={GetData}>
<h2>Login into existing ID</h2>
<Form.field>
<label>Enter Username</label>
<input type='text' placeholder='Username' onChange={(e) => setusername(e.target.value)}></input>
</Form.field>
<Form.field>
<label>Passworld</label>
<input type='password' placeholder='Password' onChange={(e) => setEmployee_password(e.target.value)}></input>
</Form.field>
{/* {<error && <div style={{ color: 'red' }}>Error</div>>} */}
<Button type='submit'>Login</Button>
</Form>
</div>
)
}
create.js
import React, { useState } from 'react';
import { Button, Form } from 'semantic-ui-react'
import axios from 'axios';
import { useNavigate } from 'react-router';
export default function Create() {
let navigate = useNavigate();
const [Employee_name, setEmployee_name] = useState('');
const [Employee_id, setEmployee_id] = useState('');
const [Employee_address, setEmployee_address] = useState('');
const [Employee_post, setEmployee_post] = useState('');
const [username, setusername] = useState('');
const [Employee_password, setEmployee_password] = useState('');
const postData = (e) => {
e.preventDefault();
if(Employee_name, Employee_id, Employee_address, Employee_post, username, Employee_password.length === 0)
{
alert('Enter Employee Details to register!')
return false
}
if(Employee_name.length === 0)
{
alert('Enter Employee Name!')
return false
}
if(Employee_id.length === 0)
{
alert('Enter Employee id!')
return false
}
if(Employee_address.length === 0)
{
alert('Enter Employee address!')
return false
}
if(Employee_post.length === 0)
{
alert('Enter Employee Position!')
return false
}
if(username.length === 0)
{
alert('Set Employee Username!')
return false
}
if(Employee_password.length === 0)
{
alert('Set Employee Password!')
return false
}
axios.post(`http://localhost:5000/qo`, {
Employee_name,
Employee_id,
Employee_address,
Employee_post,
username,
Employee_password
})
.then(() => {
navigate('/Login')
})
alert('Data Saved')
}
return (
<div>
<Form className="create-form" onSubmit={e =>postData(e)}>
<h2 class='Header'>Employee Registration form</h2>
<Form.Field required={true}>
<label>Employee Name</label>
<input type='text' placeholder='Employee Name' onChange={(e) => setEmployee_name(e.target.value)}/>
</Form.Field>
<Form.Field required={true}>
<label>Employee ID</label>
<input type='text' placeholder='Employee ID' onChange={(e) => setEmployee_id(e.target.value)}/>
</Form.Field>
<Form.Field required={true}>
<label>Employee Address</label>
<input type='text' placeholder='Employee Address' onChange={(e) => setEmployee_address(e.target.value)}/>
</Form.Field>
<Form.Field required={true}>
<label>Employee Position</label>
<input type='text' placeholder='Employee Position' onChange={(e) => setEmployee_post(e.target.value)}/>
</Form.Field>
<Form.Field required={true}>
<label>Create Username</label>
<input type='text' placeholder='Username' onChange={(e) => setusername(e.target.value)}/>
</Form.Field>
<Form.Field required={true}>
<label>Create Password</label>
<input type='password' placeholder='Password' onChange={(e) => setEmployee_password(e.target.value)}/>
</Form.Field>
<Button class='b2' onClick={postData} type='submit'>Submit</Button>
</Form>
</div>
)
}
read.js(panel for database display having update and delete functionality)
import axios from 'axios';
import React, { useEffect, useState } from 'react';
import { Table, Button } from 'semantic-ui-react';
import { useNavigate } from 'react-router-dom';
export default function Read() {
let navigate = useNavigate();
const [APIData, setAPIData] = useState([]);
useEffect(() => {
axios.get(`http://localhost:5000/emp`)
.then((response) => {
console.log(response.data)
setAPIData(response.data);
})
}, []);
const setData = (data) => {
let { Employee_name, Employee_id, Employee_address, Employee_post, username, Employee_password } = data;
localStorage.setItem('Employee ID', Employee_id);
navigate('/update')}
const getData = () => {
axios.get(`http://localhost:5000/emp`)
.then((getData) => {
setAPIData(getData.data);
})
}
const onDelete = (data) => {
const{Employee_id}=data
if(Employee_id===0){
alert("Employee id not found")
return
}
const url=`http://localhost:5000/emp/${Employee_id}`
axios.delete(url)
.then((res) => {
getData();
})
.catch(err=>console.log(err))
alert('Employee Deleted!')
}
const Data = () => {
navigate('/')
}
return (
<form>
<h2>Registered Employees</h2>
<div>
<Table singleLine>
<Table.Header>
<Table.Row>
<Table.HeaderCell>Employee Name</Table.HeaderCell>
<Table.HeaderCell>Employee ID</Table.HeaderCell>
<Table.HeaderCell>Employee Address</Table.HeaderCell>
<Table.HeaderCell>Employee Position</Table.HeaderCell>
<Table.HeaderCell>Employee Username</Table.HeaderCell>
<Table.HeaderCell>Employee Password</Table.HeaderCell>
<Table.HeaderCell>Update</Table.HeaderCell>
<Table.HeaderCell>Delete</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{APIData.map((data,index) => {
return (
<Table.Row key={index+""}>
<Table.Cell>{data.Employee_name}</Table.Cell>
<Table.Cell>{data.Employee_id}</Table.Cell>
<Table.Cell>{data.Employee_address}</Table.Cell>
<Table.Cell>{data.Employee_post}</Table.Cell>
<Table.Cell>{data.username}</Table.Cell>
<Table.Cell>{data.Employee_password}</Table.Cell>
{/* <Link to='/update'> */}
<Table.Cell>
<Button onClick={() => setData(data)}>Update</Button>
</Table.Cell>
{/* </Link> */}
<Table.Cell>
<Button onClick={() => onDelete(data)}>Delete</Button>
</Table.Cell>
</Table.Row>
)
})}
</Table.Body>
</Table>
<Button onClick={Data} type='submit'>Home</Button>
</div>
</form>
)
}
update.js
import React, { useState, useEffect } from 'react';
import { Button, Form } from 'semantic-ui-react'
import axios from 'axios';
import { useNavigate } from 'react-router-dom';
export default function Update() {
let navigate = useNavigate();
const [Employee_name, setEmployee_name] = useState('');
const [Employee_id, setEmployee_id] = useState('');
const [Employee_address, setEmployee_address] = useState('');
const [Employee_post, setEmployee_post] = useState('');
const [username, setusername] = useState('');
const[Employee_password, setEmployee_password] = useState('');
useEffect(() => {
setEmployee_id(localStorage.getItem('Employee ID'));
const id=localStorage.getItem('Employee ID')
getEmplyeeDetailsById(id)
}, []);
const getEmplyeeDetailsById=(id)=>{
const url=`http://localhost:5000/emp/${id}`
axios.get(url)
.then((res) =>{
if(res && res.data){
console.log(res.data[0])
let { Employee_name, Employee_id, Employee_address, Employee_post, username, Employee_password } =res.data[0];
setEmployee_name(Employee_name)
setEmployee_id(Employee_id)
setEmployee_address(Employee_address)
setEmployee_post(Employee_post)
setusername(username)
setEmployee_password(Employee_password)
}
// setAPIData(update.data);
})
.catch(err=>console.log(err))
}
const updateAPIData = () =>{
let jsonData={
Employee_name,
Employee_id,
Employee_address,
Employee_post,
username,
Employee_password
}
axios.put(`http://localhost:5000/emp/update`, jsonData)
.then(() => {
navigate('/read')
})
}
const back = () => {
navigate('/read')
}
return (
<div>
<Form className="create-form">
<h2>Update the Registered Data</h2>
<Form.Field>
<label>Employee Name</label>
<input placeholder='Employee Name' value={Employee_name} onChange={(e) =>setEmployee_name(e.target.value)}/>
</Form.Field>
<Form.Field>
<label>Employee ID</label>
<input placeholder='Employee ID' value={Employee_id+""} onChange={(e) => setEmployee_id(e.target.value)}/>
</Form.Field>
<Form.Field>
<label>Employee Address</label>
<input placeholder='Employee Address' value={Employee_address} onChange={(e) => setEmployee_address(e.target.value)}/>
</Form.Field>
<Form.Field>
<label>Employee Position</label>
<input placeholder='Employee Position' value={Employee_post} onChange={(e) => setEmployee_post(e.target.value)}/>
</Form.Field>
<Form.Field>
<label>Username</label>
<input placeholder='username' value={username} onChange={(e) => setusername(e.target.value)}/>
</Form.Field>
<Form.Field>
<label>Password</label>
<input type='password' placeholder='Password' onChange={(e) => setEmployee_password(e.target.value)}/>
</Form.Field>
<Button type='submit' onClick={updateAPIData}>Update</Button>
<Button onClick={back} type='submit'>Back</Button>
</Form>
</div>
)
}
Replace Form.field with Form.Field in your Login.js
In App.js You have to use '/' before every pathname. For example: path="/login".
In Login.js. Use useEffect hook for execute function. Example:
useEffect(()=>{GetData () },[])
I hope it will useful for you
import "./App.css";
import { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { addUser} from "./features/Users";
function App() {
const dispatch = useDispatch();
const userList = useSelector((state) => state.users.value);
const [name, setName] = useState("");
const [username, setUsername] = useState("");
return (
<div className="App">
{" "}
<div className="addUser">
<input
type="text"
placeholder="Name..."
onChange={(event) => {
setName(event.target.value);
}}
/>
<input
type="text"
placeholder="Username..."
onChange={(event) => {
setUsername(event.target.value);
}}
/>
<button
onClick={() => {
dispatch(
addUser({
id: userList[userList.length - 1].id + 1,
name,
username,
})
);
}}
>
{" "}
Add User
</button>
</div>
);}
I am new to react and redux. After clicking the "Add User" button, new User data from inputs in the code will be added to the backend list. I want the values in input sections to be cleared after clicking the "Add User" button, but I don't know how to do.
you need to clear your state after click on submit button. for ex: set function like =>
const clearData = {
setName("")
setUsername("")
}
and pass the func to your onClick event.
onClick={clearData}
The following code will work perfectly fine.
Just assign value={name} and value={username} to both input types respectively and when you click Add User just clear the data in both the states.
import "./App.css";
import { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { addUser} from "./features/Users";
function App() {
const dispatch = useDispatch();
const userList = useSelector((state) => state.users.value);
const [name, setName] = useState("");
const [username, setUsername] = useState("");
return (
<div className="App">
{" "}
<div className="addUser">
<input
type="text"
placeholder="Name..."
value={name}
onChange={(event) => {
setName(event.target.value);
}}
/>
<input
type="text"
placeholder="Username..."
value={username}
onChange={(event) => {
setUsername(event.target.value);
}}
/>
<button
onClick={() => {
setName("");
setUsername("");
dispatch(
addUser({
id: userList[userList.length - 1].id + 1,
name,
username,
})
);
}}
>
{" "}
Add User
</button>
</div>
);}
You can maintain a simple variable with list of form fields and can update the form state with the variable when you needed to clear form data. The below approach comes handy when you need to add additional fields as well.
import "./App.css";
import { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { addUser} from "./features/Users";
const formFields = { name: '', username: '' };
function App() {
const dispatch = useDispatch();
const userList = useSelector((state) => state.users.value);
const [params, setParams] = useState(formFields)
const handleChange = (e) => {
const { name, value } = e.target;
setParams({ ...params }, ...{[name]: value});
}
const clearForm = () => setParams(formFields);
return (
<div className="App">
<div className="addUser">
<input
type="text"
placeholder="Name..."
value={params.name}
onChange={(e) => handleChange(e)}
/>
<input
type="text"
placeholder="Username..."
value={params.username}
onChange={(e) => handleChange(e)}
/>
<button
onClick={() => {
dispatch(
addUser({
id: userList[userList.length - 1].id + 1,
...params
})
);
clearForm();
}}
>
{" "}
Add User
</button>
</div>
</div>
)
}
What the code looks like rendering the button to show the form
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { deleteSong, getSongs, updateSong } from '../../store/song';
import ReactAudioPlayer from 'react-audio-player';
import { useHistory } from 'react-router';
import SongForm from '../AddSongForm';
import EditSongForm from '../EditSongForm';
const SongList = () => {
const [addShowForm, setAddShowForm] = useState(false);
const [editShowForm, setEditShowForm] = useState(false);
const history = useHistory()
const dispatch = useDispatch();
const songsObj = useSelector((state) => state.songState.entries);
const songs = Object.values(songsObj)
const user = useSelector((state) => state.session.user);
const CurrentUserId = user?.id
const remove = (e) => {
dispatch(deleteSong(e.target.id));
}
const addFormCheck = (e) => {
if (addShowForm) setAddShowForm(false)
if (!addShowForm) setAddShowForm(true)
}
const editFormCheck = (e) => {
if (editShowForm) setEditShowForm(false)
if (!editShowForm) setEditShowForm(true)
}
useEffect(() => {
dispatch(getSongs());
}, [dispatch]);
return (
<div>
<div>
<button onClick={addFormCheck}>add a song</button>
{addShowForm ?
<SongForm />
: null}
</div>
<h1>Song List</h1>
<ol>
{songs.map(({ id, songName, songLink, userId }) => (
<div className='songdetails' key={id}>
<p key={id}>songName={songName}</p>
<ReactAudioPlayer
src={songLink}
autoPlay
controls
key={songLink}
/>
{userId === CurrentUserId ?
<>
<div>
<button id={id} onClick={remove}>remove</button>
</div>
<div>
<button id={id} onClick={editFormCheck}>edit</button>
{editShowForm ?
<EditSongForm props={id} />
: null}
</div>
</>
: null}
</div>
))}
</ol>
</div>
);
};
export default SongList;
The actual form
import { useState } from "react";
import { useDispatch } from "react-redux";
import { updateSong } from "../../store/song";
import { useSelector } from "react-redux";
const EditSongForm = ({ props }) => {
console.log(props)
const dispatch = useDispatch();
const [songName, setSongName] = useState("");
const [songLink, setSongLink] = useState("");
const [errors, setErrors] = useState([]);
const reset = () => {
setSongName("");
setSongLink("");
// setAlbumName('');
// setArtistName('')
};
const user = useSelector((state) => state.session.user);
const userId = user?.id
const handleSubmit = async (e) => {
e.preventDefault();
const updatedSongDetails = {
id: props,
songName,
songLink,
userId
};
let updatedSong = await dispatch(updateSong(updatedSongDetails))
.catch(async (res) => {
const data = await res.json()
if (data && data.errors) setErrors(data.errors)
})
reset();
};
return (
<div className="inputBox">
<h1>Update A Song</h1>
<ul>
{errors.map((error, idx) => <li className='errors' key={idx}>{error}</li>)}
</ul>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={(e) => setSongName(e.target.value)}
value={songName}
placeholder="Song Name"
name="Song Name"
/>
<input
type="text"
onChange={(e) => setSongLink(e.target.value)}
value={songLink}
placeholder="Song Link"
name="Audio File"
/>
<button type="submit">Submit</button>
</form>
</div>
);
};
export default EditSongForm;
Right now when I have a list of songs and click the button for the edit form to appear it applies to the entire list if I have more than one song uploaded. I'm not sure how to make it specific enough so only one form opens at a time.
The solution was to create a component for specific song details and then render that in the .map.
<h1>Song List</h1>
<ol>
{songs.map(({ id, songName, songLink, userId }) => (
<div>
<SpecificSong id={id} songName={songName} songLink={songLink} userId={userId} />
</div>
))}
</ol>
I have a react app (a sort of twitter clone) that uses firestore for storing posts and comments on posts. Each post is rendered as an element from an array using array.map(). Each post has a comment button that opens a form to take in a comment and add it to the post. When I enter a comment and submit it, the topmost post is always the one commented on no matter which post contained the comment button that was clicked(docId for the most recently saved firestore document is always submitted by the comment button instead of the docId corresponding to that instance of the component).
The map of the posts (called "howls"):
<div className="timeline">
{sortedHowls &&
sortedHowls.map((howl) => (
<Howl
key={howl.id}
image={howl.image}
text={howl.text}
time={howl.time}
userId={howl.userId}
docId={howl.id}
comments={howl.comments}
likes={howl.likes}
/>
))}
</div>
The Howl Component looks like this:
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { useFirestoreConnect } from "react-redux-firebase";
import { firestore } from "../../../firebase-store";
// styles
import "./Howl.scss";
// components
import Avatar from "../Avatar/Avatar";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
// functions
import timeCalc from "./timeCalc";
// icons
import { faStar, faComment } from "#fortawesome/free-solid-svg-icons";
const Howl = ({ docId, userId, text, image, time, comments, likes }) => {
useFirestoreConnect([{ collection: "users" }]);
const [commenting, toggleCommenting] = useState(false);
const [newComment, setNewComment] = useState("");
const [users, setUsers] = useState(null);
const [user, setUser] = useState(null);
const getUsers = useSelector((state) => state.firestore.ordered.users);
useEffect(() => {
if (!users) {
setUsers(getUsers);
} else {
setUser(users.find((doc) => doc.uid === userId));
}
}, [users, user, userId, getUsers]);
const handleLike = () => {
const newLikesTotal = likes + 1;
firestore.collection("howls").doc(docId).update({ likes: newLikesTotal });
};
const handleComment = () => {
toggleCommenting(!commenting);
};
const handleChange = (event) => {
setNewComment(event.currentTarget.value);
};
const submitComment = (event) => {
event.preventDefault();
const { id } = event.currentTarget;
console.log(event.currentTarget);
const resetComment = () => {
toggleCommenting(!commenting);
setNewComment("");
};
if (comments) {
firestore
.collection("howls")
.doc(id)
.update({
comments: [...comments, newComment],
})
.then(() => resetComment());
} else {
firestore
.collection("howls")
.doc(id)
.update({ comments: [newComment] })
.then(() => resetComment());
}
};
return (
<div className="howl">
<div className="avatar-container">
<Avatar
photoURL={user ? user.photoURL : ""}
displayName={user ? user.displayName : ""}
/>
</div>
<div className="name-text-img-container">
<p className="userName">
{user && user.displayName} - {timeCalc(Date.now(), time)}
</p>
<p className="howl-text">{text}</p>
<div className="img-container">
{image ? (
<img src={image} alt="user uploaded" className="img" />
) : null}
</div>
<div className="buttons-container">
<form action="" className="buttons">
<label htmlFor="comment-button">
<FontAwesomeIcon icon={faComment} className="image-icon" />
</label>
<input
id="comment-button"
type="checkbox"
onClick={handleComment}
style={{ display: "none" }}
/>
<label htmlFor="like-button">
<FontAwesomeIcon icon={faStar} className="image-icon" />
</label>
<input
id="like-button"
type="checkbox"
onClick={handleLike}
style={{ display: "none" }}
/>
<label htmlFor="like-button">{likes > 0 && likes}</label>
</form>
</div>
{commenting && (
<div className="comment-form">
<form action="submit" onSubmit={submitComment} id={docId}>
<input
type="text"
name="comment-input"
className="comment-input"
maxLength={128}
onChange={handleChange}
value={newComment}
placeholder="Enter comment"
/>
<div className="buttons">
<button type="submit">Submit</button>
<button onClick={() => toggleCommenting(!commenting)}>
Cancel
</button>
</div>
</form>
</div>
)}
<div className="comments">
{comments
? comments.map((comment, index) => {
return (
<p key={index} className="comment">
{comment}
</p>
);
})
: null}
</div>
</div>
</div>
);
};
export default Howl;
How can I get the comment button to specify the correct document to update?
Link to my full repo.
It turns out that the problem is here:
<form action="" className="buttons">
<label htmlFor="comment-button">
<FontAwesomeIcon icon={faComment} className="image-icon" />
</label>
<input
id="comment-button"
type="checkbox"
onClick={handleComment}
style={{ display: "none" }}
/>
<label htmlFor="like-button">
<FontAwesomeIcon icon={faStar} className="image-icon" />
</label>
<input
id="like-button"
type="checkbox"
onClick={handleLike}
style={{ display: "none" }}
/>
<label htmlFor="like-button">{likes > 0 && likes}</label>
</form>
By using a form and inputs as the buttons instead of using <button /> elements it somehow confused react as to which instance of <Howl /> was opening the comment form and therefore which docId was sent to submitComment. Corrected <Howl /> component:
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { useFirestoreConnect } from "react-redux-firebase";
import { firestore } from "../../../firebase-store";
// components
import Avatar from "../Avatar/Avatar";
import CommentInput from "./CommentInput";
import Comment from "./Comment";
import ViewProfile from "../ViewProfile/ViewProfile";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
// functions
import timeCalc from "./timeCalc";
// styles
import "./Howl.scss";
// icons
import { faStar, faComment } from "#fortawesome/free-solid-svg-icons";
const Howl = ({ howl }) => {
useFirestoreConnect([{ collection: "users" }]);
const [commenting, toggleCommenting] = useState(false);
const [newComment, setNewComment] = useState("");
const [users, setUsers] = useState(null);
const [op, setOp] = useState(null);
const [showProfile, setShowProfile] = useState(false);
const { docId, userId, text, likes, comments, time, image } = howl;
const getUsers = useSelector((state) => state.firestore.ordered.users);
const currentUser = useSelector((state) => state.firebase.auth);
// establish user that posted this howl (op = original poster)
useEffect(() => {
users ? setOp(users.find((doc) => doc.uid === userId)) : setUsers(getUsers);
}, [users, op, userId, getUsers]);
const handleLike = () => {
const index = likes.indexOf(currentUser.uid);
let newLikes = [...likes];
if (index > 0) {
newLikes.splice(index, 1);
} else if (index === 0) {
if (likes.length > 1) {
newLikes.splice(index, 1);
} else {
newLikes = [];
}
} else {
newLikes = [...newLikes, currentUser.uid];
}
firestore.collection("howls").doc(docId).update({ likes: newLikes });
};
const handleChange = (event) => {
setNewComment(event.currentTarget.value);
};
const submitComment = (event) => {
event.preventDefault();
const { id } = event.currentTarget;
const { uid, photoURL } = currentUser;
const resetComment = () => {
toggleCommenting(!commenting);
setNewComment("");
};
firestore
.collection("howls")
.doc(id)
.update({
comments: [
...comments,
{ uid: uid, photoURL: photoURL, text: newComment },
],
})
.then(() => resetComment());
};
return (
<div className="howl">
<div className="avatar-container">
<button className="show-profile" onClick={() => setShowProfile(true)}>
<Avatar
photoURL={op ? op.photoURL : ""}
displayName={op ? op.displayName : ""}
/>
</button>
</div>
<div className="name-text-img-container">
<p className="userName">
{op && op.displayName} - {timeCalc(Date.now(), time)}
</p>
<p className="howl-text">{text}</p>
<div className="img-container">
{image && <img src={image} alt="user uploaded" className="img" />}
</div>
<div className="buttons-container">
<div className="buttons">
<button className="comment-button">
<FontAwesomeIcon
icon={faComment}
className="image-icon"
onClick={() => toggleCommenting(!commenting)}
/>
</button>
<button className="like-button" onClick={handleLike}>
<FontAwesomeIcon
icon={faStar}
className={
currentUser && likes.includes(currentUser.uid)
? "image-icon liked"
: "image-icon"
}
/>
</button>
<p>{likes.length > 0 && likes.length}</p>
</div>
</div>
{commenting && (
<CommentInput
submitComment={submitComment}
docId={docId}
toggleCommenting={toggleCommenting}
commenting={commenting}
handleChange={handleChange}
newComment={newComment}
/>
)}
{showProfile && (
<ViewProfile
user={op}
close={() => setShowProfile(false)}
update={false}
/>
)}
<div className="comments">
{comments &&
comments.map((comment, index) => {
return <Comment key={`comment${index}`} comment={comment} />;
})}
</div>
</div>
</div>
);
};
export default Howl;
This block of code:
const data = {
user_id: userID,
comment: comment
};
const url = `http://localhost:5000/blog/comments${category}/${slug}`;
const headers = { "Access-Control-Allow-Origin": "*" };
axios.post(url, data, headers).then(resp => {
console.log(resp.data.message);
});
}
is giving me this error:
Unhandled Rejection (TypeError): cyclic object value
from what I've gathered so far "cyclic object value" refers to javascript objects that at some point refer to itself hence throws an error. From the code above it's not very obvious how I accomplished such error.
I would appreciated any pointers here.
Attempt to provide a minimal example:
Post.jsx:
import React, { useState, useEffect } from "react";
import { useGlobalContext } from "../context";
import { Link } from "react-router-dom";
import AddComment from "./AddComment";
import axios from "axios";
const Post = props => {
const { userID, trace, setTrace, isAuthenticated } = useGlobalContext();
const [obj, setObj] = useState([]);
const [comments, setComments] = useState([]);
const [comment, setComment] = useState("");
const [showLoginWarning, setShowLoginWarning] = useState(false);
const { firstColumn, secondColumn, slug, category } = props;
const handleComment = e => {
e.preventDefault();
if (!isAuthenticated) {
setShowLoginWarning(true);
} else {
const data = {
user_id: userID,
comment: comment
};
const url = `http://localhost:5000/blog/comments${category}/${slug}`;
const headers = { "Access-Control-Allow-Origin": "*" };
axios
.post(url, data, headers)
.then(resp => {
console.log(resp.data.message);
})
.catch(err => {
console.log(err);
});
}
};
useEffect(() => {
setTrace(category + `/${slug}`);
//console.log(trace);
let obj = firstColumn.filter(post => post.slug === slug);
if (obj.length === 0) {
obj = secondColumn.filter(post => post.slug === slug);
}
setObj(obj);
const headers = { "Access-Control-Allow-Origin": "*" };
const url = `http://localhost:5000/blog/comments${trace}/${slug}`;
axios
.get(url)
.then(resp => {
setComments(resp.data.message);
console.log(resp.data.message);
})
.catch(err => {
console.log(err);
});
}, []);
useEffect(() => {
const timeout = setTimeout(() => {
setShowLoginWarning(false);
}, 7000);
}, [showLoginWarning]);
const img_file = `http://localhost:5000/blog/fetch_image/post/${slug}`;
return (
<div style={{ marginTop: "40px" }} className="container">
<div className="columns">
<div className="column is-four-fifths">
<div className="content">
{obj.map(prop => (
<div key={prop.id}>
<h4 className="title is-4">
<img src={img_file} alt={prop.title} />
<br />
{prop.title}
<small>{prop.timestamp}</small>
</h4>
<br />
<p>{prop.post}</p>
</div>
))}
</div>
</div>
<div className="column">
<br />
<Link to={category} className="button is-link" id="backbtn">
Back
</Link>
</div>
</div>
<section className="section">
<h1 className="title">Section</h1>
<h2 className="subtitle">
A simple container to divide your page into <strong>sections</strong>,
like the one you're currently reading.
</h2>
<AddComment
onComment={handleComment}
showLoginWarning={showLoginWarning}
onChange={setComment}
/>
</section>
</div>
);
};
export default Post;
AddComment.jsx:
import React from "react";
const AddComment = props => {
const { showLoginWarning, onComment, onChange } = props;
return (
<>
{showLoginWarning && (
<div className="notification is-warning">
<strong>You need to be logged in to comment.</strong>
</div>
)}
<section id="textareaSection" className="section">
<form onSubmit={onComment}>
<div className="field">
<div className="control">
<textarea
className="textarea"
cols="2"
rows="2"
placeholder="What's On Your Mind ?"
onChange={onChange}
/>
<button
type="submit"
style={{ float: "right", marginTop: "1em" }}
className="button is-link"
>
Post
</button>
</div>
</div>
</form>
</section>
</>
);
};
export default AddComment;
index.js:
import React from "react";
import ReactDOM from "react-dom";
import "./assets/bulma/css/bulma.css";
import "./assets/stylesheet.css";
import Post from "./Post";
import reportWebVitals from "./reportWebVitals";
import { AppProvider } from "./context";
ReactDOM.render(
<React.StrictMode>
<AppProvider>
<Post />
</AppProvider>
</React.StrictMode>,
document.getElementById("root")
);
reportWebVitals();