Accessing array indices in React - javascript

I am new to React and Javascript as a whole. I am simply trying to access array indices after a fetch. I can log a specific index after a file update, but when I refresh the page, I get an 'Undefined' error. I feel as if I am doing something wrong with my state, but I don't know enough about React to debug this. if I attempt to access " users[0].name", I get the "TypeError: Cannot read properties of undefined (reading 'name')
(anonymous function).
import React, { useEffect, useState } from "react";
import { Container, Card, Divider, Typography } from "#mui/material";
const RestData = () => {
const [data, setData] = useState([]);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users/")
.then((response) => response.json())
.then((data) => setData(data));
}, []);
return (
<Container>
{data.map((users) => {
return (
<Card
data={data}
key={users.id}
sx={{ width: "300px", mt: 2, background: "#ff9800" }}
>
<Typography variant="h6" color="seagreen">
{users.name}
</Typography>
<Divider />
{users.email}
<Divider />
{users.company.name}
<Divider />
{users.phone}
<Divider />
{users.website}
<Divider />
</Card>
);
})}
</Container>
);
};
export default RestData;

Although I don't see this occurring in the code you posted, you are getting that particular error because you are trying to access .name when the object
being accessed is undefined.
I presume you are trying to do something like data[0].name.
The issue is, your data array starts empty, so if you try to access any index of an empty array, it will be undefined, you will get that error.
The solution is to make sure the data array already has it's elements in before you access an index of it.
Now, there are many ways to do this. Because it doesn't seem to be a part of your code, it is hard to tell you the best way. But here is a couple:
1- Do an if check before attempting to access the property:
if (data[0]) {
console.log(data[0].name)
// do stuff
}
2- Check if the property exists first using the && operator:
For this case, the attempt to access the name is only done if data[0] is truthy.
console.log(data[0] && data[0].name)
3- Use optional chaining. This is the better one imo, since it allows you to skip the if and will only attempt to access a property if it is not undefined.
console.log(data[0]?.name)
So now to put this in practice, here is a sample app:
const App = () => {
const [data, setData] = React.useState([]);
React.useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users/")
.then((response) => response.json())
.then((data) => setData(data));
}, []);
console.log("data", data);
return (
<div>
<div>
<h3>Example by accessing the index directly with Optional Chaining:</h3>
<p>{data[0] && data[0].name}</p>
</div>
<div>
<h3>Example with the map</h3>
{(data).map((d) => {
return <p>{d.name}</p>;
})}
</div>
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('app')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>

Since you're fetching the data at rendering time you don't have users[0].name, so what I would suggest is to use the conditional operator ?.
So you have something like this
data[0]?.name
What happens in this case is that typescript try to access the property name at the given index, if the property is undefined returns null otherwise returns the value.
I've also made a quick sandbox for demonstration.

Related

ReactJs page renders fine when using .map of objects but if use index of array runs on first save but crashes on reload

I am creating a flashcard app, already have a page where I use .map() to render all items. Now I created another page so I can individually show one card at a time, so instead of using .map I used items[x] so I can increment by one with a onClick button. When I first save the file the live update renders fine as predicted, yet if I reload the page it crashes saying that it cannot read the word property of undefined. Now after doing some debugging I found that If I console log the array at first it shows up as zero/0 then prints out again with the info, even though I call the useSelector function to gather the info from the state before trying to access the data. Now this happens no matter if I use the .map() function but for some reason the .map() function does not crash and render fine as expected.
import React from "react";
import FlippableFlashcard from "../FlippableFlashcards/FlippableCard/FlippableCard.jsx";
import { Button, Container, Box } from "#mui/material";
import ArrowRightIcon from "#mui/icons-material/ArrowRight";
import ArrowLeftIcon from "#mui/icons-material/ArrowLeft";
import { useSelector } from "react-redux";
import useStyles from "./styles.js";
const Practice = () => {
const classes = useStyles();
const items = useSelector((state) => state.posts);
const handleAdd = () => {};
return (
<>
<Container className={classes.centerDiv}>
<FlippableFlashcard item={console.log(items[0])} />
{/*//This fails, but this does not <FlippableFlashcard item={items.map((item) => console.log(item))} />*/}
</Container>
<Box textAlign="center" className={classes.ButtonBar}>
<Button onClick={handleAdd}>
<ArrowLeftIcon />
</Button>
<Button>
<ArrowRightIcon />
</Button>
</Box>
</>
);
};
export default Practice;
specifically this <FlippableFlashcard item={console.log(items[0])} />
Error:
FlippableCard.jsx:20 Uncaught TypeError: Cannot read properties of undefined (reading 'word')
So I know it is because of that first console log being 0/null/undefined. Tried doing { items && items[0] } as I saw some saying this would work but did not.
Thanks to Akshay Mathur, I just simply did, works great, basically checks if data exists if not then simply console logs fetching, on the second auto reload it will render.
{items.length ? (
<FlippableFlashcard item={items[x]} key={items[x]._id} />
) : (
console.log("fetching data")
)}
Do something like this:
{items.length > 0 && items.map((item,index)=>(<FlippableFlashcard item={item} key={index} />)}

React table library that supports mutable data ( useRef )

Basically i'm looking for react table library that can take a mutable object ( to be specific an useRef object) as the main source of data to be displayed.
Basically i want to do something like this:
const TableViewComponent = () =>{
const tableData = useRef([{ rowName: 'test', value:0}] -> basically an array of objects that represents data for every row (the structure doesnt matter)
# code that receives data from server and updates the tableData.current with the data needed
return(
<Table data={tableData.current}/>
)
}
Basically, since i get a bunch of messages from the server and i update the data constantly (the number of rows stays the same), i don't want to rerender the table everytime. So i want to use the useRef to change the data thats being displayed on the table without triggering a rerender from react.
Im not sure if its something that can be done but any help is appreciated :). I tried react-table, rc-table but they didnt seem to work.
Basically, it looks to me like you'll have to do it yourself.
There's some libraries that might help you (like useTable which focuses on headless components) but I don't think they offer what you're looking for.
So let's quickly do a mock-up! (note: this is a quick sketch, assume that the undefined variables are stored somewhere else or are given from the fetch)
function useTableData({ initialData, itemsPerPage, ...etc }) {
const data = useRef(initialData);
const [loading, setLoading] = useState(false);
useEffect(() => {
data.current = fetchFromSomeWhere(...etc);
() => (data.current = null);
}, [etc /* Place other dependencies that invalidate out data here*/]);
const handleNewPage = useCallback(
async ({ pageIndex }) => {
if (!data.current[pageIndex * itemsPerPage]) {
setLoading(true);
data.current = [...data.current, await fetchMoreData(pageIndex)];
}
setLoading(false);
return data.current;
},
[itemsPerPage, data, setLoading],
);
return [data, handleNewPage, loading];
}
Notice how every single thing returned from this hook is a constant reference except for the loading! Meaning, that we can safely pass this to a memoized table, and it won't trigger any re-renders.
const TableContainer = memo(etc => {
const [data, handleNewPage, loading] = useDataForTable(...etc);
return (
<>
{loading && <Spinner />}
{/* Look ma, no expensive-renders! */}
<Body {...{ data }} />
<Pagination {...{ handleNewPage }} />
<OtherActions />
</>
);
});
However, you might have noticed that the body won't re-render when we click on the next page! Which was the whole point of pagination! So now we need some sort of state that'll force the Body to re-render
const TableContainer = memo(etc => {
const [currentPage, setPage] = useState(0);
const [data, handleNewPage, loading] = useDataForTable(...etc);
return (
<>
{loading && <Spinner />}
{/* We're still re-rendering here :( */}
<Body {...{ data, currentPage }} />
<Footer {...{ handleNewPage, setPage }} />
<OtherActions />
</>
);
});
And so we're left with a table that to use properly we need to:
Exercise restraint and properly invalidate old data. Otherwise, we'd be displaying incorrect data.
'Unpack' the data from the current ref on the Body component, and then render it.
In essence, after all that work we're still left with a solution isn't particularly attractive, unless you have some really complicated actions or some expensive scaffolding around the TableComponent itself. This might however be your case.

Error : Cannot read property 'map' of undefined React.JS

I'm following the react js tutorial, and I keep running into this issue
import React from "react";
import NewsCard from "../NewsCard/NewsCard";
const NewsCards = ({ articles }) => {
return (
<div>
{articles.map((article, i) => {
<NewsCard />;
})}
</div>
);
};
export default NewsCards;
Seems like your articles does not have default value as [].
You can change as follow. And you should give key attribute when using map function.
const NewsCards = ({ articles }) => {
const data = articles ? articles : []
return (
<div>
{data.map((article, i) => {
<NewsCard key={article.id}/>;
})}
</div>
);
};
Probably articles is not initialized when you try to map throught it. Try this:
{articles?.map((article, i) => {
<NewsCard />;
})}
OR
{articles && articles.map((article, i) => {
<NewsCard />;
})}
</div>
That way you will first make sure if articles exist
This means that the articles prop is undefined.
There are several ways to solve this. The first and easiest way is by implementing the following logic:
{articles?.length ? articles.map((article, i) => <NewsCard />) : "There are no articles here."}
Another way to solve this is by implementing React proptypes - you can read about this here.
Third and "hardest" (but probably best) way to solve this is by using a static type checking tool. Flow comes to mind, but you can use TypeScript too.
If you still need help, just like what the previous answers said, make sure that articles is initialized/defined by using the && operator to make that check. Also, based upon what you wrote, the map method is returning undefined since you specified a function body (using the function body bracket notation {} ) without a return statement. So instead write the map method like this:
<div>
{articles && articles.map((article, i) => <NewsCard />)}
</div>
or like this:
<div>
{articles && articles.map((article, i) => {
return <NewsCard />
})}
</div>
The first example implies an implicit return since an arrow function is being used and a function body is not present (there are no function body brackets { }).

Mapping from passed props in a functional component

I am building a website that is reliant on a json file for all of its information.
In my app.js the json info is showing properly when I console.log() it, but when I try and pass it to my functional components it is giving me undefined.
in app.js
<Route
exact
path="/puppies"
render={props => (
<Puppies {...props} propdata={this.state.propdata} />
)}
/>
This seems to be working fine, however when I try and map it inside the component it tells me that its undefined.
function Puppies(propdata) {
return <div>{propdata.puppies.map(puppies =>
<h1>{puppies.name}</h1>
)}</div>;
}
I have done this before but with a class component. So most likely I am making a mistake with the functional component.
The full code is viewable here:
https://github.com/Imstupidpleasehelp/Puppywebsite/tree/master/src
Thank you for your time.
You'll probably need to check that the data is null of undefined. You are passing a big object with data, I recommend to pass more specific props instead of a big object.
I like to prevent my data to be undefined in 2 ways:
lodash.get
Optional Chaining
Usage:
import _ from 'lodash';
function Puppies({ propdata }) {
const puppies = _.get(propdata, 'puppies', []);
return (
<div>
{puppies.map(puppies => <h1>{puppies.name}</h1>)}
</div>
);
}
or
function Puppies({ propdata }) {
const puppies = propdata?.puppies || [];
return (
<div>
{puppies.map(puppies => <h1>{puppies.name}</h1>)}
</div>
);
}
What you have as propdata is actually just an object containing all properties that you have passed in. You should use destructuring to get the actual propdata value.
Solution:
function Puppies({propdata}) {
return (
<div>
{propdata.puppies.map(puppies =>
<h1>{puppies.name}</h1>
)}
</div>
);
}
Since this is asynchronous request to get the data, your data is not readily available hence you need to handle that scenario.
function Puppies(propdata) {
return (
{
propdata.puppies.length>0 ? <div>
propdata.puppies.map((puppies)=>{
<h1>{puppies.name}</h1>
})
</div> :null
}
)

How to pass props to subcomponent in Reactjs

I am attempting to pass props from a parent component function to a child in React without any luck. I am utilizing the React Data Table Component's filtering functionality. The example documentation in Storybook is great, however I want to change the search box into a list of tags, that when clicked, will filter the data table just the same.
The data I'm trying to parse into individual "tags" is structured like this:
[{"id":"09090","first_name":"Cynthia","last_name":"McDonald","email":"email1#gmail.com","profile_url":"https:myprofile.com/1","types":["professor","science"]},
{"id":"03030","first_name":"Ryan","last_name":"Burke","email":"email2#gmail.com","profile_url":"https://myprofile.com/2","types":["student","science"]},
{"id":"05050","first_name":"Stewart","last_name":"Hook","email":"email3#gmail.com","profile_url":"https://myprofile.com/3","types":["professor","math"]}]
I am trying to create a unique tag list of the "types" attribute that acts as a filter onClick instead of onChange, just like the original search textbox example. So based on the sample data, we would end up with tags for professor, science, student, and math.
If you look at the code in Storybook, more specifically, line 45:
const subHeaderComponentMemo = React.useMemo(() =>
<Filter onFilter={value => setFilterText(value)} />, []);
My data is loaded via an API call and I am trying to pass that data to the subHeaderComponentMemo with props, i.e.,
const subHeaderComponentMemo = React.useMemo(() =>
<Filter data={people} onFilter={value => setFilterText(value)} />, []);
I am then trying to receive and loop through that data on line 20 and am replacing most of that code so that it will render the unique tags from the types attribute of the data.
Storybook code:
const Filter = ({ onFilter }) => (
<TextField id="search" type="search" role="search" placeholder="Search Title" onChange={e => onFilter(e.target.value)} />
);
My failed code
const Filter = ({props, onFilter }) => {
// Get a unique array of types
const types = [...new Set(props.types.map(type => type))];
return types.map(type => (
<span
className="badge badge-primary"
onClick={() => onFilter({ type })}
onKeyDown={() => onFilter({ type })}
tabIndex="0"
role="button"
>
{type}
</span>
));
};
This is of course resulting in an epic fail. The module isn't even displaying.
I'm new to React, so any insight/help would be greatly appreciated.
Updated code based on feedback
const Filter = ({ data, onFilter }) => {
console.dir(data);
if (data.length === 0) {
return <div>No data</div>;
}
const types = [...new Set(data.types.map(type => type))];
return (
<>
{types.map(type => (
<span
className="badge badge-primary"
onClick={() => onFilter({ type })}
onKeyDown={() => onFilter({ type })}
tabIndex="0"
role="button"
>
{type}
</span>
))}
;
</>
);
};
One of the errors I got when I made the changes was "cannot read property of 'map' undefined", which I equated to the fact that this component may be rendering before the data gets there and it is empty. So I added an if with a return in case this happens.
I also rewrote the return the statement in Filter based on the feedback because it did look like it would loop and try to render a new component for each iteration.
However, the issue still stands with the data not being present. The table is now loading with the data, but this component is just returning the "No data" div as if nothing ever happened. I'm not sure if this is due to the useMemo hook or what. Besides that, no errors.
Thank you for your time and feedback.
Another update
UseMemo needed the prop to be a dependency in its array... I'm finally seeing data when I console.log it.
const subHeaderComponentMemo = useMemo(
() => <Filter data={people} onFilter={value => setFilterText(value)} />,
[people]
);
However, I'm getting the 'map' undefined error again, as if my data is not in the structure I expect it to be in. I have not changed it and this is the code I'm using to try to access it (as shared before):
const types = [...new Set(data.types.map(type => type))];
Thanks again everyone... getting closer!
you should rewrite you component signature to match the one you are trying to use
const Filter = ({props, onFilter }) => // you are expecting a props name props
<Filter data={people} onFilter={value => setFilterText(value)} />, []); // you are passing data and setFilterText
so you should change it to
const Filter = ({data, onFilter }) =>

Categories

Resources