I have a big issue about updating the comment of a post. The thing is the client don't want it to be public so default is no show. I have this code
import React, { useState, useEffect, Fragment } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { getPost } from '../../actions/post';
// Other parts
import ScrollToTop from '../routing/ScrollToTop';
const CommentEdition = ({ getPost, post: { post, loading }, match }) => {
const [formData, setFormData] = useState({
cstatus: false,
comment: '',
});
useEffect(() => {
getPost(match.params.id);
}, [getPost, match.params.id]);
const { cstatus, comment } = formData;
const onChange = (e) =>
setFormData({ ...formData, [e.target.name]: e.target.value });
return (
<section className='section-size-2 lighter-bg'>
<ScrollToTop />
<div className='container'>
<div className='grid'>
<div className='column-4 '>
<Link
to='/user-comments'
className='white-button'
style={{ marginBottom: 30, display: 'block' }}
>
Back to Comments
</Link>
<h4>Comments management</h4>
<h1 className='animated-text'>Review & Edit</h1>
</div>
<div className='column-8 profile-main-area'>
<Fragment>
<form className='box white shadow text-left'>
<label>Did you become a client already? *</label>
<div className='form-input-select'>
<select
id='cstatus'
name='cstatus'
value={cstatus}
onChange={(e) => onChange(e)}
>
<option value='true'>Yes</option>>
<option value='false'>No</option>
</select>
<i className='fas fa-chevron-down'></i>
</div>
<label>About You</label>
<textarea
name='comment'
placeholder='Tell us about you'
value={comment}
onChange={(e) => onChange(e)}
></textarea>
<button className='button' type='submit'>
Submit
</button>
</form>
</Fragment>
</div>
</div>
</div>
</section>
);
};
CommentEdition.propTypes = {
getPost: PropTypes.func.isRequired,
post: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
post: state.post,
});
export default connect(mapStateToProps, { getPost })(CommentEdition);
It comes from clicking on the specific comment I want to publish, but I don't have any idea how I would put the info in the form. I have getPost(match.params.id) and is showing the right post, but how can I get the that specific comment and populate in the form for update.
The link looks like this http://localhost:3000/review-commment/5e806b4d6de9c747939a1696/5e9f4ff01c70d30300c42feb
Thanks for your help, if this is too complicated and need more details please let me know.
I'm attaching an image so you can see how the two comments are loading in the state.
NEW SCREENSHOT
CODE UPDATED AND NEW SCREENSHOT:
import React, { useState, useEffect, Fragment } from 'react';
import PropTypes from 'prop-types';
import { connect, useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
import { getPost } from '../../actions/post';
// Other parts
import ScrollToTop from '../routing/ScrollToTop';
const CommentEdition = ({ getPost, post: { post, loading }, match }) => {
const [formData, setFormdata] = useState({
cstatus: false,
comment: ''
});
const { comments } = useSelector(state => ({ ...state.post.post }));
const curco =
comments && comments.filter(el => el._id === match.params.commentid);
console.log(curco);
useEffect(() => {
getPost(match.params.id);
}, [getPost, match.params.id]);
const { cstatus, comment } = formData;
const onChange = e =>
setFormdata({ ...formData, [e.target.name]: e.target.value });
return (
<section className="section-size-2 lighter-bg">
<ScrollToTop />
<div className="container">
<div className="grid">
<div className="column-4 ">
<Link
to="/user-comments"
className="white-button"
style={{ marginBottom: 30, display: 'block' }}
>
Back to Comments
</Link>
<h4>Comments management</h4>
<h1 className="animated-text">Review & Edit</h1>
</div>
<div className="column-8 profile-main-area">
<Fragment>
<form className="box white shadow text-left">
<label>Comment Status *</label>
<div className="form-input-select">
<select
id="cstatus"
name="cstatus"
value={cstatus}
onChange={e => onChange(e)}
>
<option value="true">Published</option>>
<option value="false">Draft</option>
</select>
<i className="fas fa-chevron-down"></i>
</div>
<label>Comment</label>
<textarea
name="comment"
placeholder="Comment goes here"
value={comment}
onChange={e => onChange(e)}
></textarea>
<button className="button" type="submit">
Submit
</button>
</form>
</Fragment>
</div>
</div>
</div>
</section>
);
};
CommentEdition.propTypes = {
getPost: PropTypes.func.isRequired,
post: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
post: state.post,
curco: state.curco
});
export default connect(mapStateToProps, { getPost })(CommentEdition);
I wanted to add the code from where these comments are coming, maybe that could help better.
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import Moment from 'react-moment';
import { connect } from 'react-redux';
const CommentItem = ({ auth, post: { _id, title, comments, date } }) => {
return (
<Fragment>
{auth.user.usertype === 'worker' && comments.length > 0 ? (
<div
className="quotee-post comment-list"
style={{
borderBottom: '2px solid #e6e6e6',
paddingBottom: 25,
marginBottom: 25
}}
>
<Fragment>
<div className="title">List of comments for</div>
<h4>{title}</h4>
{comments.map((comment, id) => (
<div key={id} className="caption comments-data">
<div className="first-col">
<h6>{comment.comment}</h6>
<Fragment>
<div className="sub">By {comment.name}</div>
<p>
Date <Moment format="MM/DD/YYYY">{comment.date}</Moment>
</p>
</Fragment>
</div>
<div className="second-col">
{comment.cstatus ? (
<Fragment>
<small>
Comment Status:
<br />
<span style={{ color: '#28a745' }}>
<i className="fas fa-check"></i> published
</span>
</small>
</Fragment>
) : (
<Fragment>
<small>
Comment Status:
<br />
<span style={{ color: '#fe9100' }}>
<i className="fas fa-pause-circle"></i> draft
</span>
</small>
</Fragment>
)}
<br />
<Link
className="red-button"
to={`/review-commment/${_id}/${comment._id}`}
>
Review and Edit
</Link>
</div>
</div>
))}
</Fragment>
</div>
) : null}
</Fragment>
);
};
CommentItem.propTypes = {
post: PropTypes.object.isRequired,
auth: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth
});
export default connect(mapStateToProps, {})(CommentItem);
Screenshot showing the console.
You can get the comment a list of object from store using useSelector
You can add the following code to get a list of comment object and start filtering with id to get the specific comment
const comment = useSelector(state => state.post.comments)
// I can see that state are not parent of comments, you might want to add the parent in above like
// useSelector(state => state.posts.comments)
// In this case, posts is the parent of comments
const filteredComment = comment && comment.filter(el => el._id === match.params.id)
// filteredComment.comment should be the comment you needed
Updated answer:
<textarea
name="comment"
placeholder="Comment goes here"
initialValue = {curco ? curco[0].comment || undefined}
value={comment}
onChange={e => onChange(e)}
></textarea>
I have been a little busy, but here's is the solution to this issue. The actual solution was really cleaner, what I did is just load the info here
import React, { useState, useEffect, Fragment } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { getPost, updateComment } from '../../actions/post';
// Other parts
import ScrollToTop from '../routing/ScrollToTop';
const CommentEdition = ({
getPost,
post: { post, loading },
location: { currentcom },
postId,
commentId,
updateComment,
match
}) => {
const [formData, setFormdata] = useState({
cstatus: false,
comment: '',
user: '',
name: '',
photo: ''
});
useEffect(() => {
getPost(match.params.id);
const newcstatus = currentcom && currentcom.cstatus;
const newcomment = currentcom && currentcom.comment;
const newuser = currentcom && currentcom.user;
const newphoto = currentcom && currentcom.photo;
const newname = currentcom && currentcom.name;
setFormdata({
cstatus: newcstatus,
comment: newcomment,
user: newuser,
photo: newphoto,
name: newname
});
}, [getPost, currentcom, match.params.id]);
const { cstatus, comment, user, photo, name } = formData;
const onChange = e =>
setFormdata({ ...formData, [e.target.name]: e.target.value });
const onSubmit = e => {
e.preventDefault();
postId = match.params.id;
commentId = match.params.commentid;
updateComment(postId, commentId, formData);
};
return (
<section className="section-size-2 lighter-bg">
<ScrollToTop />
<div className="container">
<div className="grid">
<div className="column-4 ">
<Link
to="/user-comments"
className="white-button"
style={{ marginBottom: 30, display: 'block' }}
>
Back to Comments
</Link>
<h4>Comments management</h4>
<h1 className="animated-text">Review & Edit</h1>
</div>
<div className="column-8 profile-main-area">
<Fragment>
<form
onSubmit={e => onSubmit(e)}
className="box white shadow text-left"
>
<label>Comment Status *</label>
<div className="form-input-select">
<select
id="cstatus"
name="cstatus"
value={cstatus}
onChange={e => onChange(e)}
>
<option value="true">Published</option>>
<option value="false">Draft</option>
</select>
<i className="fas fa-chevron-down"></i>
</div>
<label>Comment</label>
<textarea
name="comment"
placeholder="Comment goes here"
value={comment}
onChange={e => onChange(e)}
></textarea>
<label>Author of comment</label>
<input name="name" type="text" value={name} readOnly />
<label>User</label>
<input name="user" type="text" value={user} readOnly />
<label>Photo file</label>
<input name="photo" type="text" value={photo} readOnly />
<button className="button" type="submit">
Submit
</button>
</form>
</Fragment>
</div>
</div>
</div>
</section>
);
};
CommentEdition.propTypes = {
getPost: PropTypes.func.isRequired,
updateComment: PropTypes.func.isRequired,
post: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
post: state.post
});
export default connect(mapStateToProps, { getPost, updateComment })(
CommentEdition
);
What really makes it easier is that from the other component I loaded the props onto this one, then this one it turns to be just a regular form mounting the data from props, but when I save I had to splice that comment.
<Link
className="red-button"
to={{
pathname: `/review-commment/${_id}/${comment._id}`,
currentcom: comment
}}
>
Review and Edit
</Link>
Related
import React, { useContext } from "react";
import { useLocation } from "react-router";
import { Navigate, Outlet } from "react-router-dom";
function ProtectedRoute({ children }) {
const location = useLocation();
const user = sessionStorage.getItem("userDetails");
console.log(location)
return user? (
children
) : (
<Navigate
to="/login"
replace
state={{
from: location,
}}
/>
);
}
export default ProtectedRoute;
The above code is for the protected route
import React, { useContext, useState } from "react";
import "./login.css";
import Image from "../../assests/images/login-image.jpeg";
import Person from "#mui/icons-material/Person";
import RemoveRedEyeSharp from "#mui/icons-material/RemoveRedEyeSharp";
import VisibilityOffSharp from "#mui/icons-material/VisibilityOffSharp";
import ArrowBackSharp from "#mui/icons-material/ArrowBackSharp";
import { UserDetails } from "../../App";
import { motion } from "framer-motion";
import FormError from "../formError/FormError";
import { HashLoader } from "react-spinners";
import baseUrl from "../../config/BaseUrl";
import axios from "axios";
import { useLocation, useNavigate, Link } from "react-router-dom";
function Login() {
const [showPassword, setShowPassword] = useState(false);
const [showError, setShowError] = useState(false);
const [userDetails, setUserDetails] = useContext(UserDetails);
const location = useLocation();
const navigate = useNavigate();
const [userForm, setUserForm] = useState({
username: "",
password: "",
});
const [message, setMessage] = useState("Default");
const [theme, setTheme] = useState("None");
const [loading, setLoading] = useState(false);
const handleShowPassword = () => {
setShowPassword(!showPassword);
};
const handleChange = (e) => {
const { name, value } = e.target;
setUserForm({ ...userForm, [name]: value });
};
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
try {
const response = await axios.post(`${baseUrl}/user/login`, userForm);
sessionStorage.setItem("userDetails", JSON.stringify(response.data.user));
sessionStorage.setItem("token", JSON.stringify(response.data.token));
setUserDetails(response.data.user);
if(location.state?.from){
navigate(location.state.from)
}
else{
navigate("/")
}
setLoading(false);
} catch ({ response }) {
setMessage(response.data.message);
setTheme("loginError");
setShowError(true);
setLoading(false);
}
};
return (
<motion.div
className="loginBackground"
initial={{ opacity: 0 }}
animate={{
opacity: 1,
transition: {
duration: 0.15,
},
}}
exit={{
opacity: 0,
transition: {
duration: 0.15,
},
}}
>
<div className="loginForm">
{loading && (
<div className="loginLoading">
<HashLoader
color="#002db3"
loading={loading}
size={100}
></HashLoader>
</div>
)}
{!loading && (
<form className="loginFormInputs" onSubmit={handleSubmit}>
<div className="loginBack">
<ArrowBackSharp
style={{ fontSize: "1.7rem", cursor: "pointer" }}
onClick={() => navigate("/")}
></ArrowBackSharp>
</div>
<div className="loginFormHeader">
<div className="loginFormTitle">Welcome Back</div>
<div className="loginFormSubHeading">
Please login to your account to dive deep inside
</div>
</div>
<div className="loginFormInput">
<input
type="text"
name="username"
className="loginInput"
placeholder="Email"
required={true}
value={userForm.username}
onChange={handleChange}
/>
<Person></Person>
</div>
<div className="loginFormInput">
<input
type={showPassword ? "text" : "password"}
name="password"
className="loginInput"
spellCheck={false}
placeholder="Password"
required={true}
value={userForm.password}
onChange={handleChange}
/>
{showPassword ? (
<RemoveRedEyeSharp
onClick={handleShowPassword}
style={{ cursor: "pointer" }}
/>
) : (
<VisibilityOffSharp
onClick={handleShowPassword}
style={{ cursor: "pointer" }}
/>
)}
</div>
<div className="loginFormSubmit">
<button className="loginFormButton" type="submit">
Dive Deep
</button>
</div>
{showError && (
<FormError theme={theme} message={message}></FormError>
)}
<div className="loginFormSignUp">
Don't have an account?{" "}
<Link to={"/register"} style={{textDecoration:"none"}}>
<span
style={{
cursor: "pointer",
color: "blue",
fontWeight: "600",
fontSize: "1.1rem",
}}
>
Signup
</span>
</Link>
</div>
</form>
)}
<img src={Image} alt="img" className="loginImage" />
</div>
</motion.div>
);
}
export default Login;
This is the code snippet for the login component where the handle submit function is handling the redirect. Why is the protected route re-rendering twice? I tried removing the hooks and using session storage but still the protected route rendered twice and I can't understand why
in this i have put request the api its being consoled i want to show it on my screen like crud can anyone help me with it i have used axios.put i think i havnt added the code plaese help me.please help me with this i am a beginner in react.
my api link [restapi]
i am posting my code bekow please go through it:
import axios from "axios";
import react, { useEffect, useState } from "react";
import './App.css'
export default function App() {
const [users, setUsers] = useState([]);
const [searchTerm, setsearchTerm] = useState("");
const [title, setTitle] = useState('');
const [body, setBody] = useState('');
const postData =(e)=>{
e.preventDefault();
axios.post("https://reqres.in/api/users?page=1",{
title,
body
} ).then(res=>console.log('posting',res))
}
useEffect(() => {
axios.get("https://reqres.in/api/users?page=1").then((res) => {
setUsers(res.data);
});
}, []);
return (
<div className="App">
<div className="flex">
<input
onChange={(e) => {
setsearchTerm(e.target.value);
} }
type="text"
placeholder="search"
className="form-control"
style={{
width: "80%",
borderRadius: "30px 0 30px",
justifyContent: "center",
marginLeft: "100px",
}} />
<br/>
<br/>
<form>
<label>first_name</label>
<input value={title} onChange={(e)=>setTitle(e.target.value)}/>
<label>email</label>
<input value={body} onChange={(e)=>setBody(e.target.value)}/>
<button onClick={postData}>post</button>
{users?.data?.filter((val) => {
if (searchTerm === "") {
return val;
} else if (val.first_name.toLowerCase().includes(searchTerm.toLowerCase())) {
return val;
}
}).map((datas) => (
<div key={datas.id}>
<p>
<strong>{datas.first_name}</strong>
</p>
<p>{datas.email}</p>
<img key={datas.avatar} src={datas.avatar} />
</div>
))}
</form>
</div>
</div>
);
}
Try the below code. I hope that's how you wanted it to work.
Post request to api - https://reqres.in/api/users?page=1 return data in different form than the api - https://reqres.in/api/users?page=1. So in order to add and show the entered data in the users list, you have to customized the data before adding it to the users useState. Like this:
setUsers([
...users,
{
id: res.data.id,
first_name: res.data.title,
email: res.data.body,
avatar: "https://reqres.in/img/faces/5-image.jpg",
},
]);
Note - I have used a random image while adding the entered data to the users list. As no avatar was being returned from the api.
Full working code:
import axios from "axios";
import react, { useEffect, useState } from "react";
import "./App.css";
export default function App() {
const [users, setUsers] = useState([]);
const [searchTerm, setsearchTerm] = useState("");
const [title, setTitle] = useState("");
const [body, setBody] = useState("");
const postData = (e) => {
e.preventDefault();
axios
.post("https://reqres.in/api/users?page=1", {
title,
body,
})
.then((res) => {
setUsers([
...users,
{
id: res.data.id,
first_name: res.data.title,
email: res.data.body,
avatar: "https://reqres.in/img/faces/5-image.jpg",
},
]);
});
};
useEffect(() => {
axios.get("https://reqres.in/api/users?page=1").then((res) => {
setUsers(res.data.data);
});
}, []);
return (
<div className="App">
<div className="flex">
<input
onChange={(e) => {
setsearchTerm(e.target.value);
}}
type="text"
placeholder="search"
className="form-control"
style={{
width: "80%",
borderRadius: "30px 0 30px",
justifyContent: "center",
marginLeft: "100px",
}}
/>
<br />
<br />
<form>
<label>first_name</label>
<input value={title} onChange={(e) => setTitle(e.target.value)} />
<label>email</label>
<input value={body} onChange={(e) => setBody(e.target.value)} />
<button onClick={postData}>post</button>
{users
.filter((val) => {
if (searchTerm === "") {
return val;
} else if (
val.first_name.toLowerCase().includes(searchTerm.toLowerCase())
) {
return val;
}
})
.map((datas) => (
<div key={datas.id}>
<p>
<strong>{datas.first_name}</strong>
</p>
<p>{datas.email}</p>
<img key={datas.avatar} src={datas.avatar} />
</div>
))}
</form>
</div>
</div>
);
}
I am currently learning React by following a video by Clever Programmer
(https://www.youtube.com/watch?v=pUxrDcITyjg&list=PLvmRwCtZ6YKRBCjKGNEbmOd816fgvptZc&index=17&t=20s)
However, towards the end I am finding an error which is not openly reported in comments or threads.
I have localised it to the following file "Chat.js" which contains the below code.
My question is: What could be tripping the error "Each child in a list should have a unique "key" prop.".
Advice or solution would be great, but if solved, please provide reasoning as I will need to learn why it was not working!
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import "./Chat.css";
import { useStateValue } from "./StateProvider.js";
import { Avatar, IconButton } from "#material-ui/core";
import {
AttachFile,
InsertEmoticon,
Mic,
SearchOutlined,
} from "#material-ui/icons";
import MoreVert from "#material-ui/icons/MoreVert";
import db from "./firebase";
import firebase from 'firebase/compat/app';
function Chat() {
const [input, setInput] = useState("");
const [seed, setSeed] = useState("");
const { roomId } = useParams();
const [roomName, setRoomName] = useState("");
const [messages, setMessages] = useState([]);
const [{ user }, dispatch] = useStateValue();
useEffect(() => {
if (roomId) {
db.collection("rooms")
.doc(roomId)
.onSnapshot((snapshot) => setRoomName(snapshot.data().name));
db.collection('rooms')
.doc(roomId)
.collection("messages")
.orderBy('timestamp', 'asc')
.onSnapshot(snapshot =>
setMessages(snapshot.docs.map(doc => doc.data()))
);
}
}, [roomId]);
useEffect(() => {
setSeed(Math.floor(Math.random() * 5000));
}, [roomId]);
const sendMessage = (e) => {
e.preventDefault();
console.log("You typed: >>>", input);
db.collection('rooms').doc(roomId).collection('messages').add({
message: input,
user: user.displayName,
timestamp: firebase.firestore.FieldValue.serverTimestamp()
})
setInput("");
};
return (
<div className="chat">
<div className="chat__header">
<Avatar src={`https://avatars.dicebear.com/api/human/${seed}.svg`} />
<div className="chat__headerInfo">
<h3>{roomName}</h3>
<p>Last seen...</p>
</div>
<div className="chat__headerRight">
<IconButton>
<SearchOutlined />
</IconButton>
<IconButton>
<AttachFile />
</IconButton>
<IconButton>
<MoreVert />
</IconButton>
</div>
</div>
{/* The div "chat__body" incorporates the entire message structure */}
<div className="chat__body">
{messages.map((message) => (
<p className={`chat__message ${message.name === user.displayName && "chat__receiver"}`}>
<span className="chat__name">{message.name}</span>
{message.message}
<span className="chat__timeStamp">
{new Date(message.timestamp?.toDate()).toUTCString()}
</span>
</p>
))}
</div>
<div className="chat__footer">
<InsertEmoticon />
<form>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Type a message"
type="text"
/>
<button onClick={sendMessage} type="submit">
Send a message
</button>
</form>
<Mic />
</div>
</div>
);
}
export default Chat;
messages.map is returning a list, and each item in it must have a unique key value that identifies it.
Either from an id from the current item
messages.map((message) => (
<p key={message.id} className...
or the current index
messages.map((message, i) => (
<p key={i} className...
following is the AddCourse page
AddCourse.js
import React, { useEffect, useState } from 'react';
import { Button, Form, FormGroup, Label, Input, FormText, Container } from 'reactstrap';
import database from '../services/fire';
import { useSelector, useDispatch } from 'react-redux';
import uuid from 'react-uuid';
import '../App.css';
const AddCourse = () => {
const [courseId, setCourseId] = useState('');
const [courseTitle, setCourseTitle] = useState('');
const [courseDesc, setCourseDesc] = useState('');
const dispatch = useDispatch();
const user = useSelector(state => state.auth.user.uid);
useEffect(() => {
document.title = "Add Courses"
}, [])
const addCourse = () => {
const payload = { id: uuid(), courseId:courseId, courseTitle: courseTitle, courseDesc: courseDesc }
const dbcoursesWrapper = database.ref().child(user).child('courses');
// const dbcoursesWrapper = database.ref(`users/${user}/courses`).push(courseId, courseTitle, setCourseDesc);
return dbcoursesWrapper.child(payload.id).update(payload).then(() => {
setCourseId('');
setCourseTitle('');
setCourseDesc('');
dispatch({ type: "ADD_COURSES", payload });
})
}
return (
<div>
<h1 className="text-center my-3">Fill Course Detail</h1>
<Form onSubmit={(e) => {
e.preventDefault(e.target.value);
addCourse();
}}>
<FormGroup>
<label for="UserId">Course Id</label>
<Input
type="text"
value={courseId}
onChange={(e) => setCourseId(e.target.value)}
placeholder="Enter your Id"
name="userId"
id="UserId"
/>
</FormGroup>
<FormGroup>
<label for="title">Course Title</label>
<Input
type="text"
value={courseTitle}
onChange={(e)=> setCourseTitle(e.target.value)}
placeholder="Enter Course Title"
name="title"
id="title"
/>
</FormGroup>
<label for="description">Course Description</label>
<Input
value={courseDesc}
onChange={(e) => setCourseDesc(e.target.value)}
type="textarea"
placeholder="Enter Course Description"
name="description"
id="description"
style={{ height: 150 }}
/>
<Container className="text-center">
<Button color="success" type='submit'>Add Course</Button>
<Button color="warning ml-3">clear</Button>
</Container>
</Form>
</div>
);
};
export default AddCourse;
courses.js here is the update button when i click on it i want it to open the AddCourse page with the same values of the course i want to update not getting any clue how can i do this
import React from 'react';
import {
Card, CardText, CardBody,
CardTitle, Button, Container
} from 'reactstrap';
import database from '../services/fire';
import { useSelector, useDispatch } from 'react-redux';
import { fetchCourse } from '../actions/courses';
import AddCourse from './AddCourse';
const Course = ({course}) => {
const user = useSelector(state => state.auth.user.uid);
const dispatch = useDispatch();
const removeCourse = (id) => {
console.log(id);
const dbtasksWrapper = database.ref().child(user).child('courses');
dbtasksWrapper.child(id).remove().then(() => {
dispatch({ type: 'DELETE_COURSE', id: id })
dispatch(fetchCourse(user));
})
}
return (
<div>
<Card>
<CardBody className="text-center ">
<CardText className="text-center"><h2>CourseID: {course.courseId}</h2></CardText>
<CardTitle className="font-weight-bold text-center"><h1>{course.courseTitle}</h1></CardTitle>
<CardText className="text-center">{course.courseDesc}.</CardText>
<Container className="text-center">
{/* here is the update button and when onclick its goes to add course page with the course vale need to update** */}
<Button color="warning"onClick={}>Update</Button>
<Button color="danger ml-4" onClick={()=>removeCourse(course.id)}>Delete</Button>
</Container>
</CardBody>
</Card>
</div>
);
};
export default Course;
Sorry, not getting your question properly. You are trying to add a course using AddCourse.js component on submitting the form, then you want to display the course ID, Title and Description. In order to do this, you need the following:
1 - localStorage,
2 - Context API or Redux,
3 - Create a new state on your Context API or redux to store the values and pass it down to children components, in your example courses.js
If I understand correctly you want to switch between viewing a course and editing/updating a course?
One way to achieve this is:
const Course = ({ course }) => {
const user = useSelector((state) => state.auth.user.uid);
const dispatch = useDispatch();
const removeCourse = (id) => {
console.log(id);
const dbtasksWrapper = database.ref().child(user).child('courses');
dbtasksWrapper
.child(id)
.remove()
.then(() => {
dispatch({ type: 'DELETE_COURSE', id });
dispatch(fetchCourse(user));
});
};
// state to switch between updating the course and viewing the course
const [isUpdating, setIsUpdating] = useState(false);
return (
<div>
{isUpdating ? (
{/* pass the course down and a callback to close update component */}
<AddCourse course={course} finishUpdate={() => setIsUpdating(false)} />
) : (
<Card>
<CardBody className="text-center ">
<CardText className="text-center">
<h2>CourseID: {course.courseId}</h2>
</CardText>
<CardTitle className="font-weight-bold text-center">
<h1>{course.courseTitle}</h1>
</CardTitle>
<CardText className="text-center">{course.courseDesc}.</CardText>
<Container className="text-center">
{/* Set isUpdating to true */}
<Button color="warning" onClick={() => setIsUpdating(true)}>
Update
</Button>
<Button color="danger ml-4" onClick={() => removeCourse(course.id)}>
Delete
</Button>
</Container>
</CardBody>
</Card>
)}
</div>
);
};
This will change depending on your setup, if this isn't what you wanted please provide some more details about how you would like this to function.
I get data from a rest-api in context.js. And I assign the data to state. Here is the code from context.js:
export class AppProvider extends Component {
state = {
products: [],
cart: [],
dispatch: action => this.setState(state => reducer(state, action))
}
fetchData = async () => {
const products = await axios.get('http://127.0.0.1:4000/product/all', { headers: { "Access-Control-Allow-Origin": "*" } })
this.setState({
products: products.data
})
}
componentDidMount(){
this.fetchData();
}
render() {
return (
<AppContext.Provider value={this.state}>
{this.props.children}
</AppContext.Provider>
)
}
}
I'm using the data in the state in other components. But when the page is refreshed, i'm not using the data in the state in other components. When the page is refreshed, the state in context.js becomes the initial. how can i solve this problem? Here is the code from other component:
import React, { useState, useContext, useEffect } from 'react';
import { Button, Badge } from 'reactstrap';
import './ProductDetail.css';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faShoppingCart } from '#fortawesome/free-solid-svg-icons';
import { AppContext } from '../../../context/context';
import { toast, Slide } from 'react-toastify';
toast.configure();
const ProductDetail = props => {
const [inputValue, setInputValue] = useState(1);
const preventNegative = e => {
const value = e.target.value.replace(/[^\d]/, '');
if (parseInt(value) !== 0) {
setInputValue(value);
}
}
const addToCart = (e, productID) => {
// eslint-disable-next-line array-callback-return
const product = products.filter(p => {
// eslint-disable-next-line eqeqeq
if (p.id == productID) {
return p
}
})
console.log(product);
dispatch({
type: 'ADD_TO_CART',
payload: [product, inputValue]
})
toast.success("The product has been added to cart successfully !", {
position: toast.POSITION.TOP_CENTER,
autoClose: 2500,
transition: Slide
})
}
const { products, dispatch } = useContext(AppContext)
const filteredProduct = products.filter(p => p.id == props.match.params.id)
return (
<React.Fragment>
<main className="mt-5 pt-4">
<div className="container dark-grey-text mt-5">
<div className="row wow fadeIn">
<div className="col-md-6 mb-4">
<img src={filteredProduct[0].image} className="img-fluid" alt="" />
</div>
<div className="col-md-6 mb-4">
<div className="p-4">
<div className="mb-3">
<Badge color="primary">New</Badge>
<Badge color="success">Best seller</Badge>
<Badge color="danger">{filteredProduct[0].discountRate}% OFF</Badge>
</div>
<h2 className="h2">{filteredProduct[0].title}</h2>
<p className="lead">
<span className="mr-1">
<del>${filteredProduct[0].prevPrice}</del>
</span>
<span>${filteredProduct[0].price}</span>
</p>
<p className="text-muted">
{filteredProduct[0].detail}
</p>
<form className="d-flex justify-content-left">
<input
min="1"
onChange={(e) => preventNegative(e)}
value={inputValue}
type="number"
aria-label="Search"
className="form-control"
style={{ width: '100px' }} />
<Button onClick={(e) => addToCart(e, filteredProduct[0].id)} color="primary">Add to Cart <FontAwesomeIcon icon={faShoppingCart} /> </Button>
</form>
</div>
</div>
</div>
</div>
</main>
</React.Fragment>
)
}
export default ProductDetail;
Your AppProvider component is correct, the only problem you have is because you fetch the results from an API and you do that in componentDidMount which is supposed to be the correct.
However when you refresh the page the set in AppProvider is reset to initial value and as you are already on ProductDetail component, you try to access the product values even before it available from the API request and hence
const filteredProduct = products.filter(p => p.id == props.match.params.id)
will return you can empty array.
The problem occurs because you try to access filteredProduct[0].image and similar other properties.
The solution here is to use a loadingState and render a loader till the data is available
Also do make sure that when the data is available filteredProduct will never be empty
export class AppProvider extends Component {
state = {
isLoading: true,
products: [],
cart: [],
dispatch: action => this.setState(state => reducer(state, action))
}
fetchData = async () => {
const products = await axios.get('http://127.0.0.1:4000/product/all', { headers: { "Access-Control-Allow-Origin": "*" } })
this.setState({
isLoading: false,
products: products.data
})
}
componentDidMount(){
this.fetchData();
}
render() {
return (
<AppContext.Provider value={this.state}>
{this.props.children}
</AppContext.Provider>
)
}
}
and in productDetails
const ProductDetail = props => {
const [inputValue, setInputValue] = useState(1);
const preventNegative = e => {
const value = e.target.value.replace(/[^\d]/, '');
if (parseInt(value) !== 0) {
setInputValue(value);
}
}
const addToCart = (e, productID) => {
// eslint-disable-next-line array-callback-return
const product = products.filter(p => {
// eslint-disable-next-line eqeqeq
if (p.id == productID) {
return p
}
})
console.log(product);
dispatch({
type: 'ADD_TO_CART',
payload: [product, inputValue]
})
toast.success("The product has been added to cart successfully !", {
position: toast.POSITION.TOP_CENTER,
autoClose: 2500,
transition: Slide
})
}
const { products, dispatch, isLoading } = useContext(AppContext)
if(isLoading) return <div>Loading...</div>
const filteredProduct = products.filter(p => p.id == props.match.params.id)
return (
<React.Fragment>
<main className="mt-5 pt-4">
<div className="container dark-grey-text mt-5">
<div className="row wow fadeIn">
<div className="col-md-6 mb-4">
<img src={filteredProduct[0].image} className="img-fluid" alt="" />
</div>
<div className="col-md-6 mb-4">
<div className="p-4">
<div className="mb-3">
<Badge color="primary">New</Badge>
<Badge color="success">Best seller</Badge>
<Badge color="danger">{filteredProduct[0].discountRate}% OFF</Badge>
</div>
<h2 className="h2">{filteredProduct[0].title}</h2>
<p className="lead">
<span className="mr-1">
<del>${filteredProduct[0].prevPrice}</del>
</span>
<span>${filteredProduct[0].price}</span>
</p>
<p className="text-muted">
{filteredProduct[0].detail}
</p>
<form className="d-flex justify-content-left">
<input
min="1"
onChange={(e) => preventNegative(e)}
value={inputValue}
type="number"
aria-label="Search"
className="form-control"
style={{ width: '100px' }} />
<Button onClick={(e) => addToCart(e, filteredProduct[0].id)} color="primary">Add to Cart <FontAwesomeIcon icon={faShoppingCart} /> </Button>
</form>
</div>
</div>
</div>
</div>
</main>
</React.Fragment>
)
}
export default ProductDetail;