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
Related
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>}
I'm having trouble accessing the state in one of my components. I have a component where a user adds a name and a weight and then submits it. They are then redirected to the Home Page. What I want to happen is for the name that was inputed to be displayed on the Home Page. I can see the state updating, but I can't figure out how to access the state and have the name show on the Home Page. Any help would be appreciated.
Here is my Home Page component:
const HomePage = () => {
const classes = useStyles();
const name = useSelector(state => state.move.name);
const displayMovementButtons = () => {
if (name) {
return (
<Button
className={classes.movementButtons}
onClick={() => history.push('/movement/:id')}
>
<div className={classes.movementName} >{name}</div>
</Button>
)
};
return <div className={classes.noMovementsMessage} >Click add button to begin</div>
};
return (
<div className={classes.homePageContent} >
<Header title={"Home Page" }/>
<div>{displayMovementButtons()}</div>
<div className={classes.fabDiv}>
<Fab
className={classes.fab}
onClick={() => history.push(`/add`)}>
<AddIcon />
</Fab>
</div>
</div>
);
};
const mapStateToProps = (state) => {
return {
name: state.move.name,
}
};
const withConnect = connect(
mapStateToProps,
);
export default compose(withConnect)(HomePage);
Here is my reducer, where I think the problem is:
const initialState = []
const addMovementReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_MOVEMENT:
return [ ...state, {name: action.name, weight: action.weight} ]
default:
return state;
}
};
export default addMovementReducer;
Here is a screenshot showing the state (note: I added multiple names and weights, I would eventually like each 'name' to appear on the Home Page):
Your move branch of state is an array. You can't access the name by state.move.name. Instead of this you can get an array of movements from redux store and render them with Array.map() method.
const MovementButtons = ({ movements }) => {
return (
<div>
{
movements.map(({ name, weight }) => {
if (name) {
<Button
className={classes.movementButtons}
onClick={() => history.push('/movement/:id')}
key={name}
>
<div className={classes.movementName}>{name}</div>
</Button>
}
return (
<div className={classes.noMovementsMessage}>Click add button to begin</div>
)
})
}
</div>
);
}
const HomePage = () => {
const classes = useStyles();
const movements = useSelector(state => state.move);
return (
<div className={classes.homePageContent} >
<Header title={"Home Page" }/>
<MovementButtons movements={movements} />
<div className={classes.fabDiv}>
<Fab
className={classes.fab}
onClick={() => history.push(`/add`)}>
<AddIcon />
</Fab>
</div>
</div>
);
};
const mapStateToProps = (state) => {
return {
name: state.move.name,
}
};
const withConnect = connect(
mapStateToProps,
);
const CompleteDriverInfo = () => {
const [DriverInfo, setDriverInfo] = useState([]);
useEffect(async () => {
await setDriverInfo(await GetFetch('driver'));
}, []);
return (
<div>
<Link to='/adddriver'>
<button className='btn btn-primary'>Add Driver</button>
</Link>
{DriverInfo.map((EachDriver) => (
<EachDriverInfo EachDriver={EachDriver} />
))}
</div>
);
};
const EachDriverInfo = ({ EachDriver }) => {
const DeleteDriver = (e) => {
POST(
'deletedriver',
{
CustomerName: EachDriver.CustomerName,
},
e
);
};
return (
<>
<h1>Name: {EachDriver.CustomerName}</h1>
<h1>Phone Number: {EachDriver.PhoneNumber}</h1>
<h1>Email: {EachDriver.Email}</h1>
<h1>Address: {EachDriver.Address}</h1>
<h1>Country: {EachDriver.Country}</h1>
<button onClick={(e) => DeleteDriver(e)} className='btn btn-primary'>
Delete Driver
</button>
</>
);
};
When I click Delete Driver in my child component, it deletes a driver from the database, but the page doesn't re-render until refresh because of useEffect's empty dependencies.
I was thinking of setting a parent state from the child component and putting that in the dependency array but it feels like an anti-pattern
It is perfectly fine to update parent state from the child component. Just pass a function to the child component, and when child component changes the state, call this function inside the child component so that the parent component can update the app state and the rerender.
Here is a sample codesandbox:
import React, { useState, useEffect } from "react";
import axios from "axios";
const Posts = () => {
const [posts, setPosts] = useState([]);
useEffect(() => {
const getPosts = async () => {
const response = await axios.get(
`https://jsonplaceholder.typicode.com/posts`
);
setPosts(response.data);
};
getPosts();
}, []);
const handleDeletePost = (id) => {
const updatedPosts = posts.filter((p) => p.id !== id);
setPosts(updatedPosts);
};
return (
<div>
{posts.map((post) => (
<PostInfo key={post.id} post={post} deletePost={handleDeletePost} />
))}
</div>
);
};
const PostInfo = ({ post, deletePost }) => {
const DeletePost = async (id) => {
await axios.delete(`https://jsonplaceholder.typicode.com/posts/{id}`);
deletePost(id);
};
return (
<>
<h1>Id: {post.id}</h1>
<h1>Title: {post.title}</h1>
<button onClick={() => DeletePost(post.id)} className="btn btn-primary">
Delete Post
</button>
</>
);
};
export default Posts;
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.
I'm building an eCommerce project using hooks for Redux and React and I'm stumped on how to get the individual details for each item. When I click on the item, I only retrieve the details of the first item in the array no matter which item is clicked with the current set up. When I use state.products.find(item => item.id === item) instead of the code below for details const, I get a TypeError: Cannot read property x of undefined. Any help is appreciated.
function App() {
return (
<Provider store={store}>
<Navbar/>
<Switch>
<Route exact path="/" component={Home}/>
<Route exact path='/products' component={Products}/>
<Route exact path="/products/:prodId" component={ProductDetail}/>
<Route exact path="/cart" component={Cart}/>
</Switch>
</Provider>
);
}
Reducer
const initialState = {
items:[],
prodId: {},
count: 0
}
export default function (state = initialState, {payload,type}){
switch(type){
case types.GET_PRODUCTS:
return{
...state,
items: payload,
}
case types.GET_ITEM:
return{
...state,
prodId: payload
}
default:
return state;
}
}
Actions
const api = 'http://localhost:8000/products'
export const getProds = ()=> dispatch =>{
console.log('fethcin')
fetch(api)
.then(res =>res.json())
.then(data => {
// return {type: GET_PRODUCTS, payload:data}
dispatch({type: types.GET_PRODUCTS, payload:data});
}
)
}
export const getDetails = id => dispatch => {
dispatch({type: types.GET_ITEM, payload: id})
}
Product
const Product = ({product}) => {
const dispatch = useDispatch()
const {id, title, price, isFreeShipping} = product
return (
<div className='prod-container'>
<div className="product">
<h2 className='prod-title'>{title}</h2>
<p className='prod-price'>{price}</p>
<span className='prod-ship'>{isFreeShipping}</span>
<button onClick={()=> dispatch(getDetails(id))}><Link to={{pathname:`/products/${id}`}}> View More </Link> </button>
{/* <button className='btn prod-details'>View Item</button> */}
</div>
</div>
)
}
Details
const ProductDetail = ({match}) => {
const product = match.params.prodId
const details = useSelector(state => state.products.items.find(item => item.id === product))
const {sku, count, title, description, availableSizes, price, isFreeShipping} = details
return (
<div className='prodDetaul'>
<p>{title}</p>
<p>{description}</p>
<p>{price}</p>
<p>{isFreeShipping}</p>
<p>{availableSizes}</p>
<p>{sku}</p>
<Counter count={count}/>
<button className='btn btn-chkot'><Link to='/cart'>Cart</Link></button>
</div>
)
}
The problem is with this line:
const details = useSelector(state => state.products.items.find(item => item.id))
What the statement means basically is:
Find the first item in state.products.items that has an id.
You actually need to pass in a parameter, say, the ID of the clicked item. Could be like this:
const details = useSelector(state => state.products.items.find(item => item.id === myItemId))
Since you're using react-router, you can get the product ID with props.match.params.id.
The final solution:
const ProductDetail = ({ match }) => {
const productId = match.params.id
const details = useSelector(state => state.products.items.find(item => item.id === productId))
// ...
}