React Native useContext Hooks Issue - javascript

I would like to encapsulate the logic for a function to send push notifications so that it can be called from anywhere in the app. The push notifications API requires a jwtToken to be passed in the header. I want to call useContext on the AuthContextProvider to extract the token but the rules of hooks don't allow my function to call useContext since it isn't a React function component.
import React, { useContext } from "react";
import { AuthContext } from "#context/AuthContextProvider";
export const sendPushNotification = async function (notificationObject) {
const { authData } = useContext(AuthContext); //**<-- Fails with hooks error**
const authToken = authData.signInUserSession.idToken.jwtToken;
...
Here is the code that calls the function:
function AddToDoScreen() {
...
function handleSubmitAsync() {
...
let pushNotificationObject = {
profileIDs: [values.profileID],
title: "Push Notification Title",
body: "Push Notification Message",
};
sendPushNotification(pushNotificationObject);
I think this can be accomplished with a custom hook, but I'm not sure how. I tried starting the function name with use, but that didn't help.
Any pointers on how to implement this so I don't have to get the IdToken in each function that wants to call the sendPushNotification function?
BTW, I know you can't call Hooks inside nested functions. I tried moving the code up into the AddToDoScreen function but got the same error.

You've basically two options here:
Create a custom hook that encapsulates all the logic and returns a callback function a component can invoke.
import React, { useCallback, useContext } from "react";
import { AuthContext } from "#context/AuthContextProvider";
export const usePushNotification = () => {
const { authData } = useContext(AuthContext);
const authToken = authData.signInUserSession.idToken.jwtToken;
const sendPushNotification = useCallback(async (notificationObject) => {
... logic to use token and notification object and send push ...
}, [authToken]);
return {
sendPushNotification
};
};
function AddToDoScreen() {
const { sendPushNotification } = usePushNotification();
...
function handleSubmitAsync() {
...
const notification = {
profileIDs: [values.profileID],
title: "Push Notification Title",
body: "Push Notification Message",
};
sendPushNotification(notification);
}
...
}
Access the authToken value from the context in the React component and pass it to the callback function.
export const sendPushNotification = async ({
authToken,
notification,
}) => {
... logic to use token and notification object and send push ...
};
function AddToDoScreen() {
const { authData } = useContext(AuthContext);
const authToken = authData.signInUserSession.idToken.jwtToken;
...
function handleSubmitAsync() {
...
const notification = {
profileIDs: [values.profileID],
title: "Push Notification Title",
body: "Push Notification Message",
};
sendPushNotification({
authToken,
notification,
});
}
...
}

Related

React Function to Component and Setting State with Function Return

here's the jist of where I'm stuck (or just read the title for my question).
I have a firebase.js file where I have functions to authenticate. signinGithub, signinGoogle, signinEmail and so forth. The Firebase Auth business logic is in these functions.
I am showing errors with console.log or alert from these functions. The functions are imported into a Component and I don't know how to capture the functions result into the component by somehow setting state from this out-of-component function file.
Here's a basic example:
firebase.js
...
const signInWithGitHub = async () => {
try {
const res = await signInWithPopup(auth, githubProvider)
const user = res.user
} catch (err) {
alert(err) // ** I want to pass "err" from here to Login
// ** component by updating Logins state for a message
}
}
export {signinWithGitHub}
...
Login.jsx
import React, { useEffect, useState } from "react"
import { useAuthState } from "react-firebase-hooks/auth"
import {
auth,
signInWithGitHub
} from "../lib/firebase"
function Login() {
const [user, loading, error] = useAuthState(auth)
render(
{* Below is the method call from the imported custom firebase function *}
<button onClick={signInWithGitHub}>
Login with GitHub
</button>
)
}
...
I was thinking something like this but I can't fully resolve it in my mind:
Set state in Login.js const [message, setMessage] = useState('')
When the imported signinWithGitHub has an error message --
I'm stuck figuring out how to apply to function message to the state, any ideas?
You can create a custom function inside your Login. jsx file to call the original signInWithGitHub method with a try catch block. And more importantly, you should not use render inside a functional component. Use return to render the JSX in DOM.
firebase.js
export const signInWithGitHub = async () => {
try {
const res = await signInWithPopup(auth, githubProvider);
const user = res.user;
} catch (err) {
throw new Error(err?.message || "Unable to sign in with GitHub");
}
};
Login.jsx
import React, { useEffect, useState } from "react";
import { useAuthState } from "react-firebase-hooks/auth";
import { auth, signInWithGitHub } from "../lib/firebase";
function Login() {
const [user, loading, error] = useAuthState(auth);
const [errorMessage, setErrorMessage] = useState("");
const onLogin = async () => {
try {
await signInWithGitHub();
} catch (err) {
setErrorMessage(err);
}
};
return (
<>
<button onClick={onLogin}>Login with GitHub</button>
{!!errorMessage && <h5>{errorMessage}</h5>}
</>
);
}

React js how to axios post from outside of component

I am trying to write a function that will handle getting data to and from a server. This function takes the url to contact and uses the token to authorize itself against the server. This function is quite long. I would therefore want every other page in my react app to call this function with the needed url and then let this function handle everything else. I therefore need each page to await this function but I get "Error: Invalid hook call" no matter what I try.
This is the function that handles post requests to the server:
import React, { useEffect, useState, createRef, lazy, useContext } from "react";
import { UserContext } from "./UserContext";
import jwt_decode from "jwt-decode";
import axios from "axios";
export async function getProtectedAsset(url) {
const { user, setUser } = useContext(UserContext);
//If we do not have a token
if (user["userID"] == -1) {
return "Error: No token";
} else {
try {
//Get user data
const token = {
accessToken: user["accessToken"],
email: user["email"],
userID: user["userID"],
};
//Check if accessToken is about to expire (60s mairgain)
if (
Date.now() >=
jwt_decode(token["accessToken"])["exp"] * 1000 - 60000
) {
//Get new token
const res = await axios
.post("http://127.0.0.1:5002/refreshtoken", {
token: user["refreshToken"],
})
.then((res) => {
setUser({
userID: user["userID"],
email: user["email"],
accessToken: res.data["accessToken"],
refreshToken: user["refreshToken"],
accountType: user["accountType"],
});
})
.catch((err) => {
console.error(err);
});
}
//Our token is fresh
else {
const res = await axios
.post(url, token)
.then((promise) => {
return promise.data;
})
.catch((err) => {
console.error(err);
});
}
} catch (error) {
console.log(error);
throw err;
}
}
}
This is the page/component that I try to call this function from:
import React, { useState, useContext, useEffect, useCallback } from "react";
import { UserContext } from "../../UserContext";
import { getProtectedAsset } from "../../getProtectedAsset";
const Settings = () => {
const { user, setUser } = useContext(UserContext);
useEffect(async () => {
try {
let data = await getProtectedAsset("http://127.0.0.1:5002/mypage");
console.log(data);
} catch (error) {
console.error(error.message);
}
}, []);
return <></>;
};
export default Settings;
This gives me the 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:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app See https://reactjs.org/link/invalid-hook-call for tips about how to debug
and fix this problem.
I have tried everything I can imagine and read different tutorials/guides/docs but still cannot figure out the problem. Usually it is the lack of knowledge, or some thinking mistakes, but I really need help with this one!
Thank you for your help
Its because you are using useContext() hook inside getProtectedAsset() function.
Instead of using useContext inside getProtectedAsset try to pass user as parameter like url to the function.
let data = await getProtectedAsset(url, user);

How to setState in react.js using "binance.futuresMiniTickerStream()" function

When I do this It works
import React, { useState, useEffect } from 'react'
// Node Binance API
// https://github.com/binance-exchange/node-binance-api
const Binance = require('node-binance-api')
const binance = new Binance().options({
APIKEY: 'xxxxxxxxxx',
APISECRET: 'xxxxxxxxxx'
})
// BTCUSDT - price
const FuturesPrices = () => {
const [btcPrice, setBtcPrice] = useState([])
useEffect(() => {
async function fetchMyAPI() {
let response = await binance.futuresPrices()
response = response.BTCUSDT
setBtcPrice(response)
//console.log(response)
}
fetchMyAPI()
}, [btcPrice])
return <div>{btcPrice}</div>
}
export default FuturesPrices
But when I try example down below It doesn't work. Gives "error
Unhandled Rejection (Error): ws does not work in the browser. Browser clients must use the native WebSocket object"
import React, { useState, useEffect } from 'react'
// Node Binance API
// https://github.com/binance-exchange/node-binance-api
const Binance = require('node-binance-api')
const binance = new Binance().options({
APIKEY: 'xxxxxxxxxx',
APISECRET: 'xxxxxxxxxx'
})
// BTCUSDT - price
const BtcPriceTicker = () => {
const [btcPriceTicker, setBtcPriceTicker] = useState([])
useEffect(() => {
async function fetchMyAPI() {
let response = await binance.futuresMiniTickerStream( 'BTCUSDT' )
response = response.close
setBtcPriceTicker(response)
//console.log(response)
}
fetchMyAPI()
}, [btcPriceTicker])
return <div>{btcPriceTicker}</div>
}
export default BtcPriceTicker;
My GitHub
https://github.com/React-Binance/react-binance-api
Contribute to React-Binance/functional components development
By passing [btcPrice] in the dependency array, you're telling react to run this effect if the value of btcPrice changes.
Form Official docs
You can tell React to skip applying an effect if certain values haven’t changed between re-renders. To do so, pass an array as an optional second argument to useEffect
You can pass an empty array instead, which will make useEffect run only once like componentDidMount

React Context : Get Data from API and call API whenever some events happens in React Component

I am new to React Context.
I need to call the API in react context to use its data throughout my react application. Also the same API needs to be called on some CRUD operation on various component of react application.
For now I am storing API data in redux which I don't want to store.
Here is what I have tried..
context.js File
import React, { useState, createContext,useEffect } from 'react';
import {getData} from './actionMethods';
const NewContext = createContext();
function newContextProvider(props) {
useEffect(async () => {
const {dataValue} = await getData()
console.log("Data " , dataValue)
}, [])
return (
<NewContext.Provider
value={{
state: {
},
actions: {
}
}}
>
{props.children}
</NewContext.Provider>
);
}
const newContextConsumer = newContext.Consumer;
export { newContextProvider, newContextConsumer, newGridContext };
actionMethods.js
export function getData() {
let config = getInstance('GET', `${prefix}/xyz/list`)
return axios(config).then(res => res.data).catch(err => {
console.log(err)
})
}
when any CRUD operation performs , I need to call the API from the context.js file to get the data from API and store in the context.
Any help would be great.
Thank You.
First we create the Context and pass it an initial value.
In order to fetch data and keep track of the returned value, we create a state inside the component. This component will manage the fetched data and pass it in the Context Provider.
To call an async function inside useEffect we need to wrap it and call it inside useEffect callback.
export const NewContext = createContext({
my_data: {} // Initial value
});
export const NewContextProvider = props => {
const [my_data, setMyData] = useState({});
useEffect(() => {
const fetchMyData = async () => {
const { dataValue } = await getData();
if (dataValue) {
setMyData(dataValue);
} else {
// There was an error fetching the data
}
};
fetchMyData();
}, []);
return (
<NewContext.Provider
value={{
my_data
}}
>
{props.children}
</NewContext.Provider>
);
};
To use this Context in a component we use the useContext hook. Remember that this component needs to be wrapped by the Provider we just created.
import React, { useContext } from "react";
import { NewContext } from "./NewContext"; // The file where the Context was created
export const MyComponent = props => {
const { my_data } = useContext(NewContext);
return //...
};
Let me know if something is not clear.

React Hooks and ActionCable

Trying to get along with React new Hooks and ActionCable, but stuck with the problem that I can't get the right data in Rails when trying to send state.
I've tried to use send() method immediately after doing setState() and send my updated data, but for some reason, the data which received on the Rails part is old.
For example, if I put "Example" to the input I'll see "{"data"=>"Exampl"} on the Rails side. I suppose the data update the state later than my request goes.
If I send() value from e.target.value everything works fine
Therefore I've tried to use new useEffect() hook and send data there. But I get only data when rendering the page. Afterward, I don't get anything and sometimes get error RuntimeError - Unable to find subscription with an identifier. Seems like effect hook sends data too early or something.
I'm pretty new to Hooks and WebSockets. Would love to get any help here. I can share Rails code, but there is only a receiver and nothing else.
First exmaple:
import React, { useState, useEffect } from "react"
import ActionCable from 'actioncable'
function Component(props) {
const [data, setData] = useState("");
const cable = ActionCable.createConsumer('ws://localhost:3000/cable');
const sub = cable.subscriptions.create('DataChannel');
const handleChange = (e) => {
setData(e.target.value)
sub.send({ data });
}
return (
<input value={data} onChange={handleChange}/>
)
}
Tried to useEffect and move send() there:
useEffect(() => {
sub.send({ data });
}, [data]);
I'd love to find a way to correctly use React and ActionCable. And use hooks if it's possible.
I was trying an approach similar to Oleg's but I could not setChannel inside the action cable create subscription callback. I had to setChannel outside of the callback but within the useEffect hook. Below is the solution that worked for me.
create consumer in index.js and provide the consumer through Context to App.
index.js
import React, { createContext } from 'react'
import actionCable from 'actioncable'
... omitted other imports
const CableApp = {}
CableApp.cable = actionCable.createConsumer('ws://localhost:3000/cable')
export const ActionCableContext = createContext()
ReactDOM.render(
<Router>
... omitted other providers
<ActionCableContext.Provider value={CableApp.cable}>
<App />
</ActionCableContext.Provider>
</Router>,
document.getElementById('root')
)
Use the cable context in your child component and create subscription in useEffect hooks; unsubscribe in clean up
import React, { useState, useEffect, useContext } from 'react'
import { useParams } from 'react-router-dom'
... omitted code
const [channel, setChannel] = useState(null)
const { id } = useParams()
const cable = useContext(ActionCableContext)
useEffect(() => {
const channel = cable.subscriptions.create(
{
channel: 'MessagesChannel',
id: id,
},
{
received: (data) => {
receiveMessage(data)
},
}
)
setChannel(channel)
return () => {
channel.unsubscribe()
}
}, [id])
const sendMessage = (content) => {
channel.send(content)
}
You can register your cable at root component like that:
import actionCable from 'actioncable';
(function() {
window.CableApp || (window.CableApp = {});
CableApp.cable = actionCable.createConsumer('ws://localhost:3000/cable')
}).call(this);`
so it will be available as global variable;
and then in any component where you want to create channel and send data:
const [channel, setChannel] = useState(null);
useEffect(() => {
CableApp.cable.subscriptions.create(
{
channel: 'YourChannelName',
},
{
initialized() {
setChannel(this)
},
},
);
}, []);
return <button onClick={() => channel.send(some_data)} >Send counter</button>
Your problem is here:
const handleChange = (e) => {
setData(e.target.value)
sub.send({ data });
}
setData is like setState in that the state is only updated after the render i.e. after the function has exited. You are sending the current data not the new data. Try this:
const handleChange = (e) => {
const newData = e.target.value;
setData(newData)
sub.send({ data: newData });
}

Categories