I'm new to React.js and I'm actually trying to create a page structured in the following way:
-A form to insert [version,date,image,content] and post a JSON via POST request (with Axios)
-A display part in which I GET the same data and display on screen by clicking on a button
actually I'm able to do this by introducing the Get and Post logic in the used component.
In order to use Components and have a clear code, i would to have a separate component to call inside the various components to make a GET or POST request.
By using hooks I'm not able to do this. The actual code is:
This is UserGet.js
import axios from "axios";
import {useState} from "react";
const baseURL = "http://localhost:8088/";
const userURL ="changelog/version.txt";
function UserGet(props) {
const [get, setGet] = useState(null);
axios.get(baseURL+userURL).then((response) => {
setGet(response.data);
});
return [get, setGet] ;
}
export default UserGet;
while in the component I want to use as data displayer:
const DisplayInfo =(props) => {
const [items, setItems] = useState([]);
const onFinish = (() => {
setItems(UserGet());
})
const DisplayData = items.map(
(info)=>{
return(
<div className='changelogDisplay' key={info.version}>
<button className='version' >v {info.version} </button>
<div className='datelog' > {info.data} </div>
<div className='dataHeader' > {info.title} </div>
<div className='whatsnew' > {info.text} </div>
<div className='imageLog' alt='Changelog pic' >{info.img} </div>
</div>
)
});
return(
<div>
<Form onFinish={onFinish}>
<Form.Item>
<Button type="primary" htmlType="submit" name='Refresh'> Refresh list</Button>
<div className='displayData'>
{DisplayData}
</div>
</Form.Item>
</Form>
</div>
)
}
export default DisplayInfo;
Actually, if I use
const response = UserGet();
I'm able to get data and insert into items for further process. I want to get the data using Onfinish or Onclick properties but I'm not able due to Hooks that cannot stay inside a function. How can I get a solution?
I tried different ways, and I don't want to use Redux at the moment in order to improve my knowledge on this.
Hooks are not so simple to me
Currently you're setting the items state to the [get, setGet] value that returns from the UserGet hook.
Here you return the data back from the request.
async function UserGet(props) {
const response = await axios.get(baseURL + userURL);
return response.data;
}
Then in the onFinish
const onFinish = (() => {
const newItems = UserGet();
setItems(newItems);
// or
// setItems(UserGet());
});
I hope this helps you with your project!
Related
I try to populate a table with an API call in NextJS. I use getStaticProps function to get the data and pass them to the component. I tried map() function to access each data in the object but it doesn't work and doesn't populate the required data. when I run the app the console outputs the below error.
Warning: data for page "/" is 150 kB, this amount of data can reduce performance.
When I checked the page source it has the full API object and I don't understand why it does that. Below I have put the code of index.js and CountriesTable.js
index.js
import Head from "next/head";
import CountriesTable from "../components/CountriesTable/CountriesTable";
import Layout from "../components/Layouts/layout";
import SearchInput from "../components/SearchInput/SearchInput";
import styles from "../styles/Home.module.css";
export default function HomePage({ countries }) {
return (
<Layout>
<div className={styles.counts}>found {countries.length} countries</div>
<SearchInput placeholder="Search for country" />
<CountriesTable countries={countries} />
</Layout>
);
}
export const getStaticProps = async () => {
const res = await fetch("https://restcountries.com/v3.1/region/asia");
const countries = await res.json();
return {
props: {
countries,
},
};
};
CountriesTable.js
import styles from './CountriesTable.module.css';
export default function CountriesTable({countries}) {
return (
<div>
<div className={styles.heading}>
<button className={styles.heading_name}>
<div>Name</div>
</button>
<button className={styles.heading_population}>
<div>Population</div>
</button>
</div>
{countries.map((country) => {
<div className={styles.row}>
<div className={styles.name}>{country.name}</div>
<div className={styles.population}>{country.population}</div>
</div>
})}
</div>
);
};
How can I resolve the error in the console and populate the table with data? TIA!
NextJS is a server rendered platform. Any props you pass to pages/components will be serialized in the source itself. Therefore, it is very important to trim data to fields which are necessary. Otherwise you will see entire json in the HTML source. (Working codesandbox)
Reduce the amount of data returned from getStaticProps, getServerSideProps, or getInitialProps to only the essential data to render the page.
As only name & population are required we can create a new dataset with these two properties only. This should get rid of the warning as well.
export const getStaticProps = async () => {
const res = await fetch("https://restcountries.com/v3.1/region/asia");
const countries = await res.json();
const countriesWithNamePopulation = countries.map((country) => ({
name: country.name.common,
population: country.population
}));
return {
props: {
countries: countriesWithNamePopulation
}
};
};
countries.map needs a return statement
{countries.map((country) => {
return (
<div>
<div>{country.name}</div>
<div>{country.population}</div>
</div>
);
})}
I cannot reproduce your console warning but you are missing the return statement inside your map function.
This works for me:
{countries.map((country, index) => {
return(
<div key={index}>
<div>{country.name.common}</div>
<div>{country.population}</div>
</div>
)
})}
Furthermore you need cannot display country.name as it is an object. I used country.name.common which is valid as it is a string. Additionally you need to add a key property to your div inside the map function.
I'm building a blog website in Next.js, the API for the blog is from some headless CMS.
In a page I want to do the following:
List some blogs.
Set of buttons available, based on each button click different set of blogs are loading (should replace the blogs in #1).
Since SEO is needed I'm pretty confused to use which approach should I choose.
What I thinking that I generate the initial list with
getStaticProps (Static Generation), and after loading I want to replace the blogs based on user action (button click).
But I'm confused, is it possible to use static generation and SWR in single page?
Here is my implementation.
pages/index.js
export async function getStaticProps() {
const resPosts = await fetch(`${process.env.API_BASE_URL}posts?per_page=4&&_embed`)
const posts = await resPosts.json()
return {
props: {
posts
},
revalidate:10
}
}
export default function Home({posts}) {
return (
<div>
//pass data to FeaturedBlogs component (Components/featuredBlogs.js)
<FeaturedBlogs categories={categories} posts={posts} />
</div>
)
}
Components/featuredBlogs.js
const FeaturedBlogs = ({posts }) => {
return (
<div className={`${blogStyles.feature_blogs_wrap}`}>
//need to load the below blogs based on button click
<button onClick={handleClick('health')}>Health</button>
<button onClick={handleClick('latest')}>Latest</button>
//listing blogs
{posts.map((item ) => (
<Link key={item.id} href="/blog/view" passHref={true}>
<section>
<Image alt="blog_img" src={item._embedded['wp:featuredmedia'][0].media_details.sizes.medium.source_url} width="200" height="200" />
<div className={`${blogStyles.feature_blogs_content}`}>
<div className={`${blogStyles.feature_blogs_label}`}>
<span>{item._embedded['wp:term'][0][0].name}</span>
</div>
<p>{item.title.rendered}</p>
<div className={`${blogStyles.feature_blogs_author}`}>
<Image alt="author" src={item._embedded.author[0].avatar_urls[48]} width="200" height="200" />
<span>{item._embedded.author[0].name}</span>
</div>
</div>
</section>
</Link>
))}
</div>
)
}
const handleClick = (id) => {
//console.log(id)
}
What I need is to load the blogs in handleClick event, but the problem is this will not work since it's generated from the server at build time.
In the FeaturedBlogs component, you can create a state variable to keep track when a new category is selected on the client-side.
const [category, setCategory] = useState()
You can then make useSWR conditionally fetch data based on the value of this category variable.
const { data, loading } = useSWR(category ? [category] : null, fetcher)
The fetcher function would have the logic to fetch the posts for a given category.
const fetcher = async (category) => {
const response = await fetch(/* Endpoint to get posts for given category */)
return await response.json()
}
With this in place, you can have the component render the posts retrieved in getStaticProps as a default, when category is not set. This would happen on the initial render of the page. However, when a button is clicked, and category gets set, that category's data will be fetched and rendered instead.
Here's the full code of a modified version of your original component.
// Components/featuredBlogs.js
const fetcher = async (category) => {
const response = await fetch(/* Endpoint to get posts for given category */)
return await response.json()
}
const FeaturedBlogs = ({ posts }) => {
// Add state variable to keep track of the selected category
const [category, setCategory] = useState()
// Fetch posts from category only if `category` is set
const { data, loading } = useSWR(category ? [category] : null, fetcher)
const handleClick = (cat) => () => {
setCategory(cat)
}
// If `category` is set render data with post for given category, otherwise render all posts from `getStaticProps`
const itemsToRender = category ? data : posts
return (
<div className={blogStyles.feature_blogs_wrap}>
<button onClick={handleClick('health')}>Health</button>
<button onClick={handleClick('latest')}>Latest</button>
{loading && <div>Loading...</div>}
{!!itemsToRender?.length && itemsToRender.map((item) => (
<!-- Render items here -->
))}
</div>
)
}
I am trying to make a movie search app with React and have made an API call to The Movie Database API. I have this form and what I am trying to do is get the data of the movie that I am searching for.
I am not able to access the data from the API call, and I get this error of "Uncaught TypeError: Cannot read property 'map' of undefined"
I have two js files:
1 index.js
import React from 'react';
import ReactDOM from 'react-dom';
import SearchMovies from "./searchMovies";
import './style.css';
class Main extends React.Component {
render() {
return (
<div className="container">
<h1 className="title">React Movie Search</h1>
<SearchMovies/>
</div>
);
}
}
ReactDOM.render(<Main />, document.getElementById('root'));
The second file is searchMovies.js
import React, {useState} from "react";
export default function SearchMovies(){
//states- input query, movies
const [query, setQuery] = useState('');
const [movies, setMovies] = useState([]);
const searchMovies = async (e) => {
e.preventDefault();
const url = `https://api.themoviedb.org/3/movie/550?
api_key=api_key&language=en-US&query=${query}&page=1&
include_adult=false`;
try {
const res = await fetch(url);
const data = await res.json();
setMovies(data.results);
}catch(err){
console.error(err);
}
}
return(
<div>
<form className="form" onSubmit={searchMovies}>
<label htmlFor="query" className="Label">Movie Name</label>
<input className="input" type="text" name="query" placeholder="i.e. Jurassic
Park"
value={query} onChange={(e) => setQuery(e.target.value)}
/>
<button className="button" type="submit">Search</button>
</form>
<div className="card-list">
{movies.map(movie => (
<div className="card">
<img className="card--image"
src={`https://image.tmdb.org/t/p/w185_and_h278_bestv2/
${movie.poster_path}`}
alt={movie.title + ' poster'}
/>
</div>
))}
</div>
</div>
)
}
Can somebody tell me what I am doing wrong here? I am new to React.
Many thanks!
Your API response an object, not an array that's why the map function not working.
See Your API response:
{"adult":false,"backdrop_path":"/rr7E0NoGKxvbkb89eR1GwfoYjpA.jpg","belongs_to_collection":null,"budget":63000000,"genres":[{"id":18,"name":"Drama"}],"homepage":"http://www.foxmovies.com/movies/fight-club","id":550,"imdb_id":"tt0137523","original_language":"en","original_title":"Fight Club","overview":"A ticking-time-bomb insomniac and a slippery soap salesman channel primal male aggression into a shocking new form of therapy. Their concept catches on, with underground "fight clubs" forming in every town, until an eccentric gets in the way and ignites an out-of-control spiral toward oblivion.","popularity":46.801,"poster_path":"/pB8BM7pdSp6B6Ih7QZ4DrQ3PmJK.jpg","production_companies":[{"id":508,"logo_path":"/7PzJdsLGlR7oW4J0J5Xcd0pHGRg.png","name":"Regency Enterprises","origin_country":"US"},{"id":711,"logo_path":"/tEiIH5QesdheJmDAqQwvtN60727.png","name":"Fox 2000 Pictures","origin_country":"US"},{"id":20555,"logo_path":"/hD8yEGUBlHOcfHYbujp71vD8gZp.png","name":"Taurus Film","origin_country":"DE"},{"id":54051,"logo_path":null,"name":"Atman Entertainment","origin_country":""},{"id":54052,"logo_path":null,"name":"Knickerbocker Films","origin_country":"US"},{"id":25,"logo_path":"/qZCc1lty5FzX30aOCVRBLzaVmcp.png","name":"20th Century Fox","origin_country":"US"},{"id":4700,"logo_path":"/A32wmjrs9Psf4zw0uaixF0GXfxq.png","name":"The Linson Company","origin_country":""}],"production_countries":[{"iso_3166_1":"DE","name":"Germany"},{"iso_3166_1":"US","name":"United States of America"}],"release_date":"1999-10-15","revenue":100853753,"runtime":139,"spoken_languages":[{"iso_639_1":"en","name":"English"}],"status":"Released","tagline":"Mischief. Mayhem. Soap.","title":"Fight Club","video":false,"vote_average":8.4,"vote_count":20153}
I made a jsfiddle with your code for testing: https://jsfiddle.net/hqm6rcpf/
const url = `https://api.themoviedb.org/3/movie/550?
api_key=api_key&language=en-US&query=${query}&page=1&
include_adult=false`;
This code is invalid. You cannot add line breaks in a URL.
By changing to this, I was able to make it work:
const url = `https://api.themoviedb.org/3/search/movie?api_key=api_key&language=en-US&query=${query}&page=1&include_adult=false`;
first of all,
never share your personal api key on the internet!!
const data = await res.json();
Why use await again when you have already fetched the data?
also, if the response is json, it's JSON.parse(res).
Next, have you actually looked at the data you are getting?
Try console logging it.
From what I can tell, it's an object, not an array, so you can't use map.
I am working on a REACT based web-app POC in my org. There is table of issues and for each of these issues, I have to provide a button in the table which when a user clicks on - it will open up a modal, fetch data for that issue via an API call and then broadcast the data in that modal.
The problem:
Let's say I have 300 issues listed in that table, hence there are 300 clickable buttons for opening modals and calling API. Now the problem is that, whenever that table loads, it calls APIs for all 300 issues at once, but I want each API to only be called when an user clicks on the respective button!
Here is the code for Modal component which I have managed so far:
import React, { FunctionComponent, useState, useEffect } from 'react'; // importing FunctionComponent
import { Modal, Button } from 'react-bootstrap';
type IssueReportProps = {
issueInfo: any
}
const IssueReport: FunctionComponent<IssueReportProps> = ({ issueInfo }) => {
const issueNumber: string = issueInfo.number;
const [show, setShow] = useState(false);
const [diagnosisInfo, setdiagnosisInfo] = useState({});
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
useEffect(() => {
async function fetchData() {
const res = await fetch("http://api-call/?issuenumber=".concat(issueNumber));
res.json().then(res => setdiagnosisInfo(res));
}
fetchData();
}, [issueNumber]);
console.log(diagnosisInfo);
return (
<>
<Button variant="outline-primary" onClick={handleShow} size="sm">
Diagnosis
</Button>
<Modal show={show} onHide={handleClose} backdrop="static" keyboard={false}>
<Modal.Body>
<p>
Issue Info: {JSON.stringify(diagnosisInfo)}
</p>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>Close</Button>
</Modal.Footer>
</Modal>
</>
);
};
export default IssueReport;
The console.log(diagnosisInfo); confirms my suspicions that once the issue is loaded, APIs for all issues are called. How to prevent this?
Please let me know if I need to provide more details.
EDIT1: Accepted Solution-
Here is the change I made to the code post #Dykotomee's solution:
// useEffect:
useEffect(() => {
async function fetchData() {
const res = await fetch("http://api-call/?issuenumber=".concat(issueNumber));
res.json().then(res => setdiagnosisInfo(res));
}
// fetchData();
if (show){
fetchData();
}
}, [issueNumber, show]);
useEffect is called every time the component renders. Therefore, when the table loads with 300 components, the API is fetched 300 times!
You only want to fetch if the modal is showing. Try wrapping your call to fetchData in a conditional:
if (show) {
fetchData();
}
It's not ideal, considering the modal will likely show prior to the fetch being completed, but you can adjust your code or add more states to compensate.
I am currently practicing React, and my goal is to build a simple app that searches movies and display some short info about them as results. I managed to pull data from API and store em in React hooks. I can access any data, but when I try to pull images I get error:
TypeError: Cannot read property 'medium' of null.
Here are the API results:
http://api.tvmaze.com/search/shows?q=$girls
I find an image that I want to use stored in {show.image.medium}
Here is my React code:
import React, {useState, useEffect} from 'react';
import Movie from './Movie';
const App = () => {
const [movies, setMovies] = useState([]);
useEffect(() => {
getMovies();
}, []);
const getMovies = async () => {
const response = await fetch(`http://api.tvmaze.com/search/shows?q=$girls`);
const data = await response.json();
setMovies(data);
console.log(data)
;}
return (
<div>
<form className='search-form'>
<input type='text' className='search-bar' placeholder='search movie'>
</input>
<button type='submit' className='search-button'>
Search
</button>
</form>
{movies.map(movie => (
<Movie title={movie.show.name} image={movie.show.image.medium} />
))}
</div>
);
};
export default App;
and Movie.js file:
import React from 'react';
const Movie = ({title, image}) => {
return(
<div>
<h1>{title}</h1>
<img src={image} alt=''/>
</div>
);
}
export default Movie;
so I basically mapped the results in movie array, but {movie.show.image.medium} just won't work, while pulling any other data work just fine.
I know that this is probably an easy fix, but I tried everything and searched for an answer for hours and still, nothing worked. I would really appreciate it if someone can explain to me what I am doing wrong. Thanks in advance!
In the API call there is one value where movie.show.image is technically null. For null you could not get any properties, even medium.
What you can do as a solution is the following:
{
movies.map(movie =>
movie.show.image ?
<Movie title={movie.show.name} image={movie.show.image.medium} /> :
null)
}
Additionally you need to return from Array.prototype.map().
Iteration from the API on my console:
I hope that helps!