React Hooks: Component is not rendering in Nested Routing - reactjs

I am using "react-scripts": "4.0.2" and all my components are React Hooks. My logic involves nested routing but the end result is not rendered.
App.js:
<BrowserRouter>
<div className="App">
<TopNav />
<Home />
</div>
</BrowserRouter>
Home.js:
<Switch>
<Route path="/" component={Questions} />
</Switch>
Questions.js
const displayQuestion = (qid) => {
props.history.push({ pathname: "/question/" + qid });
};
//questions is an array of objects
const questionsBlocks = questions.map((quest, i) => {
return (
<QBlock
key={i + 1}
qno={i + 1}
displayQuestion={displayQuestion.bind(this, quest.qid)}
/>
);
});
return (
<div>
<h1>Questions</h1>
{questionsBlocks}
<Switch>
<Route
path="/question/:id"
render={(props) => <Question {...props} questions={questions} />}
/>
</Switch>
</div>
);
QBlock will only render buttons that will call displayQuestion on being clicked
QBlock.js:
return (
<div className="block" onClick={props.displayQuestion}>
<h1>{props.qno}</h1>
</div>
);
Question.js:
const [question, setQuestion] = useState();
const loadQuestion = () => {
console.log(props);
if (props.match.params.id) {
console.log("load called");
const qid = props.match.params.id;
const index = props.questions.findIndex((quest) => quest.qid == qid);
setQuestion(props.questions[index]);
}
};
// componentDidMount with react hooks
useEffect(() => {
console.log("Mounted");
loadQuestion();
}, []);
// componentDidUpdate with react hooks
useEffect(() => {
console.log("Updated");
loadQuestion();
}, [props.match.params.id]); //Even tried with only props
return (
<div className="Quest">
<div className="question">{question.question}</div>
<div className="options">{question.answerChoices}</div>
</div>
);
Neither of the useEffect of Question.js is not executing still I am getting the following error.

Basically, question needs to be initialized
const [question, setQuestion] = useState(null);
And another thing you need to do is to check the value of question before using it
return (
<div className="Quest">
{question && question.question && <div className="question">{question.question}</div>}
{question && question.answerChoices && <div className="options">{question.answerChoices}</div>}
</div>
);

As Vince said.. I had defined useState like const [question, setQuestion] = useState(); instead of const [question, setQuestion] = useState({});

Related

How Can I Update A React Component From a Separate Sibling Component in a Separate File?

I am trying to have my navbar update to show the user logged in info after they attempt to login and it is successful. Here's the relevant code files:
App.js
class App extends Component {
render(){
return (
<div>
<div className="App">
<header className="App-header">
<NavBar />
<Main />
</header>
</div>
</div>
);
}
}
export default App;
NavBar.js
const NavBar = () => {
const [userInfo, SetUserInfo] = useState();
useEffect(() => {
SetNavBar(null);
}, [])
function SetNavBar(element){
var user = auth.currentUser;
console.log(auth.currentUser);
if (user){
var userInfo =
React.createElement('div', {id : 'userDiv'},
React.createElement('span', {id : 'userDisplayName'}, user.email),
React.createElement('span', {id : 'signoutSpan'},
'(',
React.createElement('button', {id : 'signoutButton', onClick : (e) => SignOutUser(e.target)}, 'Signout'),
')'
)
);
SetUserInfo(userInfo);
}
else if(element != null && element.nodeName === 'A'){
var urlTO = element.href;
if(!urlTO.includes("login") && !urlTO.includes("signup")){
var userLoginSignupElement =
React.createElement('span', {id : 'loginSignupOptions'},
React.createElement(Link, {to : '/login', onClick : (e) => SetNavBar(e.target)}, 'Login'),
'/',
React.createElement(Link, {to : '/signup', onClick : (e) => SetNavBar(e.target)}, 'Signup')
);
}
else{
var userLoginSignupElement = null;
}
SetUserInfo(userLoginSignupElement);
}
else{
var userLoginSignupElement =
React.createElement('span', {id : 'loginSignupOptions'},
React.createElement(Link, {to : '/login', onClick : (e) => SetNavBar(e.target)}, 'Login'),
'/',
React.createElement(Link, {to : '/signup', onClick : (e) => SetNavBar(e.target)}, 'Signup')
);
SetUserInfo(userLoginSignupElement);
}
}
async function SignOutUser(element){
try{
await signOut(auth);
SetNavBar(null)
}
catch(err){
console.log(err);
}
}
return (
<div>
<nav id='navBar'>
<div id='logoTitleDiv'>
<img id='navBarLogo' src={logo} alt='Skeeters logo.'></img>
<Link to='/' onClick={e => SetNavBar(e.target)}>
<h2 id='pageHeader'>DJSkeeterB</h2>
</Link>
</div>
{userInfo}
</nav>
</div>
);
}
export default NavBar;
Main.js
const Main = () => {
return (
<div id='main'>
<Routes>
<Route exact path='/' element={<SongRequests/>}/>
<Route exact path='/upcoming' element={<Upcoming/>}/>
<Route exact path='/signup' element={<Signup/>}/>
<Route exact path='/login' element={<Login/>}/>
</Routes>
</div>
);
}
export default Main;
Login.js
const Login = () => {
const [navigateToHome, SetNavigateToHome] = useState(false);
const [userEmail, SetUserEmail] = useState('');
const [userPassword, SetUserPassword] = useState('');
const userEmailRef = useRef('');
userEmailRef.current = userEmail;
const userPasswordRef = useRef('');
userPasswordRef.current = userPassword;
const LoginToFirebase = async () => {
try {
await signInWithEmailAndPassword(auth, userEmailRef.current, userPasswordRef.current);
console.log(auth.currentUser);
document.getElementById('emailInput').innerHTML = '';
document.getElementById('passwordInput').innerHTML = '';
SetUserEmail('');
SetUserPassword('');
SetNavigateToHome(true);
} catch (err) {
console.error(err);
}
};
if(navigateToHome === true){
return <Navigate to='/'/>;
}
return (
<div id='loginDiv'>
<div>
<h2>Login</h2>
</div>
<div>
<label>Email: </label>
<input id='emailInput' type='email' placeholder='example#gmail.com' onChange={e => SetUserEmail(e.target.value)}/>
</div>
<div>
<label>Password: </label>
<input id='passwordInput' type='password' placeholder='Password' onChange={e => SetUserPassword(e.target.value)}/>
</div>
<button onClick={e => LoginToFirebase()}>Submit</button>
<div>
<span id='alreadySignedUpSpan'>
<Link to='/signup'>Not registered? Signup here</Link>
</span>
</div>
</div>
);
}
export default Login;
Essentially I have my app show the navbar at all times, and depending on page or user authenticated it updates to be different. The component Main is using react-router-dom to link a few pages currently. After the user signs in or signs up on Login.js or Signup.js they are redirected to the main page. They are successfully logged in using firebase authentication. After the redirect they land on the main page but the navbar isn't updating as I don't know how to call for a state change from what's happening in Login.js to affect NavBar.js.
The navbar has no way of knowing this was a successful authentication it seems. I tried treating auth and auth.currentUser as states to see if when they update they could be used in a useEffect(() => {}, [auth]) way to call the SetNavBar function in NavBar.js.
How can I update my component NavBar, at the end of my function LoginToFirebase() function?
By lifting userEmail state up to your App component (and perhaps all the login functionality) and making your Login component accept a prop of onClickLogin.

Detail Image Rendering in React

When I tried to image, image is broken
BASE_URL I want to fetch is composed like this.
{
"renderings": [
{
"_id": "image_file"(string type)
},
...more _id
}
This is a similar topic to the question I posted a few days ago.
But I see the problem seems to arise while implementing the detail page.
// App.tsx
function App() {
return (
<BrowserRouter>
<Route exact path="/">
<Gallery />
</Route>
<Route path="/detail">
<Detail />
</Route>
</BrowserRouter>
);
}
export default App;
// Gallery.tsx
function Gallery() {
const [gallery, setGallery] = useState<any>();
const getGallery = async () => {
const json: GalleriesProps = await (await fetch(BASE_URL)).json();
setGallery(json.renderings);
};
useEffect(() => {
getGallery();
}, []);
return (
<ImgContainer>
<Link to="/detail">
{gallery &&
gallery.map((x: any) => (
<img key={x._id} alt="gallery_logo" src={x._id} />
))}
</Link>
</ImgContainer>
);
}
export default Gallery;
// Detail.tsx
function Detail() {
const [galleryDetail, setGalleryDetail] = useState<any>();
const [clicked, setClicked] = useState(false);
console.log(galleryDetail);
useEffect(() => {
async function fetchGallery() {
const response = await fetch(BASE_URL);
const result: GalleriesProps = await response.json();
setGalleryDetail(result.renderings[0]._id);
}
fetchGallery();
}, []);
const prevPage = () => {
if (clicked) return;
setClicked(true);
};
const nextPage = () => {
if (clicked) return;
setClicked(true);
};
return (
<>
<Container>
<Button onClick={prevPage}>
<FontAwesomeIcon icon={faArrowAltCircleLeft} />
</Button>
<ImageDetail>
{galleryDetail && <img src={galleryDetail} />}
</ImageDetail>
<Button onClick={nextPage}>
<FontAwesomeIcon icon={faArrowAltCircleRight} />
</Button>
</Container>
</>
);
}
export default Detail;
When I click on an image, I go to the detail page, and then I have to implement that image to show,
For example,
If I click the image of the cat.jpeg file, I have to go to the detail page and change the size of cat.jpeg and show the rest as it is,
but I can't quite figure it out.

My event onClick in my map does not work. Very strange behavior

My Onclick on bestmovies map does not work. If I place it on a H1, for example, works. onClick={handleClickMovie}
// imports....
const Movies = () => {
const [popularMovies, setPopularMovies] = useState([])
const [bestMovies, setBestMovies] = useState([])
const [showPopUp, setShowPopUp] = useState(false)
const handleClickMovie = () => {
setShowPopUp(console.log('Clicked'))
}
useEffect(() => {
async function getMovies() {
const responsePopularMovies = await getPopularMovies()
setPopularMovies(responsePopularMovies.results)
const responseBestMovies = await getBestMovies()
setBestMovies(responseBestMovies.results)
}
getMovies()
}, [])
return (
<div>
<Wrapper>
{showPopUp ? <MoviePopUp /> : null}
<h1>Filmes Populares</h1>
<Content>
{popularMovies.map(item => (
<MovieItem movie={item} />
))}
</Content>
<h1>Filmes Bem Avaliados</h1>
<Content>
{bestMovies.map(item => (
<MovieItem movie={item} onClick={handleClickMovie} />
))}
</Content>
</Wrapper>
</div>
)
}
export default Movies
MovieItem.js
import React from 'react'
import { Cover, Score, Title } from './MovieItem.styles'
const MovieItems = ({ movie }) => {
return (
<Cover key={movie.id}>
<img
src={`https://image.tmdb.org/t/p/original${movie.poster_path}`}
alt="capas"
/>
<Score>{movie.vote_average}</Score>
<Title>{movie.title}</Title>
</Cover>
)
}
export default MovieItems
try wrapping in a div
<Content>
{bestMovies.map(item => (
<div onClick={handleClickMovie}>
<MovieItem movie={item} onClick={handleClickMovie} />
</div>
))}
</Content>
As #anthony_718 answered, you are calling onClick on a JSX component. JSX components aren't in the DOM and don't have click events (although they can render HTML elements if they contain them).
If you want, you can also pass the props all the way up to an actual html element the <Cover> renders.
#anthony_718's answer is correct.
The reason it didn't work it's because <MovieItem> doesn't have onClick in his props.
However, to facilitate reusability, you can modify your component like so:
const MovieItems = ({ movie, onClick }) => {
return (
<div onClick={onClick}>
<Cover key={movie.id}>
// ... rest of your stuff
</Cover>
</div>
)
}
export default MovieItems
It's essentially the same solution, but by placing <div onClick> within the component definition, you make it more reusable than the other option.
check this
bestMovies.map((item, i) => { return( <MovieItem movie={item} onClick={handleClickMovie} /> )})

While passing image via useContext from login and image is not displaying

During onSubmit in the Login, I did setLoginPhoto(res.data.photo)and then using useContext() to pass the photo information to Navigation. But apparently the photo information is getting clearing off once login is successful and displays the Home screen. Also loginPhoto.photo, photo alone is undefined, but I can see image details in loginPhoto while hover on it.
On click on Login, I have set various break points and saw the image information is actually passed to UserLoginProvider and then available in Navigation. ( please refer see screenshots)
note: Image file path saved to database using multer, ie, [ var imagePath = req.file.path;] and images are displaying in other screens, like Home, Profile screens.
Step by step:
Press on Login, I could see the loginPhoto in UserLoginProvider has got image ( please refer screenshot )
Now I could see the loginPhoto available in Navigation:
But once the login is successful and Home page displays, the loginPhoto become null and the image is not displaying in navigation. Any advise on how to fix this problem ?
Login.js
import React, { useContext, useEffect, useState } from "react";
import { UserProfileContext, UserLoginContext } from '../context';
const {loginPhoto, setLoginPhoto} = useContext(UserLoginContext);
const [helperText, setHelperText] = useState('');
const [value, setValue] = React.useState('');
const onSubmit = () => {
const fetchData = async () => {
try {
const res = await axios.post('http://localhost:8000/service/login', { email, password });
//setLoginData(res.data.loginData);
console.log("Front End success message:" + res.data.success);
console.log("My Photo Data:" + res.data.photo);
setLoginPhoto(res.data.photo);
if (res.data.success) {
setHelperText("Login successfully");
setValue(res.data.privilege);
localStorage.setItem('Privilege', res.data.privilege);
localStorage.setItem('loginEmail', email);
history.push('/')
window.location.reload(true)
}
else {
const failMessage = res.data.fail;
setHelperText(failMessage);
}
} catch (e) {
console.log(e.response.data);
setHelperText(e.response.data.fail);
}
}
fetchData();
};
UserLoginProvider.js
import { UserProfileContext, UserLoginContext } from '../context';
const UserLoginProvider = ({children}) => {
const [loginPhoto, setLoginPhoto] = useState({ photo: ''});
console.log("Nav Image:"+loginPhoto);
const value = useMemo(() => ({
loginPhoto, setLoginPhoto
}), [loginPhoto]);
return (
<UserLoginContext.Provider value={value}>
{children}
</UserLoginContext.Provider>
)
}
export default UserLoginProvider;
Navigation.js
import { UserProfileContext, UserLoginContext } from '../context';
const { loginPhoto } = useContext(UserLoginContext);
useEffect(() => {
if (loginPhoto.photo) {
const reader = new FileReader();
reader.addEventListener('load', () => {
setImgSrc(reader.result);
localStorage.setItem("imgData", reader.result);
});
reader.readAsDataURL(loginPhoto.photo);
}
}, [loginPhoto.photo])
var loginUserImg = localStorage.getItem('imgData');
console.log(loginUserImg);
<img className="nav_profile" src={loginUserImg}></img>
App.js
var ReactDOM = require("react-dom");
const App = () => {
return (
<BrowserRouter>
<UserLoginProvider>
<UserProfileProvider>
<>
<Navigation />
<Switch>
<ProtectedRoute exact path="/" component={Home} />
<ProtectedRoute path="/profile" component={Profile} />
<ProtectedRoute path="/aboutus" component={Aboutus} />
<ProtectedRoute path="/availability" component={Availability} />
<Route path="/register" component={Register} />
<Route path="/login" component={Login} />
<Route exact path="*" component={ErrorPage} />
</Switch>
</>
</UserProfileProvider>
</UserLoginProvider>
</BrowserRouter>
);
};
ReactDOM.render(
React.createElement(App, null),
document.getElementById("root")
);
export default App;
home.js
import React, { useRef, useEffect, useState } from "react";
import AlertDialog from "../modal/Dialog";
import Axios from "axios";
const Home = () => {
const [phoneTooltip, setPhoneTooltip] = useState({ show: false, position: "absolute" });
const [playerList, setPlayerList] = useState([]);
const [searchTerm, setSearchTerm] = useState("");
const [searchResults, setSearchResults] = useState([]);
const [deleteIcon, setDeleteIcon] = useState({ show: false });
const [deleteDialog, setDeleteDialog] = useState(false);
const [playerId, setPlayerId] = useState("");
const isMounted = useRef(false);
const [isLoading, setIsLoading] = useState(true);
const handleChange = event => {
setSearchTerm(event.target.value);
};
useEffect(() => {
isMounted.current = true;
return () => isMounted.current = false;
}, []);
useEffect(() => {
setTimeout ( () => {
const fetchData = async () => {
try {
const res = await Axios.get('http://localhost:8000/service/players');
if (isMounted.current) {
setPlayerList(res.data.players);
setSearchResults(res.data.players);
const privilege = localStorage.getItem('Privilege');
console.log("What is getting in Front End:" + privilege);
showDeleteIcon(privilege);
setIsLoading(false);
}
} catch (e) {
if (isMounted.current) {
setIsLoading(false);
}
console.log(e);
}
}
fetchData();
}, 1500);
}, []);
useEffect(() => {
const results = playerList.filter(player =>
player.name.toLowerCase().includes(searchTerm) || player.name.toUpperCase().includes(searchTerm) || player.position.toLowerCase().includes(searchTerm)
|| player.position.toUpperCase().includes(searchTerm)
);
setSearchResults(results);
}, [searchTerm, playerList]);
const displayPhoneToolTip = (userId) => e => {
e.preventDefault();
setPhoneTooltip(userId); // show tooltip
setTimeout(() => {
setPhoneTooltip(false); // remove/hide tooltip
}, 4000);
};
const showDeleteIcon = (privilege) => {
if (privilege === "ADMIN") {
setDeleteIcon({ show: true })
} else {
setDeleteIcon({ show: false })
}
}
const deletePlayer = (id) => e => {
setPlayerId(id);
setDeleteDialog(true);
}
const onDelete = id => () => {
try {
Axios.delete('http://localhost:8000/service/player', {
headers: {
'Content-Type': 'application/json'
},
data: {
'id': id
}
});
setDeleteDialog(false);
const restOfPlayerResults = searchResults.filter((result) => result.id !== id)
setSearchResults(restOfPlayerResults);
} catch (e) {
console.log(e);
}
}
return (
<div className="App">
<div className="wrapper">
<div className="playerList_header">
<h2>Players</h2>
<label>
<div className="playerSearch_Home">
<div className="playerSearch_Icon">
<img alt="" src="/images/search-image-player.jpg"></img>
</div>
<input type="text" className="playerSearch_Home_Input" placeholder="Search players..." value={searchTerm} onChange={handleChange} />
</div>
</label>
</div>
<div>
{!searchResults.length && !isLoading && (<div> <p className="noSearchData"> Does not match any results! </p> </div>)}
<div className="playerList_home_page">
{isLoading ? (
<div className="loader">
<div className="bubble"></div>
<div className="bubble"></div>
<div className="bubble"></div>
<div className="bubble"></div>
</div>
) : (
<div className="grid-container">
{
searchResults.map(({ id, photo, position, phonenumber, name }) => (
<div key={id} className="grid-item">
{
deleteIcon.show && (
<span className="deletePlayerButton" onClick={deletePlayer(id)}>
<img className="deletePlayerimg" src="/images/delete.png"></img>
</span>
)}
<div>
<img alt="" className="playerProfilePic_home_tile" key={photo} src={photo}></img>
</div>
<div className="playerProfile_grid_border">
<span className="rec_name_position_data">
<h3 key={name}>{name}</h3>
<span className="playerPosition_home_tile" key={position}>{position}</span>
</span>
</div>
<span className="phoneNumber_home">
<img src="/images/phone.png" alt={"phoneTooltip.show"} key={id} name="phoneNumberhomeicon" onClick={displayPhoneToolTip(id)} />
</span>
{phoneTooltip === id && (
<div className="tooltip_PhoneNumber_home" key={phonenumber}>{phonenumber}</div>
)}
</div>
))
}
</div>
)}
</div>
</div>
</div>
<AlertDialog
onDelete={onDelete}
open={deleteDialog}
onClose={() => setDeleteDialog(false)}
playerId={playerId}
/>
</div>
);
}
export default Home;
I've created a smaller example version of the app that has your issue resolved. From what I could tell, the issue was that the loginPhoto was being passed in as a string instead of an object of the form {photo: "image/example.jpeg"}.
Another issue was that window.location.reload(true) was being called after pushing to the Home page. This wiped out the context.
You can visit my Codesandbox to see exactly what I changed. Fill in your extra login inside of this shell and see if things work now.

Reach Router props.navigate

I am trying to use Reach Router to navigate to a result url after a form submit. Using props.navigate takes be to the desired url, but no component is rendered (I am trying to render <Result/> after the query.
To add to the complexity, I would like to pass the personsResult prop to Result
Here is an outline of my current approach.
const App = () => {
return (
<div>
<Router>
<Search path="/" />
<List path="list" />
<Result path="result/:query" /> //!
</Router>
</div>
)
}
const Search = props => {
const [query, setQuery] = useState("")
const [personsResult, setPersonsResult] = useState("") //Pass to result
const handleSubmit = async e => {
e.preventDefault()
const person = await personsService.get(query)
setPersonsResult(person)
props.navigate(`result/${query}`) //!
}
return (
<div>
...
<SearchForm
handleSubmit={handleSubmit}
query={query}
setQuery={setQuery}
/>
</div>
)
}
I am open to switching to react-router-dom if there is a simpler means of solving this.

Resources