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

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

Related

React State Manipulated from Another File Without Reference

I am following along in a React course on Udemy. In this module, we have a simple task app to demonstrate custom hooks. I've come across a situation where the "task" state is being managed in the App.js file, the "useHttp" custom hook has a function "fetchTasks" which accepts "transformTasks" as a parameter when called inside App.js. The issue I am having is that "tranformTasks" manipulates the "tasks" state inside App.js, but it is actually being called and executed inside the "useHttp" custom hook. Would really love some help understanding the mechanism for how this works. How can the state be manipulated while called from another file without the state being passed in? The code does work as intended. Here's the github link to the full app, and below are the two relevant files: https://github.com/yanichik/react-course/tree/main/full-course/custom-hooks-v2
Here is the App.js file:
import React, { useEffect, useMemo, useState } from "react";
import Tasks from "./components/Tasks/Tasks";
import NewTask from "./components/NewTask/NewTask";
import useHttp from "./custom-hooks/useHttp";
function App() {
// manage tasks state here at top level
const [tasks, setTasks] = useState([]);
const myUrl = useMemo(() => {
return {
url: "https://react-http-104c4-default-rtdb.firebaseio.com/tasks.json",
};
}, []);
const { isLoading, error, sendRequest: fetchTasks } = useHttp();
useEffect(() => {
// func transforms loaded data to add id (firebase-generated), push to loadedTasks, then
// push to tasks state
const transformTasks = (taskObj) => {
let loadedTasks = [];
for (const taskKey in taskObj) {
loadedTasks.push({ id: taskKey, text: taskObj[taskKey].text });
}
setTasks(loadedTasks);
};
fetchTasks(myUrl, transformTasks);
// if you add fetchTasks as a dependency this will trigger a re-render each time states
// are set inside sendRequest (ie fetchTasks) and with each render the custom hook (useHttp)
// will be recalled to continue the cycle. to avoid this, wrap sendRequest with useCallback
}, [fetchTasks, myUrl]);
const addTaskHandler = (task) => {
setTasks((prevTasks) => prevTasks.concat(task));
};
return (
<React.Fragment>
<NewTask onEnterTask={addTaskHandler} />
<Tasks
items={tasks}
loading={isLoading}
error={error}
onFetch={fetchTasks}
/>
</React.Fragment>
);
}
export default App;
And here is the "useHttp" custom hook:
import { useState, useCallback } from "react";
// NOTE that useCallback CANNOT be used on the top level function
function useHttp() {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
const sendRequest = useCallback(async (httpConfig, applyFunction) => {
setIsLoading(true);
setError(false);
try {
const response = await fetch(httpConfig.url, {
method: httpConfig.method ? httpConfig.method : "GET",
headers: httpConfig.headers ? httpConfig.headers : {},
body: httpConfig.body ? JSON.stringify(httpConfig.body) : null,
});
// console.log("response: " + response.method);
if (!response.ok) {
throw new Error("Request failed!");
}
const data = await response.json();
applyFunction(data);
// console.log("the formatted task is:" + applyFunction(data));
} catch (err) {
setError(err.message || "Something went wrong!");
}
setIsLoading(false);
}, []);
return { sendRequest, isLoading, error };
}
export default useHttp;
Sounds like you're learning from a decent course. The hook is using a technique called "composition". It knows you'll want to do some processing on the data once it has been fetched and let's you pass in (the applyFunction variable) your own snippet of code to do that processing.
Your snippet of code is just a function, but all parties agree on what parameters the function takes. (This is where using typescript helps catch errors.)
So you pass in a function that you write, and your function takes 1 parameter, which you expect will be the data that's downloaded.
The useHttp hook remembers your function and once it has downloaded the data, it calls your function passing in the data.
If you've used some of your own variables within the function you pass to the hook, they get frozen in time ... sort-of. This can of worms is a topic called 'closures' and I'm sure it will come up in the course if it hasn't already.

React Native Firebase Authentication with Hooks loses userState on app resume

I'm currently creating a React Native mobile application with Typescript.
The application uses the Firebase authentication with the Google OAuth Provider.
In order to use the username and some other details (retrieved from Firestore) I'm using a React Provider like shown in the following example:
import React, {useState, useEffect} from 'react';
import auth from '#react-native-firebase/auth';
import { GoogleSignin } from '#react-native-community/google-signin';
import firestore from '#react-native-firebase/firestore';
GoogleSignin.configure({
webClientId: 'x.googleusercontent.com',
});
const getUserById = async (id: string) => {
const admin = await firestore().collection("users").doc(id).collection("priv").doc("admin").get();
const prot = await firestore().collection("users").doc(id).collection("priv").doc("protected").get();
const jsonData = {
admin: admin.data(),
protected: prot.data(),
};
return jsonData;
}
const AuthContext = React.createContext({});
function AuthProvider(props: any) {
const [user, setUser] = useState(auth().currentUser);
const [details, setDetails] = useState({});
const [initializing, setInitializing] = useState(true);
const onAuthStateChanged = async (authUser: any) => {
setUser(authUser);
if (authUser !== null)
refreshDetails();
}
const refreshDetails = async () => {
const details = (await getUserById(user.uid));
setDetails(details);
}
useEffect(() => {
const subscriber = auth().onAuthStateChanged(onAuthStateChanged);
return subscriber; // unsubscribe on unmount
}, []);
const loginWithGoogle = async () => {
const { idToken } = await GoogleSignin.signIn();
// Create a Google credential with the token
const googleCredential = auth.GoogleAuthProvider.credential(idToken);
// Sign-in the user with the credential
return auth().signInWithCredential(googleCredential);
}
const logout = () => {
auth()
.signOut()
}
return (
<AuthContext.Provider value={{user, loginWithGoogle, logout, refreshDetails, details, initializing}} {...props}></AuthContext.Provider>
)
}
const useAuth = () => {
const state = React.useContext(AuthContext);
return {
...state,
};
}
export {AuthProvider, useAuth};
As you can see in the example I'm using this useEffect method from React to subscribe to authentication changes.
Unfortunately if I close the app and reopen it again, this authentication change isn't triggered so the user state isn't set and I get a bunch of errors.
What would be the best practice in a scenario like this? I think I only need to trigger the onAuthStateChangeEvent when the app was started again.
Thanks for all help
IJustDev
onAuthStateChanged function must be triggered when the app re-opens. However, it's supposed to run asynchronously you have to implement the case user's value is invalid.

Using .map() with useEffect and Api

I am trying to use the useEffect to grab some data from an API. I am succesful in grabbing the data but after I set my state and try to map through it I just get "Can't read map of undefined". I think the problem is that it's running my .map() code before it gets the response. i am just unsure of how to solve this
This is the api response:
data: {count: 87, next: "https://swapi.co/api/people/?page=2", previous: null, results: Array(10)}
Here is my code
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import './App.css';
import CharacterMap from './characterMap'
const App = () => {
let [getChars, setChars] = useState(0);
useEffect(() => {
axios.get(`https://swapi.co/api/people/`)
.then(res => setChars(res) )
},[]);
console.log(getChars.data.map((e) => e))
return (
<div className="App">
<CharacterMap info={getChars} />
</div>
);
}
export default App;
axios.get is an async function and you are trying to get the data outside of an async function which is no completed yet.
You could use useEffect with dependency array which is equal to componentDidUpdate to get the data.
Initialized the state with the same datatype that you expect, in this case we expect an array you initialized ith with empty array.
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import './App.css';
import CharacterMap from './characterMap'
const App = () => {
let [chars, setChars] = useState([]);
useEffect(async () => {
try{
let response = await axios.get(`https://swapi.co/api/people/`)
let data = await response.json();
setChars(data);
} catch(error) {
console.error(error.message);
}
},[]);
// If you want to access the updated state then use this.
useEffect(() => {
let newState = chars.map((e) => e); // map your state here
setChars(newState); // and then update the state
console.log(newState);
},[getChars]);
return (
<div className="App">
<CharacterMap info={chars} />
</div>
);
}
export default App;
The second useEffect hook trigger on each state update and so you can get the updated state here.
It will also trigger a re-render so you can also use the map in return statement;
Or you could update the data on axios response and then set the state. Recommended
useEffect(async () => {
try{
let response = await axios.get(`https://swapi.co/api/people/`)
let data = await response.json();
let newState = data.map((e) => e); // map your state here
setChars(newState); // and then update the state
console.log(newState);
} catch(error) {
console.error(error.message);
}
},[]);
Keep the default values as array
let [getChars, setChars] = useState([]);
you are setting data to array chars. instead of that set array(results) that you are getting in response.
As you defined let [getChars, setChars] = useState([]);
useEffect(async () => {
axios
.get(`https://swapi.co/api/people/`)
.then(res=> setChars(res.data.results))
.catch(err=> console.log(err))
},[]);

Trigger useEffect in Jest and Enzyme testing

I'm using Jest and Enzyme to test a React functional component.
MyComponent:
export const getGroups = async () => {
const data = await fetch(groupApiUrl);
return await data.json()
};
export default function MyWidget({
groupId,
}) {
// Store group object in state
const [group, setGroup] = useState(null);
// Retrive groups on load
useEffect(() => {
if (groupId && group === null) {
const runEffect = async () => {
const { groups } = await getGroups();
const groupData = groups.find(
g => g.name === groupId || g.id === Number(groupId)
);
setGroup(groupData);
};
runEffect();
}
}, [group, groupId]);
const params =
group && `&id=${group.id}&name=${group.name}`;
const src = `https://mylink.com?${params ? params : ''}`;
return (
<iframe src={src}></iframe>
);
}
When I write this test:
it('handles groupId and api call ', () => {
// the effect will get called
// the effect will call getGroups
// the iframe will contain group parameters for the given groupId
act(()=> {
const wrapper = shallow(<MyWidget surface={`${USAGE_SURFACES.metrics}`} groupId={1} />)
console.log(wrapper.find("iframe").prop('src'))
})
})
The returned src doesn't contain the group information in the url. How do I trigger useEffect and and everything inside that?
EDIT: One thing I learned is the shallow will not trigger useEffect. I'm still not getting the correct src but I've switched to mount instead of shallow
Here's a minimal, complete example of mocking fetch. Your component pretty much boils down to the generic fire-fetch-and-set-state-with-response-data idiom:
import React, {useEffect, useState} from "react";
export default function Users() {
const [users, setUsers] = useState([]);
useEffect(() => {
(async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/users");
setUsers(await res.json());
})();
}, []);
return <p>there are {users.length} users</p>;
};
Feel free to run this component in the browser:
<script type="text/babel" defer>
const {useState, useEffect} = React;
const Users = () => {
const [users, setUsers] = useState([]);
useEffect(() => {
(async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/users");
setUsers(await res.json());
})();
}, []);
return <p>there are {users.length} users</p>;
};
ReactDOM.render(<Users />, document.querySelector("#app"));
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
You can see the component initially renders a value of 0, then when the request arrives, all 10 user objects are in state and a second render is triggered showing the updated text.
Let's write a naive (but incorrect) unit test, mocking fetch:
import {act} from "react-dom/test-utils";
import React from "react";
import Enzyme, {mount} from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import Users from "../src/Users";
Enzyme.configure({adapter: new Adapter()});
describe("Users", () => {
let wrapper;
let users;
beforeEach(() => {
const mockResponseData = [{id: 1}, {id: 2}, {id: 3}];
users = mockResponseData.map(e => ({...e}));
jest.clearAllMocks();
global.fetch = jest.fn(async () => ({
json: async () => mockResponseData
}));
wrapper = mount(<Users />);
});
it("renders a count of users", () => {
const p = wrapper.find("p");
expect(p.exists()).toBe(true);
expect(p.text()).toEqual("there are 3 users");
});
});
All seems well--we load up the wrapper, find the paragraph and check the text. But running it gives:
Error: expect(received).toEqual(expected) // deep equality
Expected: "there are 3 users"
Received: "there are 0 users"
Clearly, the promise isn't being awaited and the wrapper is not registering the change. The assertions run synchronously on the call stack as the promise waits in the task queue. By the time the promise resolves with the data, the suite has ended.
We want to get the test block to await the next tick, that is, wait for the call stack and pending promises to resolve before running. Node provides setImmediate or process.nextTick for achieving this.
Finally, the wrapper.update() function enables synchronization with the React component tree so we can see the updated DOM.
Here's the final working test:
import {act} from "react-dom/test-utils";
import React from "react";
import Enzyme, {mount} from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import Users from "../src/Users";
Enzyme.configure({adapter: new Adapter()});
describe("Users", () => {
let wrapper;
let users;
beforeEach(() => {
const mockResponseData = [{id: 1}, {id: 2}, {id: 3}];
users = mockResponseData.map(e => ({...e}));
jest.clearAllMocks();
global.fetch = jest.fn(async () => ({
json: async () => mockResponseData
}));
wrapper = mount(<Users />);
});
it("renders a count of users", async () => {
// ^^^^^
await act(() => new Promise(setImmediate)); // <--
wrapper.update(); // <--
const p = wrapper.find("p");
expect(p.exists()).toBe(true);
expect(p.text()).toEqual("there are 3 users");
});
});
The new Promise(setImmediate) technique also helps us assert on state before the promise resolves. act (from react-dom/test-utils) is necessary to avoid Warning: An update to Users inside a test was not wrapped in act(...) that pops up with useEffect.
Adding this test to the above code also passes:
it("renders a count of 0 users initially", () => {
return act(() => {
const p = wrapper.find("p");
expect(p.exists()).toBe(true);
expect(p.text()).toEqual("there are 0 users");
return new Promise(setImmediate);
});
});
The test callback is asynchronous when using setImmediate, so returning a promise is necessary to ensure Jest waits for it correctly.
This post uses Node 12, Jest 26.1.0, Enzyme 3.11.0 and React 16.13.1.
With jest you can always mock. So what you need is:
In your unit test mock useEffect from React
jest.mock('React', () => ({
...jest.requireActual('React'),
useEffect: jest.fn(),
}));
That allows to mock only useEffect and keep other implementation actual.
Import useEffect to use it in the test
import { useEffect } from 'react';
And finally in your test call the mock after the component is rendered
useEffect.mock.calls[0](); // <<-- That will call implementation of your useEffect
useEffect has already been triggered and working, the point is that its an async operation. So you need to wait for the fetch to be completed. one of the ways that you can do that is:
1. write your assertion(s)
2. specify the number of assertion(s) in your test, so that jest knows that it has to wait for the operation to be completed.
it('handles groupId and api call ', () => {
// the effect will get called
// the effect will call getGroups
// the iframe will contain group parameters for the given groupId
expect.assertions(1)
const wrapper = shallow(<UsageWidget surface={`${USAGE_SURFACES.metrics}`} groupId={2} />)
wrapper.update()
expect(whatever your expectation is)
});
since in this example i just wrote on assertion,
expect.assertions(1)
if you write more, you need to change the number.
You can set a timeout to asynchronously check if the the expected condition has been met.
it('handles groupId and api call ', (done) => {
const wrapper = shallow(<UsageWidget surface={`${USAGE_SURFACES.metrics}`} groupId={1} />)
setTimeout(() => {
expect(wrapper.find("iframe").prop('src')).toBeTruthy(); // or whatever
done();
}, 5000);
}
The timeout lets you wait for the async fetch to complete. Call done() at the end to signal that the it() block is complete.
You probably also want to do a mock implementation of your getGroups function so that you're not actually hitting a network API every time you test your code.

Infinite loop with a custom hook with UseEffect

I'm trying to create a custom hook and I have problems with an infinite loop.
There is the piece of code that implements the custom hook on my page:
const handleOnFinish = response => {
const {data} = response
setIsLoading(false)
setTableData(data)
setPage(page)
}
const handleOnInit = () => setIsLoading(true)
useEffectUseCaseTokenValidation({
onFinish: handleOnFinish,
onInit: handleOnInit,
params: {nameToFilter: nameFilter, page},
useCase: 'get_clients_use_case'
})
And this is my custom hook:
import {useContext, useEffect} from 'react'
import Context from '#s-ui/react-context'
const noop = () => {}
export function useEffectUseCaseTokenValidation({
onFinish = noop,
onInit = noop,
params = {},
useCase = ''
}) {
const {domain} = useContext(Context)
const config = domain.get('config')
useEffect(() => {
onInit()
domain
.get(useCase)
.execute(params)
.then(response => {
const {error} = response
if (error && error.message === 'INVALID_TOKEN') {
window.location.replace(config.get('LOGIN_PAGE_URL'))
}
onFinish(response)
})
}, [params]) // eslint-disable-line
}
With this, the useEffect is released again and again, instead of taking params into account. I add a console.log for params and is always receiving the same.
I was using this useCase correctly without the custom hook, so that is not the problem.
I want to use this custom hook to avoid to copy and paste the redirection on all UseEffects for all project pages.
Thank you!
The problem is the object ref, that means that you are passing {nameToFilter: nameFilter, page} as params but each time the components renders a new object ref is creating so, react compare both with the ===, so if you run this code in your console
var params1 = { name: 'mike', age: 29 };
var params2 = { name: 'mike', age: 29 };
console.log(params1 === params2); // it will console false
that's because object declaration are not the same event when its key/value pairs are the same.
So to avoid infinite loop into your hook, you should use useMemo to avoid that, so try this
import { useMemo } from 'react';
const params = useMemo(() => ({ nameToFilter: nameFilter, page }), [nameFilter, page])
useEffectUseCaseTokenValidation({
onFinish: handleOnFinish,
onInit: handleOnInit,
params: params,
useCase: 'get_clients_use_case'
})
useMemo will avoid recreating object reft in each render phase of your component
Please read the useMemo react official docs
Please read this post to know the differences between values VS references comparison

Categories