Search Box implementation and filter posts? - javascript

I've written the code to filter the post in the useEffect. I can't find a way to implement it. I already have 5 posts in my database. I'm bringing all 5 posts using useSelector but I can't find a way to filter while I am typing in the search bar. If I try to set the filtered value in the setListsPosts2(filteredPostList) when fetching those data using the map I didn't get my all posts.
when I do const [listPosts2, setListPosts2] = useState([posts]); it started continues loop and giving error also as asking me add posts in the lists of useEffect dependencies but when i add it it started continues loop of error
here is my homepage.js file
import { useEffect, useState } from 'react';
import { Col, Container, Form, Row } from 'react-bootstrap';
import { ToastContainer } from 'react-toastify';
import { useDispatch, useSelector } from 'react-redux';
import { listPosts } from '../actions/postActions';
import Banner from '../components/Banner';
import Post from '../components/Post';
import Paginate from '../components/Paginate';
const HomePage = ({ match }) => {
const [keyword, setKeyword] = useState('');
const [listPosts2, setListPosts2] = useState([]);
const pageNumber = match.params.pageNumber || 1;
const dispatch = useDispatch();
const postLists = useSelector((state) => state.postLists);
const { posts, pages, page } = postLists;
const postCreate = useSelector((state) => state.postCreate);
const { success: successCreate } = postCreate;
const postUpdate = useSelector((state) => state.postUpdate);
const { success: successUpdate } = postUpdate;
const postDelete = useSelector((state) => state.postDelete);
const { success: deleteSuccess } = postDelete;
useEffect(() => {
const filteredPostList = posts?.filter((post) =>
post.title.toLowerCase().includes(keyword.toLowerCase())
);
setListPosts2(filteredPostList);
dispatch(listPosts(pageNumber));
}, [dispatch, pageNumber, deleteSuccess, successCreate, successUpdate]);
return (
<div>
<ToastContainer />
<Banner />
<div className="my-3">
<Container id="posts">
<Form className="searchBoxForm">
<Form.Control
type="text"
name="q"
onChange={(e) => setKeyword(e.target.value)}
placeholder="Search Posts..."></Form.Control>
</Form>
<h2 className="mt-3" style={{ letterSpacing: '4px' }}>
POSTS
</h2>
<Row className="mt-3">
<Col>
<Row>
{posts
?.map((post) => (
<Col key={post._id} md={3} sm={4}>
<Post post={post} />
</Col>
))
.sort()
.reverse()}
</Row>
<Paginate pages={pages} page={page} />
</Col>
</Row>
</Container>
</div>
</div>
);
};
export default HomePage;

Related

Uncaught ReferenceError: Cannot access 'ordersToDisplay' before initialization - trying to update value on re-render React

I am trying to set the title of my document to "/{orderNumber}orders" and for that orderNumber value to update every time user clicks the button and different number of orders from filtered array are displayed on the screen.
For context, I am importing a json file, filtering it to display the correct elements I want decided by user input, and I am then calculating the length of that array, of which that integer needs to be stored in orderNumber variable to update document title.
I know I am accessing the correct value of the arrays as I have console logged it, my issue is how to update this state change every re render without this error throwing: (Uncaught ReferenceError: Cannot access 'ordersToDisplay' before initialization)
Code:
import { Col, Row } from "antd";
import { useContext, useEffect, useMemo, useState } from "react";
import Order from "../components/Order";
import { AppContext } from "../context/Context";
import AntButton from "../elements/Button";
import ordersToDisplay from "../orders.json";
const OrdersPage = () => {
const [filteringStatus, setFilteringStatus] = useState("");
const {orderAmount, setOrderAmount} = useContext(AppContext)
const [test, setTest] = useState("")
const setDiplayAcceptedOrders = () => {
setFilteringStatus("accepted");
setTest(ordersToDisplay.length)
};
const setDiplayCompletedOrders = () => {
setFilteringStatus("complete");
setTest(ordersToDisplay.length)
};
const setDiplayInProgressOrders = () => {
setFilteringStatus("inProgress");
setTest(ordersToDisplay.length)
};
const ordersToDisplay = useMemo(() => {
if (filteringStatus) {
return ordersToDisplay.filter((i) => i.orderStatus === filteringStatus);
}
return ordersToDisplay;
}, [filteringStatus]);
console.log("Orders to display: ", ordersToDisplay);
console.log("test value: ", test)
return(
<div className="container">
<Row justify="space-between" align="middle">
<Col span={6}>
<h1>Orders</h1>
</Col>
<Col span={6}>
<AntButton onClick={setDiplayAcceptedOrders} name="Accepted"/>
</Col>
<Col span={6}>
<AntButton onClick={setDiplayInProgressOrders} name="In Progress"/>
</Col>
<Col span={6}>
<AntButton onClick={setDiplayCompletedOrders} name="Complete"/>
</Col>
</Row>
<Row>
<Col span={12}>
<h3>{filteringStatus == "" ? "All Orders" : filteringStatus}</h3>
{ordersToDisplay.map((e) => {
return(
<Order
key={e.id}
productName={e.productName}
dateOrdered={e.dateOrdered}
orderStatus={e.orderStatus}
/>
)
})}
</Col>
</Row>
</div>
)
}
export default OrdersPage;
app
const App = () => {
const [orderAmount, setOrderAmount] = useState("")
const Routes = useRoutes([
{path: "/", element: <HomePage/>},
{path: `/(${orderAmount})orders`, element: <OrdersPage/>}
])
return (
<AppContext.Provider value={{
orderAmount, setOrderAmount
}}>
<div>
{Routes}
</div>
</AppContext.Provider>
);
};
export default App;
You are masking the imported ordersToDisplay with what you are trying to memoize. Rename the memoized version/variable. You need only store in state the current filteringStatus state, the test state seems unnecessary and isn't used from what I see.
To update the orderAmount state in the context, use a useEffect hook with a dependency on the computed/memoized orders value to issue a side-effect to update the orderAmount value.
Example:
import { Col, Row } from "antd";
import { useContext, useEffect, useMemo, useState } from "react";
import Order from "../components/Order";
import { AppContext } from "../context/Context";
import AntButton from "../elements/Button";
import ordersToDisplay from "../orders.json";
const OrdersPage = () => {
const [filteringStatus, setFilteringStatus] = useState("");
const { orderAmount, setOrderAmount } = useContext(AppContext);
const setDiplayAcceptedOrders = () => {
setFilteringStatus("accepted");
};
const setDiplayCompletedOrders = () => {
setFilteringStatus("complete");
};
const setDiplayInProgressOrders = () => {
setFilteringStatus("inProgress");
};
// rename to something else, anything but ordersToDisplay
const orders = useMemo(() => {
if (filteringStatus) {
return ordersToDisplay.filter((i) => i.orderStatus === filteringStatus);
}
return ordersToDisplay;
}, [filteringStatus]);
useEffect(() => {
console.log("Orders to display: ", orders); // <-- output derived value
// update amount when orders array updates
setOrderAmount(orders.length);
}, [orders, setOrderAmount]);
return (
<div className="container">
<Row justify="space-between" align="middle">
<Col span={6}>
<h1>Orders</h1>
</Col>
<Col span={6}>
<AntButton onClick={setDiplayAcceptedOrders} name="Accepted"/>
</Col>
<Col span={6}>
<AntButton onClick={setDiplayInProgressOrders} name="In Progress"/>
</Col>
<Col span={6}>
<AntButton onClick={setDiplayCompletedOrders} name="Complete"/>
</Col>
</Row>
<Row>
<Col span={12}>
<h3>{filteringStatus == "" ? "All Orders" : filteringStatus}</h3>
{orders.map((e) => { // <-- use here
return (
<Order
key={e.id}
productName={e.productName}
dateOrdered={e.dateOrdered}
orderStatus={e.orderStatus}
/>
)
})}
</Col>
</Row>
</div>
);
};
export default OrdersPage;

Strange State / UseEffect Behaviour using NextJS Link

I am experiencing so really strange behaviour in this personal project i am doing. In short it is a recipe website, with buttons at the top that direct to different pages that ultimately query firebase and pull down a query.
Index.js File - Queries Firestore passes props to FoodCard.js to Render a list of all recipes
breakfast.js - Queries Firestore with a filter and passes same props down (with different results) to FoodCard.js
Behaviour
When i click on Breakfast JS, it brings up my list of filtered results correctly, however when i click my Next Link "Welcome to the Family Heirloom" to return to the index the first click doesnt respond, then the second click returns home, but with the filtered breakfast result concatonated with all the original results (effectively causing duplicates)Index on First Render
Breakfast filter successful
index with the now duplicate pancake result
I have messed about wondering if useEffect is not getting triggered which you may see in the code, but that didnt seem to work, so at a loss
Index.js
import { React, useEffect, useState } from 'react'
import { useRouter } from 'next/dist/client/router'
import { useTheme } from '#mui/material/styles'
import { makeStyles } from '#mui/styles'
import Modal from '#mui/material/Modal'
import FoodCard from '../src/ui/FoodCard.js'
import firebase from '../firebase/initFirebase'
import Box from '#mui/material/Box'
import Typography from '#mui/material/Typography'
const useStyles = makeStyles(theme => ({
mainContainer: {
}
}))
const style = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'red',
border: '2px solid #000',
boxShadow: 24,
p: 4,
};
export default function CookBook() {
const router = useRouter();
const { id } = router.query;
const classes = useStyles()
const theme = useTheme()
const [loading, setLoading] = useState(true);
const [recipes, setRecipes] = useState([]);
const [open, setOpen] = useState(false);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
useEffect(() => {
const getRecipesFromFirebase = [];
const subscriber = firebase.firestore()
.collection("recipes")
.onSnapshot((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(querySnapshot)
getRecipesFromFirebase.push({
...doc.data(), //spread operator
key: doc.id, // `id` given to us by Firebase
});
});
setRecipes(getRecipesFromFirebase);
console.log(recipes);
setLoading(false);
});
return () => subscriber();
}, [loading, router.events]); // empty dependencies array => useEffect only called once
if (loading) {
return (
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={style}>
<Typography id="modal-modal-title" variant="h6" component="h2">
Loading Data
</Typography>
</Box>
</Modal>)
}
return (
<FoodCard recipes={recipes} />
)
}
_app.js
import * as React from 'react';
import PropTypes from 'prop-types';
import Head from 'next/head';
import { ThemeProvider } from '#mui/material/styles';
import CssBaseline from '#mui/material/CssBaseline';
import { CacheProvider } from '#emotion/react';
import theme from '../src//ui/theme';
import createEmotionCache from '../src/createEmotionCache';
import Header from '../src/ui/Header';
import Grid from '#mui/material/Grid'
import Typography from '#mui/material/Typography'
import { Link as MUILink } from '#mui/material/'
import NextLink from 'next/link'
// Client-side cache, shared for the whole session of the user in the browser.
const clientSideEmotionCache = createEmotionCache();
export default function MyApp(props) {
const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;
return (
<CacheProvider value={emotionCache}>
<Head>
<meta name="viewport" content="initial-scale=1, width=device-width" />
</Head>
<ThemeProvider theme={theme}>
<Header />
<Grid container justify="center" alignItems="center" direction="column" >
<Grid item>
<NextLink href="/" passHref>
<MUILink underline="none" color="secondary" variant="h1">
Welcome To the Family Heirloom
</MUILink>
</NextLink>
</Grid>
</Grid>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<Component {...pageProps} />
</ThemeProvider>
</CacheProvider>
);
}
MyApp.propTypes = {
Component: PropTypes.elementType.isRequired,
emotionCache: PropTypes.object,
pageProps: PropTypes.object.isRequired,
};
Breakfast.js
import { React, useEffect, useState } from 'react'
import FilterMains from '../../src/ui/FilterMains'
import { useRouter } from 'next/dist/client/router'
import FoodCard from '../../src/ui/FoodCard'
import {
getFirestore, collection, query, where, onSnapshot
} from 'firebase/firestore'
const Breakfast = () => {
const router = useRouter();
const { id } = router.query;
const [breakfastloading, setBreakfastLoading] = useState(true);
const [breakfastRecipe, setBreakfastRecipe] = useState([]);
const db = getFirestore()
const docRef = collection(db, 'recipes')
//Query
const q = query(docRef, where("category", "==", 'Breakfast'))
useEffect(() => {
const getBreakfastFromFirebase = [];
onSnapshot(q, (snapshot) => {
snapshot.docs.forEach((doc) => {
getBreakfastFromFirebase.push({ ...doc.data() })
})
setBreakfastRecipe(getBreakfastFromFirebase)
setBreakfastLoading(false)
console.log(breakfastRecipe)
})
}, [breakfastloading, router.events]);
if (breakfastloading) {
return (
<h2>Loading Data</h2>
)
}
return (
<FoodCard recipes={breakfastRecipe} />
// <FoodCard recipes={recipes} />
)
}
export default Breakfast
FoodCard.js
import React from 'react'
import Card from '#mui/material/Card'
import CardHeader from '#mui/material/CardHeader';
import CardMedia from '#mui/material/CardMedia';
import Grid from '#mui/material/Grid'
import Container from '#mui/material/Container';
import Link from 'next/link'
import CardActionArea from '#mui/material/CardActionArea';
function FoodCard(props) {
return (
<div>
<Container>
< Grid container justify="center" alignItems="center" direction="row" >
<Grid container spacing={2}>
{props.recipes.map((recipe) => (
<Link href={`/recipes/${recipe.key}`} passHref>
<Grid key={recipe.id} item xs={12} md={6}>
<Card elevation={3} sx={{ maxWidth: 400 }}>
<CardActionArea>
<CardHeader
titleTypographyProps={{ fontWeight: "Bold" }}
title={recipe.title}
subheader={recipe.description}
/>
<CardMedia
component="img"
height="194"
image="/assets/comingsoon.jpg"
alt="Mac and Cheese"
/>
</CardActionArea>
</Card>
</Grid>
</Link>
))}
</Grid>
</Grid >
</Container>
</div>
)
}
export default FoodCard
Cracked it, although i can't fully explain why. In my learning i used two different methods for interfacing with firebase. The first method on the index page was what i would call the older way. The second method i used was in all of my other files, specific to interfacing with Version 9 of Firebase.
from this:
const subscriber = firebase.firestore()
.collection("recipes")
.onSnapshot((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(querySnapshot)
getRecipesFromFirebase.push({
...doc.data(), //spread operator
key: doc.id, // `id` given to us by Firebase
});
});
to this:
let getRecipesFromFirebase = [];
getDocs(colRef)
.then((snapshot) => {
let getRecipesFromFirebase = [];
snapshot.docs.forEach((doc) => {
getRecipesFromFirebase.push({ ...doc.data(), key: doc.id })
})
setRecipes(getRecipesFromFirebase);
console.log(recipes);
setLoading(false);
})
.catch(error => {
console.log(error.message)
})
Would love somebody more knowledgable (3 months into React) than myself to tell me as to why though.

onClick in React changes all of the classes states in react

I have creates a aside in react and I want to change their state to active when clicked on each of them, but when I click on any item all of them suddenly get activated. How to fix this?
import React, { useState, useEffect } from "react";
import { Col, Image, Row } from "react-bootstrap";
import "./Company.scss";
// * api
import { getCoin } from "../services/api";
// *spinner
import Loader from "./Loader";
const Company = () => {
const [changeClass, setChangeClass] = useState(false);
const [coins, setCoins] = useState([]);
useEffect(() => {
const fetchAPI = async () => {
const data = await getCoin();
setCoins(data);
};
fetchAPI();
}, []);
const myHandler = () => {
setChangeClass(true);
console.log("trued");
};
const myHandler2 = () => {
console.log();
};
return (
<>
{coins.length ? (
coins.map((coin) => (
<Row
className={
changeClass
? "p-2 border-top d-flex align-items-center company-list-single-active"
: "p-2 border-top d-flex align-items-center company-list-single"
}
onClick={() => {
myHandler();
myHandler2();
}}
key={coin.id}
>
<Col xxl="2" xl="2" lg="2" md="2" sm="2" xs="2">
<Image
src={coin.image}
alt={coin.name}
className="coin-image mx-2"
fluid
/>
</Col>
<Col>
<span>{coin.name}</span>
</Col>
</Row>
))
) : (
<Loader />
)}
</>
);
};
export default Company;
you use a test to display selected coin by the same state and it is toggled between true or false.
I added a state to save the selected coin and test on it on looping.
On the onClick we can directly update the selected item.
you cannot do that "coins.length ?" this will be always true if type of coins is array i added a test like "coins.length > 0 ?"
here the solution
import React, { useState, useEffect } from 'react';
import { Col, Image, Row } from 'react-bootstrap';
import './Company.scss';
// * api
import { getCoin } from '../services/api';
// *spinner
import Loader from './Loader';
const Company = () => {
const [selectedCoin, setSelectedCoin] = useState(null);
const [coins, setCoins] = useState([]);
useEffect(() => {
const fetchAPI = async () => {
const data = await getCoin();
setCoins(data);
};
fetchAPI();
}, []);
return (
<>
{coins.length > 0 ? (
coins.map((coin) => (
<Row
className={
selectedCoin === coin.id
? 'p-2 border-top d-flex align-items-center company-list-single-active'
: 'p-2 border-top d-flex align-items-center company-list-single'
}
onClick={() => setSelectedCoin(coin.id)}
key={coin.id}
>
<Col xxl="2" xl="2" lg="2" md="2" sm="2" xs="2">
<Image src={coin.image} alt={coin.name} className="coin-image mx-2" fluid />
</Col>
<Col>
<span>{coin.name}</span>
</Col>
</Row>
))
) : (
<Loader />
)}
</>
);
};
export default Company;

Converting class component to functional component Props in Functional component not passing properly and Query not being received

Hey I am trying to covert a class component into a functional component and for some reason the props I'm passing into the form component are being passed as object values. The original class component works fine but I am not sure what is causing this issue with props. what it looks like with
what it should look like with class component:
what I currently have with functional component:
function component home page
import React from "react";
// import Jumbotron from "react-bootstrap/Jumbotron";
import Row from "react-bootstrap/Row";
import Card from "../components/Card";
import Form from "../components/Form";
import Col from "react-bootstrap/Col";
import Container from "react-bootstrap/Container";
import Jumbotron from "react-bootstrap/Jumbotron";
import { useState } from "react";
import API from "../utils/API";
import Book from "../components/Book";
import Button from "react-bootstrap/Button";
import { List } from "../components/List";
import Footer from "../components/Footer";
import "./style.css";
export default function Home() {
let [books, setBooks] = useState([]);
let [q, setQ] = useState("");
let [message, setMessage] = useState("Search For A Book to Begin");
// const handleInputChange = (event) => {
// let { name, value } = event.target;
// setQ(([name] = value));
// };
const handleInputChange = (event) => {
setQ(event.target.value)
};
let getBooks = () => {
API.getBooks(q)
.then((res) => setBooks(res.data))
.catch(() => setBooks([]));
setMessage("No New Books Found, Try a Different Query");
};
const handleFormSubmit = (event) => {
event.preventDefault();
getBooks();
};
let handleBookSave = (id) => {
const book = books.find((book) => book.id === id);
API.saveBook({
googleId: book.id,
title: book.volumeInfo.title,
subtitle: book.volumeInfo.subtitle,
link: book.volumeInfo.infoLink,
authors: book.volumeInfo.authors,
description: book.volumeInfo.description,
image: book.volumeInfo.imageLinks.thumbnail,
}).then(() => getBooks());
};
return (
<div>
<Container>
<Row>
<Col md={12}>
<Jumbotron className="rounded-3 mt-4">
<h1 className="text-center ">
<strong>(React) Google Books Search</strong>
</h1>
<h2 className="text-center">
Search for and Save Books of Interest.
</h2>
</Jumbotron>
</Col>
<Col md={12}>
<Card title="Book Search" icon=" fa-book">
<Form
handleInputChange={handleInputChange}
handleFormSubmit={handleFormSubmit}
q={q}
/>
</Card>
</Col>
</Row>
<Row>
<Col md={12}>
<Card title="Results">
{books.length ? (
<List>
{books.map((book) => (
<Book
key={book.id}
title={book.volumeInfo.title}
subtitle={book.volumeInfo.subtitle}
link={book.volumeInfo.infolink}
authors={book.volumeInfo.authors.join(", ")}
description={book.volumeInfo.description}
image={book.volumeInfo.imageLinks.thumbnail}
Btn={() => (
<Button
onClick={() => handleBookSave(book.id)}
variant="primary"
className="ml-2"
>
Save
</Button>
)}
/>
))}
</List>
) : (
<h2 className="text-center">{message}</h2>
)}
</Card>
</Col>
</Row>
<Footer />
</Container>
</div>
);
}
Form component
import React from "react";
function Formmy({q, handleInputChange, handleFormSubmit }) {
return (
<form>
<div className="form-group">
<label htmlFor="Query">
<strong>Book</strong>
</label>
<input
className="form-control"
id="Title"
type="text"
value={q}
placeholder="Ready Player One"
name="q"
onChange={handleInputChange}
required
/>
</div>
<div className="float-end">
<button
onClick={handleFormSubmit}
type="submit"
className="btn btn-lg btn-danger float-right"
>
Search
</button>
</div>
</form>
);
}
export default Formmy;

using useparams and array.find to display detailed data

I am making a small blog application using react js. I have a context api for the user inputs, so that the data can be used globally across components (InputContext.js). Using react router, the user is able to view a list of all blog entries (AllBlogs.js) and view each one of them in detail (BlogDetail.js). What I am trying to achieve is, allow the user to get a detailed view of an individual blog post component from the AllBlogs.js page. All blogs have an "id" property, which is used to query the url and using the array.find method, it is supposed to show a detailed view of the blog with the matching id. The problem is "findBlogs" in BlogDetails that is being passed as a prop to display the detailed individual blog data always only returns the most recent user input value, therefore all blogs show the exact same information. I am unsure as to why this is happening, any guidance towards the right direction is greatly appreciated.
InputContext.js
import React, { useState, createContext, useMemo } from 'react'
//create context
export const InputContext = createContext();
const InputContextProvider = (props) => {
const [blogPost, setBlogPost] = useState({
id: '',
title: '',
author: '',
text: ''
});
//create an array to push all the blogPosts
const [allBlogPosts, setAllBlogPosts] = useState([]);
console.log(allBlogPosts)
//put value inside useMemo so that the component only rerenders when there is change in the value
const value = useMemo(() => ({ blogPost, setBlogPost, allBlogPosts, setAllBlogPosts }), [blogPost, allBlogPosts])
return (
<InputContext.Provider value={value}>
{props.children}
</InputContext.Provider>
)
}
export default InputContextProvider;
WriteBlogPost.js
import React, { useState, useContext, Fragment } from 'react'
import { useHistory } from 'react-router-dom'
import { InputContext } from '../Contexts/InputContext'
import { TextareaAutosize } from '#material-ui/core'
import { v4 as uuidv4 } from 'uuid';
import { Box, TextField, Button, makeStyles } from '#material-ui/core'
const useStyles = makeStyles({
root: {
justifyContent: 'center',
alignItems: 'center',
textAlign: 'center'
}
})
export const WriteBlogPost = () => {
const classes = useStyles();
const [blog, setBlog] = useState({
id: '',
title: '',
author: '',
text: ''
});
const history = useHistory();
const { setBlogPost } = useContext(InputContext);
const { allBlogPosts, setAllBlogPosts } = useContext(InputContext)
const handleBlogPost = () => {
setBlogPost(blog);
setAllBlogPosts([...allBlogPosts, blog]);
history.push("/blogs")
console.log({ blog })
console.log({ allBlogPosts })
}
const handleChange = (e) => {
const value = e.target.value
setBlog({
...blog,
id: uuidv4(),
[e.target.name]: value
})
}
return (
<Fragment>
<Box className={classes.root}>
<div>
<TextField id="standard-basic" onChange={handleChange} value={blog.title} name="title" label="Title" />
</div>
<div>
<TextField id="standard-basic" onChange={handleChange} value={blog.author} name="author" label="Author" />
</div>
<div>
<TextareaAutosize aria-label="minimum height" minRows={20} style={{ width: '70%' }} placeholder="Your blog post"
onChange={handleChange}
value={blog.text}
name="text" />
</div>
<div>
<Button variant="contained" color="primary" onClick={handleBlogPost}>
Submit</Button>
</div>
</Box>
</Fragment>
)
}
AllBlogs.js
import React, { useContext } from 'react'
import { InputContext } from '../Contexts/InputContext'
import { Card, CardContent, Typography } from '#material-ui/core'
import { makeStyles } from '#material-ui/core'
import { Link } from 'react-router-dom'
const useStyles = makeStyles({
root: {
justifyContent: 'center',
alignItems: 'center',
display: 'flex',
textAlign: 'center',
},
text: {
textAlign: 'center'
}
})
export const AllBlogs = () => {
const classes = useStyles();
const { allBlogPosts, blogPost } = useContext(InputContext)
console.log(allBlogPosts)
return (
<div>
<Typography color="textPrimary" variant="h3" className={classes.text}>All blogs</Typography>
{allBlogPosts.map((post, i) =>
<Card variant="outlined" key={i} className={classes.root}>
<CardContent>
<Typography color="textPrimary" variant="h5">
{post.title}
</Typography>
<Typography color="textPrimary" variant="h6">
{post.author}
</Typography>
<Typography color="textPrimary" variant="body2" component="p">
{post.text}
</Typography>
<Link to={`/blogs/${blogPost.id}`}>
Read blog
</Link>
</CardContent>
</Card>
)}
</div>
)
}
BlogDetail.js
import React, { useContext } from 'react'
import { useParams, Route } from 'react-router'
import { SingleBlog } from './SingleBlog';
import { InputContext } from '../Contexts/InputContext';
export const BlogDetail = () => {
const params = useParams();
console.log(params.blogId)
const { allBlogPosts } = useContext(InputContext)
const findBlog = allBlogPosts.find((post) => post.id === params.blogId)
console.log(findBlog)
if (!findBlog) {
return <p>No blogs found.</p>
}
return (
<div>
<h1>Blog details</h1>
<SingleBlog post={findBlog} />
</div>
)
}
Issue
Ah, I see what is happening... had to dig back through your edits to when you included your context code.
In your provider you for some reason store an array of blogs (this part makes sense), but then you also store the last blog that was edited.
const InputContextProvider = (props) => {
const [blogPost, setBlogPost] = useState({
id: '',
title: '',
author: '',
text: ''
});
//create an array to push all the blogPosts
const [allBlogPosts, setAllBlogPosts] = useState([]);
//put value inside useMemo so that the component only rerenders when there is change in the value
const value = useMemo(() => ({
blogPost, // <-- last blog edited
setBlogPost,
allBlogPosts,
setAllBlogPosts
}), [blogPost, allBlogPosts])
return (
<InputContext.Provider value={value}>
{props.children}
</InputContext.Provider>
)
}
export const WriteBlogPost = () => {
...
const [blog, setBlog] = useState({
id: '',
title: '',
author: '',
text: ''
});
...
const { setBlogPost } = useContext(InputContext);
const { allBlogPosts, setAllBlogPosts } = useContext(InputContext)
const handleBlogPost = () => {
setBlogPost(blog); // <-- saves last blog edited/added
setAllBlogPosts([...allBlogPosts, blog]);
history.push("/blogs");
}
const handleChange = (e) => {
const value = e.target.value
setBlog({
...blog,
id: uuidv4(),
[e.target.name]: value
})
}
return (
...
)
}
When you are mapping the blog posts you form incorrect links.
export const AllBlogs = () => {
const classes = useStyles();
const {
allBlogPosts,
blogPost // <-- last blog updated
} = useContext(InputContext);
return (
<div>
...
{allBlogPosts.map((post, i) =>
<Card variant="outlined" key={i} className={classes.root}>
<CardContent>
...
<Link to={`/blogs/${blogPost.id}`}> // <-- link last blog updated id
Read blog
</Link>
</CardContent>
</Card>
)}
</div>
)
}
Solution
Use the current blog post's id when mapping to form the link correctly.
export const AllBlogs = () => {
const classes = useStyles();
const { allBlogPosts } = useContext(InputContext);
return (
<div>
...
{allBlogPosts.map((post, i) =>
<Card variant="outlined" key={post.id} className={classes.root}>
<CardContent>
...
<Link to={`/blogs/${post.id}`}> // <-- link current post id
Read blog
</Link>
</CardContent>
</Card>
)}
</div>
)
}

Categories