ReactJS async axios in Context API - javascript

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;

Related

Is there anything i am doing wrong while mapping or while using props

In src folder, I have the following components,
addNotes.js
import noteContext from '../context/notes/noteContext';
const AddNote = () => {
const context = useContext(noteContext);
const [note, setNote] = useState({title:" ",description:" ",tag:"default "})
const { addNote } = context;
const handleClick = (e) => {
e.preventDefault();
addNote(note.title, note.description,note.tag);
}
const onChange = (e) => {
setNote({...note,[e.target.name]:e.target.value})
}
return (
<div> <div className="container my-3">
<h2>ADD A NOTE</h2>
{/* FORM */}
<form className='my-3'>
<div className="mb-3">
<label htmlFor="exampleInputEmail1" className="form-label">Title</label>
<input type="text" className="form-control" id="title" name='title' onChange={onChange} aria-describedby="emailHelp" />
</div>
<div className="mb-3">
<label htmlFor="description" className="form-label">Description</label>
<input type="text" className="form-control" id="description" name='description' onChange={onChange} />
</div>
<div className="mb-3 form-check">
<input type="checkbox" className="form-check-input" id="exampleCheck1" />
<label className="form-check-label" htmlFor="exampleCheck1">Check me out</label>
</div>
<button type="submit" className="btn btn-primary" onClick={handleClick}>ADD NOTE</button>
</form>
</div></div>
)
}
export default AddNote
Home.js
import Notes from './Notes'
export default function Home() {
return (
<div >
<Notes/>
</div>
)
}
Navbar.js
import { NavLink ,useLocation} from "react-router-dom";
export default function Navbar() {
let location = useLocation();
useEffect(()=>{
// console.log(location.pathname);
},[location])
return (
<>
<nav className="navbar navbar-expand-lg bg-light navbar-light py-3 shadow-sm">
<div className="container">
<NavLink className="navbar-brand fw-bold f-4 fs-2" to="/">iNotebook</NavLink>
<button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span className="navbar-toggler-icon"></span>
</button>
<div className="collapse navbar-collapse" id="navbarSupportedContent">
<ul className="navbar-nav mx-auto mb-2 mb-lg-0 fs-4">
<li className="nav-item">
<NavLink className={`nav-link ${location.pathname === "/"?"active":" "}` }aria-current="page" to="/">Home</NavLink>
</li>
<li className="nav-item">
<NavLink className={`nav-link ${location.pathname === "/about"?"active":" "}` }to="/about">About</NavLink>
</li>
</ul>
<form className="d-flex" role="search">
<input className="form-control me-2" type="search" placeholder="Search" aria-label="Search" />
<button className="btn btn-outline-dark" type="submit">Search</button>
</form>
</div>
</div>
</nav>
</>
)
}
NoteItem.js
import React,{useContext} from 'react'
import noteContext from '../context/notes/noteContext';
const NoteItem = (props) => {
const context = useContext(noteContext);
const {deleteNote} =context;
const {note} = props;
// console.log(note.tittle);
return (
<div className='col-md-3 '>
<div className="card my-3">
<div className="card-body">
<div className="d-flex align-items-center">
<h5 className="card-title">{note.tittle}</h5>
<i className="fa-solid fa-trash-can mx-2" onClick={()=>{deleteNote(note._id)}}></i>
<i className="fa-solid fa-pen-to-square mx-2"></i>
</div>
<p className="card-text">{note.description} </p>
</div>
</div>
</div>
)
}
export default NoteItem
Notes.js
import noteContext from '../context/notes/noteContext';
import AddNote from './AddNote';
import NoteItem from './NoteItem';
const Notes = () => {
const context = useContext(noteContext);
const { notes, getNotes } = context;
useEffect(() => {
getNotes()
}, [])
console.log(notes);
if(notes.length>0){
return (<>
<AddNote />
<div className=" row my-3 mx-3">;
<h3>YOUR NOTE</h3>
{notes.map((note,index) => {
return <NoteItem key={index} note={note} />
})}
</div>
</>
)}else{
return (
<div>Loading....</div>
)
}
}
export default Notes;
Then in src, I have folder named context in context and I have another folder which is notes. There are two more files,
noteContext.js
import { createContext } from "react";
const noteContext = createContext();
export default noteContext;
NoteState.js
import { useState } from "react";
import NoteContext from "./noteContext";
const NoteState = (props) => {
const host = "http://localhost:5000"
const notesInitial = [];
const [notes, setNotes] = useState(notesInitial)
// FETCH A NOTE
const getNotes = async () => {
// API CALL
const response = await fetch(`${host}/api/notes/fetchallnotes`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
"auth-token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNjJiNDRmMjIzNWVhZmNkM2FhODRmMTg1In0sImlhdCI6MTY1NTk4NzgyNH0.LRueOf_bDWJB6NJ5jmN-ZQxStPPUt0ppW2G0s5VcRr4"
}
});
const json = await response.json()
console.log(json);
setNotes(json);
}
// ADD A NOTE
const addNote = async (title, description, tag) => {
// API CALL
const response = await fetch(`${host}/api/notes/addnote`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
"auth-token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNjJiNDRmMjIzNWVhZmNkM2FhODRmMTg1In0sImlhdCI6MTY1NTk4NzgyNH0.LRueOf_bDWJB6NJ5jmN-ZQxStPPUt0ppW2G0s5VcRr4"
},
body: JSON.stringify({ title, description, tag })
});
console.log("Adding a new note");
const note = {
"_id": "62b45b1eff7f",
"user": "62b44f2235eafcd3aa84f185",
"title": title,
"description": description,
"tag": tag,
"date": "2022-06-24T10:55:43.455Z",
"__v": 0
};
setNotes(notes.concat(note))
}
// DELETE A NOTE
// TODO API CALL
const deleteNote = (id) => {
console.log("This is deleting node" + id);
const newNotes = notes.filter((note) => { return note._id !== id })
setNotes(newNotes)
}
// EDIT A NOTE
const editNote = async (id, title, description, tag) => {
// API CALL
const response = await fetch(`${host}/api/notes/updatenote/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
"auth-token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNjJiNDRmMjIzNWVhZmNkM2FhODRmMTg1In0sImlhdCI6MTY1NTk4NzgyNH0.LRueOf_bDWJB6NJ5jmN-ZQxStPPUt0ppW2G0s5VcRr4"
},
body: JSON.stringify({ title, description, tag })
});
// const json = response.json()
// LOGIC TO EDIT IN CLIENT
for (let index = 0; index < notes.length; index++) {
const element = notes[index];
if (element._id === id) {
element.title = title;
element.description = description;
element.tag = tag;
}
}
}
return (
<NoteContext.Provider value={{ notes, addNote, deleteNote, editNote, getNotes }}>
{props.children}
</NoteContext.Provider>
)
}
// console.log(notes);
export default NoteState;
And my App.js looks like below,
import './App.css';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Navbar from './components/Navbar';
import Home from './components/Home';
import About from './components/About';
import NoteState from './context/notes/NoteState';
import Alert from './components/Alert';
function App() {
return (
<>
<NoteState>
<Router>
<Navbar />
{/* <Alert message="this is amazing react course"/> */}
<div className="container">
<Routes>
<Route path="/" exact element={<Home />} />
<Route path="/about" exact element={<About />} />
</Routes>
</div>
</Router>
</NoteState>
</>
);
}
export default App;
Here are two more files which I have not included which is alert.js and About.js as they are of no use.
This is the whole code of all the components that are there with the context API code.
The main problem occurring in noteItem.js is I am getting an array of notes with id and title in the console but when I use it in a component as {note.title} or {note.description}. It is showing undefined.
const Notes = () => {
const context = useContext(noteContext);
const { notes, getNotes } = context;
useEffect(() => {
getNotes()
}, [])
if(notes.length>0){
return (<>
<AddNote />
<div className=" row my-3 mx-3">;
<h3>YOUR NOTE</h3>
{notes.map((note,index) => {
return <NoteItem key={index} note={note} />
})}
</div>
</>
)}else{
return (
<div>Loading....</div>
)
}
}
export default Notes;

How to pass useState amoung the components in react?

I have a register page and Modal component. In register has a useState for visibility of the Modal. I'm passing it as a prop to Modal. When the modal is closed how to change the useState value in the register page.
Register page:
import React, { useState } from 'react'
import {
CCard,
CButton,
CCardBody,
CCardHeader,
CCol,
CForm,
CFormInput,
CFormLabel,
CSpinner,
CRow,
} from '#coreui/react'
import CIcon from '#coreui/icons-react'
import { cilSend } from '#coreui/icons'
import Alert from 'src/components/Alert'
import Modal from 'src/components/Modal'
const FormControl = () => {
const [disabled, setDisabled] = useState(false)
const [visible, setVisible] = useState(false)
const [email, setEmail] = useState('')
const [name, setName] = useState('')
const handleAddMember = async () => {
try {
const data = { email, name }
const _data = await fetch('http://localhost:4000/api/v1/member/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + localStorage.getItem('token'),
},
body: JSON.stringify(data),
})
if (_data.status === 201) {
setVisible(true)
setDisabled(false)
} else if (_data.status === 422) {
setDisabled(false)
} else {
setDisabled(false)
throw new Error()
}
} catch (err) {
setDisabled(false)
}
}
return (
<CRow>
<Modal visible={visible} message="Member added to your community successfully!" />
<CCol xs={6}>
<CCard className="mb-4">
<CCardHeader>
<strong>Add New Member</strong>
</CCardHeader>
<CCardBody>
<p className="text-medium-emphasis small">
Fill in the email address field and name field to add a new member to your community.
</p>
<CForm>
<div className="mb-3">
<CFormLabel>Email address:</CFormLabel>
<CFormInput
type="email"
placeholder="name#example.com"
onChange={(e) => {
setEmail(e.target.value)
}}
/>
</div>
<div className="mb-3">
<CFormLabel>Name:</CFormLabel>
<CFormInput
type="text"
placeholder="Perera's Home"
onChange={(e) => {
setName(e.target.value)
}}
/>
</div>
<div className="mb-3">
<CButton color="primary" disabled={disabled} onClick={() => handleAddMember()}>
{disabled ? (
<CSpinner component="span" className="me-2" size="sm" aria-hidden="true" />
) : (
<CIcon icon={cilSend} className="me-2" />
)}
Submit
</CButton>
</div>
</CForm>
</CCardBody>
</CCard>
</CCol>
</CRow>
)
}
export default FormControl
Modal component:
import React, { useState } from 'react'
import PropTypes from 'prop-types'
import { CButton, CModal, CModalBody, CModalFooter, CModalHeader, CModalTitle } from '#coreui/react'
const Modal = (props) => {
const [visible, setVisible] = useState(props.visible)
return (
<CModal alignment="center" visible={visible} onClose={() => setVisible(false)}>
<CModalHeader>
<CModalTitle>Success!</CModalTitle>
</CModalHeader>
<CModalBody>{props.message}</CModalBody>
<CModalFooter>
<CButton color="primary" onClick={() => setVisible(false)}>
Close
</CButton>
</CModalFooter>
</CModal>
)
}
Modal.propTypes = {
visible: PropTypes.bool,
message: PropTypes.string,
}
export default React.memo(Modal)
You should have just one visible state member, either in the parent component or in the child (Modal), rather than having it in both places.
If you put it in the parent, you can pass it to the child just like any other prop:
return <Modal visible={visible} setVisible={setVisible}>{/*...*/}</Modal>
Modal's code can then call props.setVisible with the appropriate flag.
If you only want Modal to be able to hide itself (not show itself), you might instead pass a wrapper function that calls setVisible(false):
const hide = useCallback(() => setVisible(false), [setVisible]);
// Optional, see below −−−−−−−−−−−−−^^^^^^^^^^
// ...
return <Modal visible={visible} hide={hide}>{/*...*/}</Modal>
...and then Modal's code calls hide() to hide the modal.
(Making setVisible a dependency in the useCallback call is optional; state setter functions are stable; they don't change during the lifetime of the component. Some linters aren't quite smart enough to realize that and may nag you if you don't include it, but most are smarter than that.)
Here's a highly simplified example:
const {useState} = React;
const Example = () => {
const [visible, setVisible] = useState(false);
return <div>
<input type="button" value="Open" disabled={visible} onClick={() => setVisible(true)} />
<Modal visible={visible} setVisible={setVisible} />
</div>;
};
const Modal = (props) => {
if (!props.visible) {
return null;
}
return <div className="modal">
<div>This is the modal</div>
<input type="button" value="Close" onClick={() => props.setVisible(false)} />
</div>;
};
ReactDOM.render(<Example />, document.getElementById("root"));
.modal {
border: 1px solid grey;
padding: 4px;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
Or with destructuring (I generally use destructuring with props, but it didn't look like you were):
const {useState} = React;
const Example = () => {
const [visible, setVisible] = useState(false);
return <div>
<input type="button" value="Open" disabled={visible} onClick={() => setVisible(true)} />
<Modal visible={visible} setVisible={setVisible} />
</div>;
};
const Modal = ({visible, setVisible}) => {
if (!visible) {
return null;
}
return <div className="modal">
<div>This is the modal</div>
<input type="button" value="Close" onClick={() => setVisible(false)} />
</div>;
};
ReactDOM.render(<Example />, document.getElementById("root"));
.modal {
border: 1px solid grey;
padding: 4px;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
you can pass the setVisible as well in the modal component and then use the same setState on both component
<Modal visible={visible} setVisible={setVisible} message="Member added to your community successfully!" />
use this like
props.visible
props.setVisible

TypeError: Cannot read properties of undefined (reading 'preventDefault') React

Hi I am getting TypeError: Cannot read properties of undefined (reading 'preventDefault') on both my fetches. I have a usEffect that is being used to set the pagination. Which is where the problem seems to be but I'm not sure how I can solve this. When I remove the useEffect the search works but the pagination doesn't so I am a bit confused as to why I am getting the error.
Any help is appreciated.
Thanks
import React, {useState, useEffect} from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import { Alert, Col, Row, ListGroup, Container } from 'react-bootstrap';
import { MovieCard } from '../Components/MovieCard';
import { CustomPagination } from '../Components/CustomPagination';
export const Search = () => {
const [page, setPage] = useState(1)
const [numOfPages, setNumOfPages] = useState();
const [query, setQuery] = useState("");
const [movies, setMovies] = useState([]);
const [show, setShow] = useState(true);
// eslint-disable-next-line
const [sayt, setSayt] = useState([]);
const fetchResults = e => {
e.preventDefault();
setQuery(e.target.value);
fetch(`https://api.themoviedb.org/3/search/movie?api_key=${process.env.REACT_APP_TMDB_KEY}&page=${page}&language=en-US&include_adult=false&query=${e.target.value}`
)
.then((res) => res.json())
.then((data) => {
if (!data.errors) {
console.log(data)
setSayt(data.results);
}else{
<Alert variant="danger">Error</Alert>
}
})
}
const fetchSearch = e => {
e.preventDefault();
setQuery(e.target.value);
fetch(`https://api.themoviedb.org/3/search/movie?api_key=${process.env.REACT_APP_TMDB_KEY}&page=${page}&language=en-US&include_adult=false&query=${e.target.value}`
)
.then((res) => res.json())
.then((data) => {
if (!data.errors) {
console.log(data)
setMovies(data.results);
setNumOfPages(data.total_pages);
}else{
<Alert variant="danger">Error</Alert>
}
});
setShow((s) => !s)
}
useEffect (()=>{
fetchSearch();
fetchResults();
// eslint-disable-next-line
}, [page])
const saytShow = e => {
setShow((s) => !s)
}
return (
<div>
<div className="search-container">
<div className="row height d-flex justify-content-center align-items-center">
<div className="col-xs-12 col-sm-12 col-md-8 col-lg-8 my-4">
<form fetchSearch={fetchSearch} value={query}>
<div className="search"> <i className="fa fa-search" onClick={saytShow}></i>
<input
type="text"
className="form-control"
placeholder="Search for a movie..."
onChange={fetchResults}
onClick={saytShow}
/>
{sayt.length > 0 && (
<ListGroup className="sayt" style={{ display: show ? "block" : "none" }}>
{sayt.map(suggestions => (
<ListGroup.Item action type="submit" onClick={fetchSearch} value={suggestions.title}>
{suggestions.title}
</ListGroup.Item>
))}
</ListGroup>
)}
<button type="submit" onClick={fetchSearch} value={query} className="search-button btn btn-primary">Search</button>
</div>
</form>
</div>
</div>
</div>
<Container fluid className="movie-grid">
{movies.length > 0 && (
<Row>
{movies.map(movieresults => (
<Col className="movie-grid" key={movieresults.id}>
<MovieCard movieresults={movieresults}/>
</Col>
))}
</Row>
)}
</Container>
<CustomPagination setPage={setPage} numOfPages={setNumOfPages} />
</div>
)
}
When you call fetchResults fetchSearch in useEffect you haven't event object. Just write correct conditions. Example:
const fetchResults = e => {
e && e.preventDefault() && setQuery(e.target.value);
fetch(`YOUR_URL?&query=${e ? e.target.value : query}`)
.then((res) => res.json())
.then((data) => {
if (!data.errors) {
console.log(data)
setSayt(data.results);
}else{
<Alert variant="danger">Error</Alert>
}
})
}
the same with fetchSearch function
Try change the button type from submit to button.
<button type="button" onClick={fetchSearch} value={query} className="search-button btn btn-primary">Search</button>
This way you don't need to prevent the default function of type submit.

State not updating in different component

I have a searchbar component that I used context to import into another component. The state of the searchbar in its own component works but when I use the context to import it to another component it does not work. I have used other contexts in my project and they have worked but the searchbar state doesn't. I have no idea where to start, or how to go about fixing it. Can someone point me in the right direction?
export const SearchInput = () => {
const [searchInput, setSearchInput] = useState('');
const handleSubmit = (e) => {
e.preventDefault()
}
return (
<div>
<form onSubmit={handleSubmit}>
<input type='text'
className='search-input'
name='search-movies'
value={searchInput}
onChange={(e) => setSearchInput(e.target.value)} />
</form>
</div>
)
}
//Use Context Component
export const SearchContext = React.createContext()
export function SearchProvider({ children }) {
const [searchInput, setSearchInput] = useState('');
const value = {
searchInput
}
return (
<div>
<SearchContext.Provider value={value}>
{children}
</SearchContext.Provider>
</div>
)
}
const Movies = () => {
const { data, loading, isErr } = useFetch([
`https://api.themoviedb.org/3/list/7077601?api_key=${process.env.REACT_APP_API_KEY}&language=en-US`,
`https://api.themoviedb.org/3/list/7078334?api_key=${process.env.REACT_APP_API_KEY}&language=en-US`,
`https://api.themoviedb.org/3/list/7078244?api_key=${process.env.REACT_APP_API_KEY}&language=en-US`
]);
const { watchList, handleClick } = useContext(WatchListContext);
const { searchInput } = useContext(SearchContext)
const [moviePoster, setmoviePoster] = useState(`giphy (1).gif`);
const [movieTitle, setmovieTitle] = useState('');
const [movieDescription, setmovieDescription] = useState('')
const styles = {
backgroundImage: `url(${moviePoster})`
};
SwiperCore.use([Navigation, Pagination, Scrollbar, A11y]);
return (
<div className='movie-container'>
{isErr && <div className="error">{isErr}</div>}
{loading && <Spinner animation="border" variant="secondary" className="spinner" >
<span>Loading...</span>
</Spinner>}
<div className='movie-hero' style={styles}></div>
<div className="contains-descriptions">
<h2 className="hero-movie-title show-movie">{movieTitle}</h2>
<p className="hero-movie-description show-movie">{movieDescription}</p>
</div>
<section className="movies">
<h2 style={{ color: 'white', marginLeft: '20px' }}>Action </h2>
{data && <Swiper
spaceBetween={10}
slidesPerView={6}
pagination={{ clickable: true }}
scrollbar={{ draggable: true }}
onSlideChange={() => console.log('slide change')}
onSwiper={(swiper) => console.log(swiper)}
>
{data && data[0].items.map(movie =>
<SwiperSlide key={movie.id}>
<div className='movie' >
<img onMouseOver={() => {
setmoviePoster(`${"https://image.tmdb.org/t/p/original" + movie.poster_path}`);
setmovieTitle(movie.original_title);
setmovieDescription(movie.overview);
}}
src={'https://image.tmdb.org/t/p/original' + movie.poster_path} width='250' height='300'
alt='Promotional Poster For Movie'
/>
<button className="watchlist-btn"
onClick={() => handleClick(movie.original_title)}>
{watchList.includes(movie.original_title) ?
<i className="fas fa-minus-circle"></i> :
<i className="fas fa-plus-circle"></i>}
</button>
</div>
</SwiperSlide>
)
}
</Swiper>}
</section>
I'm assuming a component tree that looks something like this:
+-- SearchProvider
| +-- SearchInput
| +-- Movies
Your SearchProvider should be providing both the state and the state setter as its value:
export const SearchContext = React.createContext()
export function SearchProvider({ children }) {
const [searchInput, setSearchInput] = useState('');
const value = {
searchInput,
setSearchInput
};
return ...
}
Your SearchInput should no longer be controlling its own local state. That state now has to be shared with the rest of the tree. Instead, it subscribes to the context and updates it directly:
export const SearchInput = () => {
const { searchInput, setSearchInput } = React.useContext(SearchContext);
const handleSubmit = (e) => {
e.preventDefault()
};
return ...
}
Why are you using context and not just useState and props?
I think it would work with something like the following:
export const SearchInput = (props) => {
const handleSubmit = (e) => {
e.preventDefault()
}
return (
<div>
<form onSubmit={handleSubmit}>
<input type='text'
className='search-input'
name='search-movies'
value={props.value}
onChange={(e) => props.onChange(e.target.value)} />
</form>
{props.children}
</div>
)
}
export function SearchCompoent({ children }) {
const [searchInputValue, setSearchInputValue] = useState('');
return (
<SearchInput value={searchInputValue}>
{children}
</SearchInput>
)
}

How to update the comment of my post in React

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>

Categories

Resources