Detail Image Rendering in React - reactjs

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.

Related

cannot read properties of undefined on props

const Page: FC<PageProps> = () => {
const dispatch = useAppDispatch();
const querystring = window.location.search;
const urlparameter = new URLSearchParams(querystring);
const postid = Number(urlparameter.get("id"));
const [COM] = GET_COMMENT(4); //retrieve Comments with axios
const [data, loading, error] = RETRIEVE_POST("qna", 4); //retrieve Post Detail with axios
useEffect(() => {
dispatch(changeCurrentPage({ type: "/page/:uri", data: data }));
return () => {
dispatch(changeCurrentPage({ type: "/", data: {} }));
};
}, []);
return (
<>
<div>
{/* error */}
<PostContent data={data} />
</div>
</>
);
};
export default Page;
const PostContent: FC<ContentProps> = ({ data }) => {
const { user, content } = data;
return (
<div>
<div>
{/* Post Content */}
{content}
</div>
<div>
{/* Post Writer */}
<Link to={user.profileImageUrl}>
<Writer
imgUrl={user.profileImageUrl}
userName={user.nickname}
/>
</Link>
</div>
</div>
);
};
I want to send data from Page to PostContent.
But in ContentProps(PostContent) an error occur.
Post Writer Part has trouble.
user information is undefined and all page is break.
How can I send data to child props without error?
Check if data arrived first since you are passing undefined to the child. That is why it crashes.
{data?.user?
<PostContent data={data} />
: <p>No data here</p>}

How do I make my post load only if it has been fetched

Each time I open a new post page, the data from the last post shows on the screen for a couple of miliseconds and then the new data is shown.
I have tried to check if the post is loading and set it in the state with the setIsLoading function, but it does not work.
See the code below:
const PostPage = ({post, setPost, isLoading, setIsLoading}) => {
const location = useLocation()
const pathname = location.pathname.split("/")[2]
useEffect(() => {
setIsLoading(true)
const fetchData = async () => {
let res = await axios.get(`http://localhost:4000/posts/${pathname}`)
const postData = res.data
setPost(postData)
setIsLoading(false)
}
fetchData()
}, [])
return (
<>
{!isLoading && (
<div>
<h1>{post.title}</h1>
<p>{post.text}</p>
</div>
)}
</>
)
}
These are my routes:
<Switch>
<Route path="/" exact>
{isLogged && (
<AddPost
posts={posts}
setPosts={setPosts}
postTitle={postTitle}
setPostTitle={setPostTitle}
postText={postText}
setPostText={setPostText}
setIsLoading={setIsLoading}
/>
)}
<div className="posts-row">
{posts.slice(0).reverse().map(post => (
<Post
key={post.id}
id={post.id}
postTitle={post.title}
postText={post.text}
isLogged={isLogged}
posts={posts}
setPosts={setPosts}
/>
))}
</div>
</Route>
<Route path="/posts/:id" exact>
<PostPage
post={post}
setPost={setPost}
isLoading={isLoading}
setIsLoading={setIsLoading}
/>
</Route>
</Switch>
In App.js I have isLoading set to true, but it works only for the first post.
For the sake of React please don't report my question :D
Edit:
Post.js
const Post = ({postTitle, postText, isLogged, posts, setPosts, id}) => {
const removePost = async (id) => {
const postArray = posts.filter((post) => (post.id !== id))
let res = await axios.delete(`http://localhost:4000/posts/${id}`)
setPosts(postArray)
}
return (
<div className="post">
<Link to={`/posts/${id}`}>
<strong>
{postTitle}
</strong>
</Link>
<p>{postText}</p>
{isLogged && (
<button onClick={() => removePost(id)}>
🗑️
</button>
)}
</div>
)
}
Your effect hook only runs when the component is mounted for the first time. If you want to run it every time pathname changes, add it to its dependency array:
useEffect(() => {
setIsLoading(true)
const fetchData = async () => {
let res = await axios.get(`http://localhost:4000/posts/${pathname}`)
const postData = res.data
setPost(postData)
setIsLoading(false)
}
fetchData()
}, [pathname])
You need to put setIsLoading inside the fetchData function:
and also add the pathname to useEffect dependencies and also check the pathname to have value:
const fetchData = async () => {
setIsLoading(true);
let res = await axios.get(`http://localhost:4000/posts/${pathname}`);
const postData = res.data;
setPost(postData);
setIsLoading(false);
};
useEffect(() => {
if (pathname) {
fetchData();
}
}, [pathname]);
EDIT:
so here is what you need to do:
import { useParams } from "react-router-dom";
const PostPage = ({ post, setPost, isLoading, setIsLoading }) => {
const { id } = useParams();
const fetchData = async () => {
setIsLoading(true);
let res = await axios.get(`http://localhost:4000/posts/${id}`);
const postData = res.data;
setPost(postData);
setIsLoading(false);
};
useEffect(() => {
if (id) {
fetchData();
}
}, [id]);
return (
<>
{!isLoading && (
<div>
<h1>{post.title}</h1>
<p>{post.text}</p>
</div>
)}
</>
);
};

Load state/data before render?

I have implemented the authentication part of my app (built using the MERN stack). The login action validates login data, then loads user data, then pushes the route to /dashboard. On the dashboard page, I have a simple Welcome to the dashboard, {email}! however I am getting an error telling me that it can not return data of null. I also have the users First & Last name as well as their email in the navbar, that also spawns an error of returning null data. I have a useEffect that loads the user data in my App.js but i'm still receiving the null errors.
Is there a way to load the data prior to render?
Index.js
ReactDOM.render(
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</PersistGate>
</Provider>,
document.getElementById('root')
);
App.js
const App = () => {
const [loading, setLoading] = useState(true);
const dispatch = useDispatch();
useEffect(() => {
// check for token in LS
if (localStorage.token) {
setAuthToken(localStorage.token);
}
dispatch(attemptGetUser())
.then(() => setLoading(false))
.catch(() => setLoading(false));
// Logout user from all tabs if they logout in another tab
window.addEventListener('storage', () => {
if (!localStorage.token) dispatch({ type: LOGOUT });
});
// eslint-disable-next-line
}, []);
return loading ? (
<Loading cover="page" />
) : (
<div className="App">
<Switch>
<Route path="/" component={Views} />
</Switch>
</div>
);
};
redux/thunks/Auth.js
export const attemptLogin = (formData) => async (dispatch) => {
await postLogin(formData)
.then((res) => {
dispatch(login(res.data));
dispatch(push('/dashboard'));
})
.then(() => {
dispatch(attemptGetUser());
})
.catch((error) => {
const errors = error.response.data.message;
dispatch(setAlert('Uh-oh!', errors, 'error'));
});
};
redux/thunks/User.js
export const attemptGetUser = () => async (dispatch) => {
await getUser()
.then((res) => {
dispatch(setUser(res.data));
})
.catch((error) => {
const errors = error.response.data.message;
console.log(errors);
dispatch(setAlert('Uh-oh!', errors, 'danger'));
});
};
views/app-views/dashboard
const Dashboard = () => {
const { email } = useSelector((state) => state.user.user);
return (
<div>
Welcome to the dashboard,
<strong>{email}</strong>!
</div>
);
};
export default Dashboard;
components/layout-components/NavProfile.js
export const NavProfile = () => {
const { firstName, lastName, email } = useSelector(
(state) => state.user.user
);
const dispatch = useDispatch();
const onLogout = () => {
dispatch(attemptLogout());
};
const profileImg = '/img/avatars/thumb-1.jpg';
const profileMenu = (
<div className="nav-profile nav-dropdown">
<div className="nav-profile-header">
<div className="d-flex">
<Avatar size={45} src={profileImg} />
<div className="pl-3">
<h4 className="mb-0">{firstName} {lastName}</h4>
<span className="text-muted">{email}</span>
</div>
</div>
</div>
<div className="nav-profile-body">
<Menu>
{menuItem.map((el, i) => {
return (
<Menu.Item key={i}>
<a href={el.path}>
<Icon className="mr-3" type={el.icon} />
<span className="font-weight-normal">{el.title}</span>
</a>
</Menu.Item>
);
})}
<Menu.Item key={menuItem.legth + 1} onClick={onLogout}>
<span>
<LogoutOutlined className="mr-3" />
<span className="font-weight-normal">Logout</span>
</span>
</Menu.Item>
</Menu>
</div>
</div>
);
return (
<Dropdown placement="bottomRight" overlay={profileMenu} trigger={['click']}>
<Menu className="d-flex align-item-center" mode="horizontal">
<Menu.Item>
<Avatar src={profileImg} />
</Menu.Item>
</Menu>
</Dropdown>
);
};
export default NavProfile;
So the error is telling you that in your redux state that state.user.user is undefined, this is why you can't destructure firstName, lastName, email values.
If in your store state.user.user is at least a defined, empty object ({}) then the access of null errors should resolve.
const userReducer = (state = { user: {} }, action) => {
...
}
This can still potentially leave your UI rendering "undefined", so in the component code you'll want to provide default values, i.e.
const { firstName = '', lastName = '', email = '' } = useSelector(
(state) => state.user.user
);
The alternative is to have fully qualified initial state in your user reducer slice.
const initialState = {
user: {
firstName: '',
lastName: '',
email: '',
},
};
const userReducer = (state = initialState, action) => {
...
}
Seems like you could fix this by changing your Redux store initial state.
Taking your Dashboard component as an example:
const Dashboard = () => {
const { email } = useSelector((state) => state.user.user);
return (
<div>
Welcome to the dashboard,
<strong>{email}</strong>!
</div>
);
};
It expects that there is a user object with an email string in the user slice of state in your Redux store. As noted in their documentation
You could update your createStore call to include a initial value for the redux store like createStore({'user': {'user': {'email': ''}}}); for example

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.

How to define match router prop at my shallow test

I'm trying to do jest+enzyme tests with my react app but i'm getting an error at the route component. I'm using router v4.
I did try to use MemoryRouter Wrapper to the shallow component, or use mount instead of shallow. Everything didn't work.
My test:
describe('Movie Page tests', () => {
const wrapper = shallow(<Movie />).toJSON()
it('should call Movie snapshot correctly', () => {
const tree = renderer
.create(wrapper)
.toJSON();
expect(tree).toMatchSnapshot();
});
})
Full component:
export const Movie = ({
match,
searchMovieAction,
movies,
totalResults,
pending,
error,
}, props) => {
const [ showModal, setShowModal ] = useState(false);
//const { searchMovieAction } = useActions();
const { values, handleInputChange } = useFormInput({
searchValue: '',
});
console.log('PROPES ------>', error,);
/* const {
movie: {
movie,
totalResults,
pending,
error,
},
} = useSelector(state => state); */
const handleSubmit = (e) => {
e.preventDefault()
const { searchValue } = values;
if(searchValue) {
searchMovieAction(searchValue);
}
}
const toggleModal = () => {
setShowModal(!showModal);
}
return (
<div className='movies'>
<div>
<StyledForm onSubmit={handleSubmit}>
<DefaultInput
name='searchValue'
value={values.searchValue}
placeholder='Search a movie...'
handleChange={handleInputChange}
/>
<Button solid rounded right >Search</Button>
</StyledForm>
</div>
{ pending && <LoadingSpinner medium />}
{ error && <Error message={error} /> }
{ movies && movies.length > 0 && !pending && !error && (
<p>
We found <strong>{ totalResults }</strong>
{totalResults == 1 ? 'result!' : ' results!'}
</p>
)}
<StyledMovies>
{movies && movies.map((m) => {
const { Title, Poster, Plot, imdbID } = m
return(
<StyledMovieItem key={uniqueId()}>
<Link to={`${match.url}/${imdbID}`} onClick={setShowModal}>
<MovieSummary data={{Title, Poster, Plot}} />
</Link>
</StyledMovieItem>
)
})
}
<Modal handleClose={toggleModal} show={showModal}>
<Route
exact
path={`${ match.path }/:imdbID`}
render={(props) => <MovieDetail {...props} /> }
/>
</Modal>
</StyledMovies>
</div>
)
}
The error:
TypeError: Cannot read property 'path' of undefined
99 | <Route
100 | exact
101 | path={`${ match.path }/:imdbID`}
| ^
102 | rende
The application is working, but at the test the match param is empty. Does someone knows what can be?
You need to wrap your component into any <MemoryRouter> and <Router> like
const wrapper = shallow(<MemoryRouter><Route component={Movie} /></MemoryRouter>).dive().dive()
dive() is needed because otherwise only <MemoryRouter> itself is rendered by shallow().
See article with more detailed explanation: https://medium.com/#antonybudianto/react-router-testing-with-jest-and-enzyme-17294fefd303
If you don't want to wrap your component with a router in your tests, you can mock match like any other props:
const mockMatch = {
path: 'my-path/some-value',
url: 'my-path/some-value'
}
const wrapper = shallow(<Movie match={mockMatch}/>).toJSON()

Resources