Here I let user to view the details of the event. There will be a button at the end of the event details for them to delete the event. However, how should I go about doing this?
Should I implement the delete function here in the viewing event details file? Or should I create another .jsx file that handles the delete? The delete.jsx file below contains the handleDelete method that will delete the event from the redux store, which then updates the delete to the firestore
VIEWING EVENT DETAILS FILE
import React from "react";
import { connect } from "react-redux";
import { firestoreConnect } from "react-redux-firebase";
import { compose } from "redux";
import { Redirect } from "react-router-dom";
import moment from "moment";
import DeleteEvent from "./DeleteEvent";
const EventDetails = props => {
const { event, auth } = props;
if (!auth.uid) return <Redirect to="/signin" />;
if (event) {
return (
<div className="container section project-details">
<div className="card z-depth-0">
<div className="card-content">
<span className="card-title">Subject: {event.subject}</span>
<p>Venue: {event.venue}</p>
<p>Time: {event.time}</p>
<p>Description: {event.description}</p>
<p>Class Size: {event.size}</p>
</div>
<div className="card-action grey lighten-4 grey-text">
<div>
Tutor: {event.authorFirstName} {event.authorLastName}
</div>
<div>
Time of class created:
{moment(event.createdAt.toDate()).calendar()}
</div>
<DeleteEvent event={event} />
</div>
</div>
</div>
);
} else {
return (
<div className="container centre">
<p>Loading event....</p>
</div>
);
}
};
const mapStateToProps = (state, ownProps) => {
const id = ownProps.match.params.id;
const events = state.firestore.data.events;
const event = events ? events[id] : null;
return {
event: event,
auth: state.firebase.auth
};
};
export default compose(
connect(mapStateToProps),
firestoreConnect([
{
collection: "events"
}
])
)(EventDetails);
DELETE.JSX FILE
import React, { Component } from "react";
import { connect } from "react-redux";
import { Redirect } from "react-router-dom";
import { deleteEvent } from "../../store/actions/eventActions";
const DeleteEvent = ({ event }) => {
const handleDelete = e => {
e.preventDefault();
this.props.deleteEvent(event);
//this.props.history.push("/"); // redirect to homepage after deletion
};
return (
<div>
<button onClick={this.handleDelete} className="btn green z-depth-0">
Delete
</button>
</div>
);
};
const mapDispatchToProps = dispatch => {
return {
deleteEvent: event => dispatch(deleteEvent(event))
};
};
export default connect(null, mapDispatchToProps)(DeleteEvent);
Related
import { useNavigate} from 'react-router-dom';
import React from 'react'
import CustomButton from '../custom-button/custom-button.component';
import './cart-dropdown.styles.scss';
import CartItem from '../cart-item/cart-item.component';
import {connect} from 'react-redux';
import { selectCartItems } from '../../redux/cart/cart.selectors';
import {createStructuredSelector} from 'reselect';
const CartDropDown = ({cartItems }) => {
return (
<div className="cart-dropdown">
<div className="cart-items">
{cartItems.length
? cartItems.map(cartItem =><CartItem key={cartItem.id} item={cartItem} />)
: <span className="empty-message">Your cart is empty</span>
}
<CustomButton onClick{() => window.history.push('/checkout')}>
GO TO CHECKOUT
</CustomButton>
</div>
</div>
)
}
const mapStateToProps = createStructuredSelector({
cartItems: selectCartItems
})
export default useNavigate(connect(mapStateToProps) (CartDropDown));
useNavigate isn't a Higher Order Component, it's a React hook, it must be used within a function component or custom React hook.
Move it into the function body and fix the navigation.
const CartDropDown = ({ cartItems }) => {
const navigate = useNavigate(); // <-- use hook in component
return (
<div className="cart-dropdown">
<div className="cart-items">
{cartItems.length
? cartItems.map(cartItem => (
<CartItem key={cartItem.id} item={cartItem} />
))
: <span className="empty-message">Your cart is empty</span>
}
<CustomButton
onClick{() => navigate('/checkout')} // <-- call navigate in handler
>
GO TO CHECKOUT
</CustomButton>
</div>
</div>
)
}
const mapStateToProps = createStructuredSelector({
cartItems: selectCartItems
})
export default connect(mapStateToProps)(CartDropDown);
A react hook must be inside the component. You get a navigate function from useNavigate(), and then you call that to navigate.
import { useNavigate } from 'react-router-dom'
import React from 'react'
import CustomButton from '../custom-button/custom-button.component'
import './cart-dropdown.styles.scss'
import CartItem from '../cart-item/cart-item.component'
import { connect } from 'react-redux'
import { selectCartItems } from '../../redux/cart/cart.selectors'
import { createStructuredSelector } from 'reselect'
const CartDropDown = ({ cartItems }) => {
const navigate = useNavigate() // <-- hooks must be INSIDE the component
const onCustomButtonClick = (event) => {
//window.history.push('/checkout') // <-- no
navigate('/checkout')
}
return (
<div className="cart-dropdown">
<div className="cart-items">
{
cartItems.length
? cartItems.map(cartItem => <CartItem key={cartItem.id} item={cartItem} />)
: <span className="empty-message" > Your cart is empty</span>
}
<CustomButton onClick={onCustomButtonClick}> GO TO CHECKOUT </CustomButton>
</div>
</div>)
}
const mapStateToProps = createStructuredSelector({
cartItems: selectCartItems
})
//export default useNavigate(connect(mapStateToProps)(CartDropDown))
export default CartDropDown
I´m new to NextJS and React at all so I ask for your forgiveness.
I want to know how to pass an users written text from an input field (inside of Header) into the getStaticProbs function of a specific page via the react context api.
I tried the following source but it doesn`t work - it throws out an error that my way to build leads to an invalid hook call.
Here is my context source:
import React, { createContext, useState } from 'react';
export const SearchContext = createContext();
export const SearchProvider = ({ children }) => {
const [keyword, setKeyword] = useState('');
return (
<SearchContext.Provider
value={{
keyword,
setKeyword,
}}
>
{children}
</SearchContext.Provider>
);
};
to fetch the written string of SearchBar.js:
import React, { useContext, useState } from 'react';
import { useRouter } from 'next/router';
import Image from 'next/image';
import loupe from '../public/images/loupe.png';
import { SearchContext } from '../lib/searchCtx';
const SearchBar = () => {
const search = useContext(SearchContext);
const router = useRouter();
const submitAction = (e) => {
e.preventDefault();
router.push(`/searchResults`);
};
return (
<div className={styles.searchBar}>
<input
type='text'
placeholder='Suche...'
onChange={(e) => search.setKeyword(e.target.value)}
/>
<button className={styles.searchBtn} type='submit' onClick={submitAction}>
<Image src={loupe} alt='' />
</button>
</div>
);
};
export default SearchBar;
and pass it over _app.js:
import Header from '../components/Header';
import Footer from '../components/Footer';
import { SearchProvider } from '../lib/searchCtx';
function MyApp({ Component, pageProps }) {
return (
<>
<SearchProvider>
<Header />
<Component {...pageProps} />
</SearchProvider>
<Footer />
</>
);
}
}
export default MyApp;
to get the value into getStaticProbs of searchResults.js:
import { useEffect, useState, useContext } from 'react';
import { fetchData } from '../lib/utils';
import styles from '../styles/Playlist.module.scss';
import Image from 'next/image';
import { SearchContext } from '../lib/searchCtx';
export default function SearchResults({ videos }) {
console.log(videos);
const sortedVids = videos
.sort((a, b) =>
Number(
new Date(b.snippet.videoPublishedAt) -
Number(new Date(a.snippet.videoPublishedAt))
)
)
return (
<>
<div className={`${styles.playlist_container} ${styles.search}`}>
<h1>Search results</h1>
{sortedVids
.map((vid, id) => {
return (
<div className={styles.clip_container}>
<Image
className={styles.thumbnails}
src={vid.snippet.thumbnails.medium.url}
layout='fill'
objectFit='cover'
alt={vid.snippet.title}
/>
</div>
<div className={styles.details_container}>
<h3>{vid.snippet.title}</h3>
</div>
);
})}
</div>
</>
);
}
export async function getStaticProps() {
const search = useContext(SearchContext);
const { YOUTUBE_KEY } = process.env;
const uploadsURL = `https://youtube.googleapis.com/youtube/v3/search?part=snippet&channelId=UCbqKKcML7P4b4BDhaqdh_DA&maxResults=50&key=${YOUTUBE_KEY}&q=${search.keyword}`;
async function getData() {
const uploadsData = fetchData(uploadsURL);
return {
videos: await uploadsData,
};
}
const { videos } = await getData();
return {
revalidate: 86400,
props: {
videos: videos.items,
},
};
}
Would you help me by 1) telling me the main failure I did and 2) providing me a working source?
How can I achieve it to get the keyword from SearchContext into the uploadsURL (inside of getStaticProbs) or isn`t it possible?
Thanks in advance!!
You can create a dynamic pages under your page folder one called say index.js and one called [slug].js (all under one folder) In the index page you can have your normal search input, when the users submit the query you can do
<a
onClick={() =>
router
.push(`/movies/${search.keyword}`)
.then(() => window.scrollTo(0, 0))}>
search
</a>
and in your [slug].js page you can retrieve that information like so
export async function getServerSideProps(pageContext) {
const pageQuery = pageContext.query.slug;
const apiCall= await fetch(
``https://youtube.googleapis.com/youtube/v3/search?part=snippet&channelId=UCbqKKcML7P4b4BDhaqdh_DA&maxResults=50&key=${YOUTUBE_KEY}&q=${pageQuery}`
);
const results = await apiCall.json();
return {
props: {
data: results,
},
};
}
I don't know if this will work for you but is a solution
I tried to import ResponseForm
but
the error
Failed to compile. ./src/components/projects/ProjectDetails.js
Attempted import error: 'ResponseForm' is not exported from './ResponseForm'.
happens.
ResponseForm component actually exists.
The path seems to be correct.
How should I fix it?
Why is import error occuring?
import {Component} from 'react'
import {connect} from 'react-redux'
import {createProject} from '../../store/actions/projectActions'
import { Redirect } from 'react-router-dom'
const ResponseForm = () => {
state = {
content: ''
}
handleChange = (e) => {
this.setState({
[e.target.id]: e.target.value
})
}
handleSubmit = (e) => {
e.preventDefault()
this.props.createProject(this.state)
}
return (
<div className="container">
<form onSubmit={handleSubmit} className="white">
<h5 className="grey-text text-darken-3">KAITO</h5>
<div className="input-field">
<button className="btn pink lighten-1 z-depth-0">TEST</button>
</div>
</form>
</div>
)
}
export default ResponseForm;
import { connect } from "react-redux";
import { firestoreConnect,useFirestoreConnect } from "react-redux-firebase";
import { compose } from "redux";
import { ResponseForm } from "./ResponseForm";
const ProjectDetails = (props) => {
const { project } = props;
if (project) {
return (
<div className="container section project-details">
<div className="card z-depth-0">
<div className="card-content">
<span className="card-title">{project.title}</span>
<p>{project.content}</p>
</div>
<ResponseForm />
<div className="project-list section"></div>
</div>
</div>
);
} else {
return (
<div className="container center">
<p>Loaging project...</p>
</div>
);
}
};
Because you're using export default, your import statement should look like this:
import ResponseForm from "./ResponseForm";
(no curly braces)
Alternatively, you could change your first file to this:
export const ResponseForm = () => {
and remove the export default line at the end. Then, you could keep your current import syntax.
This is because you are exporting ResponseFrom as default and try to import using {}
which need to like this
import ResponseForm from "./ResponseForm";
instead of
import { ResponseForm } from "./ResponseForm";
in ProjectDetails
Answer
Response form is the default export:
export default ResponseForm;
You should import it like this:
import ResponseForm from "./ResponseForm";
Using non default exports
If you want to avoid making it the default export you can do this:
import {Component} from 'react'
import {connect} from 'react-redux'
import {createProject} from '../../store/actions/projectActions'
import { Redirect } from 'react-router-dom'
export const ResponseForm = () => {
state = {
content: ''
}
handleChange = (e) => {
this.setState({
[e.target.id]: e.target.value
})
}
handleSubmit = (e) => {
e.preventDefault()
this.props.createProject(this.state)
}
return (
<div className="container">
<form onSubmit={handleSubmit} className="white">
<h5 className="grey-text text-darken-3">KAITO</h5>
<div className="input-field">
<button className="btn pink lighten-1 z-depth-0">TEST</button>
</div>
</form>
</div>
)
}
And import it as above:
import { ResponseForm } from "./ResponseForm";
I'm having trouble working with useEffect to fetch comments when using a modal. I have a PostMain component that is displayed inside a modal, as seen below. Inside this, there is a CommentsList child component that fetches comments left under the post from the server. I have created a custom hook to handle this, as seen below. The problem I'm facing is whenever I exit the modal, then reopen it, useEffect is triggered even though its dependencies (pageNumber, postId) haven't changed. A server request similar to the initial one is sent, with the same comments being added to the list, as seen in the screenshots below. Obviously, this is not ideal. So, what am I doing wrong? How do I fix this?
Fetch Comments Custom Hook
import { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchComments } from '../store/comments/actions';
function useFetchComments(pageNumber, commentsPerRequest = 5, postId) {
const { error, hasMoreComments, isLoading, commentList } = useSelector(
({ comments }) => ({
error: comments.error,
hasMoreComments: comments.hasMoreComments,
isLoading: comments.isLoading,
commentList: comments.commentList,
})
);
const currentCommentListLength = commentList.length || 0;
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchComments(pageNumber, commentsPerRequest, currentCommentListLength, postId));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pageNumber, postId]);
return {
error,
hasMoreComments,
isLoading,
commentList,
};
}
export default useFetchComments;
Post Component
import React from 'react';
import { useSelector } from 'react-redux';
import { Image, Modal } from 'semantic-ui-react';
import CommentForm from '../../forms/comment';
import CommentList from '../../shared/comment-list';
function PostMain({ post }) {
const { isLoggedIn } = useSelector(({ auth }) => ({
isLoggedIn: auth.isLoggedIn,
}));
return (
<Modal size="tiny" trigger={<Image src={post.url} />}>
<Modal.Content>
<div>
<Image src={post.url} />
<CommentList postId={post._id} />
{isLoggedIn && (
<CommentForm postId={post._id} />
)}
</div>
</Modal.Content>
</Modal>
);
}
export default PostMain;
Comment List Component
import React, { useState } from 'react';
import { useFetchComments } from '../../../hooks';
function CommentList({ postId }) {
const COMMENTS_PER_REQUEST = 5;
const [pageNumber, setPageNumber] = useState(1);
const { error, isLoading, commentList, hasMoreComments } = useFetchComments(
pageNumber,
COMMENTS_PER_REQUEST,
postId
);
const handleFetchMoreComments = () => {
setPageNumber((previousNumber) => previousNumber + 1);
};
return (
<div>
<div>
{commentList.map((comment) => (
<div key={comment._id}>{comment.body}</div>
))}
{hasMoreComments && (
<p onClick={handleFetchMoreComments}>View More</p>
)}
</div>
{isLoading && <p>Loading...</p>}
{error && <p>{JSON.stringify(error)}</p>}
</div>
);
}
export default CommentList;
First instance of opening modal
Second instance of opening modal
I have a button that adds counters. It works but its a matter of UI structuring. When I click Add a counter, adds individual counters.
What I need is to have the independent counters, perhaps by programmatically the same result instead of onClick the button, having the +/- like <button> {counter} </button> directly.
What I have:
What I need (without clicking the above button):
When I click + or - then
TypeError: this.props.onIncrement is not a function
onClick
src/js/components/Posts.js:33
30 | <div>
31 | <span>{this.props.value}</span>
32 | <button
> 33 | onClick={() => this.props.onIncrement()}>
| ^ 34 | +
35 | </button>
Code:
Counter.js
// ./src/js/components/Counter.js
import React, { Component } from 'react';
class Counter extends Component {
render() {
return (
<div>
<span>{this.props.value}</span>
<button
onClick={() => this.props.onIncrement()}>
+
</button>
<button
onClick={() => this.props.onDecrement()}>
-
</button>
</div>
);
}
}
export default Counter;
Action
// ./src/js/actions/counters.js
export const increment = (id) => {
return {
type: "INCREMENT",
id
};
};
export const decrement = (id) => {
return {
type: "DECREMENT",
id
};
};
export const add_counter = () => {
return {
type: "ADD_COUNTER"
};
};
AddButton.js:
import React from 'react';
import { add_counter } from '../actions/counters';
import { connect } from 'react-redux';
const AddButton = ({dispatch}) => (
<button
onClick={() => {
dispatch(add_counter());
}}>
Add a counter
</button>
);
export default connect()(AddButton);
counterlist.js
// ./src/js/components/CounterList.js
import React from 'react';
import { connect } from 'react-redux';
import { increment, decrement } from '../actions/counters';
import Counter from './Counter';
const CounterList = ({
counters,
onIncrement,
onDecrement
}) => (
<ul>
{counters.map(counter =>
<Counter
key={counter.id}
value={counter.count}
onIncrement={() => onIncrement(counter.id)}
onDecrement={() => onDecrement(counter.id)}
/>
)}
</ul>
);
const mapStateToProps = (state) => {
return {
counters: state
};
};
const mapDispatchToProps = (dispatch) => {
return {
onIncrement: (id) => dispatch(increment(id)),
onDecrement: (id) => dispatch(decrement(id))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(CounterList);
Posts.js
import React, {Component} from 'react';
import logo from '../../logo.svg';
import '../../App.css';
import { connect } from 'react-redux';
import Counter from './Counter'
import AddButton from './AddButton';
class Posts extends Component {
constructor(props) {
super(props);
this.state = {
response: ''
};
}
render() {
return (
<div className="App">
{Array.isArray(this.state.response) &&
this.state.response.map(resIndex => <div>
<AddButton/>
<Counter/>
{onIncrement(counter.id)}>
<h5> { resIndex.title } </h5>
<h5> { resIndex.body } </h5>
</div>
)}
</div>
)
}
}
export default connect()(Posts);
You can do what you want with Redux given what you have above, but you won't have access to your counter_id inside your Posts component to increment/decrement a given counter. You would need to somehow get that information into your Posts component, which is where you wanted to call increment/decrement programatically. This project would need restructured to resolve that issue. I would ditch Redux as it appears you're learning and I would focus on mastering state and props within React without Redux first.
This is how you would do it with Redux if you had access to a counter's ID. You'll pass into your component the increment/decrement functions as props by using the mapDispatchToProps function. You didn't have that in your Posts component which is why it was telling you your function was not defined. You have to have mapDispatchToProps for it to define the function on this.props.
I wasn't sure what you're doing to need to call this.props.onIncrement, so I just put it in componentDidMount for demonstration purposes.
import React, {Component} from 'react';
import { connect } from 'react-redux';
import Counter from './Counter';
import { increment, decrement } from '../actions/counters';
import AddButton from './AddButton';
class Posts extends Component {
constructor(props) {
super(props);
this.state = {
response: ''
};
}
componentDidMount() {
this.props.onIncrement(<your_counter_id>);
}
render() {
return (
<div className="App">
{Array.isArray(this.state.response) &&
this.state.response.map(resIndex => <div>
<AddButton/>
<Counter/>
<h5> { resIndex.title } </h5>
<h5> { resIndex.body } </h5>
</div>
)}
</div>
)
}
}
const mapDispatchToProps = (dispatch) => {
({
onIncrement: (id) => dispatch(increment(id)),
onDecrement: (id) => dispatch(decrement(id))
});
};
export default connect({}, mapDispatchToProps)(Posts);