ReactJs .map in data received by api - reactjs

and thank you in advance for any help.
I am trying to build a web app that fetches data from an API, in this case a movie database API, but when i am trying to map all the movies from a specific title search i get the .map is not a function error, what i am doing wrong ? Can't i use useState to display the data ?
When i do console.log (search) i can see the array with all the data :
import React, {useEffect, useState} from 'react';
import axios from 'axios';
export default function RandomFacts() {
const [input, setInput] = useState('');
const [search, setSearch] = useState(['']);
useEffect(() => {
apiCall();
}, [input]);
const moviesList = search && search.map((movie, index) =>
<div className="movies" key="index">
<li><h2>{movie.Title}</h2></li>
<li><img src={movie.Poster} alt="poster" /></li>
</div>,
);
const apiCall = async () => {
const url = 'http://www.omdbapi.com/?s='+input+'&page=1&apikey=536a34c3';
try {
const response = await axios.get(url);
if (response.status === 200 && response !== undefined) {
const data = response.data;
setSearch(data.Search);
console.log(search);
}
} catch (error) {
console.log(error);
}
};
return (
<div className="main">
<h1>Movies</h1>
<div className="textInput">
<form>
<label>
<input type="text" value={input}
onChange={(e) => setInput(e.target.value)}
/>
</label>
</form>
</div>
<div className="movies">
{moviesList}
</div>
</div>
);
}

The API is returning a response Object with a data key containing the keys Search, TotalResults, and Response. You're trying to map this response Object instead of the Array contained in response.data.Search.
So you should be using setSearch(response.data.Search).

Related

checkBox and State

hi i'm doing a little social network with reactjs
i've created an api with express ans sequelize where i can find the users and the messages that users have done
i want that on the wall, users can like a message
the route is done in my backend and it work but i have an issu with my front
when i want to check the checkox of a message all of theme are checked
i don't understand why all of my checkBox checked at the same time and i don't see how can i check only one and let the other do there life
here is the code
import React, { useEffect, useState } from 'react'
import './Wall.scss'
import axios from 'axios'
import {Link} from "react-router-dom"
import jwt_decode from "jwt-decode"
const token = localStorage.getItem('token')
const config = {
headers: { authorization: `Bearer ${JSON.parse(token)}` }
}
const getAllMessage = 'http://localhost:3001/api/message/'
function Wall() {
const [message, setMessage] = useState([])
useEffect(() => {
axios.get(getAllMessage, config)
.then((res)=>{setMessage(res.data) ; console.log('useEffect')})
.catch((err) => { console.log(err)})
},[])
const [isChecked, setIsChecked] = useState()
const checkHandler = (e) =>{
let item = e.target.closest('[data-id]')
const disLikeMessage = `http://localhost:3001/api/like/dislike/${item.dataset.id}`
const likeMessage = `http://localhost:3001/api/like/${item.dataset.id}`
if(isChecked === item.checked){
console.log('unchecked')
axios.post(disLikeMessage,{}, config)
.then((res)=>{setIsChecked(!isChecked)})
.catch((err) => { console.log(err)})
} else{
console.log('checked')
axios.post(likeMessage,{}, config)
.then((res)=>{setIsChecked(!isChecked)})
.catch((err) => { console.log(err)})
}
}
return (
<div className='containerWall'>
<section className='cardWall'>
<ul className='cardUl'>
{message.map(item =>(
<li key = {item.id} >
<div className ='cardMessage'>
<Link to = {`/Wall/Message/${item.id}`}>
<img src = {item.picture} alt="" className='cardImage'/>
<div className='cardContent'>
<h2>{item.title}</h2>
<p>{item.content} </p>
<p>{item.createdAt}</p>
</div>
</Link>
<div className='likeSystem'>
<input type="checkbox" className='like'data-id={item.id checked={isChecked} onChange={checkHandler} />
<p>{item.likes}</p>
</div>
</div>
</li>
))}
</ul>
</section>
</div>
)
}
export default Wall
HAve any idea how to solve this?
(ps i'm a react beginner so be easy on me i want to understand :) ty for your time )
You have a single state for all the checkbox so they all share the same isChecked value.
If you want to keep one checked value in state per message, you could refactor it in its own component.
A better approach would be to not store any checked state and just rely on the item.checked attribute, when it's updated, you refetch the messages and update the state. React query allows to do this very easily.
const Like = ({ item }) => {
const [checked, setChecked] = useState(item.checked);
function checkHandler({target}) {
const url = `http://localhost:3001/api/like/${target.checked ? 'dislike/' + item.id : item.id}`
axios
.post(url, {}, config)
.then((res) => {
setChecked(prev => !prev);
})
.catch((err) => {
console.log(err);
});
}
return (
<div className="likeSystem">
<input
type="checkbox"
className="like"
checked={checked}
onChange={checkHandler}
/>
<p>{item.likes}</p>
</div>
);
};

Simple weather app breaking at weather.main.temp variable

I'm building a really basic weather app with React hooks, codesandbox.io and OpenWeatherAPI.
It's breaking at const temp = forecast.main.temp; and returning "TypeError: Cannot read properties of undefined (reading 'temp')" It actually does run when I initially write it but breaks if I add another variable or have to edit the temp variable in any way.
import React, { useState } from "react";
import "../styles.css";
import CityCard from "./cityCard";
export default function GetWeather() {
const [city, setCity] = useState('');
const [forecast, setForecast] = useState([]);
const getCity = async (e) => {
e.preventDefault();
//API variables
const url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&units=imperial&appid=${APIkey}`;
//fetch response
try {
const res = await fetch(url);
const data = await res.json();
setForecast(data);
} catch (err) {
console.error(err);
}
};
return (
//basic input form to search by city
<div>
<h1 className="title">Weather App</h1>
<CityCard forecast={forecast} />
<form className="form" onSubmit={getCity}>
<label className="label" htmlFor="city">
<p>Search by City</p>
</label>
<input
className="input"
type="text"
name="query place"
placeholder="i.e. Seattle"
value={city} onChange={(e) => setCity(e.target.value)}
>
</input>
<button
className="button"
type="submit"
>Search</button>
</form>
</div>
);
}
CityCard
import React from "react";
import "../../src/styles.css";
export default function CityCard({ forecast }) {
//pass props to cards
const cityName = forecast.name;
const temp = forecast.main.temp;
return (
<div className="card-container">
<p className="card-title">{cityName}</p>
<p>Temp: {temp} </p>
</div>
);
}
Issue
The initial forecast state is declared as an array:
const [forecast, setForecast] = useState([]);
and passed to CityCard on forecast prop:
<CityCard forecast={forecast} />
then accessed as if it were an object:
function CityCard({ forecast }) {
//pass props to cards
const cityName = forecast.name; // OK, undefined
const temp = forecast.main.temp; // Not OK, can't access temp of undefined
return (
<div className="card-container">
<p className="card-title">{cityName}</p>
<p>Temp: {temp}</p>
</div>
);
}
Solution
Use Optional Chaining operator to protect against null/undefined property accesses.
function CityCard({ forecast }) {
//pass props to cards
const cityName = forecast?.name;
const temp = forecast?.main?.temp;
return (
<div className="card-container">
<p className="card-title">{cityName}</p>
<p>Temp: {temp}</p>
</div>
);
}
Or wait to render CityCard when there's a valid forecast data to display.
const [forecast, setForecast] = useState();
...
{forecast?.name && forecast?.main?.temp && <CityCard forecast={forecast} />}
OpenWeather JSON API response

Rerender sibling component in React

I am new to React. I am stuck on this problem for days now.
I have got a parent component which wraps two sibling components, "FileUpload" and "Documents"
The "FileUpload" is for uploading a file and "Documents" is for displaying all the uploaded files.
I want the "Documents" rerender after a new file is uploaded via "FileUpload", so that it shows the new file in the UI.
What would be the best approach to achieve this ?
Below is the code I have written so far for the sibling components:
FileUpload:
import React, { useState } from "react";
import Axios from "axios";
const FileUpload = (props) => {
const [files, setFiles] = useState([]);
const onInputChange = (e) => {
setFiles(e.target.files);
};
const handleSubmit = async (e) => {
e.preventDefault();
const data = new FormData();
for (let i = 0; i < files.length; i++) {
// console.log(files);
data.append("file", files[i]);
}
data.append("parentDbId", props.parentDbId);
data.append("parentObject", props.parentObject);
//console.log(data);
try {
await Axios.post("http://localhost:5000/upload", data);
} catch (err) {
console.error(err.message);
}
};
return (
<form
// action="http://localhost:5000/upload"
// method="POST"
//encType="multipart/form-data"
onSubmit={handleSubmit}
>
<div className="row mb-3">
<div className="col-lg-4">
<label htmlFor="formFileMultiple" className="form-label mb-0">
Add files
</label>
<input
className="form-control"
type="file"
id="formFileMultiple"
name="file"
multiple
onChange={onInputChange}
/>
</div>
<div className="col-lg-4 mt-0 gx-0">
<button type="submit" className="btn btn-primary mt-4">
Upload
</button>
</div>
</div>
</form>
);
};
export default FileUpload;
====================================================================
Documents:
import React, { useState, useEffect } from "react";
import axios from "axios";
const Documents = (props) => {
const parentDbId = props.parentDbId;
const [documents, setDocuments] = useState([]);
//mount
useEffect(() => {
console.log("first use effect");
loadDocuments();
}, []);
const loadDocuments = async () => {
const result = await axios.get(
`http://localhost:5000/documents/${parentDbId}`
);
setDocuments(result.data);
};
return (
<>
<div className="row">
{documents.map((document, index) => (
<div className="col-lg-3" key={index}>
<a href={document.filePath}>{document.fileName}</a>
</div>
))}
</div>
</>
);
};
export default Documents;
Thanks,
Jimmy
Simple, just have the parent control document state and pass the state and callback down to the children as a prop. Now the siblings are referencing the same state and will be re-rendered when props (ie document state) changes. The parent can also handle the data fetching and uploading.
it will look like this:
const Parent = () => {
const [documents, setDocuments] = useState([]);
...do data fetching here
const handleSubmit = useCallback(async () => {}, []); // You might want to reset document state here?
return (
<div>
<Docs documents={documents} />
<Upload onUpload={setDocuments} onSubmit={handleSubmit} />
</div>
);
}
I wonder if you should actually have two documents components, one for displaying the files being uploaded, and one for displaying the already uploaded files. You would embed one within the Upload component and the other would fetch documents from the api every time onUpload completes

NextJS getStaticProps() not updating from form values

After submitting with UpdateParams, the new url is called and a JSON object with the new queried data is returned as expected.
The form updates the two state vars.
However, the products in the all-products view are not updated to reflect the form input.
What do I need to do to to refresh the render to reflect the new data in product?
//all-products.js
import Link from 'next/link'
import React from 'react';
import { useState } from 'react';
//gets data from local api
async function getData(rank, keyword){
const res = await fetch(`http://localhost:4000/api?top=${rank}&keyword=${keyword}`);
return res;
}
export async function getStaticProps() {
const rank = 5;
const keyword = "shorts";
const response = await getData(rank, keyword);
const products = await response.json();
console.log(products);
if (!products) {
return {
notFound: true,
}
}
return {
props: {
products,
},
}
}
export default function AllProducts(stuff) {
let {products} = stuff;
const [rank, setRank] = useState("3");
const [keyword, setKeyword] = useState("shoes");
//from form
const updateParams = async (e) => {
e.preventDefault();
const response= await getData(rank, keyword);
products = await response.json();
}
return (
<div>
<input
type='text'
placeholder='topRank'
value={rank}
onChange={e => setRank(e.target.value)}
/>
<input
type="text"
placeholder='searchTerm'
value={keyword}
onChange={e => setKeyword(e.target.value)}
/>
<button
type='submit'
onClick={updateParams}>
Update Params</button>
<ul>
{products.Products.map((product) => {
return (
<div key={product.Id}>
<li>{product.Name}</li>
<li><img width={300} src={ product.imgUrl } alt="product image" /></li>
</div>
) }
)}
</ul>
</div>
)
}
getStaticProps is run at build-time so it'll provide the data that's available at that time. To update the UI after the user interacts with the form you should put products into state and update it once new params are submitted and you retrieve the new products.
// all-products.js - removed irrelevant code for simplicity
export default function AllProducts(stuff) {
const [products, setProducts] = useState(stuff.products);
//...
const updateParams = async (e) => {
e.preventDefault();
const response = await getData(rank, keyword);
const newProducts = await response.json();
setProducts(newProducts);
}
return (
//...
<ul>
{products.Products.map((product) => {
return (
<div key={product.Id}>
<li>{product.Name}</li>
<li><img width={300} src={product.imgUrl} alt="product image" /></li>
</div>
)
})}
</ul>
//...
)
}

Why is the API response not being rendered by useState?

I am trying to render data fetched from an API using axios but nothing renders on screen. Note that data is actually available as indicated on the console log. Here is the code and what I have tried.
import React, { useState, useEffect } from "react";
import axios from "axios";
function Test() {
const [movie, setMovie] = useState([]);
const [query, setQuery] = useState("pulp fiction");
const [queryFromButtonClick, setQueryFromButtonClick] = useState(
"pulp fiction"
);
const handleClick = () => {
setQueryFromButtonClick(query);
};
useEffect(() => {
axios
.get(`http://www.omdbapi.com/?apikey=fd010aa6&s=${queryFromButtonClick}`)
.then(({ data }) => {
console.log(data);
setMovie(data.Search);
});
}, [queryFromButtonClick]);
return (
<div>
<input
type="text"
value={query}
onChange={(e) => {
setQuery(e.target.value);
}}
/>
<button onClick={handleClick}>Fetch movies</button>
<div>{movie.Title}</div>
</div>
);
}
export default Test;
Why are the search query results not being rendered on screen and how can I go on about that?
Movie is defined as an array and apparently data.Search is also an array.
You need to iterate over movie array to get the data about each movie.
Like this:
return (
<div>
<input
type="text"
value={query}
onChange={(e) => {
setQuery(e.target.value);
}}
/>
<button onClick={handleClick}>Fetch movies</button>
<div>{movie.map((el)=>el.Title)}</div>
</div>
);

Resources