import React from "react";
import { UserContext } from "./../contexts";
import {
removeStoredAuthData,
storedAuthIsValid,
storeNewAuthData,
} from "./../utils/auth";
import { getUserInfos } from "./../api/userAuthentication";
class UserProvider extends React.Component {
constructor(props) {
super(props);
this.state = {
user: "",
};
}
render() {
return (
<UserContext.Provider
value={{
user: this.state.user,
clearUserProfile: () => {
const user = "";
removeStoredAuthData();
this.setState({ user });
},
saveUserProfile: (response) => {
const user = response.data;
storeNewAuthData(response);
this.setState({ user });
},
populateUserProfile: (displayLoader, hideLoader) => {
const storedToken = localStorage.getItem("appsante-token");
const storedId = localStorage.getItem("appsante-id");
if (storedAuthIsValid()) {
displayLoader(() => {
getUserInfos(storedId)
.then((response) => {
const user = { ...response.data, token: storedToken };
this.setState({ user }, hideLoader());
})
.catch((error) => console.log(error));
});
}
},
}}
>
{this.props.children}
</UserContext.Provider>
);
}
}
export default UserProvider;
Hi everyone !
I trying to convert a React class component into a function component, with hooks.
But I can't find a way to deal properly with that line :
this.setState({ user }, hideLoader());
Unlike setState in class components, useState doesn't take a callback as second parameter, and I can't find how to achieve it with useEffect.
Could anyone help me ? Thanks !
Because the loader's presence can't be determined from the value in / change in user alone, you'll need another state variable, maybe one that contains the callback - perhaps call it hideLoader. After getUserInfos resolves, call setHideLoader with the callback, so that a useEffect hook with that function as a dependency can see the change and call the callback:
const [hideLoader, setHideLoader] = useState();
useEffect(() => {
if (hideLoader) {
hideLoader(); // or, if this is a HOF: hideLoader()()
setHideLoader(); // callback done; remove callback from state
}
}, [hideLoader]);
// ...
populateUserProfile: (displayLoader, hideLoaderParam) => {
// ...
getUserInfos(storedId)
.then((response) => {
setUser({ ...response.data, token: storedToken }); // hook version
setHideLoader(hideLoaderParam);
})
and the rest of your code can be mostly the same - only call setHideLoader up above, inside getUserInfos.
I think you should do this :-
import React, { useState } from 'react';
const [user, setUser] = useState("");
populateUserProfile: async (displayLoader, hideLoader) => {
const storedToken = localStorage.getItem("appsante-token");
const storedId = localStorage.getItem("appsante-id");
if (storedAuthIsValid()) {
displayLoader();
let response = await getUserInfos(storedId)
const user = { ...response.data, token: storedToken };
setUser(user);
hideLoader();
};
}
Related
I am working on an assignment involving simple auth app in MERN stack. Everything is set just one problem is occurring when I am calling the UpdateUser function from another file it is not getting read/recognized by React. Also, when I import another function from the same file i.e logoutUser, it is working perfectly fine.
Dashboard.js-File where function is imported
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { logoutUser } from "../../actions/authActions";
import { UpdateUser } from "../../actions/authActions";
import classnames from "classnames";
import M from "materialize-css";
import "react-phone-number-input/style.css";
class Dashboard extends Component {
constructor() {
super();
this.state = {
age: "",
gender: "",
dob: "",
mobile: "",
errors: {},
};
this.onValueChange = this.onValueChange.bind(this);
}
componentWillReceiveProps(nextProps) {
if (nextProps.errors) {
this.setState({
errors: nextProps.errors,
});
}
}
onChange = (e) => {
this.setState({ [e.target.id]: e.target.value });
};
onValueChange(event) {
this.setState({
selectedOption: event.target.value,
gender: event.target.value,
});
console.log(this.state.selectedOption);
}
onDateChange = (val) => {
val = val.toString();
this.setState({ dob: val });
console.log(val, typeof val);
};
onMobileChange = (value) => {
this.setState({ mobile: value });
console.log(this.state.mobile);
};
onUpdateClick = (e) => {
e.preventDefault();
const UpdatedUser = {
id: this.props.auth.user.id,
age: this.state.age,
gender: this.state.gender,
dob: this.state.dob,
mobile: this.state.mobile,
};
console.log(UpdatedUser);
this.props.UpdateUser(UpdatedUser, this.props.history);
};
onLogoutClick = (e) => {
e.preventDefault();
this.props.logoutUser();
};
componentDidMount() {
var context = this;
var options = {
defaultDate: new Date(),
setDefaultDate: true,
onSelect: function(date) {
context.onDateChange(date);
// Selected date is logged
},
};
var elems = document.querySelector(".datepicker");
var instance = M.Datepicker.init(elems, options);
// instance.open();
instance.setDate(new Date());
}
render(){
return(JSX)
}
authActions.js- File from where the function is imported
import axios from "axios";
import setAuthToken from "../utils/setAuthToken";
import jwt_decode from "jwt-decode";
import { GET_ERRORS, SET_CURRENT_USER, USER_LOADING } from "./types";
// Register User
export const registerUser = (userData, history) => (dispatch) => {
axios
.post("/api/users/register", userData)
.then((res) => history.push("/login"))
.catch((err) =>
dispatch({
type: GET_ERRORS,
payload: err.response.data,
})
);
};
//Update User
export const UpdateUser = (userData, history) => (dispatch) => {
axios
.post("/api/users/update", userData)
.then((res) => history.push("/login"))
.catch((err) =>
dispatch({
type: GET_ERRORS,
payload: err.response.data,
})
);
};
// Login - get user token
export const loginUser = (userData) => (dispatch) => {
axios
.post("/api/users/login", userData)
.then((res) => {
// Save to localStorage
// Set token to localStorage
const { token } = res.data;
localStorage.setItem("jwtToken", token);
// Set token to Auth header
setAuthToken(token);
// Decode token to get user data
const decoded = jwt_decode(token);
// Set current user
dispatch(setCurrentUser(decoded));
})
.catch((err) =>
dispatch({
type: GET_ERRORS,
payload: err.response.data,
})
);
};
// Set logged in user
export const setCurrentUser = (decoded) => {
return {
type: SET_CURRENT_USER,
payload: decoded,
};
};
// User loading
export const setUserLoading = () => {
return {
type: USER_LOADING,
};
};
// Log user out
export const logoutUser = () => (dispatch) => {
// Remove token from local storage
localStorage.removeItem("jwtToken");
// Remove auth header for future requests
setAuthToken(false);
// Set current user to empty object {} which will set isAuthenticated to false
dispatch(setCurrentUser({}));
};
One more thing to add, when I call the function directly instead of using this.props.UpdateUser, it is getting recognized by React and the error is gone too but the content of the function is not executing. PLEASE HELP I DONT HAVE MUCH TIME FOR THIS PROJECT SUBMISSION.
Neither of your imports do anything in this file. They are never called. What gets called are similar functions that are passed down to this component as props from some parent component. In your parent component you are passing logoutUser but forgetting to pass updateUser. Find the root file where logoutUser is imported and add updateUser to it.
I am relatively new to react hooks and I am trying to create this custom hook to handle CRUD operations for my API.
This is the hook file:
import React, { useState, useEffect } from "react";
const useApi = (url, headers = { method: "GET" }, payload = null) => {
const [isLoading, setIsLoading] = useState(true);
const [apiData, setApiData] = useState(null);
const [serverError, setServerError] = useState(null);
const [api, setApi] = useState({});
const list = async () => {
try {
const resp = await fetch(url);
const data = await resp?.json();
setApiData(data);
setIsLoading(false);
} catch (error) {
setServerError(error);
} finally {
setIsLoading(false);
}
};
const create = async () => {
try {
const resp = await fetch(url, (headers = { method: "POST" }), payload);
const data = await resp?.json();
setApiData(data);
setIsLoading(false);
} catch (error) {
setServerError(error);
} finally {
setIsLoading(false);
}
};
setApi({
...api,
list: list,
create: create
});
return { isLoading, apiData, serverError, api };
};
export default useApi;
However, when I call api.list() in my main component inside a useEffect() hook, I get an infinite loop.
Sample component call:
import { useEffect } from "react";
import useApi from "./useApi";
export default function App() {
const {
isLoading: loading,
apiData: students,
serverError: error,
api
} = useApi("https://59f0f160ce72350012bec011.mockapi.io/students");
console.log(loading, students, error, api);
useEffect(() => {
api.list();
}, [api]);
return (
<div className="App">
<h1>list</h1>
{loading ? "loading" : students.map((x) => x.name)}
</div>
);
}
Here's the sandbox for it:
https://codesandbox.io/s/cocky-chebyshev-d9q89?file=/src/App.js:0-492
Can anyone help me understand the issue?
Thank you in advance!
This is what is causing the infinite loop:
setApi({
...api,
list: list,
create: create
});
You are not supposed to call setState() during a render.
In your case, you don't need to useState for the api object, you can just return it on every render:
return {
isLoading,
apiData,
serverError,
api: { list, create }
};
Here is a link to the fixed sandbox
Also, another warning: this code will repeatedly call api.list().
useEffect(() => {
api.list();
}, [api]);
Since api changes on every render, it will repeatedly call api.list().
This is the object that changes on every render:
return { isLoading, apiData, serverError, api };
You can ensure that you only call api.list() one time by using a ref.
import { useRef } from 'react'
// In the component
const gotRef = useRef(false)
useEffect(() => {
if (!gotRef.current) {
api.list();
gotRef.current = true
}
}, [api]);
I'm working on a React project and I reuse a fetchAPIcall action since I make 3 different initial API calls, and further, I plan on using more to add and edit my Items.
So to have control over the correct order of the API call I tried using a flag at the end, being a state of the component.
And since I am using many API calls, to add some Items to favorites and be removed quickly by a like button, I'd like to know what is the best practice when using many async functions or API calls?
I can think of only 1) using flags, and 2) having the API call-actions separate for each, but in my case that would be a lot of code (get user)(get, add, delete FavList)(get Items)(add, edit remove Item).
By the way, the API is mine, made it in rails.
Here are the main code&files for my issue:
This is from my GetItemsNFavlist Component, this is where I load all the info of items and favList items. I made it into a component that I call because I thought it was a good idea so when I add an Item to the Favorites List I can just call this component to update my FavoritesList (but that 'updating' part isn't working great just yet, I'm having to go back to the User and again to the Fav List to see the update or even logout and in again to see the change).
Here I call the action "fetchAPIcall" and I check the status and response data with the "fetchCall" store object. Also here I do 2 API calls, 1) to get all the Items and 2) to get the FavoritesList for the User:
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import PropTypes from 'prop-types';
import * as MyActions from '../actions';
const GetItemsNFavlist = props => {
const {
actions, items, fetchCall, favList, user,
} = props;
const [apiFlag, setApiFlag] = useState({ itm: false, fvl: false });
const itemsUrl = 'https://findmyitem-api.herokuapp.com/items';
const favListUrl = `https://findmyitem-api.herokuapp.com/users/${user.id}/favorites_lists`;
useEffect(() => { // #1
if (!apiFlag.itm && !apiFlag.fvl) actions.fetchAPIcall(itemsUrl, 'get', {});
}, []);
useEffect(() => {
if (!fetchCall.apiData && items[0]) {
actions.fetchAPIcall(favListUrl, 'get', {});
setApiFlag({ itm: true, fvl: false });
}
}, [items]);
useEffect(() => {
if (fetchCall.apiData && !items[0] && !favList[0]) {
actions.setItems(fetchCall.apiData);
actions.fetchAPIreset();
}
if (apiFlag.itm && fetchCall.apiData && !favList[0]) actions.setFavList(fetchCall.apiData);
});
useEffect(() => {
if (favList[0]) {
actions.fetchAPIreset();
setApiFlag({ itm: true, fvl: true });
}
}, [favList]);
return (<> </>);
};
GetItemsNFavlist.propTypes = {
user: PropTypes.objectOf(PropTypes.any).isRequired,
actions: PropTypes.objectOf(PropTypes.any).isRequired,
items: PropTypes.arrayOf(PropTypes.any).isRequired,
favList: PropTypes.arrayOf(PropTypes.any).isRequired,
fetchCall: PropTypes.objectOf(PropTypes.any).isRequired,
};
const mapStateToProps = ({
user, items, fetchCall, favList,
}) => ({
user, items, fetchCall, favList,
});
function mapActionsToProps(dispatch) {
return {
actions: bindActionCreators({ ...MyActions }, dispatch),
};
}
export default connect(mapStateToProps, mapActionsToProps)(GetItemsNFavlist);
And these are my actions (actions/index.js), where I have the API call function:
import axios from 'axios';
const addUsername = username => ({
type: 'SET_NAME',
username,
});
const setUserInfo = user => ({
type: 'SET_USER',
user,
});
const setItems = items => ({
type: 'SET_ITEMS',
items,
});
const setFavList = favList => ({
type: 'SET_FAVLIST',
favList,
});
const fetchAPIbegin = callHeader => ({
type: 'FETCH_API_BEGIN',
callHeader,
});
const fetchAPIsuccess = payload => ({
type: 'FETCH_API_SUCCESS',
payload,
});
const fetchAPIfailure = error => ({
type: 'FETCH_API_FAILURE',
payload: error,
});
const fetchAPIsuccesResp = payload => ({
type: 'FETCH_API_SUCCESS_RESP',
payload,
});
function handleErrors(response) {
if (!response.ok && response.error) { throw Error(JSON.stringify(response)); }
return response;
}
function fetchAPIcall(url, restAct, options) {
return dispatch => {
dispatch(fetchAPIbegin(url, options));
setTimeout(() => axios[restAct](url, options)
.then(handleErrors)
.then(rsp => {
dispatch(fetchAPIsuccesResp(rsp));
return rsp;
})
.then(resp => resp.data)
.then(jsonResp => dispatch(fetchAPIsuccess(jsonResp)))
.catch(err => dispatch(fetchAPIfailure(`${err}`))), 1000);
};
}
const fetchAPIreset = () => ({ type: 'FETCH_API_RESET' });
export {
addUsername,
setUserInfo,
setItems,
setFavList,
fetchAPIcall,
fetchAPIbegin,
fetchAPIsuccess,
fetchAPIfailure,
fetchAPIreset,
fetchAPIsuccesResp,
};
And Just in case, this is the link to my repo: find-my-item repo.
Thanks in advance!!
Best regards
I am a newbie in React and Next JS, I want to set initial auth user data on initial load from the __app.js. But using dispatch throwing error "Invalid hook call". I know according to docs calling hooks in render function is wrong. but I am looking for an alternate solution to this.
How I can set auth data one-time so that will be available for all the pages and components.
I am including my code below.
/contexts/app.js
import { useReducer, useContext, createContext } from 'react'
const AppStateContext = createContext()
const AppDispatchContext = createContext()
const reducer = (state, action) => {
switch (action.type) {
case 'SET_AUTH': {
return state = action.payload
}
default: {
throw new Error(`Unknown action: ${action.type}`)
}
}
}
export const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, {})
return (
<AppDispatchContext.Provider value={dispatch}>
<AppStateContext.Provider value={state}>
{children}
</AppStateContext.Provider>
</AppDispatchContext.Provider>
)
}
export const useAuth = () => useContext(AppStateContext)
export const useDispatchAuth = () => useContext(AppDispatchContext)
/_app.js
import 'bootstrap/dist/css/bootstrap.min.css'
import '../styles/globals.css'
import App from 'next/app'
import Layout from '../components/Layout'
import { mutate } from 'swr'
import { getUser } from '../requests/userApi'
import { AppProvider, useDispatchAuth } from '../contexts/app'
class MyApp extends App {
render() {
const dispatchAuth = useDispatchAuth()
const { Component, pageProps, props } = this.props
// Set initial user data
const setInitialUserData = async () => {
if (props.isServer) {
const initialData = {
loading: false,
loggedIn: (props.user) ? true : false,
user: props.user
}
const auth = await mutate('api-user', initialData, false)
dispatchAuth({
type: 'SET_AUTH',
payload: auth
})
}
}
//----------------------
// Set initial user data
setInitialUserData()
//----------------------
return (
<AppProvider>
<Layout>
<Component {...pageProps} />
</Layout>
</AppProvider>
)
}
}
MyApp.getInitialProps = async (appContext) => {
let isServer = (appContext.ctx.req) ? true : false
let user = null
let userTypes = {}
// Get user server side
if (isServer) {
await getUser()
.then(response => {
let data = response.data
if (data.status == true) {
// Set user
user = data.data.user
userTypes = data.data.user_types
//---------
}
})
.catch(error => {
//
})
}
//---------------------
return {
props: {
user,
userTypes,
isServer
}
}
}
export default MyApp
I believe this is the intended use of the useEffect hook with an empty array as its second argument:
https://reactjs.org/docs/hooks-effect.html
import {useEffect} from 'react'
class MyApp extends App {
useEffect(()=> {
setInitialUserData()
},[])
render() {
...
}
}
Need help in getting response from a function written inside reducer function
functional component
import {
getAssets,
} from '../../reducers';
const myFunctionalComponent = (props) => {
const dispatch = useDispatch();
const onLinkClick = () => {
dispatch(getAssets());
}
}
return (
<div>
<mainContent />
</div>
)
}
In my reducer
const reducer = (state = initialState, action) => {
switch (action.type) {
case ASSETS_LIST: {
return {
...state,
successToast: true,
isLoading: false,
data: action.payload,
};
}
}
export const listsDispactcher = () => dispatch => {
dispatch({ type: SHOW_LOADER });
performGet(ENDPOINT URL)
.then(response => {
debugger;
const payload = response.data;
dispatch({
type: ASSETS_LIST,
payload: {
...payload,
data: payload.results,
},
});
dispatch({ type: HIDE_LOADER });
})
.catch(err => {
dispatch({ type: GET_ASSETS_ERROR, payload: err });
);
});
};
when i click the link ,am getting my api called in function in reducer and its getting response in newtwork tab in developer console , but how to get the response (that is successToast,data,isLoading )in my functional component and to pass the same to child components ?
I advice you to change the structure of your project. Place all your network calls in a file and call them from your component. It is better for readability and understandability
import {
getAssets,
} from './actions';
const myFunctionalComponent = (props) => {
const dispatch = useDispatch();
const onLinkClick = async () => {
const data = await dispatch(getAssets());
}
}
return (
<div>
<mainContent />
</div>
)
}
In ./actions.js
const getAssets =()=>async dispatch =>{
const res = await axios.get();
dispatch(setYourReduxState(res.data));
return res.data;
}
Now your component will get the data of network call. and Your redux state also will get update
For functional components, to access state stored centrally in redux you need to use useSelector hook from react-redux
import React from 'react'
import { useSelector } from 'react-redux'
export const CounterComponent = () => {
const counter = useSelector(state => state.counter)
return <div>{counter}</div>
}
Official doc:
https://react-redux.js.org/api/hooks#useselector-examples
Also found this working example for you to refer.
https://codesandbox.io/s/8l0sv