React renders before API call - javascript

So I make an API call to server to get the currentUser,
useEffect(() => {
loadUser()
},[])
Since behaviour of React is like render first run lifecycle methods second, at first, my selector for user returns null which what I expect.
However I still got user is null error, so this is my code =>
const isAuthenticated = useSelector(state => state.auth.isAuthenticated)
const user = useSelector(state => state.auth.user)
const authLinks = (
<nav className="auth-navbar">
<div className="auth-navbar__dropdown">
<button type="button" className="dropdown-btn" onClick={dropdown}><img src={profilephoto}></img></button>
<div className="dropdown-menu">
<Link to={`u/${user.username}`} className="dropdown-link">Profile</Link>
<Link to="/settings" className="dropdown-link">Settings</Link>
<Link to="/" onClick={onClickHandler} className="dropdown-link">Logout</Link>
</div>
</div>
</nav>
)
if (user) {
return (
<header className="header">
<Link to="/" className="logo" >
<img src={logo} alt="logo"/>
</Link>
{isAuthenticated ? authLinks : guestLinks}
</header>
)
} else {
return <p>loading..</p>
}
Questions like this have been asked before on stackoverflow but solutions are similar to mine and it still doesn't work. Please help me.
P.S: the default of user is null in the reducer.
EDIT: The action creator to load user =>
export const loadUser = () => (dispatch) => {
dispatch({ type: USER_LOADING })
const config = {
withCredentials: true
}
axios.get('http://127.0.0.1:8000/auth/user/', config)
.then(res => {
dispatch({
type: USER_LOADED,
payload: res.data
})
}).catch(err => {
console.log(err)
dispatch({
type: AUTH_ERROR
})
})
}
Normally user loads without error =>
reducer =>
const initialState = {
isAuthenticated: false,
isLoading: false,
user: null,
}
export default function(state=initialState, action){
switch(action.type){
case USER_LOADING:
return {
...state,
isLoading: true
}
case USER_LOADED:
return {
...state,
isAuthenticated: true,
isLoading: false,
user: action.payload
}

I think you need to make authLinks a function which executes and returns the relevant JSX only when user exists. As it is, the variable will try to access the property user.username before it has been initialised.
const authLinks = () => (
...
);
And then call it in the return.
{isAuthenticated ? authLinks() : guestLinks}

Related

Prevent re-render using React.memo and React.useCallback

For learning purpose,
I am trying prevent re-render on <InputWithLable /> component whenever i Dismiss a search result (see deploy in Full code)
I have use React.memo but it still re-render. So I think maybe its props is the culprit. I use React.useCallback to handleSearch prop, but it doesn't work.
Full code
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
import React from 'react';
const API_ENDPOINT = 'https://hn.algolia.com/api/v1/search?query=';
const useSemiPersistentState = (key, initialState) => {
const [value, setValue] = React.useState(
localStorage.getItem(key) || initialState
);
React.useEffect(() => {
localStorage.setItem(key, value);
}, [value, key]);
return [value, setValue];
};
function storiesReducer(prevState, action) {
switch (action.type) {
case "SET":
return { ...prevState, data: action.data, isLoading: false, isError: false };
case "REMOVE":
return {
...prevState,
data: prevState.data.filter(
story => action.data.objectID !== story.objectID
)
}
case "ERROR":
return { ...prevState, isLoading: false, isError: true };
default:
throw new Error();
}
}
const App = () => {
const [searchTerm, setSearchTerm] = useSemiPersistentState(
'search',
'Google'
);
const [stories, dispatchStories] = React.useReducer(storiesReducer, { data: [], isLoading: true, isError: false });
const [url, setUrl] = React.useState("");
const handleFetchStories = React.useCallback(() => {
fetch(url)
.then((response) => response.json())
.then((result) => {
console.log(result);
dispatchStories({ type: "SET", data: result.hits })
})
.catch(err => dispatchStories({ type: "ERROR", data: err }))
}, [url])
React.useEffect(() => {
handleFetchStories();
}, [handleFetchStories])
const handleRemoveStory = React.useCallback(
(item) => {
dispatchStories({ type: "REMOVE", data: item });
},
[], // chi render 1 lan vi props khong thay doi
)
const handleSearch = React.useCallback(
(e) => {
setSearchTerm(e.target.value);
},
[],
)
// Chuc nang filter la cua server (vd: database)
// const searchedStories = stories.data ? stories.data.filter(story =>
// story.title.toLowerCase().includes(searchTerm.toLowerCase())
// ) : null; // nghich cai nay!
console.log('App render');
return (
<div>
<h1>My Hacker Stories</h1>
<InputWithLabel
id="search"
value={searchTerm}
isFocused
onInputChange={handleSearch}
>
<strong>Search:</strong>
</InputWithLabel>
<button onClick={() => setUrl(API_ENDPOINT + searchTerm)}>Search!</button>
<hr />
{stories.isError && <h4>ERROR!</h4>}
{stories.isLoading ? <i>Loading...</i>
: <List list={stories.data} onRemoveItem={handleRemoveStory} />}
</div>
);
};
const InputWithLabel = React.memo(
({
id,
value,
type = 'text',
onInputChange,
isFocused,
children,
}) => {
const inputRef = React.useRef();
React.useEffect(() => {
if (isFocused) {
inputRef.current.focus();
}
}, [isFocused]);
console.log('Search render')
return (
<>
<label htmlFor={id}>{children}</label>
<input
ref={inputRef}
id={id}
type={type}
value={value}
onChange={onInputChange}
/>
</>
);
}
);
// Prevent default React render mechanism: Parent rerender -> Child rerender
const List = React.memo(
({ list, onRemoveItem }) =>
console.log('List render') || list.map(item => (
<Item
key={item.objectID}
item={item}
onRemoveItem={onRemoveItem}
/>
))
);
const Item = ({ item, onRemoveItem }) => (
<div>
<span>
<a href={item.url}>{item.title}</a>
</span>
<span>{item.author}</span>
<span>{item.num_comments}</span>
<span>{item.points}</span>
<span>
<button type="button" onClick={() => onRemoveItem(item)}>
Dismiss
</button>
</span>
</div>
);
export default App;
You should not be looking at how many times a component's render function gets called; React is free to call it as many times as it likes (and indeed, in strict mode, it calls them twice to help you not make mistakes).
But to answer your question (with the actual code that uses children):
<InputWithLabel>
<strong>Search:</strong>
</InputWithLabel>
compiles down to
React.createElement(InputWithLabel, null,
React.createElement("strong", null, "Search:"))
the identity of the children prop (the <strong /> element) changes for each render of the parent component since React.createElement() returns new objects for each invocation. Since that identity changes, React.memo does nothing.
If you wanted to (but please don't), you could do
const child = React.useMemo(() => <strong>Search:</strong>);
// ...
<InputWithLabel>{child}</InputWithLabel>
but doing that for all of your markup leads to nigh-unreadable code.

Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement? React

I'm having a little problem trying to do this:
Basically, I want to show another div if any error exists. This error comes from the payload to the next request.
But when I make the request for that (to have the error) and when I try to grab this error from the Redux state I'm having the problem that I put in the title 'Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement'
This is the function with problem:
Note: If the error doesn't not exist I can render very well the component.
Even when I have the error, if I reload the page the error "Error: Rendered fewer hooks than expected." disappears and is showing me correctly the div that I want "You don't have the correct access".
const Users: FC = () => {
const { error } = useSelector((state: State) => state.FleetUsers);
return (
<>
<Layout title={t('Fleet_users.table.My_users')} />
<LayoutFleet />
{error ? (
<div className="col-md-6 offset-md-3 mt-3">
<h2>You dont have the correct access</h2>
</div>
) : (
<div className="table-container">
<h2>You have the correct access</h2>
</div>
</div>
)}
</>
);
};
Code behavior:
I'm using Redux state so basically if I have the "error" is in the memory and I grab it directly using useSelector.
const { error } = useSelector((state: State) => state.FleetUsers);
This is the request :
export const fetchGroups = (
token: string,
): ThunkAction<void, State, null, FluxStandardAction> => {
return async (dispatch) => {
dispatch(FetchAllGroupsRequest());
try {
const response = await axios({
method: 'GET',
url: `${API}/users/organization-user-group`,
headers: {
Authorization: `Bearer ${token}`,
},
});
await response.data;
dispatch(FetchAllGroupsSuccess(response.data));
} catch (err) {
console.log(err);
dispatch(FetchAllGroupsFailure(err));
}
};
};
This is the reducer:
case FLEET_FETCH_ALL_GROUPS_REQUEST:
return {
...state,
loading: true,
};
case FLEET_FETCH_ALL_GROUPS_SUCCESS:
return {
...state,
loading: false,
groups: action.payload,
};
case FLEET_FETCH_ALL_GROUPS_FAILURE:
return {
...state,
loading: false,
error: action.payload.message,
};
If someone knows how to fix this React problem I will appreciate it a lot.

How to run useEffect part first in React-Hooks?

How to execute the axios part and send the updated states props to Important component.
When I console.log I see that state passed as props with an empty object but after a fraction of seconds again states is updated with a new fetched value that means my return is running first then my usEffect axios part is running,
How can I make sure that axios part should run first then my return part. In first go updated part should be sent not the blank empty part
const initialState = {
Important: [{}],
Error: false
}
const reducer = (state, action) => {
switch (action.type) {
case "STEPFIRST":
return {
...state,
Important: action.payload,
};
case "STEPSecond":
return {
Error: true,
};
default:
return state;
}
}
const Landing = () => {
const [states, dispatch] = useReducer(reducer, initialState)
console.log(states)
useEffect(() => {
axios.get("https://example.com/")
.then(response => {
dispatch({
type: "STEPFIRST",
payload: response.data
});
})
.catch(error => {
dispatch({
type: "STEPSecond"
});
});
},[]);
const [xyz, xyzfn] = useState();
console.log(xyz)
return (
<div>
<Important states = {states} xyzfn={xyzfn} />
<Foo xyz={xyz}/>
</div>
);
};
export default Landing;
useEffect will always run after first rendering is done. You can have a loading state in your state and return the component accordingly.
const initialState = {
Important: [{}],
Error: false,
isLoading: true
}
const reducer = (state, action) => {
switch (action.type) {
case "STEPFIRST":
return {
...state,
Important: action.payload,
isLoading: false
};
case "STEPSecond":
return {
Error: true,
isLoading: false
};
default:
return state;
}
}
const Landing = () => {
const [states, dispatch] = useReducer(reducer, initialState)
console.log(states)
useEffect(() => {
axios.get("https://example.com/")
.then(response => {
dispatch({
type: "STEPFIRST",
payload: response.data
});
})
.catch(error => {
dispatch({
type: "STEPSecond"
});
});
},[]);
const [xyz, xyzfn] = useState();
console.log(xyz)
if(state.isLoading){
return <div>Loading....</div>
}
return (
<div>
<Important states = {states} xyzfn={xyzfn} />
<Foo xyz={xyz}/>
</div>
);
};
useEffect callback runs after the render phase.
Also, fetch calls are asynchronous, so you want to use conditional rendering:
const Landing = () => {
const [states, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
axios
.get("https://example.com/")
.then((response) => {
dispatch({
type: "STEPFIRST",
payload: response.data,
});
})
.catch((error) => {
dispatch({
type: "STEPSecond",
});
});
}, []);
// Use any comparison function to indicate that `states` changed.
// like deep comparison function `isEqual` from lodash lib.
return (
<div>
{!lodash.isEqual(states, initialState) && (
<Important states={states} xyzfn={xyzfn} />
)}
</div>
);
};

React-Redux unique key warning on store change

I'm working on a todo app and everything works fine when it first loads in. However, when I add a new todo and the store updates, I get the unique key warning, when the key is defined in the array components:
render() {
const todoList = this.props.todos.map(todo => {
return <Todo todo={todo} key={todo._id}/>
})
return (
<div className={styles.todoContainer}>
{todoList}
</div>
);
}
Todo Component:
return (
<div className={styles.todo}>
<h2 className={styles.todoText}>{props.todo.name}</h2>
</div>
);
Adding todo:
//actions.js
export function addTodo(todo){
let config = {
headers: {
token: localStorage.getItem('token')
}
};
return function(dispatch){
return axios.post('http://localhost:8082/api/todos/create', todo, config)
.then(msg => {
dispatch({type: ADD_TODO, payload: todo})
})
.catch(err => console.log(err));
}
}
//reducer.js
case ADD_TODO:
const data = [...state.data];
data.push(action.payload);
return {
...state,
data: data
};
Is this a problem I should worry about fixing, or is it a bug? Thanks!

Actions does not fire off redux/redux thunk

So basically, I am calling loadUser which gets the User from the backend, it consistently works fine but appearantly whenever i refresh Dashboard page it does not fire off any actions, even though i would try calling loadUser inside of the useEffect which is in Dashboard page, still it no actions gets fired and i do not have access to the user, this is something i need cause i have to have access to the users ID. Also I am using redux thunk, I heard there are side effects that do exists, but still i would truly love to get help :)
I will link the github repo down below and paste code that seem related to this issue. If you do need anymore code the repo is here too:
https://github.com/tigerabrodi/eBuy
Dashboard Component
import React, {useEffect, Fragment, useState} from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import Pagination from '../products/Pagination';
import ProductItem from '../products/ProductItem';
import { getUserProducts } from '../../redux/product/product.actions';
import {loadUser} from "../../redux/auth/auth.actions";
const Dashboard = ({product: {products, loading, totalProducts}, loadUser, getUserProducts, auth: {user}}) => {
const [currentPage, setCurrentPage] = useState(1);
const [productsPerPage] = useState(6);
const paginate = pageNumber => setCurrentPage(pageNumber);
useEffect(() => {
getUserProducts(user._id, currentPage);
}, [currentPage, getUserProducts, user._id]);
return (
<Fragment>
<div className="container">
<div className="row">
<div className="col text-center">
<h1 className="text-monospace text-info display-2">Dashboard</h1>
<Link to="/add-product" className="btn btn-block btn-warning">Add Product <i className="far fa-money-bill-alt" /> </Link>
</div>
</div>
</div>
<br />
<div className="container">
<div className="row">
{products.map(product => (
<div className="col-md-4 col-6">
<ProductItem key={product._id} product={product} />
</div>
))};
<div className="col-12">
{products && (
<Pagination productsPerPage={productsPerPage} totalProducts={totalProducts} paginate={paginate} />
)}
</div>
</div>
</div>
</Fragment>
);
}
const mapStateToProps = state => ({
product: state.product,
auth: state.auth
})
export default connect(mapStateToProps, {getUserProducts, loadUser})(Dashboard);
auth reducer
import {AuthActionTypes} from "./auth.types";
const initialState = {
token: localStorage.getItem("token"),
isAuthenticated: null,
loading: true,
user: null
}
const authReducer = (state = initialState, action) => {
const {type, payload} = action;
switch (type) {
case AuthActionTypes.USER_LOADED:
return {
...state,
isAuthenticated: true,
loading: false,
user: payload
};
case AuthActionTypes.REGISTER_SUCCESS:
case AuthActionTypes.LOGIN_SUCCESS:
localStorage.setItem('token', payload.token);
return {
...state,
...payload,
isAuthenticated: true,
loading: false
};
case AuthActionTypes.REGISTER_FAIL:
case AuthActionTypes.AUTH_ERROR:
case AuthActionTypes.LOGIN_FAIL:
case AuthActionTypes.LOGOUT:
case AuthActionTypes.ACCOUNT_DELETED:
case AuthActionTypes.USER_ERROR:
localStorage.removeItem('token');
return {
...state,
token: null,
isAuthenticated: false,
loading: false
};
default:
return state;
}
}
export default authReducer
auth actions
import axios from "axios";
import {setAlert} from "../alert/alert.actions"
import {AuthActionTypes} from "./auth.types"
import setAuthToken from "../../utils/setAuthToken"
// Load User
export const loadUser = () => async dispatch => {
if (localStorage.token) {
setAuthToken(localStorage.token);
}
try {
const res = await axios.get('/auth');
dispatch({
type: AuthActionTypes.USER_LOADED,
payload: res.data
});
} catch (err) {
dispatch({
type: AuthActionTypes.AUTH_ERROR
});
}
};
// Register User
export const register = ({ name, email, password }) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ name, email, password });
try {
const res = await axios.post('/auth/signup', body, config);
dispatch({
type: AuthActionTypes.REGISTER_SUCCESS,
payload: res.data
});
dispatch(loadUser());
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach(error => dispatch(setAlert(error.msg, 'danger')));
}
dispatch({
type: AuthActionTypes.REGISTER_FAIL
});
}
};
// Login User
export const login = (email, password) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ email, password });
try {
const res = await axios.post('/auth/signin', body, config);
dispatch({
type: AuthActionTypes.LOGIN_SUCCESS,
payload: res.data
});
dispatch(loadUser());
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach(error => dispatch(setAlert(error.msg, 'danger')));
}
dispatch({
type: AuthActionTypes.LOGIN_FAIL
});
}
};
// Logout / Clear Profile
export const logout = () => dispatch => {
dispatch({ type: AuthActionTypes.LOGOUT });
};

Categories