React: Passing value to a functional component results in undefined value - javascript

I'm trying to pass values one by one to a functional component from array.map function. Unfortunately the component is not redering the value.
This is what I'm getting. There are room names stored in DB that should be printed here.
Homescreen.js
import React, { useState, useEffect } from "react";
import axios from "axios";
import Room from "../components/Room";
export default function Homescreen() {
const [rooms, setRooms] = useState([]);
const [loading, setLoading] = useState();
const [error, setError] = useState();
useEffect(async () => {
try {
setLoading(true);
const data = (await axios.get("/api/rooms/getallrooms")).data;
setRooms(data);
setLoading(false);
setError(false);
} catch (error) {
setLoading(false);
setError(true);
console.log(error);
}
}, []);
return (
<div>
{loading ? (
<h1>Loading...</h1>
) : error ? (
<h1>Error fetching details from API</h1>
) : (
rooms.map((room) => {
return (<div>
<Room key={room._id} room={room}></Room>
</div>
)
})
)}
</div>
);
}
Room.js (Funcitonal component that should print room names):
import React from "react";
function Room(room){
console.log(room.name)
return(
<div>
<h1>Room name: {room.name}.</h1>
</div>
)
}
export default Room;
The data is fetched correctly from db because if, instead of passing the value to component I print directly into my main screen, the values are printed.
In otherwords, in Homescreen.js, doing <p>{room.name}</p> instead of <Room key={room._id} room={room}></Room> print room names correctly.
So I reckon the problem is coming when I'm passing the values as props.
Any help is much appreciated. Thanks.

The parameter passed to a function component is the props object which contains the passed props, so you just need to grab props.room from there:
function Room(props){
console.log(props.room.name)
return(
<div>
<h1>Room name: {props.room.name}.</h1>
</div>
)
}
Or, with object destructuring:
function Room({ room }){
console.log(room.name)
return(
<div>
<h1>Room name: {room.name}.</h1>
</div>
)
}

Related

Error: Objects are not valid as a React child (found: [object Promise]).….. While getting data from supabase

I am having a problem while getting data from supabase .
Could any one help me
`
import Link from "next/link";
import { supabase } from "../../supabase"
async function Index(){
const { data, error} = await supabase.from("Employees").select("*")
return (
<>
<div className="container flex justify-center">
<h1>Employees</h1>
</div>
{data.map((index) => {
return (
<h1>{index.name}</h1>
)
})}
<Link href="/employees/addemployee">
<h1>Add employee</h1>
</Link>
</>
)
}
export default Index;
`
I tried using map, other function, and looked it up yet nothing works
The problem is how you are fetching the data. Try fetching your data inside an useEffect hook:
import Link from "next/link";
import { supabase } from "../../supabase";
import { useState, useEffect } from "react";
function Index() {
// const { data, error } = await supabase.from("Employees").select("*")
const [data, setData] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
let cancelFetch = false; // to avoid race conditions on React18
supabase
.from("Employees")
.select("*")
.then((res) => {
if (!cancelFetch) {
setData(res.data);
setError(res.error);
}
});
return () => {
cancelFetch = true;
};
}, []);
return (
<>
<div className="container flex justify-center">
<h1>Employees</h1>
</div>
{data.map((index) => {
return <h1>{index.name}</h1>;
})}
<Link href="/employees/addemployee">
<h1>Add employee</h1>
</Link>
</>
);
}
export default Index;
More info on fetching and useEffect here: https://beta.reactjs.org/apis/react/useEffect#fetching-data-with-effects
Your source code is invalid. React components should always be a function (or class) that returns a react object. it does not accept a promise that returns a react object.
You will probably want to use react's useEffect to solve this problem:
import { useState, useEffect } from "react";
import Link from "next/link";
import { supabase } from "../../supabase"
async function Index(){
const [data, setData] = useState()
const [error, setError] = useState()
useEffect(() => {
supabase.from("Employees").select("*")
.then(data => setData(data))
.catch(err => setError(err))
}, [])
return (
<>
<div className="container flex justify-center">
<h1>Employees</h1>
</div>
{data.map((index) => {
return (
<h1>{index.name}</h1>
)
})}
<Link href="/employees/addemployee">
<h1>Add employee</h1>
</Link>
</>
)
}
export default Index;
Your component cannot be async, because it returns a Promise and React doesn't like that.
There is a cool function on Next.js that allows you to fetch data asynchronously, try that:
function Index({ data }) {
return (
<>
<div className="container flex justify-center">
<h1>Employees</h1>
</div>
{data.map((index) => {
return (
<h1>{index.name}</h1>
)
})}
<Link href="/employees/addemployee">
<h1>Add employee</h1>
</Link>
</>
)
}
export default Index;
export async function getServerSideProps() {
const { data, error} = await supabase.from("Employees").select("*")
return {
props: {
data: data
}
}
}
More here: https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props
Based on the way you are fetching data, I believe you are using next13 and you are in app directory. When you rendered jsx
{data.map((index) => {
return (
<h1>{index.name}</h1>
)
})}
index refers to each element inside the data array. Most likely index.name is an object. that is why it is throwing that error.
console.log("index name",index.name)
If you are using async functional component, you should be using Suspense api. Create a separeate component maybe async Users, fetch the data inside this component, and when you want to display the users inside the Index
import {Suspense} from "react"
function Index(){
return (
<>
....
<Suspense fallback={ <h1>Users are loading...</h1>} >
<Users/>
</Suspense>
....
</>
)
}
You only use async component inside app folder and server component.

Prevent losing data when refreshing on a different route - react

I wanted to prevent losing state on page refresh while being on a different route path. Im curious why the first example does not work. From what i understand when app mounts first thing that gonna render is component itself and then useEffects run. Since i got 3 here, first fetches and saves the data to the invoiceList state and then next useEffect that run should fill localStorage key with invoiceList state data. The last one obviously retrieve the data.
The second one does fill the "invoiceData" localStorage key with an empty array. Why is this happening if the invoiceList state already have the data after the first useEffect?
The second example that i provided works. I removed second useEffect and set localStorage key in the first useEffect with response data that i get from fetch.
I also wonder if im doing everything correct here. Any feedback appreciated :)
First example (not working):
import { ReactElement, useEffect, useState } from "react";
import { Outlet } from "react-router-dom";
import { Bar } from "../../components/Bar/Bar";
import { Invoice } from "./Root.utils";
type Props = {};
const Root = (props: Props): ReactElement => {
const [invoiceList, setInvoiceList] = useState<Invoice[]>([]);
useEffect(() => {
const fetchData = async () => {
const response = await fetch("./data.json");
const data = await response.json();
setInvoiceList(data);
};
fetchData();
}, []);
useEffect(() => {
window.localStorage.setItem("invoiceData", JSON.stringify(invoiceList));
}, []);
useEffect(() => {
setInvoiceList(
JSON.parse(window.localStorage.getItem("invoiceData") || "[]")
);
}, []);
return (
<div>
<Bar />
<Outlet context={{ invoiceList }} />
</div>
);
};
export default Root;
Second example (working):
import { ReactElement, useEffect, useState } from "react";
import { Outlet } from "react-router-dom";
import { Bar } from "../../components/Bar/Bar";
import { Invoice } from "./Root.utils";
type Props = {};
const Root = (props: Props): ReactElement => {
const [invoiceList, setInvoiceList] = useState<Invoice[]>([]);
useEffect(() => {
const fetchData = async () => {
const response = await fetch("./data.json");
const data = await response.json();
window.localStorage.setItem("invoiceData", JSON.stringify(data));
setInvoiceList(data);
};
fetchData();
}, []);
useEffect(() => {
setInvoiceList(
JSON.parse(window.localStorage.getItem("invoiceData") || "[]")
);
}, []);
return (
<div>
<Bar />
<Outlet context={{ invoiceList }} />
</div>
);
};
export default Root;
The first example is never storing the data into the localStorage because the fetch is an asynchronous function that and you are writing basically always the empty array into your localStorage.
The order of execution in the first example will be:
fetchData called
window.localStorage.setItem("invoiceData", JSON.stringify(invoiceList)); <- still empty array
setInvoiceList(JSON.parse(window.localStorage.getItem("invoiceData") || "[]"));
response.json() called
setInvoiceList(data); called
I would also recommend to improve your code a little like that:
import React, { useEffect, useState } from "react";
import { Outlet } from "react-router-dom";
import { Bar } from "../../components/Bar/Bar";
import { Invoice } from "./Root.utils";
const Root: React.FC = () => {
const [invoiceList, setInvoiceList] = useState<Invoice[]>([]);
useEffect(() => {
setInvoiceList(
JSON.parse(window.localStorage.getItem("invoiceData") || "[]")
);
const fetchData = async () => {
const response = await fetch("./data.json");
const data = await response.json();
window.localStorage.setItem("invoiceData", JSON.stringify(data));
setInvoiceList(data);
};
fetchData();
}, []);
return (
<div>
<Bar />
<Outlet context={{ invoiceList }} />
</div>
);
};
export default Root;
You can use the Link component from react-router and specify to={} as an object where you specify pathname as the route to go to. Then add a variable e.g. data to hold the value you want to pass on. See the example below.
Using the <Link /> component:
<Link
to={{
pathname: "/page",
state: data // your data array of objects
}}
>
Using history.push()
this.props.history.push({
pathname: '/page',
state: data // your data array of objects
})
Using either of the above options you can now access data on the location object as per the below in your page component.
render() {
const { state } = this.props.location
return (
// render logic here
)
}

Showing data from state variable in ReactJS forms infinite loop

I'm trying to show data from an API call. The structure of the application looks like
MainComponent -> RefreshButton (this will fetch the data)
MainComponent -> ShowData (this will show the data that is being fetched)
MainComponent has a state userData that will store the response that was received from the API. Now the issue is, whenever I'm clicking the button, it is getting into an infinite loop of rendering and calls the API infinite times.
This is what the error shows:
Here is my MainComponent -
import React, { useEffect, useState } from "react";
import RefreshButton from "./RefreshButton";
import ShowData from "./ShowData";
const MainComponent = () => {
const [userData, setUserData] = useState();
useEffect(() => {
console.log(userData);
}, [userData]);
return (
<div>
<p style={{ textAlign: "center" }}>Main Component</p>
<RefreshButton setUserData={setUserData} />
{userData && <ShowData userData={userData} />}
</div>
);
};
export default MainComponent;
Here is my RefreshButton component -
import React from "react";
import axios from "axios";
const RefreshButton = ({ setUserData }) => {
const getData = () => {
axios
.get(`https://jsonplaceholder.typicode.com/todos`)
.then((response) => {
if (response.status === 200) setUserData(response.data);
})
.catch((err) => {
console.log(err);
});
};
return (
<div className="button-container">
<button className="fetch-data-button" onClick={() => getData()}>
Fetch new data
</button>
</div>
);
};
export default RefreshButton;
And here is my ShowData component -
import React from "react";
const ShowData = ({ userData }) => {
console.log("Here", userData);
return (
<>
{userData.map((info, idx) => (
<div className="user-data" key={idx}>
{info}
</div>
))}
</>
);
};
export default ShowData;
PS - I'm new to React and couldn't find a potential solution on this, there are several tutorials on how to fetch data from API calls and show it, but I wanted to know what I'm doing wrong here. Thanks in advance!
You might have misunderstood with the infinite loop error
It's actually a render error as being shown here:
To fix your render error, simply put an actual string variable in the {}
Because the response was an array of this object, so you can't simply render the whole object but need to pick an actual string variable inside:
[{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}],
Change to something like this:
const ShowData = ({ userData }) => {
console.log("Here", userData);
return (
<>
{userData.map((info, idx) => (
<div className="user-data" key={idx}>
{info.title} // <-- Put a title here.
</div>
))}
</>
);
};
Remove
useEffect(() => {
console.log(userData);
},[userData])
This will reevaluate component whenever user data changes, which Leeds to call showData infinitely

How to pass fetched data as props to a component with react hooks?

I'm trying to create a component with data from a API, but i don't find a way to pass the data as props to my 'Episode' component.
Here is the code:
App.js
import React, { useState, useEffect } from 'react';
const url = 'https://swapi.co/api/films/'
function Episode(props){
return (
<div>
{props.title}
{props.release_date}
</div>
)
}
function App() {
const [content, setContent] = useState(null)
useEffect(async () => {
const response = await fetch(url)
const data = await response.json()
const [...movies] = data.results
setContent(movies)
}, [])
return (
<div>
{content && <Episode movie={content[0]}/>}
</div>
)
}
export default App;
your data is in a movie prop:
<Episode movie={content[0]}/>
So in your component you need to access props.movie:
function Episode(props){
return (
<div>
{props.movie.title}
{props.movie.release_date}
</div>
)
}

Foreach loop in return statement of react

I have fetched some information an API and now I am trying to show the information fetched from it. The information which I have fetched includes books_authors , books_id's , price and the dataset is quite large and I am unable to display this information from my following approach...can someone help me with it... I am new to react
This is what I have tried so far:
import React from "react";
import Head from './head';
function App(){
let s;
const proxy = 'http://cors-anywhere.herokuapp.com/';
const api = `${proxy}http://starlord.hackerearth.com/books`;
fetch(api)
.then(response =>{
return response.json();
})
.then(data =>{
console.log(data);
data.forEach((index) => {
s=index;
<Head s/>
});
});
return(
<Head />
);
}
export default App;
//the head component
import React from "react";
function Head(props){
return(
<div className="app">
<div className="heading">
<h1>BOOK_CAVE</h1>
<div className="heading_description">So many books...so
little time...</div>
</div>
<div className="author">{props.authors}</div>
<div className="id">{props.bookID}</div>
<div className="price">{props.price}</div>
</div>
);
}
export default Head;
You can do this using Hooks, useState to store data and useEffect to call API,
import React, {useState,useEffect} from "react";
import Head from './head';
function App(){
const [data, setData] = useState([])
useEffect(() => {
const proxy = 'http://cors-anywhere.herokuapp.com/';
const api = `${proxy}http://starlord.hackerearth.com/books`;
fetch(api).then(response => {
setData(response.json())
})
},[])
return(
<div>
{data.length>0 && data.map(book => <Head book={book} />)
</div>
);
}
And you Head component should be,
function Head(props){
return(
<div className="app">
<div className="heading">
<h1>BOOK_CAVE</h1>
<div className="heading_description">So many books...so
little time...</div>
</div>
<div className="author">{props.book.authors}</div>
<div className="id">{props.book.bookID}</div>
<div className="price">{props.book.price}</div>
</div>
);
}
The books array you fetch from the API should be stored in a state and you should render the app according to that state. The data fetching should happen when the component mounted, so you make the call on componentDidMount lifecycle method, and update the state when the data finished fetching. Also, the Head component recieves three props, but you pass only one.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
books: [],
fetching: true,
}
componentDidMount() {
const proxy = 'http://cors-anywhere.herokuapp.com/';
const api = `${proxy}http://starlord.hackerearth.com/books`;
fetch(api)
.then(response => response.json() )
.then(data => this.setState({books: data, fetching: false,}) );
}
render() {
if (this.state.fetching) {
return <div>Loading...</div>
}
const headArray = this.state.books.map(book => (
<Head
authors={book.authors}
bookID={book.bookID}
price={book.price}
/>
));
return(
<div>
{headArray}
</div>
);
}
}
You need to:
Enclose the fetch n a lifecycle method or a useEffect hook
Put the API's response in a state (which will cause a re-render)
Iterate over the state in the return statement, using map, not forEach
Example using hooks:
function App(){
const [apiData, setApiData] = useState([])
const [isLoading, setIsLoading] = useState(true)
useEffect(
() => {
const proxy = 'http://cors-anywhere.herokuapp.com/';
const api = `${proxy}http://starlord.hackerearth.com/books`;
fetch(api).then(response => {
setApiData(response.json())
setIsLoading(false)
})
},
[]
)
const authors = data.map((index) => index.authors).flat()
return(
<div>
{authors.map((author) => <Head author{author} />)
</div>
);
}

Categories

Resources