Sorting data from an API (redux-toolkit) - arrays

I'm building a crypto app with react and redux-toolkit.
I'm trying to find a way to manage the data from the API. More specifically i want to be able to sort by value, volume, etc. and add an "isFavourite" property for each coin but i think (correct me if i'm wrong) that the only way to do this is by copying the data to another state. What i've tried so far was adding another state where i passed the data like this:
const [list, setList] = useState()
useEffect(() => {
setList(data)
}, [data])
//"const coinData = list?.data?.coins" instead of "const coinData = data?.data?.coins"
but then an error occured because the data on the "list" were "undefined".
The code bellow is the one that is running without any problems. How can i manage the API data? Am i on the right path or is there a more slick way to do what i want? Thank you!
function Main () {
const { data, error, isFetching } = useGetCryptosQuery()
if(isFetching) return 'Loading...'
const globalStats = data?.data?.stats
const coinData = data?.data?.coins
const coinList = coinData.map(coin => {
return (
<Coin
price = {coin.price}
key = {coin.uuid}
id = {coin.uuid}
name = {coin.name}
icon = {coin.iconUrl}
/>)
})
return (
<div>
<h2>Main</h2>
{coinList}
</div>
)
}
export default Main

You are on the right track - I set up something similar and added a check for null trying to map the data, and that avoids the error you probably got.
const coinList = coinData ? coinData.map((coin) => {
///...coin component
}) : <div></div>;
Then, instead of an error for undefined data, it will return an empty div - until the data is there, then it will render ok.

Related

Why do my ReactJS changes disappear on refreshing the page

I'm new to React and I'm trying to render a list of Pokemons.
I'm fetching the pokemon names from a local file and then using those names to trigger HTTP calls to my backend server, to get the pokemon images. Here's my code so far:
function PokemonList(props) {
const [pokemonList, setPokemonList] = useState([]);
const [isFetched, setIsFetched] = useState(false);
const [renderedList, setRenderedList] = useState([]);
useEffect(() => {
fetch(raw)
.then((r) => r.text())
.then((text) => {
setPokemonList(text.split("\n"));
setIsFetched(true);
});
}, []);
// I believe this is to blame, but I don't know why. On refresh, pokemonList becomes empty
useEffect(() => {
setRenderedList(populateRenderedList(pokemonList));
}, []);
return !isFetched ? null : (
<div className="list">
{renderedList}
<PaginationBar listSize={renderedList.length} list={renderedList} />
</div>
);
}
function populateRenderedList(pokemonList) {
let pokemonAPI = "https://pokeapi.co/api/v2/pokemon-form/";
const temp = [];
console.log(pokemonList);
pokemonList.forEach((pokemonName) => {
let renderedPokemon = (
<a className="pokemonLink" href={pokemonAPI + pokemonName.toLowerCase()}>
<PokemonDiv name={pokemonName.toLowerCase()} />
<h3>{pokemonName}</h3>
</a>
);
temp.push(renderedPokemon);
});
return temp;
}
As I have commented on the code, the 'pokemonList' renders fine when I make any changes to the PokemonList function. But the moment I refresh my page, 'pokemonList' becomes empty. Why is that?
I previously was not using 'useState' to populate my 'renderedList' list. So I believe the problem is happening because I'm using 'useState' , but I don't know why that's happening.
I had tried making 'renderedList' not a state, but I had to, for I am thinking about passing it as props to another child component, in order to change it's state.

Mapping through JSON data that contain additional status and results from messages

I'm trying to map through JSON data from React with this code...
import {useEffect,useState} from 'react'
function Tourapi() {
const[tours,settour]=useState([])
const[loading,setloading]=useState(true)
useEffect(()=>{
fetchtour()
},[])
const fetchtour= async()=> {
const furl='https://www.natours.dev/api/v1/tours'
const res = await fetch(furl)
const data = await res.json()
settour(data)
setloading(false)
}
if(!loading)
{
return (
<div>
{tours.map((tour)=>(
<h3> {tour.name}</h3>
))}
</div>
)
}
else
{
return <h3>LOADING.....</h3>
}
}
export default Tourapi
but it looks like that JSON data contain additional things and the data array so how can I Map through to get the name and description and so on...
the error that I got TypeError: tours.map is not a function
the JSON data begin with
{"status":"success","results":10,"data":{"data":[{"startLocation":{"type":"Point","coordinates":[-80.185942,25.774772],"description":"Miami, USA","address":"301 Biscayne Blvd, Miami, FL 33132, USA"}
what I mean how can I get out of ("status":"success","results":10,"data":) and get the data its self because it's not my API to control with the response
thanks.
This is what you need, I hope it's help
import { useState, useEffect } from "react";
function TourAPI() {
const [tours, settour] = useState([]);
const [loading, setloading] = useState(true);
useEffect(() => {
fetchtour();
}, []);
const fetchtour = async () => {
const furl = "https://www.natours.dev/api/v1/tours";
const res = await fetch(furl);
const data = await res.json();
await settour(data.data.data);
setloading(false);
};
if (!loading) {
return (
<>
<div className="tour-section">
{tours.map((tours, i) => {
return <h3 key={i}>{tours.name}</h3>;
})}
</div>
</>
);
} else {
return <h3>LOADING.....</h3>;
}
}
export default TourAPI;
I'll walk you through what's in the data, if you console.log(data) in your fetchtour, it'll contain an object that contains properties. It's like this
{status: 'success', results: 10, data: {data}}
So, you want to access one more data inside of that data
That'll lead to console.log(data.data) to see what's in there.
And it'll appear like this.
{data: Array(10)}
So, the data key actually contains the data's array inside of it, the data.data contains an array with 10 indexes in it as you can see. You can try in your browser to see that as well, and each index will contain the properties you want in there, which is name, and many more properties that you need to get like location, etc.
So lead us to the last data, if you console.log(data.data.data), it'll access to data that contains the 10 indexes, which it'll be an array [...] so that we can use map.
That's what you need to setState for the tours
And from that, we're good to use map, because we actually are able to map through an array that contains indexes.
And don't forget to set key for each div inside the map or you'll get the warning fire out from the console in your browser.
My English is not too well so my explanation might be a little bit hard but I hope you can understand what's going on.

SetState inside UseEffect causing infinite loop

export const Upload = ({ initialfileList = [] }) => {
console.log("called...")
const [files,setFiles] = useState(initialfileList)
useEffect(() => {
setFiles(initialfileList)
}, [initialfileList])
return(
.....
)
}
I will not be sending initialfileList in intial render.
so I'm trying to update the state (files) when intialfileList value Changes.
But I'm not able to get why code is going into infinite loop.. Can someone help...
EDIT:
what I'm trying to achieve is there are two uploads in the form (say upload A and upload B) and checkbox as well. when the user uploads an image (say imgc) in Upload A and hits the checkbox, I wanted img c to be autouploaded in Upload B as well..
Ah, I see. How about this?
export const Upload = ({ initialfileList = [] }) => {
const [files, setFiles] = useState(initialfileList);
useEffect(() => {
function isDifferentFiles(originFiles, newFiles) {
return originFiles.length !== newFiles.length; // or whatever logic here.
}
if (isDifferentFiles(files, initialfileList)) {
setFiles(initialfileList);
}
}, [initialfileList]);
return <div>{files.length}</div>;
};
Btw, you might need to consider move the state to parent.
It sounds like you need to lift your state up - rather than storing fileLists locally, store a single fileList in the parent component, pass that down to both Upload A and Upload B, and also pass a setFileList function (which updates the state in the parent component).
i.e.:
// parent component
const Parent = () => {
const [fileList, setFileList] = useState([])
return (
<Upload fileList={fileList} setFileList={setFileList} />
<Upload fileList={fileList} setFileList={setFileList} />
)
}
const Upload = ({fileList, setFileList}) => {
return (
<UploadComponent files={fileList} onUpload={setFileList} />
)
}
This way, either upload component being updated will update both.

Cant use values from (fetched) context(provider) as input to my useState

Short version:
I have a useEffect in a ContextProvider where I fetch some data from server. In my component I
use this Context and want to init another useState()-hook with the "incoming" data. The value in
[value, setValue] = useState(dataFromContext) does not get set/inited with dataFromContext. Why, oh why?
The very long version:
Im trying to follow (and expand on) this example of how to use context/useContext-hook in react
So, I have an AppContext and a ContextProvider, I wrap my in in index.js, everything in the example works nice.
Now I want to load data in useEffect inside my ContextProvider, so my context provider now looks like this
const AppContextProvider = ({ children }) => {
const profileInfoUrl = BASE_URL + `users/GetCurrentUsersProfileInfo/`
const {loading, getTokenSilently} = useAuth0();
const [profileInfo, setProfileInfo] = useState( {})
useEffect(() => {
if (!loading && profileInfo.user.username === '' ) {
Communication.get(profileInfoUrl, getTokenSilently, setProfileInfo);
}
},[loading, getTokenSilently])
const context = {
profileInfo,
setProfileInfo
};
return (
<AppContext.Provider value={ context }>
{children}
</AppContext.Provider>
);
}
Now, in my component, I want to display this data. One of them is users selected categories, a list of an id and isSelected, which should feed a checkboxlist.
const TsProfileSettingsPage = ( ) => {
//..
const { profileInfo, setProfileInfo } = useContext(AppContext);
const userCategories = Object.assign({}, ...profileInfo.userDetails.categories.map((c) => ({[c.id]: c})));
const [checkedCategories, setCheckedCategories] = useState(userCategories);
console.log("profileInfo.userDetails.categories : " + JSON.stringify(profileInfo.userDetails.categories));
console.log("userCategories : " + JSON.stringify(userCategories));//correct dictionary
console.log("checkedCategories : " + JSON.stringify(checkedCategories)); //empty object!
//...rest of code
So I load users selection and converts is to a dictionary that ends up in "userCategories". I use that "userCategories" to init checkedCategories. BUT, from the console.logs below, userCategories is set correctly, but checkedCategories is not, its becomes an empty object!
So my question is. Am I thinking wrong here? (obviously I am), I just want to have an external state.
Everything loads ok, but when I use it to init a useState() in my component it does not get set. I have tested to have another useEffect() in my component, but I just get the feeling that Im doing something more basic error.
Thanks for any help

React JS Error: Invalid attempt to destructure non-iterable instance

I have a sort filter that takes an array to populate the options. Trying to see the option value equal to the text within the array but I get the error within the title:
Invalid attempt to destructure non-iterable instance
I need to pass the text as the value within the option tag so that when the user updates the filter, the correct text displays to the choice the user made.
Here is my code:
function Sorting({by, order, rp}: SortingProps) {
const opts = [
['Price (low)', 'price', 'asc'],
['Price (high)', 'price', 'desc'],
['Discount (low)', 'discount', 'asc'],
['Discount (high)', 'discount', 'desc'],
['Most popular', 'arrival', 'latest'],
['Most recent', 'arrival', 'latest'],
];
const onChange = (i) => {
const [text, by, order] = opts[i];
refresh({so: {[by]: order}});
/* GA TRACKING */
ga('send', 'event', 'My Shop Sort By', text, 'Used');
};
return (
<div className={cn(shop.sorting, rp.sorting.fill && shop.sortingFill)}>
<Select className={shop.sortingSelect} label="Sort By" onChange={onChange} value={`${by}:${order}`}>
{opts.map(([text], i) =>
<Option key={i} value={text}>{text}</Option>
)}
</Select>
</div>
)
}
I caused this error a few times because whenever I write a useState hook, which I would do often, I'm used to using an array to destructure like so:
const [ state, setState ] = useState();
But my custom hooks usually return an object with properties:
const { data, isLoading } = useMyCustomFetchApiHook();
Sometime I accidentally write [ data, isLoading ] instead of { data, isLoading }, which tiggers this message because you're asking to destructure properties from an array [], when the object you're destructuring from is an object {}.
I also encountered a similar error and honestly, I did a very silly mistake maybe because of editor autocomplete.
I had to make use of the useState hook but somehow due to autocomplete, I wrote it like this.
const [state, setState] = useEffect(defaultValue);
instead of :(.
const [state, setState] = useState(defaultValue);
Hope it will help as an error message, in this case, was not helpful at all until I spent some time debugging this.
The error Invalid attempt to destructure non-iterable instance occurs because of a logic/coding error. The following javascript is used to illustrate the problem:
[aaa,bbb] = somefunc()
When somefunc() is called it must return an array of at least two items. If it doesn't there is no way to convert the result from somefunc() into values for aaa and bbb. For example, the code:
[aaa,bbb] = { 'some': 'object'}
would produce this error.
So the error is really a Javascript coding error and it is just happening inside React code that handles this situation by printing the error shown. See MDN for destructuring assignment documentation.
As #Mayank Shukla states in his answer, the answer to the OP question is to fix this line of code:
const [text, by, order] = opts[i];
By changing it to this:
const [text, by, order] = opts[i.target.value];
With my above description it should be clearer that opts[i] the original code by the OP was not returning an array of at least 3 items so the javascript runtime was not able to set the values of the variables text, by and order. The modified/fixed code does return an array so the variables can be set.
After looking for an answer to this question I realized that the other answers were correct, and I am just summarizing the root cause of the error message.
If anybody is using useState() hooks, and facing above issue while using context. They can try below solution.
In place of []
const [userInfo, setUserInfo] = useContext(userInfoContext);
Use {}
const {userInfo, setUserInfo} = useContext(userInfoContext); // {} can fix your issue
I straight up tried to assign it an empty object!
Bad :(
const [state, setState] = {};
Good :)
const [state, setState] = useState({});
You aren't passing an argument along with your onChange, it's a pretty common thing to miss - however a little less obvious with a select/option combination.
It should look something like:
class Sorting extends React.Component {
constructor(props) {
super(props);
this.opts = [
['Price (low)', 'price', 'asc'],
['Price (high)', 'price', 'desc'],
['Discount (low)', 'discount', 'asc'],
['Discount (high)', 'discount', 'desc'],
['Most popular', 'arrival', 'latest'],
['Most recent', 'arrival', 'latest'],
];
this.state = {
selected: 0, // default value
}
this.onChange = this.onChange.bind(this);
}
onChange(i) {
const [text, by, order] = opts[i.target.value];
};
render() {
return (
<div>
<select onChange={this.onChange} value={this.state.selected}>
{this.opts.map(([text], i) =>
<option key={i} value={i}>{text}</option>
)}
</select>
</div>
)
}
}
ReactDOM.render(<Sorting />, document.getElementById("a"));
Note I stripped out your classes and styles to keep it simple. Also note you were using uppercase Select and Option - unless these are custom in house components, they should be lowercase.
Note2 I also introduced state, because the state of the select needs to be stored somewhere - if you are maintaining the state of the select box outside of this component, you can obviously use a combination of props/callbacks to maintain that value one level higher.
http://codepen.io/cjke/pen/egPKPB?editors=0010
I encountered this question because I had the same error, but in order to make it work for me, I wrote
const [someRef] = useRef(null);
When it should have been
const someRef = useRef(null); // removed the braces []
Make sure your useState is a function call not an array type.
useState('') not useState['']
Problem is with variable i, i will be the event object, use i.target.value to get the value selected by the user, one more thing you used text as the value of the options, instead of that use the index, it will work, try this:
const onChange = (i) => {
const [text, by, order] = opts[i.target.value];
refresh({so: {[by]: order}});
/* GA TRACKING */
ga('send', 'event', 'My Shop Sort By', text, 'Used');
};
return (
<div className={cn(shop.sorting, rp.sorting.fill && shop.sortingFill)}>
<select className={shop.sortingSelect} label="Sort By" onChange={onChange} value={`${by}:${order}`}>
{opts.map(([text], i) =>
<option key={i} value={i}>{text}</option>
)}
</select>
</div>
)
Check this fiddle: https://jsfiddle.net/5pzcr0ef/
This error can also happen if you have an async function that returns an array, but you forget to run it with await. This will result in that error:
const myFunction = async () => {
return [1, 2]
}
const [num1, num2] = myFunction();
Whereas this will succeed:
const [num1, num2] = await myFunction();
Invalid attempt to destructure non-iterable instance
says the instance you are trying to iterate is not iterable. What you should do is checking whether the opt object is iterable and can be accessed in the JSX code.
When using React Context API, this error can occur if you try to use React.useContext() in a component that is not wrapped in the <ContextProvider>
For example, the following code would throw this error:
const App = () => {
const [state, setState] = React.useContext(MyContext)
return (
<ContextProvider>
<SubComponent />
</ContextProvider>
);
}
You can use the line:
const [state, setState] = React.useContext(MyContext)
inside the SubComponent, but not inside the App component. If you want to use it in the App component, place App component inside another component and wrap the App component in <ContextProvider></ContextProvider>.
const App = () => {
const [state, setState] = React.useContext(MyContext)
return (
<div>
<SubComponent />
</div>);
}
const Master = () => {
<ContextProvider>
<App/>
</ContextProvider>
}
In my Case i did this mistake
const {width,height} = Dimensions("window") to const[width ,height] = Dimensions("window)
For me the issue was that I tried to destructure useState incorrectly.
I wrote
const [counter] = useState(0)[0];
instead of
const counter = useState(0)[0];
My 5 cents.
I did
const [seconds, setSeconds] = 0
instead of
const [seconds, setSeconds] = useState(0)
Hope it helps someone. It got me mad for a minute or two because "it worked yesterday" and error was reported on top of functional component body actually, so it wasn't giving right clues and I had to dig deeper. I commented out whole function body just to make sure everything was OK with my arguments... and the error was below in code.

Resources