I want to have a way to get and fetch the current user using React Context (to not pass props to all my components)
I tried using React Context but didn't understand how I would achieve something like const { currentUser, fetchCurrentUser } = useCurrentUser() from the docs.
here is what i did for my project:
// src/CurrentUserContext.js
import React from "react"
export const CurrentUserContext = React.createContext()
export const CurrentUserProvider = ({ children }) => {
const [currentUser, setCurrentUser] = React.useState(null)
const fetchCurrentUser = async () => {
let response = await fetch("/api/users/current")
response = await response.json()
setCurrentUser(response)
}
return (
<CurrentUserContext.Provider value={{ currentUser, fetchCurrentUser }}>
{children}
</CurrentUserContext.Provider>
)
}
export const useCurrentUser = () => React.useContext(CurrentUserContext)
and then use it like this:
setting up the provider:
// ...
import { CurrentUserProvider } from "./CurrentUserContext"
// ...
const App = () => (
<CurrentUserProvider>
...
</CurrentUserProvider>
)
export default App
and using the context in components:
...
import { useCurrentUser } from "./CurrentUserContext"
const Header = () => {
const { currentUser, fetchCurrentUser } = useCurrentUser()
React.useEffect(() => fetchCurrentUser(), [])
const logout = async (e) => {
e.preventDefault()
let response = await fetchWithCsrf("/api/session", { method: "DELETE" })
fetchCurrentUser()
}
// ...
}
...
the full source code is available on github: https://github.com/dorianmarie/emojeet
and the project can be tried live at: http://emojeet.com/
If useContext returns undefined, then you might have forgotten the Provider or need to move your provider up the stack.
U dont explained what u want to do with the data but...After u exec the function fetch in use effect.
Now u have the object user in the state currentUser.
Try to console log after the use effect the currentUser and see what dat inside it.
After u can use it with currentUser."whatever prop inside"..
Related
I created a Context object named AuthContext to hold user information on the application. After I get the user information with this Context object and do the necessary operations, I save the information with the AsnycStore and direct it to the Main.js page. but sometimes I need to change this information. I created a file named API/index.js and wrote a function that can re-login according to the user's status. when I run this function it will need to trigger a function under the AuthContext I created but I can't call the function in the AuthContext
AuthContext.js
import AsyncStorage from '#react-native-async-storage/async-storage';
import React, { createContext, useEffect, useState } from 'react';
export const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
//const [test, setTest] = useState("test tuta");
const [userToken, setUserToken] = useState(null);
const [userInfo, setUserInfo] = useState(null);
const [isLoading, setIsLoading] = useState(null);
const [guest, setGuest] = useState(null)
const login = (userInfo) => {
setIsLoading(true);
setUserToken(userInfo.kullanici_id);
setUserInfo(userInfo);
AsyncStorage.setItem("userToken", JSON.stringify(userInfo.kullanici_id));
AsyncStorage.setItem("localUserInfo", JSON.stringify(userInfo));
setIsLoading(false)
}
const isGuest = () => {
setIsLoading(true);
setGuest(true);
AsyncStorage.setItem("guest", "true");
setIsLoading(false)
}
const test= ()=>{ //I will call this function in API/index.js
console.log("test log")
}
const logout = () => {
setIsLoading(true);
setUserToken(null);
setUserInfo(null);
setGuest(null);
AsyncStorage.removeItem("userToken");
AsyncStorage.removeItem("localUserInfo");
AsyncStorage.removeItem("guest")
setIsLoading(false)
}
const isLoggedIn = async () => {
try {
setIsLoading(true);
let userToken = await AsyncStorage.getItem("userToken");
setUserToken(userToken);
let userInfo = await AsyncStorage.getItem("localUserInfo");
setUserInfo(JSON.parse(userInfo));
console.log("------------- userlocal")
console.log(userInfo);
setIsLoading(false);
} catch (e) {
console.log("isLoggenIn error ${e}")
}
}
const isGuestIn = async () => {
try {
setIsLoading(true);
let guestToken = await AsyncStorage.getItem("guest");
setGuest(guestToken);
setIsLoading(false);
} catch (e) {
console.log("isLoggenIn error ${e}")
}
}
useEffect(() => {
isLoggedIn(),
isGuestIn()
}, [])
return (
<AuthContext.Provider value={{ login, logout, isLoading, userToken, guest, isGuest,userInfo,deneme }}>
{children}
</AuthContext.Provider>
)
}
API/index.js
import AsyncStorage from "#react-native-async-storage/async-storage";
import axios from "axios";
import { useContext } from "react";
import { BASE_URL } from "../config";
import { AuthContext,AuthProvider } from "../context/AuthContext";
export const oturumKontrol = async () => {
const { test} = useContext(AuthContext);
test(); //Im using test function inside AuthContext
console.log("oturum kontrol")
}
Error
Possible Unhandled Promise Rejection (id: 0):
Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
How can I access the function inside the AuthContext?
You cannot use hooks in normal functions. They must be used in functional components at top level.
You can pass the function from the hook as an argument to another function and use it like that.
//SomeComponent where you want to call oturumKontrol
const { test} = useContext(AuthContext); // get function
//call this on press or in useEffect
const handleTest = async () => {
await oturumKontrol(test) //pass function
}
export const oturumKontrol = async (test) => {
test(); //call function
console.log("oturum kontrol")
}
Make sure that you pass test function in your AuthContext first
Case
I want to make isLoading (global state using React Context) value and changeIsLoading function (its changing function from IsLoadingContext.js file) becomes accessible to all files (function components and simple javascript functions).
I know that React Hooks can only be called inside of the body of a function component.
Question: So in my case here, how could I called isLoading and changeIsLoading inside a util file (non-function component or just a simple javascript function)?
What should I change from the code?
Code flow
(location: SummariesPage.js) Click the button inside SummariesPage component
(location: SummariesPage.js) Call onApplyButtonIsClicked function in SummariesPage component
(location: SummariesPage.js) Change isLoading global state into true then call fetchAPISummaries function
(location: fetchAPISummaries.js) Call fetchAPICycles function
(location: fetchAPICycles.js) Call exportJSONToExcel function
(location: exportJSONToExcel.js) Export the JSON into an Excel file then change isLoading global state into false
IsLoadingContextProvider component will be rerendered and the isLoading value in SummariesPage will be true
Error logs
Uncaught (in promise) Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
The code
IsLoadingContext.js:
import React, { useState } from 'react'
const IsLoadingContext = React.createContext()
const IsLoadingContextProvider = (props) => {
const [isLoading, setIsLoading] = useState(false)
const changeIsLoading = (inputState) => {
setIsLoading(inputState)
}
return(
<IsLoadingContext.Provider
value={{
isLoading,
changeIsLoading
}}
>
{props.children}
</IsLoadingContext.Provider>
)
}
export { IsLoadingContextProvider, IsLoadingContext }
SummariesPage.js:
import React, { useContext } from 'react'
// CONTEXTS
import { IsLoadingContext } from '../../contexts/IsLoadingContext'
// COMPONENTS
import Button from '#material-ui/core/Button';
// UTILS
import fetchAPISummaries from '../../utils/export/fetchAPISummaries'
const SummariesPage = () => {
const { isLoading, changeIsLoading } = useContext(IsLoadingContext)
const onApplyButtonIsClicked = () => {
changeIsLoading(true)
fetchAPISummaries(BEGINTIME, ENDTIME)
}
console.log('isLoading', isLoading)
return(
<Button
onClick={onApplyButtonIsClicked}
>
Apply
</Button>
)
}
export default SummariesPage
fetchAPISummaries.js:
// UTILS
import fetchAPICycles from './fetchAPICycles'
const fetchAPISummaries = (inputBeginTime, inputEndTime) => {
const COMPLETESUMMARIESURL = .....
fetch(COMPLETESUMMARIESURL, {
method: "GET"
})
.then(response => {
return response.json()
})
.then(responseJson => {
fetchAPICycles(inputBeginTime, inputEndTime, formatResponseJSON(responseJson))
})
}
const formatResponseJSON = (inputResponseJSON) => {
const output = inputResponseJSON.map(item => {
.....
return {...item}
})
return output
}
export default fetchAPISummaries
fetchAPICycles.js
// UTILS
import exportJSONToExcel from './exportJSONToExcel'
const fetchAPICycles = (inputBeginTime, inputEndTime, inputSummariesData) => {
const COMPLETDEVICETRIPSURL = .....
fetch(COMPLETDEVICETRIPSURL, {
method: "GET"
})
.then(response => {
return response.json()
})
.then(responseJson => {
exportJSONToExcel(inputSummariesData, formatResponseJSON(responseJson))
})
}
const formatResponseJSON = (inputResponseJSON) => {
const output = inputResponseJSON.map(item => {
.....
return {...item}
})
return output
}
export default fetchAPICycles
exportJSONToExcel.js
import { useContext } from 'react'
import XLSX from 'xlsx'
// CONTEXTS
import { IsLoadingContext } from '../../contexts/IsLoadingContext'
const ExportJSONToExcel = (inputSummariesData, inputCyclesData) => {
const { changeIsLoading } = useContext(IsLoadingContext)
const sheetSummariesData = inputSummariesData.map((item, index) => {
let newItem = {}
.....
return {...newItem}
})
const sheetSummaries = XLSX.utils.json_to_sheet(sheetSummariesData)
const workBook = XLSX.utils.book_new()
XLSX.utils.book_append_sheet(workBook, sheetSummaries, 'Summaries')
inputCyclesData.forEach(item => {
const formattedCycles = item['cycles'].map((cycleItem, index) => {
.....
return {...newItem}
})
const sheetCycles = XLSX.utils.json_to_sheet(formattedCycles)
XLSX.utils.book_append_sheet(workBook, sheetCycles, item['deviceName'])
})
XLSX.writeFile(workBook, `......xlsx`)
changeIsLoading(false)
}
export default ExportJSONToExcel
I believe the real problem you are facing is managing the asynchronous calls. It would be much readable if you use async/await keywords.
const onApplyButtonIsClicked = async () => {
changeIsLoading(true)
await fetchAPISummaries(BEGINTIME, ENDTIME)
changeIsLoading(false)
}
You will need to rewrite fetchAPICycles to use async/await keywords instead of promises.
const fetchAPICycles = async (
inputBeginTime,
inputEndTime,
inputSummariesData
) => {
const COMPLETDEVICETRIPSURL = ...;
const response = await fetch(COMPLETDEVICETRIPSURL, {
method: "GET",
});
const responseJson = await response.json();
exportJSONToExcel(inputSummariesData, formatResponseJSON(responseJson));
};
I would like to ask you how to reload a component after modifying the data of a form, then I have my component:
export default function MyComponent() {
const url = "/api/1";
const [resData, setResData] = useState(null);
useEffect(() => {
const jwt = getJwt();
const fetchData = async () => {
const resP = await axios(url);
setResData(resP.data);
};
fetchData();
}, []);
return <EditComponent={resData} />
}
This component passes my data to the "EditCompoent" child component in which there is a form that is filled with data from the parent component that I can modify in which there is a save button that when I click allows me to send the modified data to my beckend:
const handleConfirm = () => {
axios.put(url, data).then((res) => {
//Reload Component
})
}
I would like to be able to reload the parent component as soon as this works is successful what could I do? I don't want to reload the whole page I just want to reload the parent component that is "MyComponent", I hope I have well posed the problem.
I'd pass the whole useEffect callback down so that handleConfirm can call it again after the axios.put, after which the resData state in the parent will be updated:
export default function MyComponent() {
const url = "/api/1";
const [resData, setResData] = useState(null);
const tryLoginJWT = () => {
const jwt = getJwt();
const resP = await axios(url);
setResData(resP.data);
};
useEffect(tryLoginJWT, []);
return <EditComponent {...{ resData, tryLoginJWT }} />
}
const handleConfirm = () => {
axios.put(url, data)
.then(tryLoginJWT)
.catch(handleErrors); // don't forget to catch here in case there's a problem
}
I have a react class component with rather lengthy onSubmit function that I have put into another file in order to keep the code a bit tidier.
I tried to convert the class component to a functional one, replacing all of my state and setState functions with useState but now my useState state updaters are returning undefined inside the imported function. Am I able to update state using an imported function with a functional component? The function worked fine when it was imported into a class component and my state updater was setState();
//Imported function in utils.js
export const loginUser = async function (email, password) {
try {
const login = await axios.post('http://localhost:5000/api/v1/auth/login', {
email,
password
});
const options = {
headers: {
Authorization: `Bearer ${login.data.token}`
}
};
const getUser = await axios.get(
'http://localhost:5000/api/v1/auth/me',
options
);
const user = getUser.data.data;
setAuthenticated(true);
setUser(getUser.data.data);
setEmail('');
setPassword('');
localStorage.setItem('user', JSON.stringify(user));
console.log(localStorage.getItem('user'));
} catch (err) {
console.log(err);
}
};
// Functional component with imported function
import React, { useState } from 'react';
import axios from 'axios';
import PropTypes from 'prop-types';
import { Login } from './Login';
const { loginUser } = require('../utils/utils');
export const Splash = () => {
const [user, setUser] = useState(null);
const [error, setError] = useState(null);
const [authenticated, setAuthenticated] = useState(false);
const [msg, setMsg] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const _handleEmail = (e) => {
setEmail(e.target.value);
};
const _handlePass = (e) => {
setPassword(e.target.value);
};
const _handleSubmit = async (e) => {
e.preventDefault();
loginUser(email, password);
if (user) {
console.log(user);
this.props.onHandleUser(user);
}
};
return (
<div className='splashStyle'>
{!authenticated && (
<Login
handleEmail={_handleEmail}
handlePass={_handlePass}
handleSubmit={_handleSubmit}
isAuthenticated={authenticated}
/>
)}
</div>
);
};d
EDIT: My issue that setAuthenticated, setUser, setEmail, and setPassword are coming undefined in utils.js
Thanks!
One way of achieving that would be passing all the set methods as a paramters to loginUser function.
But a better way of doing this will be like:
create two separate files
1 for login api call like :
login.js
function login(email, password){
const login = await axios.post('http://localhost:5000/api/v1/auth/login', {
email,
password
});
return login.data;
}
another for getting data
getProfile.js
function getProfile(token){
const options = {
headers: {
Authorization: `Bearer ${token}`
}
};
const getUser = await axios.get(
'http://localhost:5000/api/v1/auth/me',
options
);
return getUser.data;
}
Now do you setting state stuff in actuall component submit call function like
const _handleSubmit = async (e) => {
e.preventDefault();
const token = await login(email, password);
const user = await getProfile(token);
if (user) {
console.log(user);
props.onHandleUser(user);
setAuthenticated(true);
setUser(getUser.data.data);
setEmail('');
setPassword('');
localStorage.setItem('user', JSON.stringify(user));
console.log(localStorage.getItem('user'));
}
};
You need to pass the setAuthenticated function to the loginUser function before calling it in that.
return an onSubmiHandler function from your login user hook.
const doLogin = (email , password) => {
/// your code here
}
return {doLogin}
then use the doLogin function inside your main component
//inside functional component
const {doLogin} = loginUser();
onSubmit = () => doLogin(email, password)
for more you can see how to use custom hooks from here
https://reactjs.org/docs/hooks-custom.html
To start loginUser can't know about the setState you insert there try to pass it as arguments and it will fix it 😁
another problem I see is that you use the this keyword and in the functional component you use the just props.
and just for you to know don't pass null as the initial value pass an empty string, number, etc..
Update
this is how you also pass a setState as argument
loginUser((e)=>setEmail(e))
I have a custom hook to fetch data on form submit
export const getIssues = ({ user, repo }) => {
const [issues, setIssues] = useState([]);
const handleInputChange = (e) => {
e.preventDefault();
axios.get(`https://api.github.com/repos/${user}/${repo}/issues`)
.then((response) => {
setIssues(response.data);
})
.catch((err) => console.log(err));
};
return {
issues,
onSubmit: handleInputChange,
};
};
In my component I call it like this
const response = getIssues({ user: user.value, repo: repo.value })
return (
<form className={css['search-form']} {...response}>...</form>
)
The problem is that I want to get my issues value from the hook in another component. For that I wanted to use Context. But I have no idea how to do it.
I could call this function and pass it to Provider, but I can't call it without arguments. So I kind of stuck.
All the help will be much appreciated.
You are right by saying you need React.Context to handle this situation.
You need to wrap your components into this context.
import React from "react";
const IssuesStateContext = React.createContext();
const IssuesDispatchContext = React.createContext();
function issuesReducer(state, action) {
switch (action.type) {
case "setIssues": {
return [...action.payload];
}
default: {
throw new Error(`Unhandled action type: ${action.type}`);
}
}
}
function IssuesProvider({ children }) {
const [state, dispatch] = React.useReducer(issuesReducer, []);
return (
<IssuesStateContext.Provider value={state}>
<IssuesDispatchContext.Provider value={dispatch}>
{children}
</IssuesDispatchContext.Provider>
</IssuesStateContext.Provider>
);
}
function useIssuesState() {
const context = React.useContext(IssuesStateContext);
if (context === undefined) {
throw new Error("useIssuesState must be used within a IssuesProvider");
}
return context;
}
function useIssuesDispatch() {
const context = React.useContext(IssuesDispatchContext);
if (context === undefined) {
throw new Error("useIssuesDispatch must be used within a IssuesProvider");
}
return context;
}
export { IssuesProvider, useIssuesState, useIssuesDispatch };
By using this separation in context you will be able to set issues coming from github in one component and render them in a completely different one.
Example:
App.js
ReactDOM.render(
<IssuesProvider>
<Component1 />
<Component2 />
</IssuesProvider>
)
Component 1
import React from 'react'
import { useIssuesDispatch } from './issues-context'
function Component1() {
const dispatch = useIssuesDispatch()
// fetch issues
// .then dispatch({ type: 'setIssues', payload: response })
// render
}
Component 2
import React from 'react'
import { useIssuesState } from './issues-context'
function Component2() {
const issues = useIssuesState()
// if issues.length > 0 ? render : null
}
You can write a Issues context provider that will provide {issues,useIssues} where issues are the issues and useIssues is a function that takes {user,repo}.
export const Issues = React.createContext();
export default ({ children }) => {
const [issues, setIssues] = useState([]);
const useIssues = ({ user, repo }) => {
useEffect(() => {
axios
.get(
`https://api.github.com/repos/${user}/${repo}/issues`
)
.then(response => {
setIssues(response.data);
})
.catch(err => console.log(err));
}, [user, repo]);
return issues;
};
return (
<Issues.Provider value={{ issues, useIssues }}>
{children}
</Issues.Provider>
);
};
The component that has all the components that need issues can import this issues provider:
import IssuesProvider from './IssuesProvider';
export default () => (
<IssuesProvider>
<ComponentThatNeedsIssues />
<ComponentThatSetsAndGetsIssues />
</IssuesProvider>
);
For a component that needs to set issues you can get useIssues from context:
const { useIssues } = useContext(Issues);
const issues = useIssues({user,repo});
For a component that only needs issues:
const { issues } = useContext(Issues);
To see it all work together there is a codepen here