Proper on implementing incremental values - reactjs

Now I know the title may be a bit vague so let me help you by explaining my current situation:
I have an array worth of 100 object, which in turn contain a number between 0 and 1. I want to loop through the array and calculate the total amount e.g (1 + 1 = 2).
Currently using .map to go through every object and calaculate the total. When I am counting up using the useState hook, it kinda works. My other approach was using a Let variabele and counting up like this. Although this is way to heavy for the browser.
I want to render the number in between the counts.
const[acousticness, setAcousticness] = useState(0);
let ids = [];
ids.length == 0 && tracks.items.map((track) => {
ids.push(track.track.id);
});
getAudioFeatures(ids).then((results) => {
results.map((item) => {
setAcousticness(acousticness + item.acousticness)
})
})
return (
<div>
Mood variabele: {acousticness}
</div>
)
What is the proper way on doing this?

I think this is roughly what you are after:
import {useMemo, useEffect, useState} from 'react';
const MiscComponent = ({ tracks }) => {
// Create state variable / setter to store acousticness
const [acousticness, setAcousticness] = useState(0);
// Get list of ids from tracks, use `useMemo` so that it does not recalculate the
// set of ids on every render and instead only updates when `tracks` reference
// changes.
const ids = useMemo(() => {
// map to list of ids or empty array if `tracks` or `tracks.items` is undefined
// or null.
return tracks?.items?.map(x => x.track.id) ?? [];
}, [tracks]);
// load audio features within a `useEffect` to ensure data is only retrieved when
// the reference of `ids` is changed (and not on every render).
useEffect(() => {
// create function to use async/await instead of promise syntax (preference)
const loadData = async () => {
// get data from async function (api call, etc).
const result = await getAudioFeatures(ids);
// calculate sum of acousticness and assign to state variable.
setAcousticness(result?.reduce((a, b) => a + (b?.acousticness ?? 0), 0) ?? 0)
};
// run async function.
loadData();
}, [ids, setAcousticness])
// render view.
return (
<div>
Mood variabele: {acousticness}
</div>
)
}

Related

React(Next.js) Set Timeout and useState not doing as should

So I basically want a tag that looks like this.
This is tyty<span> {textArray[Index]}</span>
and I want textArray[Index] to show a different value every 3 seconds. Currently my code cycles through the array, however, it seems to gofrom Array 0 to Array 2 then Array 4.
I want it to Show array 0 then 3 seconds later array 1, 3 secs, array 2, 3 secs, array 3, then back to array 0 and repeat.
My code looks like this:
const textArray = [
"digital",
"development",
"graphics",
"blog"
]
const [Index, setIndex] = useState(0);
const intervalID = setTimeout(changeText, 3000);
function changeText(){
if(Index >= 0 && Index < 4) {
setIndex((prevIndex) => ++prevIndex);
}
if(Index > 3){
setIndex((prevIndex) => prevIndex=0);
}
}
Why can't I also seem to get it to go through array 1 and array 3.
When I go to google dev mode, I type in Index into the console and it says its not defined. Same with textArray.
First - memoize your array or other objects using useState or useMemo. Without that - objects and arrays will be recreated on each render. And if for primitives like boolean, numbers, strings it is +- ok and depsArrays of useMemo and useEffect will handle them right - for arrays and object it will not be ok due to even if object is the same in terms of values - reference to this object will be changed (new object created) and that will cause pretty bad behavior and tons of unneded re-renders, especially if you pass those objects and arrays down to the child components.
Next - it is setInterval, not setTimeout.
Last - always clear Intervals you created.
import { useState, useEffect, useMemo } from "react";
export default function App() {
// memoize the array and dont recreate it on each render
const textArray = useMemo(
() => ["digital", "development", "graphics", "blog"],
[]
);
const [index, setIndex] = useState(0);
// Will be recalculated when index or textArray changed
const text = useMemo(() => {
return textArray[index];
}, [index, textArray]);
useEffect(() => {
// setInterval, not the setTimeout
const intervalId = setInterval(() => {
// Index will go up but % will cut it down
setIndex((prev) => (prev + 1) % textArray.length);
}, 3000);
return () => clearInterval(intervalId);
}, [textArray]);
return (
<div className="App">
This is{" "}
<a href="https://nextjs.org">
tyty<span> {text}</span>
</a>
</div>
);
}

React State Fails To Update with UseEffect

When attempting to update an array via React state management, the state array is populated, but the user interface fails to update. The user interface only updates after I click on the navbar, and reroute to the current page (in which case useEffect does not run again, but the UI is updated).
State Code
const[isFetched, setIsFetched] = useState(false);
const[balances, setBalances] = useState<IBalance[]>([]);
const[num, setNum] = useState(0);
useEffect(() => {
console.log(balances);
// LOGS A POPULATED ARRAY
console.log(balances.length);
// LOGS 0
}, [balances]);
useEffect(() => {
const fetchBalances = async() =>{
let bals:IBalance[] = await kryptikService.getBalanceAllNetworks(kryptikWallet);
console.log("RECIEVED BALANCES:");
console.log(bals);
console.log(bals.length);
setBalances(bals);
setIsFetched(true);
}
fetchBalances();
}, []);
UI Code
<h2>Your Balances</h2>
<Divider/>
{
!isFetched?<p>Loading Balances.</p>:
<ul role="list" className="divide-y divide-gray-200 dark:divide-gray-700">
{balances.map((balance:IBalance) => (
<ListItem title={balance.fullName} imgSrc={balance.iconPath} subtitle={balance.ticker} amount={balance.amountCrypto}/>
))}
</ul>
}
</div>
Fetch Handler (called in UseEffect)
getBalanceAllNetworks = async(walletUser:IWallet):Promise<IBalance[]> =>{
let networksFromDb = this.getSupportedNetworkDbs();
// initialize return array
let balances:IBalance[] = [];
networksFromDb.forEach(async nw => {
let network:Network = new Network(nw.fullName, nw.ticker);
let kryptikProvider:KryptikProvider = await this.getKryptikProviderForNetworkDb(nw);
if(network.getNetworkfamily()==NetworkFamily.EVM){
if(!kryptikProvider.ethProvider) throw Error(`No ethereum provider set up for ${network.fullName}.`);
let ethNetworkProvider:JsonRpcProvider = kryptikProvider.ethProvider;
console.log("Processing Network:")
console.log(nw.fullName);
// gets all addresses for network
let allAddys:string[] = await walletUser.seedLoop.getAddresses(network);
// gets first address for network
let firstAddy:string = allAddys[0];
console.log(`${nw.fullName} Addy:`);
console.log(firstAddy);
console.log(`Getting balance for ${nw.fullName}...`);
// get provider for network
let networkBalance = await ethNetworkProvider.getBalance(firstAddy);
console.log(`${nw.fullName} Balance:`);
console.log(networkBalance);
// prettify ether balance
let networkBalanceAdjusted:Number = BigNumber.from(networkBalance)
.div(BigNumber.from("10000000000000000"))
.toNumber() / 100;
let networkBalanceString = networkBalanceAdjusted.toString();
let newBalanceObj:IBalance = {fullName:nw.fullName, ticker:nw.ticker, iconPath:nw.iconPath,
amountCrypto:networkBalanceString}
// add adjusted balance to balances return object
balances.push(newBalanceObj);
}
});
return balances;
}
Note: The array is a different reference, so there should be no issue with shallow equality checks. Also, the updated balances array contains objects, but the length is logged as zero as shown in the first code snippet. Any help will be much apreciated!
Issue
The issue is that you are iterating the networksFromDb array in a forEach loop with an asynchronous callback. The asynchronous callback ins't the issue, it is that Array.protptype.forEach is synchronous, the the getBalanceAllNetworks callback can't wait for the loop callbacks to resolve. It returns the empty balances array to the caller before the array is populate.
The array is still populated however, and the clicking the link is enough to trigger a React rerender and expose the mutated balances state array.
Solution
Instead of using a .forEach loop for the asynchronous callback, map networksFromDb to an array of Promises and use Promise.all and wait for them all to resolve before returning the populated balances array.
Example:
const getBalanceAllNetworks = async (
walletUser: IWallet
): Promise<IBalance[]> => {
const networksFromDb = this.getSupportedNetworkDbs();
const asyncCallbacks = networksFromDb
.filter((nw) => {
const network: Network = new Network(nw.fullName, nw.ticker);
return network.getNetworkfamily() == NetworkFamily.EVM;
})
.map(async (nw) => {
const kryptikProvider: KryptikProvider = await this.getKryptikProviderForNetworkDb(
nw
);
if (!kryptikProvider.ethProvider) {
throw Error(`No ethereum provider set up for ${network.fullName}.`);
}
const ethNetworkProvider: JsonRpcProvider = kryptikProvider.ethProvider;
// gets all addresses for network
const allAddys: string[] = await walletUser.seedLoop.getAddresses(
network
);
// gets first address for network
const firstAddy: string = allAddys[0];
// get provider for network
const networkBalance = await ethNetworkProvider.getBalance(firstAddy);
// prettify ether balance
const networkBalanceAdjusted: Number =
BigNumber.from(networkBalance)
.div(BigNumber.from("10000000000000000"))
.toNumber() / 100;
const networkBalanceString = networkBalanceAdjusted.toString();
const newBalanceObj: IBalance = {
fullName: nw.fullName,
ticker: nw.ticker,
iconPath: nw.iconPath,
amountCrypto: networkBalanceString
};
// add adjusted balance to balances return object
return newBalanceObj;
});
const balances: IBalance[] = await Promise.all(asyncCallbacks);
return balances;
};
You are mutating balances instead of updating it.
Change balances.push to setBalances(prevState => [...prevState, newBalance])

Rendering and best practices in React

I am a beginner in React, I have a question regarding rendering, which consists of I use useEfect to render the change in a variable, which is decorated in a useState
Here are the statements:
const CardRepo: React.FC<DadosCardRepo> = ({ Nome }) => {
const [nomeRepo, setNomeRepo] = useState(Nome);
const [data, setData] = useState({});
const [languages, setLanguages] = useState<Languages[]>([]);
This is a component, which receives an object with a Name, and sets this value Name in nameRepo
Here are the two methods:
useEffect(() => {
getRepo(nomeRepo)
.then(data => {
setData(data);
});
}, [nomeRepo]);
useEffect(() => {
getLanguages(data['languages_url'])
.then(data => {
let total = 0;
const languagesRef: Languages[] = [];
Object.keys(data).map((key) => {
total += data[key];
languagesRef.push({ Nome: key, Porct: data[key] });
});
languagesRef.map((language, i, array) => {
setLanguages((oldValues) => [...oldValues, { Nome: language.Nome.toLowerCase(), Porct: +((language.Porct * 100) / total).toFixed(2) }]);
});
});
}, [data]);
Where the first useEfect I run when making a change to nomeRepo, and in this method, I make a request to the getRepo service
And the second one, when I change the date, and in this method, I make a request to the getLanguages ​​service, and do the processing of the response to make a list with it, where I assign languages.
Here is the listing:
{
languages.map((el, i) => {
return (
<div className="progress-bar" id={el.Nome} key={i} style={{ width: el.Porct + '%' }}></div>
);
})
}
My question is related to rendering, I know that I will only be able to list something, if I use useEfect in the variable "languages", where I will be assigning values ​​and with the changes in the variable to render again, otherwise I couldn't.
But when I make any changes to the code, it renders only where it was changed, but it assigns the same values ​​that were assigned, for example: [1,2,3] => [1,2,3,1,2,3].
I tried to put setLanguages ​​([]), but it has the same behavior
I was wondering how to fix this and if it's a good practice to make calls and list that way.
first, there is one bad practice where you create a derived state from props. nomeRepo comes from props Nome, you should avoid this derived states the most of you can. You better remove nomeRepo state and use only Nome.
your first use Effect becomes:
useEffect(() => {
getRepo(Nome)
.then(data => {
setData(data);
});
}, [Nome]);
you second useEffect has a setState triggered inside a map. That will trigger your setState multiple times, creating several rerenders. It's best to prepare your array of languages first, then call setState once.
Also, you should use forEach instead of map if you are not consuming the returned array, and only running some script on each element.
One thing to notice, is you are adding new languages to old ones. I assume that languages should be related data['languages_url'] value, this way you wouldn't include the old languages to the setLanguages.
One thing you could consider is to keep your total values instead of percentages. you can store total into a variable with useMemo, that will depend on languages. And declare a your percentage function into a utils folder and import it as need it.
refactored second useEffect:
// calculate total value that gets memoized
const totalLangCount = useMemo(() => languages.reduce((total, val) => total + val.count , 0), [languages])
useEffect(() => {
getLanguages(data['languages_url'])
.then(data => {
const languagesRef: Languages[] = [];
Object.keys(data).forEach((key) => {
// prepeare your languages properties
const nome = key.toLowerCase();
const count = data[key];
languagesRef.push({ nome, count });
});
// avoid multiple setLanguages calls
setLanguages(languagesRef)
});
}, [data]);
languages rendered:
{
languages.map((el, i) => {
return (
// here we are using array's index as key, but it's best to use some unique key overall instead
<div className="progress-bar" id={el.nome} key={i} style={{ width: percentage(el.count, total) }}></div>
);
})
}
percentage function reusable:
// utils percentage exported
export const percentage = (count, total) => ((count * 100) / total).toFixed(2) + '%'

Can someone explain how input functions are used in functions in reselect library?

https://github.com/reduxjs/reselect/blob/master/src/index.js#L89
export function defaultMemoize(func, equalityCheck = defaultEqualityCheck) {
let lastArgs = null
let lastResult = null
// we reference arguments instead of spreading them for performance reasons
return function () {
if (!areArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) {
// apply arguments instead of spreading for performance.
lastResult = func.apply(null, arguments)
}
lastArgs = arguments
return lastResult
}
}
export function createSelectorCreator(memoize, ...memoizeOptions) {
return (...funcs) => {
let recomputations = 0
const resultFunc = funcs.pop()
const dependencies = getDependencies(funcs)
const memoizedResultFunc = memoize(
function () {
recomputations++
// apply arguments instead of spreading for performance.
return resultFunc.apply(null, arguments)
},
...memoizeOptions
)
...}
}
export const createSelector = createSelectorCreator(defaultMemoize)
So if I create createSelector(getUsers, (users) => users) for making a simple example. How is it run behind with the codes from above ?
createSelectorCreator(defaultMemoize) is called with getUsers, (users) => users inputs. Now defaultMemoize is also a function that returns a function. How are they all interacting to return the value ?
I think more important to how reselect works is why one should use it. The main reasons are composability and memomization:
Composability
Another way of saying this is that you write a selector once and re use it in other more detailed selectors. Lets say I have a state like this: {data:{people:[person,person ...]} Then I can write a filterPerson like this:
const selectData = state => state.data;
const selectDataEntity = createSelector(
selectData,//re use selectData
(_, entity) => entity,
(data, entity) => data[entity]
);
const filterDataEntity = createSelector(
selectDataEntity,//re use selectDataEntity
(a, b, filter) => filter,
(entities, filter) => entities.filter(filter)
);
If I move data to state.apiResult then I only need to change selectData, this maximizes re use of code and minimizes duplication of implementation.
Memoization
Memoization means that when you call a function with the same arguments multiple times the function will only be executed once. Pure functions return the same result given the same arguments no matter how many times they are called or when they are called.
This means that you don't need to execute the function when you call it again with the same parameters because you already know the result.
Memoization can be used to not call expensive functions (like filtering a large array). In React memoization is important because pure components will re render if props change:
const mapStateToProps = state => {
//already assuming where data is and people is not
// a constant
return {
USPeople: state.data.people.filter(person=>person.address.countey === US)
}
}
Even if state.data.people didn't change the filter function would return a new array every time.
How it works
Below is a re write of createSelector with some comments. Removed some code that would safety check parameters and allow you to call createSelector with an array of functions. Please comment if there is anything you have difficulty understanding.
const memoize = fn => {
let lastResult,
//initial last arguments is not going to be the same
// as anything you will pass to the function the first time
lastArguments = [{}];
return (...currentArgs) => {//returning memoized function
//check if currently passed arguments are the same as
// arguments passed last time
const sameArgs =
currentArgs.length === lastArguments.length &&
lastArguments.reduce(
(result, lastArg, index) =>
result && Object.is(lastArg, currentArgs[index]),
true
);
if (sameArgs) {
//current arguments are same as last so just
// return the last result and don't execute function
return lastResult;
}
//current arguments are not the same as last time
// or function called for the first time, execute the
// function and set last result
lastResult = fn.apply(null, currentArgs);
//set last args to current args
lastArguments = currentArgs;
//return result
return lastResult;
};
};
const createSelector = (...functions) => {
//get the last function by popping it off of functions
// this mutates functions so functions does not have the
// last function on it anymore
// also memoize the last function
const lastFunction = memoize(functions.pop());
//return a selector function
return (...args) => {
//execute all the functions (last was already removed)
const argsForLastFunction = functions.map(fn =>
fn.apply(null, args)
);
//return the result of a call to lastFunction with the
// result of the other functions as arguments
return lastFunction.apply(null, argsForLastFunction);
};
};
//selector to get data from state
const selectData = state => state.data;
//select a particular entity from state data
// has 2 arguments: state and entity where entity
// is a string (like 'people')
const selectDataEntity = createSelector(
selectData,
(_, entity) => entity,
(data, entity) => data[entity]
);
//select an entity from state data and filter it
// has 3 arguments: state, entity and filterFunction
// entity is string (like 'people') filter is a function like:
// person=>person.address.country === US
const filterDataEntity = createSelector(
selectDataEntity,
(a, b, filter) => filter,
(entities, filter) => entities.filter(filter)
);
//some constants
const US = 'US';
const PEOPLE = 'people';
//the state (like state from redux in connect or useSelector)
const state = {
data: {
people: [
{ address: { country: 'US' } },
{ address: { country: 'CA' } },
],
},
};
//the filter function to get people from the US
const filterPeopleUS = person =>
person.address.country === US;
//get people from the US first time
const peopleInUS1 = filterDataEntity(
state,
PEOPLE,
filterPeopleUS
);
//get people from the US second time
const peopleInUS2 = filterDataEntity(
state,
PEOPLE,
filterPeopleUS
);
console.log('people in the US:', peopleInUS1);
console.log(
'first and second time is same:',
peopleInUS1 === peopleInUS2
);

State getting reset to 0 after render

I am rendering photos from unsplash api. And I am keeping the index of the photos to be used in the lightbox, after the initial render state of imageindex goes back to 0, how can I retain its value?
I will show some code
const ImageList = ({ image, isLoaded }) => {
const [imageIndex, setImageIndex] = useState(0);
const [isOpen, setIsOpen] = useState('false');
const onClickHandler = (e) => {
setIsOpen(true);
setImageIndex(e.target.id);
};
const imgs = image.map((img, index) => (
<img
id={index}
key={img.id}
src={img.urls.small}
onClick={onClickHandler}
if (isOpen === true) {
return (
<Lightbox
onCloseRequest={() => setIsOpen(false)}
mainSrc={image[imageIndex].urls.regular}
onMoveNextRequest={() => setImageIndex((imageIndex + 1) % image.length)}
onMovePrevRequest={() => setImageIndex((imageIndex + image.length - 1) % image.length)}
nextSrc={image[(imageIndex + 1) % image.length].urls.regular}
prevSrc={image[(imageIndex + image.length - 1) % image.length].urls.regular}
/>
after the initial render state, imageIndex goes back to 0.
That makes sense, the initial render would use whatever you set as the default value. You can use something like local storage to help you keep track of the index of the last used item. It's a bit primitive, but until you integrate something like Node/MongoDB for database collections, this will be perfect.
In your component, import useEffect() from React. This hook lets us execute some logic any time the state-index value changes, or anything else you might have in mind.
import React, { useEffect } from "react"
Then inside your component, define two useEffect() blocks.
Getting last used index from localStorage on intitial load:
useEffect(() => {
const lastIndex = localStorage.getItem("index", imageIndex)
setImageIndex(imageIndex)
}, []) //set as an empty array so it will only execute once.
Saving index to localStorage on change:
useEffect(() => {
localStorage.setItem("index", imageIndex)
}, [imageIndex]) //define values to subscribe to here. Will execute anytime value changes.

Resources