How to create context from axios response?
import React, { createContext } from 'react';
import axios from 'axios';
const AppContext = createContext({
lang: 'en',
currency: 'USD',
name: 'Demo store'
});
const token = JSON.parse(localStorage.getItem('token'));
axios.get('http://localhost:3000/storedata', {
headers: {
'Authorization' : `Bearer ${token}`
}
})
.then(response => {
//set context
})
.catch(error => {
});
export default AppContext;
Header.js
import React, { useContext } from 'react';
import AppContext from '../../context/AppContext';
const Header = props => {
const appContext = useContext(AppContext);
console.log(appContext);
}
This is my code. I was storing lang, currency and name in local storage when login and getting values from local storage wherever need.
Now I want to store in global state when login and pass value to other components. I am not able to create context from API call.
In my react apps I fetch data to context like that
// context.js
export const MyContextData = createContext(null); // null is the default value
export const MyContext = (props) => {
const [myState, SetMyState] = useState(null);
useEffect(() => {
axios
.get("http://localhost:3000/storedata", {
headers: {
Authorization: `Bearer ${token}`,
},
})
.then((response) => {
setMyState(response); // update your state
})
.catch((error) => {
// handle errors
});
}, []);
return (
<MyContextData.Provider
value={myState} // value of your context
>
{props.children}
</MyContextData.Provider>
);
};
// index.js
//wrap your app with the context so the whole app re-render when the context update
<MyContext>
<App />
</MyContext>;
// app.js
const App = () => {
const context = useContext(MyContextData);
// if the context is null then the data have not been fetched yet
if (!context) {
return; // maybe a loading indicator
} else {
return; //data have been fetched and you can use it
}
};
You need to create a context provider and store the settings in a state:
Context
const AppContext = createContext();
const ContextProvider = () => {
const [settings, setSettings] = useState({
lang: 'en',
currency: 'USD',
name: 'Demo store'
});
// In your axios call, use setSettings to update the settings
return <Context.Provider value={{settings}}/>
};
Related
I am using tokens around my ReactJS application. This is how I can get the value of the token in a page:
pages/Profile.js
import React from 'react';
import useToken from '../useToken';
export default function Profile() {
// Get token
const { token, setToken } = useToken();
const { userObject } = useToken();
return(
<div>
<p>
<b>Name:</b> {userObject?.name}<br />
<b>Email:</b> {userObject?.email}<br />
<b>Token:</b> {token}
</p>
</div>
);
}
Now I want to use the token inside a component. However the same appoch did not work, as it gives me an error "Unexpected token. A constructor, method, accessor, or property was expected.ts(1068)".
components/MyKnownDevices.js
import React from 'react';
import axios from 'axios';
import useToken from '../useToken';
export default class MyKnownDevices extends React.Component {
// Get token
const { token, setToken } = useToken(); // <---- THIS GIVES ERROR
const { userObject } = useToken(); // <---- THIS GIVES ERROR
// Respone handler
state = {
myKnownDevices: []
}
// Call API
componentDidMount() {
let config = {
headers: {
Accept: 'application/json',
'Access-Control-Allow-Origin': '*',
rejectUnauthorized: false,
}
}
let data = {
'HTTP_CONTENT_LANGUAGE': 'no',
rejectUnauthorized: false,
}
axios.get('https://127.0.0.1:5000/api/users/get_my_known_devices', data, config)
.then(res => {
const myKnownDevices = res.data;
this.setState({ myKnownDevices });
})
};
render() {
return (
<ul>
{
this.state.myKnownDevices
.map((device, index) => {
return (
<li key={index}>
<span>{device.known_device_updated_timestamp_saying}</span>
</li>
);
}
)
}
</ul>
)
};
};
useToken.js
import { useState, useEffect } from 'react';
import jwt_decode from "jwt-decode"
// const userObject = null;
export default function useToken() {
// Get token
const getToken = () => {
const userToken = localStorage.getItem('token');
return userToken
};
// Consts
const [token, setToken] = useState(getToken());
const [userObject, setUserObject] = useState(null);
// Effect
useEffect(() => {
if(token && token !== "undefined" && !userObject){
setUserObject(jwt_decode(token));
}
},[token]);
// Save token
const saveToken = userToken => {
if(userToken === null){
localStorage.removeItem('token');
setToken(null);
}
else{
localStorage.setItem('token', userToken.token);
setToken(userToken.token);
}
};
// Return value
return {
setToken: saveToken,
token,
userObject,
}
}
How can I get the value of the token inside my component MyKnownDevices.js?
This issue here is that useToken is a hook (https://reactjs.org/docs/hooks-intro.html) and hooks can only be used in functional components.
Given your code, I think the simplest way would be to rewrite your class component as a functional component, this should do the trick :
import React, {useEffect, useState} from "react";
import axios from "axios";
import useToken from "../useToken";
export default function MyKnownDevices() {
// Get token
const { token, setToken, userObject } = useToken();
const [myKnownDevices, setMyKnownDevices] = useState([]);
// Call API
// This is the same thing as your componentDitMount
useEffect(() => {
let config = {
headers: {
Accept: "application/json",
"Access-Control-Allow-Origin": "*",
rejectUnauthorized: false,
},
};
let data = {
HTTP_CONTENT_LANGUAGE: "no",
rejectUnauthorized: false,
};
axios
.get(
"https://127.0.0.1:5000/api/users/get_my_known_devices",
data,
config
)
.then((res) => {
const myKnownDevices = res.data;
setMyKnownDevices(myKnownDevices);
});
}, []);
return (
<ul>
{myKnownDevices.map((device, index) => {
return (
<li key={index}>
<span>{device.known_device_updated_timestamp_saying}</span>
</li>
);
})}
</ul>
);
}
You must not use react hooks inside class components. You should rewrite your class component to functional
created a state using redux-toolkit but right after i refresh page or go to another one the state resets. i've been broking my head for 4 hours, can't stand it anymore.
there is code on github gists
Also here it is:
index.ts file: configuration of redux store
import { configureStore } from "#reduxjs/toolkit";
import authStateSlice from "redux/authStateSlice";
import deviceTypeReducer from "redux/deviceTypeSlice";
import userDataSlice from "redux/userDataSlice";
const store = configureStore({
reducer: {
isMobile: deviceTypeReducer,
isLogin: authStateSlice,
userData: userDataSlice,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;
signin.tsx: there i fetch data from a server and put it in my redux state:
import React, { useEffect, useState } from "react";
import { useAppSelector } from "lib";
import { useAppDispatch } from "../../lib/useAppDispatch";
import { userActionState } from "redux/userDataSlice";
export const SignIn: React.FC = () => {
const { isUser } = useAppSelector((state) => state.userData);
console.log("IS USER: ", isUser);
const dispatch = useAppDispatch();
const loginHandler = async () => {
try {
const response = await fetch(
"...link to api",
{
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({ email, password }),
}
);
if (!response.ok) {
const err = await response.json();
throw new Error(err)
}
const data = await response.json();
dispatch(userActionState({ isUser: true }));
} catch (error) {
throw new Error(err)
}
};
return (
<div>
...
</div>
);
};
userDataSlice.tsx: my user's data slice:
import { createSlice } from "#reduxjs/toolkit";
import { ResponseLoginDataType } from "#types";
const initialState: {
isUser: boolean | undefined;
} = {
isUser: undefined,
};
const userDataSlice = createSlice({
name: "isUser",
initialState,
reducers: {
userActionState(
state,
action: {
payload: {
isUser: boolean | undefined;
};
}
) {
state.isUser = action.payload.isUser;
},
},
});
export default userDataSlice.reducer;
export const { userActionState } = userDataSlice.actions;
userInfo.tsx: there i tried to get that state, but it gives my default one:
import React, { useEffect, useState } from "react";
import { useAppSelector } from "lib";
export const UserInfo: React.FC<UserInfo> = ({
name,
nickname,
picture,
activity,
active,
}) => {
const { isUser } = useAppSelector((state) => state.userData);
console.log("USER:: ", isUser);
return (
<>
.....
</>
);
};
Your Redux state doesn't persist to any non-volatile storage, such as local storage or cookies. redux-persist could be an option for you to keep your application's state.
This blog post might help: https://blog.logrocket.com/persist-state-redux-persist-redux-toolkit-react/
When you refresh or leave page, you are restarting the redux store so it will always go back to the default. If you want to persist the store you will need to use a tool like redux-persist. Here is how you could setup your store with redux-persist.
One thing that is annoying is making sure your redux store persists nested objects, secondly since you are in typescript don't try to persist the root reducer.
import storage from "redux-persist/lib/storage";
import autoMergeLevel2 from "redux-persist/lib/stateReconciler/autoMergeLevel2";
import persistReducer from "redux-persist/es/persistReducer";
import persistStore from "redux-persist/es/persistStore";
const persistConfigOne = {
key: "ex1",
storage,
stateReconciler: autoMergeLevel1, // Shallow level persisted objects
};
const persistConfigTwo = {
key: "ex2",
storage,
stateReconciler: autoMergeLevel2, // This will persist deeper nested objects if needed.
};
//Can't get state typings if persisting root reducer. Persisted by reducer works.
const reducers = combineReducers({
ex1: persistReducer<Ex1State, any>(persistConfigOne, ex1Reducer),
ex2: persistReducer<Ex2State, any>(persistConfigTwo, ex2Reducer),
});
export const store = configureStore({
reducer: reducers,
devTools: process.env.NODE_ENV !== "production", // May want to add this for Redux Dev Tools
middleware: [thunk], // Probably will need to use thunk at some point
});
I want to integrate to React query for fetching the data from server.
So far I've been fetching the rest api via Axios.
I have created a custom hook for fetching and want to transform and implement with react query.
While trying to implement the same logic I encountered an error trying to destructure the fetched data const { data } = useApiRequest(headersUrl):
error - TypeError: (0 , _hooks_useApiRequest__WEBPACK_IMPORTED_MODULE_1__.UseApiRequest) is not a function
Here is the old logic for fetching
import * as React from "react";
import { useState, useEffect } from "react";
import axios from "axios";
import { HeaderToken } from "../services/api";
export const useApiRequest = (url: any) => {
const [data, setData] = useState<[] | any>([]);
useEffect(() => {
const fetchData = () => {
axios
.get(url, {
headers: {
Authorization: `Basic ${HeaderToken}`,
},
})
.then((response) => {
setData(response.data);
})
.catch((error) => console.error(error));
};
fetchData();
}, [url]);
return { data };
};
And here is how I'm trying to convert it:
import { HeaderToken } from "../services/api";
import { useQuery } from "react-query";
export const useApiRequest = (url: any) => {
const { isLoading, data } = useQuery("bc", async () => {
const response = await fetch(url, {
method: "get",
headers: {
Authorization: `Basic ${HeaderToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) throw new Error(response.statusText);
return await response.json();
});
return { data };
};
I can't see the problem, actually, I tried the same code you shared with a local API I have and it's working
The Hook
import { useQuery } from 'react-query'
export const clientAPI = (url) => {
const { isLoading, data } = useQuery("bc", async () => {
const response = await fetch(url, {
method: "get"
});
if (!response.ok) throw new Error(response.statusText);
return await response.json();
});
return { data };
};
React Component
import * as React from "react";
import { clientAPI } from "../hooks/clientAPI";
export default function Home() {
const { data } = clientAPI('http://localhost:5000/')
return (
<div>
{JSON.stringify(data)}
</div>
)
}
_app.js
import { QueryClient, QueryClientProvider, useQuery } from 'react-query'
const queryClient = new QueryClient()
function MyApp({ Component, pageProps }) {
return (<QueryClientProvider client={queryClient}>
<Component {...pageProps} />
</QueryClientProvider>)
}
export default MyApp
I'm using next#11.1.2, react-query#3.28.0
what are the versions you are using?
I have two custom hooks i.e useFetch and useAuth. useAuth has all API calls methods (e.g logIn, logOut, register, getProfile etc) and they use useFetch hook method for doing API calls. useFetch also uses these methods for example logOut method when API return 401, setToken etc. So, they both need to share common methods. But that results into circular dependency and call size stack exceeded error. How to manage this
UseFetch.js
import React, { useState, useContext } from "react";
import { AuthContext } from "../context/authContext";
import { baseURL } from "../utils/constants";
import { useAuth } from "./useAuth";
const RCTNetworking = require("react-native/Libraries/Network/RCTNetworking");
export const useFetch = () => {
const {token, setAuthToken, isLoading, setIsLoading, logIn, logOut} = useAuth();
const fetchAPI = (method, url, body, isPublic, noBaseURL) => {
setIsLoading(true);
const options = {
method: method
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
},
};
return fetch(url, options, isRetrying).then(() => {
......
})
......
};
return { fetchAPI };
};
UseAuth.js
import React, { useContext, useEffect } from "react";
import { AuthContext } from "../context/authContext";
import { useFetch } from "./useFetch";
export const useAuth = () => {
const {
removeAuthToken,
removeUser,
setUser,
...others
} = useContext(AuthContext);
const { fetchAPI } = useFetch();
const register = (body) => {
return fetchAPI("POST", "/customers/register", body, true);
};
const logIn = (body) => {
return fetchAPI("POST", "/customers/login", body, true);
};
const logOut = () => {
return (
fetchAPI("POST", "/customers/logout")
.catch((err) => console.log("err", err.message))
.finally(() => {
removeAuthToken();
removeUser();
})
);
......
};
return {
...others,
register,
logIn,
logOut,
};
};
I am still navigating React hooks and contexts and have been attempting to use both to store user information for further usage in other portions of my app after successful authentication from an axios request. With my code that follows, I successfully set the state used in the context, but when the state is accessed following a redirect that occurs directly after setting the value, it comes back as undefined and I'm not sure what is preventing the value from being stored.
Provided is my context and hook (AppSession.js):
import React, { createContext, useContext, useState } from 'react'
export const SessionContext = createContext(null);
const AppSession = ({ children }) => {
const [user, setUser] = useState()
if (user){
console.log("useState: Authenticated")
console.log(user)
} else {
console.log("useState: Not authenticated")
console.log(user)
}
return (
<SessionContext.Provider value={{user, setUser}}>
{children}
</SessionContext.Provider>
)
}
export const getUserState = () => {
const { user } = useContext(SessionContext)
return user;
}
export const updateUserState = () => {
const { setUser } = useContext(SessionContext)
return (user) => {
setUser(user);
}
}
export default AppSession;
**Provided is the axios request and console logs upon successful response (login.js):**
axios.post(
'/api/auth/signin/',
{ email, password },
{
headers: {
'Content-Type': 'application/json'
},
withCredentials: true
}).then((res) => {
console.log(res.data) // {authenticated: true, user_id: "071c7b80-6b4d-462c-8c4a-4fa613a7e8b6", user_email: "Alysson_Runolfsdottir#yahoo.com"}
const data = res.data; //
console.log("updateUserState")
setUser(data)
}).then(()=> {
return window.location = '/app/profile/'
}).catch((err) => {
console.log(err)
})
// Console.logs
{ authenticated: true, user_id: "071c7b80-6b4d-462c-8c4a-4fa613a7e8b6", user_email: "Alysson_Runolfsdottir#yahoo.com" } // login.js
updateUserState // login.js
useState: Authenticated // AppSession.js
{ authenticated: true, user_id: "071c7b80-6b4d-462c-8c4a-4fa613a7e8b6", user_email: "Alysson_Runolfsdottir#yahoo.com" } // AppSession.js
Then the code for profile.js which is the result of redirect to /app/profile with console logs:
import React from 'react'
import { getUserState } from '../../contexts/AppSession'
import Layout from '../../components/Universal/Layout'
export default function Profile(props) {
const checkUser = getUserState()
console.log(checkUser)
console.log(props)
return (
<Layout
title="Signin"
description="TEST"
>
<h1>Protected Page</h1>
<p>You can view this page because you are signed in.</p>
<br />
<b>Check User: {checkUser}</b>
</Layout>
)
}
// Console.logs
useState: Not authenticated // AppSession.js
undefined // AppSession.js (console.log(user))
undefied // profile.js (console.log(checkUser))
As you can see the storage is short-lives as the subsuquent page that loads upon redirect access the user state and it is undefined. Any idea why this might be?