im calling an object from the pokeapi, exactly the name property and on first render after saving the file i get the name but i dont know why, re render and then the propertie is null and i get an error
this is my component card
import {
EditOutlined,
EllipsisOutlined,
SettingOutlined,
} from "#ant-design/icons";
import { Avatar, Card, Col, Row } from "antd";
function Pokecard(values: any) {
const { response} = values;
const { Meta } = Card;
return (
<Row gutter={[10, 10]}>
<Col>
<Card
style={{ width: 300 }}
cover={
<img
alt={"" }
src={response && response['sprites']['front_default']}
/>
}
actions={[
<SettingOutlined key="setting" />,
<EditOutlined key="edit" />,
<EllipsisOutlined key="ellipsis" />,
]}
>
<Meta
avatar={<Avatar src="https://joeschmoe.io/api/v1/random" />}
title={response.name}
description=""
/>
</Card>
</Col>
</Row>
);
}
export default Pokecard;
this is my view
import { Methods } from "../interfaces/request";
import { useEffect, useState } from "react";
import Pokecard from "../components/pokecard/Pokecard";
import useAxios from "../plugins/Useaxios";
function App2() {
const { response, loading, error } = useAxios({
method: Methods["get"],
url: "/ditto",
body: JSON.stringify({}),
headers: JSON.stringify({}),
});
const [data, setData] = useState([]);
useEffect(() => {
if (response !== null) {
setData(response);
}
}, [response]);
let args: any = {
response,
};
return (
<>
<Pokecard {...args} />;
</>
);
}
export default App2;
and this is my plugin axios
import axios from "axios";
import Request from "../interfaces/request";
import { useState, useEffect } from "react";
enum Methods {
get = "get",
post = "post",
default = "get",
}
const useAxios = ({ url, method, body, headers }: Request) => {
axios.defaults.baseURL = "https://pokeapi.co/api/v2/pokemon";
const [response, setResponse] = useState(null);
const [error, setError] = useState("");
const [loading, setloading] = useState(true);
const fetchData = () => {
axios[method](url, JSON.parse(headers), JSON.parse(body))
.then((res: any) => {
setResponse(res.data);
})
.catch((err: any) => {
setError(err);
})
.finally(() => {
setloading(false);
});
};
useEffect(() => {
fetchData();
}, [method, url, body, headers]);
return { response, error, loading };
};
export default useAxios;
im learning to destructuring objects
im tried saving the object in the store but i got an Undifined
sorry for my english
you can try something like this
title={response?.name || ''}
Try using the resonse directly
const { response, loading, error } = useAxios({
method: Methods["get"],
url: "/ditto",
body: JSON.stringify({}),
headers: JSON.stringify({}),
});
const name = response?.name;
const src = response?.sprites?.?front_default;
// use the properties directly inside the child
return (
<>
<Pokecard name={name} src={src}/>
</>
);
You can check examples of how when useEffect is not needed
Related
I have custom hook which is catching data from dummyjson API. When I render products, it works fine and perfectly. When I try to catch only one product with this hook via parameter passed via url with useParams in the end it catch this one product, but it cannot render. It seems that a single product didn't manage to load with the help of the hook before it renders. So what is difference when all products are catched are rendered correctly
import axios, { Canceler } from 'axios';
import { useEffect, useState } from 'react';
import { dummyProductType } from '../types/types';
export const useFetch = ({ limit, id }: any) => {
const [products, setProducts] = useState<dummyProductType[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [hasMore, setHasMore] = useState(false);
const [error, setError] = useState(false);
useEffect(() => {
let cancel: Canceler;
const config =
id === null || id === undefined
? {
method: 'GET',
url: `https://dummyjson.com/products/`,
params: { limit: limit },
cancelToken: new axios.CancelToken((c) => (cancel = c)),
}
: {
method: 'GET',
url: `https://dummyjson.com/products/${id}`,
cancelToken: new axios.CancelToken((c) => (cancel = c)),
};
async function fetchData() {
setIsLoading(true);
{
await axios(config)
.then((response) => {
if (Object.hasOwn(config, 'params')) {
setProducts((prev) => {
return [...prev, ...response.data.products];
});
} else {
setProducts({ ...response.data });
}
if (products.length < response.data.total) setHasMore(true);
setIsLoading(false);
})
.catch((err) => {
if (axios.isCancel(err)) return;
setError(true);
});
}
}
fetchData();
return () => cancel();
}, [limit, id]);
return { products, isLoading, error, hasMore };
};
import React, { useCallback, useRef, useState } from 'react';
import { Link } from 'react-router-dom';
import { useFetch } from '../../hooks/useFetch';
import { CardProduct } from '../CardProduct';
import styles from './Cards.module.scss';
const { wrapperContainer } = styles;
const Cards = () => {
const [limit, setLimit] = useState(10);
const { products, isLoading, hasMore } = useFetch({ limit: limit });
const observer = useRef<IntersectionObserver | null>(null);
const lastProduct = useCallback(
(node: Element) => {
if (isLoading) {
return;
}
if (observer.current) {
observer.current.disconnect();
}
observer.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasMore) {
setLimit((prev) => prev + 10);
}
});
if (node) observer.current.observe(node);
},
[isLoading, hasMore]
);
console.log(products);
return (
<div className={wrapperContainer}>
{products.map((product, index) => {
if (products.length === index + 1) {
return (
<Link to={`books/${index + 1}`}>
<CardProduct
key={`${index} ${product.title}`}
{...product}
innerRef={lastProduct}
/>
</Link>
);
} else {
return (
<Link to={`books/${index + 1}`}>
<CardProduct key={`${index} ${product.title}`} {...product} />
</Link>
);
}
})}
</div>
);
};
export default Cards;
import {
Button,
CardContent,
Card,
CardHeader,
CardMedia,
dividerClasses,
} from '#mui/material';
import { useParams } from 'react-router-dom';
import { useFetch } from '../../hooks/useFetch';
export const CardDetail = () => {
const { id } = useParams();
console.log(id);
const { products, isLoading, hasMore } = useFetch({
id: Number.parseInt(id),
});
console.log(products, isLoading, hasMore);
return (
<Card key={id}>
<CardHeader title={products[0].title}></CardHeader>
<CardMedia
component='img'
image={products[0].thumbnail}
sx={{ height: '150px' }}></CardMedia>
</Card>
);
};
What am I doing wrong? Or maybe it should be done different?
//Newtask.js
import React, { useCallback } from "react";
import { useState } from "react";
import useHttp from "../hookes/useHttp";
import Taskform from "./taskform";
const newTask = (props) => {
const createTask = (tastk, taskdata) => {
const data = { id: taskdata.name, text: tastk };
props.onAddTask(data);
};
const { isloading, error, sendRequest: fetchRequest } = useHttp();
const enterTaskHandler = async (tasktext) => {
fetchRequest(
{
url: "https://react-prep-d4d1d-default-rtdb.firebaseio.com/comments.json",
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: { text: tasktext },
},
createTask.bind(null, tasktext)
);
};
return (
<section>
<Taskform onEnterTask={enterTaskHandler} loading={isloading} />
{error && <p>{error}</p>}
</section>
);
};
export default newTask;
Here above i am calling TaskForm compoenet and passing arguments ( props)
//taskform.js
import React from "react";
import { useState } from "react";
import "./taskforms.css";
function Taskform(props) {
const [text, settext] = useState();
const Inputchangehandler = (event) => {
settext(event.target.value);
};
console.log(props.onEnterTask);
const submithandler = (text) => {
props.onEnterTask(text);
settext("");
};
return (
<div>
<input
type="text"
value={text}
onChange={Inputchangehandler}
className="asit"
/>
<button className="asit" onClick={submithandler}></button>
</div>
);
}
export default Taskform;
//Here in taskform i am calling the props.onEnterTask(text)....but i am getting error
Error : Uncaught TypeError: props.onEnterTask is not a function
//useHttp.js ( hook)
import { useState, useCallback } from "react";
const useHttp = () => {
const [isLoading, setisLoading] = useState();
const [error, setError] = useState(null);
const sendRequest = useCallback(async (requestConfig, applydata) => {
setisLoading(true);
setError(null);
try {
const response = await fetch(requestConfig.url, {
method: requestConfig.method ? requestConfig.method : "GET",
headers: requestConfig.headers ? requestConfig.headers : {},
body: requestConfig.body ? JSON.stringify(requestConfig.body) : null,
});
if (!response.ok) {
throw new Error("Request failed!");
}
const data = await response.json();
applydata(data);
} catch (err) {
setError(err.message || "Something went wrong!");
}
setisLoading(false);
}, []);
return {
isLoading,
error,
sendRequest,
};
};
export default useHttp;
Please kindly go through this above files and let me know the issue.
Sorry the issue is not with props , some other component issue was there in app file , i was giving some other componet as start file //////
I'm building a searchable index of lessons in Next.js for a school using Airtable as my backend, and I am able to pull the initial page of lessons with getStaticProps and display them. I added a search form that passes an array of search terms through context to a search API component, and though I am able to console log the search terms, I think I am passing the searchTerms array incorrectly, or the search API is not parsing them.
I'm using Auth0 so the component are wrapped with auth, and that all works. Just not returning anything from Airtable when I submit the form.
What I suspect is that I am mangling the fetch or not passing the searchTerms array correctly.
// index.js
import Head from "next/head";
import Image from "next/image";
import Navbar from "../components/Navbar";
import Lesson from "../components/Lesson";
import LessonSearchForm from "../components/LessonSearchForm";
import { table, minifyRecords } from "./api/utils/Airtable";
import { lessonsProvider, LessonsContext } from "../contexts/LessonsContext";
import { useEffect, useState, useContext } from "react";
import { useUser, withApiAuthRequired, getSession } from "#auth0/nextjs-auth0";
export default function Home({ initialLessons }) {
const { lessons, setLessons } = useContext(LessonsContext);
const { searchTerms, setSearchTerms } = useContext(LessonsContext);
const { user, error, isLoading } = useUser();
const [lessonsError, setLessonsError] = useState(null);
useEffect(() => {
setLessons(initialLessons);
}, []);
return (
<div>
<Head>
<title>Lesson Locator</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<Navbar user={user} />
<main>
{user && (
<>
<h2 className="text-2xl text-center mb-4">Lesson Locator Search</h2>
<LessonSearchForm />
{lessons &&
lessons.map((lesson) => (
<Lesson key={lesson.id} lesson={lesson} />
))}
</>
)}
</main>
</div>
);
}
export async function getServerSideProps(context) {
try {
const lessons = await table.select({}).firstPage();
return {
props: {
initialLessons: minifyRecords(lessons),
},
};
} catch (err) {
console.error(err);
return {
props: {
err: "Something went wrong!",
},
};
}
}
// LessonSearchForm.js component ( everything before the return statement )
import React, { useState, useContext } from "react";
import { LessonsContext } from "../contexts/LessonsContext";
export default function LessonSearchForm() {
const [lessonTitle, setLessonTitle] = useState("");
const { findLessons } = useContext(LessonsContext);
const [lessonSource, setLessonSource] = useState("");
const [lessonNumber, setLessonNumber] = useState("");
const [textFieldOption, setTextFieldOption] = useState("text");
const [position, setPosition] = useState("");
const [lessonErrorMsg, setLessonErrorMsg] = useState("");
const [isFindingLesson, setIsFindingLesson] = useState(false);
const { searchTerms, setSearchTerms } = useContext(LessonsContext);
const handleChange = (e) => {
setSearchTerms({
...searchTerms,
// Trimming any whitespace
[e.target.name]: e.target.value.trim(),
});
};
const handleSubmit = (e) => {
e.preventDefault();
console.log({ searchTerms });
findLessons([searchTerms]);
setLessonErrorMsg("");
setIsFindingLesson(false);
};
// LessonsContext.js
import { createContext, useState } from "react";
const LessonsContext = createContext();
const LessonsProvider = ({ children }) => {
const [lessons, setLessons] = useState([]);
const [searchTerms, setSearchTerms] = useState([]);
const refreshLessons = async () => {
try {
const res = await fetch("/api/getLessons");
const lessons = await res.json();
setLessons(initialLessons);
} catch (error) {
console.log(err);
}
};
const findLessons = async ([searchTerms]) => {
try {
const res = await fetch("/api/searchLessons", {
method: "GET",
// body: JSON.stringify(searchTerms),
// fields: [searchTerms],
headers: { "Content-Type": "application/json" },
});
const foundLessons = await res.json();
console.log(foundLessons);
} catch (error) {
console.error("Error performing search", error);
}
};
return (
<LessonsContext.Provider
value={{
lessons,
setLessons,
refreshLessons,
searchTerms,
setSearchTerms,
findLessons,
}}
>
{children}
</LessonsContext.Provider>
);
};
export { LessonsProvider, LessonsContext };
// searchLessons.js in API (Just trying to parse the Title field for testing)
import { table, minifyRecords, findRecordByFilter } from "./utils/Airtable";
import { useUser, withApiAuthRequired, getSession } from "#auth0/nextjs-auth0";
// export default withApiAuthRequired(async (req, res) => {
const searchLessons = async (req, res) => {
// const { user } = useUser();
// const searchTerms = req.fields;
try {
const records = await table
.select({
filterByFormula: `FIND("${req.title}",{Title})`,
})
.firstPage();
const minifiedRecords = minifyRecords(records);
res.statusCode = 200;
res.json(minifiedRecords);
} catch (err) {
res.statusCode = 500;
res.json({ meg: "something went wrong!" }, err);
}
};
export default searchLessons;
// Airtable.js (Utils for Airtable)
const Airtable = require("airtable");
const base = new Airtable({ apiKey: process.env.AIRTABLE_API_KEY }).base(
process.env.AIRTABLE_BASE_ID
);
const table = base(process.env.AIRTABLE_TABLE_NAME);
const getMinifiedRecord = (record) => {
if (!record.fields.completed) {
record.fields.completed = false;
}
return {
id: record.id,
fields: record.fields,
};
};
const minifyRecords = (records) => {
return records.map((record) => getMinifiedRecord(record));
};
export { table, getMinifiedRecord, minifyRecords };
Working with a simple card component, to which I load data from an API with Axios get by using UseEffect. The purpose is that when the component is loaded, it loads the data to it once.
The issue is that data is not loaded correctly on load but repeatedly after it when the component updates. How can I make it so that it loads the data once when the page is loaded but not after that?
I have omitted the API url but it is tested and returns data correctly from backend.
Card component:
import React from 'react';
import { Card, CardImg, CardBody, CardTitle, CardSubtitle, Container, Row, Col } from 'reactstrap';
import { useEffect, useState } from "react";
import { APIGet } from '../API.js';
function RenderCardLoadMore() {
const [data, setData] = useState([]);
const resource = 'search';
const params = { 'limit': '2' };
const results = APIGet(resource, params);
useEffect(() => {
console.log("initialload");
setData(results);
}, []);
return (
<Container fluid>
<Container>
<Row>
{data.map((v, i) => (
<Col className="py-2" key={i} xs={12} md={4} lg={3}>
<Card className="explore_event_card shadow">
<CardBody>
<CardTitle className="card_header">{v.Title}</CardTitle>
<CardSubtitle>{v.SubTitle}</CardSubtitle>
</CardBody>
</Card>
</Col>
))}
</Row>
</Container>
</Container>
);
}
export default RenderCardLoadMore;
API component:
import { useEffect, useState } from 'react';
import axios from 'axios';
const API_URL = 'https://myapi/'; /*url omitted for this question, tested and working*/
const session_id = localStorage.getItem("session_id");
export function APIGet (resource, params) {
const [data, setData] = useState([]);
const url = API_URL + resource;
params['session_id'] = session_id;
useEffect(() => {
axios.get(url, {params: params}).then((v) => {
setData(v.data)
}).catch( (err) => console.log(["APIGet error:", err]) )
}, [url]); //url as 2nd argument not needed?
return data;
}
You could remove url and use an empty dependency array so the useEffect hook is triggered only once after the initial render. From what I can tell though, APIGet doesn't need to be a hook and doesn't need to use the useState hook. I can simply return the Promise chain returned from axios.
API
import axios from 'axios';
const API_URL = 'https://myapi/.....';
export function APIGet (resource = "", params = {}) {
const session_id = JSON.parse(localStorage.getItem("session_id"));
const url = API_URL + resource;
params['session_id'] = session_id;
return axios.get(url, {params: params})
.then((v) => {
return v.data
})
.catch((err) => console.log(["APIGet error:", err]));
}
RenderCardLoadMore - Call APIGet in the useEffect hook and update the state when Promise resolves.
import { APIGet } from '../API.js';
function RenderCardLoadMore() {
const [data, setData] = useState([]);
const resource = 'search';
const params = { 'limit': '2' };
useEffect(() => {
console.log("initialload");
APIGet(resource, params)
.then(results => {
setData(results);
});
}, []);
return (....);
}
I don't know if I need to use global state like useContext for this ( I am not using redux in this project) but what I want to do is, once I have uploaded a new image it sends the image data back from the server and I set that state. I want to then replace the existing image on the screen with the newly uploaded one.
So, here is my file input component:
import React, { useState } from "react";
import ProgressBar from "../../../shared/components/progressBar/ProgressBar";
const UploadForm = () => {
const [file, setFile] = useState(null);
const [error, setError] = useState(null);
const types = ["image/png", "image/jpg", "image/jpeg"];
const changeHandler = (e) => {
let selected = e.target.files[0];
if (selected && types.includes(selected.type)) {
setFile(selected);
setError("");
} else {
setFile(null);
setError("Please select an image file(png or jpg");
}
};
return (
<form>
<input type="file" onChange={changeHandler} name="image"></input>
<div className="output">
{error && <div className="error">{error}</div>}
{file && <div>{file.name}</div>}
{file && <ProgressBar file={file} setFile={setFile} />}
</div>
</form>
);
};
export default UploadForm;
My progress bar component:
import React, { useEffect } from "react";
import useStorage from "../../hooks/use-storage";
import { motion } from "framer-motion";
import "./ProgressBar.css";
const ProgressBar = ({ file, setFile }) => {
const { url, progress } = useStorage(file);
useEffect(() => {
if (url) {
setFile(null);
}
}, [url, setFile]);
return (
<motion.div
className="upload-progress"
initial={{ width: 0 }}
animate={{ width: progress + "%" }}
></motion.div>
);
};
export default ProgressBar;
And, my upload custom hook. You can see here I am setting the state of the image url here but I don't know how to then update my Avatar component once the upload is complete.
import React, { useState, useEffect, useContext } from "react";
import Axios from "axios";
import { AuthContext } from "../context/auth-context";
const useStorage = (file) => {
const auth = useContext(AuthContext);
const [progress, setProgress] = useState(0);
const [error, setError] = useState(null);
const [url, setUrl] = useState(null);
useEffect(() => {
const formData = new FormData();
formData.append("image", file);
try {
const sendImage = async () => {
const response = await Axios.post(
"http://localhost:8000/api/v1/users/update-avatar",
formData,
{
headers: {
"Content-type": "multipart/form-data",
Authorization: "Bearer " + auth.token,
},
onUploadProgress: (progressEvent) => {
setProgress(
parseInt(
Math.round((progressEvent.loaded * 100) / progressEvent.total)
)
);
},
}
);
// get the new file name from the server so you can show it right away after upload
const { filename, path } = response.data.file;
setUrl({ filename, path });
};
sendImage();
} catch (err) {
setError(err);
console.log(err.response);
}
}, [file]);
return { progress, url, error };
};
export default useStorage;
Avatar component
import React, { useContext, useEffect, useState } from "react";
import Axios from "axios";
import { AuthContext } from "../../../shared/context/auth-context";
const Avatar = () => {
const auth = useContext(AuthContext);
const [avatar, setAvatar] = useState(null);
useEffect(() => {
const getAvatarImage = async () => {
const response = await Axios.get(
"http://localhost:8000/api/v1/users/get-avatar",
{ headers: { Authorization: "Bearer " + auth.token } }
);
setAvatar(response.data.avatar);
};
if (auth.token) getAvatarImage();
}, [auth.token]);
return (
<div>
{avatar && (
<img
src={`http://localhost:8000/uploads/images/${avatar}`}
width="200"
alt="avatar image"
/>
)}
</div>
);
};
export default Avatar;
Auth Context
import { createContext } from "react";
export const AuthContext = createContext({
isLoggedIn: false,
userId: null,
token: null,
email: null,
firstName: null,
login: () => {},
logout: () => {},
});