Link to single post in react - reactjs

I fetch and map posts from https://jsonplaceholder.typicode.com/posts
Already I have posts on my website but how can I go to single post and have unique information I mean body and title by id of post?
Any idea how can i do this?
function App() {
return (
<Layout>
<Switch>
<Route path='/' exact={true}>
<Posts />
</Route>
<Route path='/favorite-posts'>
<FavoritePosts />
</Route>
<Route path='/single-post:id'>
<SinglePost />
</Route>
<Route>
<Page404 path='*' />
</Route>
</Switch>
</Layout>
)
}
Looks like pages are working but how can I pass title and body from fetch into SinglePage?
const Posts = () => {
const [data, setData] = useState([])
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
fetchData()
}, [])
// useEffect(() => {
// fetchImg()
// }, [])
const fetchData = async () => {
let response = await fetch('https://jsonplaceholder.typicode.com/posts')
await response
.json()
.then((finish) => {
setIsLoading(false)
setData(finish) //.splice(0, 5)
console.log(finish)
})
.catch((error) => {
console.error('Houston, we have a problem.. with fetch')
})
}
//loading animation
if (isLoading) {
return <Loading />
}
return (
<>
<BlogPosts datas={data} />
</>
)
}
export default Posts
const Post = ({ title, body, random, id }) => {
const favoritesCtx = useContext(FavoritesContext)
const itemIsFavorite = favoritesCtx.itemIsFavorite(id)
function toggleFavoriteStatusHandler() {
if (itemIsFavorite) {
favoritesCtx.removeFavorite(id)
} else {
favoritesCtx.addFavorite({
id: id,
title: title,
body: body,
})
}
}
return (
<article className={styles.box}>
<Link to={`/single-post:${id}`}>
<img
className={styles.box__image}
// src={`https://rickandmortyapi.com/api/character/avatar/${random}.jpeg`}
src={`https://rickandmortyapi.com/api/character/avatar/19.jpeg`}
alt='test'
/>
</Link>
<button
className={itemIsFavorite ? styles.box__btn_two : styles.box__btn}
onClick={toggleFavoriteStatusHandler}
>
{itemIsFavorite ? 'Remove from Favorites' : 'Add to Favorites'}
</button>
<h4 className={styles.box__title}>{title}</h4>
</article>
)
}
const SinglePost = ({ title, body, id }) => {
return (
<section>
<p>{id}</p>
<h1>{title}</h1>
<h2>{body}</h2>
<p>hey</p>
</section>
)
}
export default SinglePost

You need to make few adjustments for that. If you want to have a dedicated page for each post, then SinglePost will act as a page.
You'll have to add a route which accepts the post id as a param and on basis of that fetches the data for that particular post and renders this component. Something like this:
<Switch>
//...
<Route path='/post/:id'>
<SinglePost />
</Route>
//...
</Switch>
Now in SinglePost file, get the id from route param, and make an api call.
import {useParams, useState, useEffect} from 'react'
const SinglePost = (props) => {
//this is where we will store data after getting from api
const [post, setPost] = useState();
// get id from route param using this hook
const id = useParams().id;
// then in useEffect call the api to fetch data for single post
useEffect(()=>{
axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`).then(res => {
setPost(res.data);
})
},[])
return (
<section>
<h1>{post?.title}</h1>
<h2>{post?.body}</h2>
</section>
)
}
export default SinglePost;
Hope you get the picture.

Add this to your route
<Route path='/post:id'>
<Post />
</Route>
And add Link to your Post component to redirect
<Link to={"/post"+{item._id}/>

Ok I did it !
import { useLocation } from 'react-router'
import { useEffect, useState } from 'react'
import styles from './SinglePost.module.css'
import Loading from '../ui/Loading'
import Comments from './Comments'
const SinglePost = () => {
const location = useLocation()
const path = location.pathname.split('/')[2]
const [data, setData] = useState([])
const [isLoading, setIsLoading] = useState(true)
const [comm, setComm] = useState([])
useEffect(() => {
fetchData()
}, [])
useEffect(() => {
fetchComment()
}, [])
const fetchData = async () => {
let response = await fetch(
'https://jsonplaceholder.typicode.com/posts/' + path
)
await response.json().then((finish) => {
setIsLoading(false)
setData(finish)
console.log(finish)
})
}
const fetchComment = async () => {
let response = await fetch('https://jsonplaceholder.typicode.com/comments/')
await response.json().then((finish) => {
setIsLoading(false)
setComm(finish.splice(0, 7)) //random
console.log(finish)
})
}
if (isLoading) {
return <Loading />
}
return (
<section className={styles.single}>
<div className={styles.single__text}>
<h1>{data.title}</h1>
<p>{data.body}</p>
<h4>Comments:</h4>
</div>
<div className={styles.comments}>
{comm.map((comme) => {
return (
<Comments
key={comme.id}
name={comme.name}
email={comme.email}
body={comme.body}
/>
)
})}
</div>
</section>
)
}
export default SinglePost

Related

Changing props state using api causes infinite loop

I don't know why, changing the props state inside useEffect causes infinite loop of errors. I used them first locally declaring within the function without using props which was running ok.
EDIT:
Home.js
import Axios from "axios";
import React, { useEffect, useState } from "react";
function Home(props) {
// const [details, setDetails] = useState({});
// const [login, setLogin] = useState(false);
useEffect(() => {
try {
const data = localStorage.getItem("expensesAccDetails");
if (data) {
Axios.post("http://localhost:3001/eachCollectionData", {
collection: data,
}).then((res) => {
if (res.data.err) {
console.log("Error");
} else {
console.log(res.data[0]);
props.setLogin(true);
props.setUserdetails(res.data[0]);
}
});
}
} catch (err) {
console.log(err);
}
}, []);
return props.login ? (
<div>
<div>Welcome {props.setUserdetails.FullName}</div>
</div>
) : (
<div>You need to login first</div>
);
}
export default Home;
App.js
function App() {
const [login, setLogin] = useState(false);
const [userdetails, setUserdetails] = useState({});
return (
<Router>
<Routes>
<Route
path="/Home"
element={
<>
<Home
setLogin={setLogin}
login={login}
setUserdetails={setUserdetails}
userdetails={userdetails}
/>
<Bars login={login} />
</>
}
/>
<Routes>
<Router>
);
Here I initialized the states directly in App.js so I don't have to declare it on every page for the route renders. I just passed them as props to every component.
I suggest to create a componente Home with the post and two sub-component inside:
const Home = () => {
const [userDetails, setUserDetails] = useState({});
const [login, setLogin] = useState(false);
useEffect(() => {
// api call
}, []);
return (
<>
<Welcome login={login} details={userDetails} />
<Bars login={login} details={userDetails} />
</>
);
};
where Welcome is the following:
const Welcome = ({ userdetails, login }) => (
<>
login ? (
<div>
<div>Welcome {userdetails.FullName}</div>
</div>
) : (
<div>You need to login first</div>
);
</>
);
A better solution is to use only one state variable:
const [userDetails, setUserDetails] = useState(null);
and test if userDetails is null as you test login is true.
An alternative if you have to maintain the call as you write before, you can use two state as the follow:
function App() {
const [userdetails, setUserdetails] = useState(null);
return (
<Router>
<Routes>
<Route
path="/Home"
element={
<>
<Home
setUserdetails={setUserdetails}
/>
<Bars login={!!userdetails} />
</>
}
/>
<Routes>
<Router>
);
and on Home component use a local state:
const Home = ({setUserdetails}) => {
const [userDetailsLocal, setUserDetailsLocal] = useState(null);
useEffect(() => {
// api call
// ... on response received:
setUserdetails(res.data[0]);
setUserDetailsLocal(res.data[0]);
// ...
}, []);
userDetailsLocal ? (
<div>
<div>Welcome {userDetailsLocal.FullName}</div>
</div>
) : (
<div>You need to login first</div>
);
};
I advise to follow Max arquitecture for your solution. the problem lies in the Router behavior. React Router is not part of React core, so you must use it outside your react logic.
from documentation of React Router:
When you use component (instead of render or children, below) the router uses React.createElement to create a new React element from the given component. That means if you provide an inline function to the component prop, you would create a new component every render.
https://v5.reactrouter.com/web/api/Route/component
Edit:
ok, you make me write it. A solution could be like:
function App() {
const [login, setLogin] = useState(false);
const [userdetails, setUserdetails] = useState({});
useEffect(() => {
try {
const data = localStorage.getItem("expensesAccDetails");
if (data) {
Axios.post("http://localhost:3001/eachCollectionData", {
collection: data,
}).then((res) => {
if (res.data.err) {
console.log("Error");
} else {
console.log(res.data[0]);
setLogin(true);
setUserdetails(res.data[0]);
}
});
}
} catch (err) {
console.log(err);
}
}, []);
return (
<Router>
<Routes>
<Route
path="/Home"
element={
<>
<Home
login={login}
userdetails={userdetails}
/>
<Bars login={login} />
</>
}
/>
<Routes>
<Router>
);

Send catch error state from child to parent and show message

I have a page with 2 different containers that each of them make a data request but if fails I want replace ALL page with a big error message in the middle of the page, all blank
If User catch any error, should fail everything and not just show the message where is, but replace all UserApps code, User and UserProjects will be replaced by the big blank page with error message in the middle
(this is just an example):
Parent Component - UsersApp.tsx
import React, { FC } from "react"
import User from "../containers/User"
import UserProjects from "../containers/UserProjects"
const UsersApp: FC = () => {
return (
<>
<div>
<h1>Welcome!</h1>
</div>
<hr />
<div>
{/* //this error must be from child <User/> catch error component */}
{error ? (
<ErrorState />
) : (
<User />
<UserProjects />
)}
</div>
</>
)
}
export default UsersApp
Child Component - User.tsx
import React, { FC, useState, useEffect } from "react"
import { getUserData } from "../requests/getUserData"
const User: FC<{}> = () => {
const [error, setError] = useState(false)
const [loading, setLoading] = useState(false)
const [name, setName] = useState("")
const [age, setAge] = useState("")
const [country, setCountry] = useState("")
const getData = async () => {
setLoading(true)
setError(false)
const response = await getUserData()
const data = response.data
return data
}
useEffect(() => {
getData()
.then((data) => {
setName(data.name)
setAge(data.age)
setCountry(data.country)
})
.catch((error) => {
setLoading(false)
setError(true)
})
return
}, [])
return (
<div>
<h1>{name}</h1>
<p>{age}</p>
<p>{country}</p>
</div>
)
}
export default User
ErrorState.tsx
import React, { FC } from 'react'
const ErrorState: FC = (props) => (
<div>
<h1>Something went wrong!!!!! </h1>
</div>
)
export default ErrorState
Your parent needs to hold the error state. Pass the setError as a prop to the children
const UsersApp = () => {
const [error, setError] = useState(false)
return (
<>
<div>
<h1>Welcome!</h1>
</div>
<hr />
<div>
{error && <ErrorState/>}
{!error && (
<User setError={setError} />
<UserProjects setError={setError} />
)}
</div>
</>
)
}
const User = ({ setError }) => {
//remove set error state from child
}
const UserProjects = ({ setError }) => {
//remove set error state from child
}
You can use Error Boundaries, it will catch all the errors.

Track how many times a user clicks a button and later display it with React and Firebase

I making a fact generator website and I have a generate button on my homepage, when a user is logged in I want to track how many times they have clicked the button and later display it on their homepage. I am very new to React and Firebase so if any additional information is needed please inform me.
My app.js (has firebase functions & generate button)
import React, { useState, useEffect } from "react";
import "./App.css";
import BackGround from "./components/BG";
import FactGen from "./components/FactGen";
import Navbar from "./components/Navbar";
import {
BrowserRouter as Router,
Switch,
Route,
} from "react-router-dom";
import About from "./components/About";
import NoMatchPage from "./components/NoMatchPage";
import "react-loader-spinner/dist/loader/css/react-spinner-loader.css";
import Login from "./components/Login";
import fire from "./components/fire";
import Dashboard from "./components/Dashboard";
const App = () => {
const [user, setUser] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [emailError, setEmailError] = useState("");
const [passwordError, setPasswordError] = useState("");
const [hasAccount, setHasAccount] = useState(false);
const clearInputs = () => {
setEmail("");
setPassword("");
};
const clearErrors = () => {
setEmailError("");
setPasswordError("");
};
const handleLogin = () => {
clearErrors();
fire
.auth()
.signInWithEmailAndPassword(email, password)
.catch((err) => {
switch (err.code) {
case "auth/invalid-email":
case "auth/user-disabled":
case "auth/user-not-found":
setEmailError(err.message);
break;
case "auth/wrong-password":
setPasswordError(err.message);
break;
}
});
};
const handleSignup = () => {
clearErrors();
fire
.auth()
.createUserWithEmailAndPassword(email, password)
.catch((err) => {
switch (err.code) {
case "auth/email-already-in-use":
case "auth/invalid-email":
setEmailError(err.message);
break;
case "auth/weak-password":
setPasswordError(err.message);
break;
}
});
};
const handleLogout = () => {
fire.auth().signOut();
};
const authListener = () => {
fire.auth().onAuthStateChanged((user) => {
if (user) {
clearInputs();
setUser(user);
} else {
setUser("");
}
});
};
useEffect(() => {
authListener();
}, []);
return (
<Router>
<div className="App">
<BackGround />
<Navbar />
<Switch>
{/* <Route path="/Userconnections" component={Userconnections} /> */}
<Route path="/" exact component={Home} />
<Route path="/about" component={About} />
<Route
path="/Auth"
render={(props) => (
<Login
email={email}
setEmail={setEmail}
password={password}
setPassword={setPassword}
handleLogin={handleLogin}
hasAccount={hasAccount}
setHasAccount={setHasAccount}
emailError={emailError}
passwordError={passwordError}
user={user}
handleSignup={handleSignup}
/>
)}
/>
<Route
path="/dashboard"
render={(props) => (
<Dashboard handleLogout={handleLogout} user={user} />
)}
/>
<Route component={NoMatchPage} />
</Switch>
</div>
</Router>
);
};
const Home = () => {
const [refresh, setRefresh] = useState(0);
const refresher = () => {
setRefresh(refresh + 1);
console.log(refresh);
};
return (
<div className="App">
<FactGen key={refresh} />
<center>
<button className="generate-button" onClick={refresher}>
Generate
</button>
</center>
</div>
);
};
export default App;
My dashboard.js (Want it to display the value where it says 100 on line 14 right now)
import React from "react";
import { BrowserRouter as Router, useHistory } from "react-router-dom";
const Dashboard = ({ handleLogout, user }) => {
const history = useHistory();
function homeRedirect() {
history.push("/");
}
return (
<Router>
{user ? null : homeRedirect()}
<div>
<h1 className="welcome-text">Welcome, User</h1>
<h1 className="page-header">You have generated 100 Facts!</h1>
<center>
<button className="logout-button" onClick={handleLogout}>
Logout
</button>
</center>
</div>
</Router>
);
};
export default Dashboard;
Any help would be greatly appreciated!
You can achieve this by creating a state variable on the parent component and passing the callback to the child:
// Parent.js :
const [counter, setCounter] = useState(0)
...
return (
...
<ChildComponent setCounter={setCounter} counter={counter} />
<p> You have clicked the button {counter} times </p>
...
)
Then, since you pass the callback, you can do this in your ChildComponenet
//Child.js
const Child = (props) => {
const { setCounter, counter } = props
return (
...
<button onClick={() => setCounter(counter + 1)}> Counter Button </button>
...
)
}

Redux Action only dispatched on refresh and not after redirect

I used a history.push("/") to redirect to my homepage from another page and the homepage has a ProjectList component that dispatches an action:
App.js
function App(){
return(
<Router>
<NavBar></NavBar>
<Switch>
<Route
path="/createProject"
component={withAuthentication(CreateProjectPage)}
></Route>
<Route
exact
path="/"
component={withAuthentication(HomePage)}
></Route>
</Switch>
</Router>
);
}
export default App
CreateProjectPage
function CreateProject(){
const dispatch = useDispatch();
const history = useHistory();
const submitHandler = (e) => {
...
dispatch(createNewProject({...}));
history.push("/");
}
return (
<div>
...
<Button
onClick={submitHandler}
variant="contained"
color="primary"
>
Submit
</Button>
</div>
)
}
HomePage
export default function HomePage(props){
return (
<div>
<ProjectList level="one"/>
...
</div>
);
}
ProjectList Component:
export default function ProjectList({level}){
console.log(level); //onRedirect, this gets called
const dispatch = useDispatch();
useEffect(() => {
console.log("dispatch returnAllProjects called") //onRedirect, this does not get called
dispatch(returnAllProjects());
}, [dispatch]);
...
return ...
}
only on refresh this returnAllProjects is called.
This results in getting undefined objects.
Any help would be greatly appreciated!
You should add location as dependency if you want the effect to get called after redirect:
import { useLocation } from 'react-router-dom';
export default function ProjectList({level}){
const location = useLocation(); // <--- listen to location
const dispatch = useDispatch();
useEffect(() => {
dispatch(returnAllProjects());
}, [dispatch, location]); //<--- location is added as dependency
...
return ...
}

A Hooks Context give me en error for loop render why?

I'm beginning to learn React Js. I was trying to create a auth with hooks.
But I recived en error:
Unhandled Rejection (Error): Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside
componentWillUpdate or componentDidUpdate. React limits the number of
nested updates to prevent infinite loops.
This is my code I tried to simplify the components, I hope it's clear
export const AuthContext = React.createContext();
export const AuthProvider = ({children}) => {
const [currentUser, setCurrentUser] = useState(null);
useEffect( () => {
//const token = localStorage.getItem( 'token' );
//const userName = localStorage.getItem( 'userName' );
console.log('useEffect Auth Provider');
console.log(currentUser);
}, [] );
return (
<AuthContext.Provider
value={
[currentUser, setCurrentUser]
}
>
{children}
</AuthContext.Provider>
);
}
When I try to login in Login.js :
export const Login = () => {
const [ currentUser, setCurrentUser ] = useContext( AuthContext );
// Login
const handleLogin = (event) => {
event.preventDefault();
const { email, password } = event.target.elements;
console.log(email.value, password.value);
const siteUrl = clientConfig.serverUrl;
const loginData = {
email: email.value,
password: password.value
};
axios.post( `${siteUrl}/api/users/login`, loginData )
.then( res => {
setCurrentUser(res.data);
console.log(res.data);
});
}
if (currentUser) {
return <Redirect to="/" />
}
else {
return (
<form onSubmit={handleLogin}>
<input name="email" type="text" placeholder="E-Mail"></input>
<input name="password" type="password" placeholder="**************"></input>
<button type="submit">Login</button>
</form>
);
}
};
App.js:
function App() {
return (
<AuthProvider>
<Router>
<Switch>
<PrivateRoute exact path="/" component={Home} />
<Route exact path="/login" component={Login} />
<Route path="*" component={NotFound} />
</Switch>
</Router>
</AuthProvider>
);
}
export default App;
// PrivateRoute
import React, { useContext } from "react";
import { Route, Redirect } from "react-router-dom";
import { AuthContext } from "../context/auth";
export const PrivateRoute = ({ component: RouteComponent, ...rest }) => {
const {currentUser} = useContext(AuthContext);
return (
<Route
{...rest}
render={routeProps =>
!!currentUser ? (
<RouteComponent {...routeProps} />
) : (
<Redirect to={"/login"} />
)
}
/>
);
};
Where am I wrong? Thanks to anybody who want to help me.
Marco.Italy
Here's a working version of your code:
https://codesandbox.io/s/focused-dubinsky-yhpcl
The problem was in the way you were accessing your current user on your PrivateRoute. It was coming back as undefined.
const { currentUser } = useContext(AuthContext);
You can't destructure an array like that. So I changed to this:
const [currentUser, setCurrentUser] = useContext(AuthContext);
NOTE: I know you don't need the setCurrentUser on PrivateRoute. But it's just a way to make it work clearly as is. You can also do it like this:
const [currentUser] = useContext(AuthContext); // THIS WORKS WHEN YOU'RE GETTING THE FIRST ARRAY VALUE
PrivateRoute.js
export const PrivateRoute = ({ component: RouteComponent, ...rest }) => {
console.log("Rendering PrivateRoute...");
const [currentUser, setCurrentUser] = useContext(AuthContext); // <-------------
console.log("currentUser: " + currentUser);
return (
<Route
{...rest}
render={routeProps =>
!!currentUser ? (
<RouteComponent {...routeProps} />
) : (
<Redirect to={"/login"} />
)
}
/>
);
};

Resources