Passing API request parameter via props from child to parent - reactjs

I have 2 Api requests, one of them hangs on the child - from it, when I click on the picture, I get the track key and give it to the parent in the second Api request as a parameter so that it displays the information I need. But when I output this to the console, I get an empty message =(
result console.log
CHILD
import React from 'react'
import { AppContext } from '../MainContent'
import TrackTranding from './TrackTranding'
const ContentTrending = ({ onClick }) => {
const { articles, setArticles } = React.useContext(AppContext)
const handleClick = (e) => {
e.preventDefault();
onClick(articles[e.target.dataset.attr].key) // get key from first api
}
return (
<div className="main__content-trending">
<p>Tranding right now</p>
<div className="noflex-oneblock">
<div className="main__content-trending-artist-title ">
{
articles.map((tracks, index) => {
return (
<div className='one' key={tracks.key}
onClick={handleClick}
>
<TrackTranding
tracks={tracks}
index={index}
/>
<audio src="#" id="audioTrack"></audio>
</div>
)
})
}
</div>
</div>
</div>
)
}
export default ContentTrending
PARENT
import axios from 'axios'
import React from 'react'
import MainContentBg from './MainBcg/ContentBg'
import MainContentTitle from './MainTitle/ContentTitle'
import ContentTrending from './MainTrending/ContentTrending'
export const AppContext = React.createContext()
const MainContent = () => {
const [articles, setArticles] = React.useState([])
const [detailse, setDetailse] = React.useState([])
const [name, setName] = React.useState('');
const handleClick = (name) => {
setName(name)
console.log(detailse) // --------------------------- error =()
}
const fetchData = () => {
const chart = {
method: 'GET',
url: 'https://shazam.p.rapidapi.com/charts/track',
params: { locale: 'en-US', pageSize: '20', startFrom: '0' },
headers: {
'X-RapidAPI-Key': '10eb9d1c65msh1029069c658be40p1197a5jsne7f1ee8c9f88',
'X-RapidAPI-Host': 'shazam.p.rapidapi.com'
}
};
axios.request(chart).then(data => {
const tracksApi = data.data.tracks
setArticles(tracksApi)
})
const details = {
method: 'GET',
url: 'https://shazam.p.rapidapi.com/songs/get-details',
params: { key: { name }, locale: 'en-US' }, // ---------------------------------- param
headers: {
'X-RapidAPI-Key': '10eb9d1c65msh1029069c658be40p1197a5jsne7f1ee8c9f88',
'X-RapidAPI-Host': 'shazam.p.rapidapi.com'
}
};
axios.request(details).then(data => {
const tracksDetails = data.data
setDetailse(tracksDetails)
})
}
React.useEffect(() => {
fetchData()
}, []);
return (
<main className="main">
<div className="main__content">
<AppContext.Provider value={{ articles, setArticles }}>
<MainContentTitle />
<MainContentBg />
<ContentTrending onClick={handleClick} />
</AppContext.Provider>
</div>
</main>
)
}
export default MainContent

Currently you call fetchData when the component first mounts the name state is set to an empty string "".
React.useEffect(() => {
fetchData();
}, []);
Which you're passing in the details so essentially the object looks like this:
const details = {
method: "GET",
url: "https://shazam.p.rapidapi.com/songs/get-details",
params: { key: { name: "" }, locale: "en-US" }, // notice the empty string
headers: {
"X-RapidAPI-Key": "10eb9d1c65msh1029069c658be40p1197a5jsne7f1ee8c9f88",
"X-RapidAPI-Host": "shazam.p.rapidapi.com",
},
};
Instead what you can do is trigger the useEffect when the name changes. Which will now have the updated name from the handleClick from the Child.
React.useEffect(() => {
fetchData();
}, [name]);
You probably want to change, currently you're setting the key to a object with another prop called name.
params: { key: { name }, locale: 'en-US' }
to, this will make sure the key prop has the value from name
params: { key: name, locale: 'en-US' }

Related

when re rendering react, object is null

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

PATCH request seems like a step behind

Hey folks really hope someone can help me here. I'm successfully updating my object in my mongo cluster, it updates but it does not render that update straight away to the browser. It will only update after a reload or when I run my update function again, it doesn't fetch that update straight away and I can't understand why. Does anyone have any suggestions?
I'm using context and reducer.
PlantDetails
import { usePlantsContext } from "../hooks/usePlantsContext";
import formatDistanceToNow from "date-fns/formatDistanceToNow";
import { useState } from "react";
import CalendarComponent from "./CalendarComponent";
const PlantDetails = ({ plant }) => {
const [watered, setWatered] = useState(false)
const [newWaterDate, setNewWaterDate] = useState("")
const { dispatch } = usePlantsContext();
const handleClick = async () => {
const response = await fetch("/api/plants/" + plant._id, {
method: "DELETE",
});
const json = await response.json();
if (response.ok) {
dispatch({ type: "DELETE_PLANT", payload: json });
}
};
const updatePlant = async (e) => {
e.preventDefault()
plant.nextWaterDate = newWaterDate
const response = await fetch("api/plants/" + plant._id, {
method: "PATCH",
body: JSON.stringify(plant),
headers: {
'Content-Type': 'application/json'
}
})
const json = await response.json()
if(response.ok) {
dispatch({ type: "UPDATE_PLANT", payload: json })
}
console.log('updated')
setWatered(false)
}
return (
<div className="plant-details">
<h4>{plant.plantName}</h4>
<p>{plant.quickInfo}</p>
<p>
{formatDistanceToNow(new Date(plant.createdAt), { addSuffix: true })}
</p>
<span onClick={handleClick}>delete</span>
<div>
<p>next water date: {plant.nextWaterDate}</p>
<input onChange={(e) => setNewWaterDate(e.target.value)}/>
<button onClick={updatePlant}>update</button>
<input value={watered} type="checkbox" id="toWater" onChange={() => setWatered(true)}/>
<label for="toWater">watered</label>
{watered && <CalendarComponent updatePlant={updatePlant} setNextWaterDate={setNewWaterDate}/>}
</div>
</div>
);
};
export default PlantDetails;
Context which wraps my
import { createContext, useReducer } from 'react'
export const PlantsContext = createContext()
export const plantsReducer = (state, action) => {
switch(action.type) {
case 'SET_PLANTS':
return {
plants: action.payload
}
case 'CREATE_PLANT':
return {
plants: [action.payload, ...state.plants]
}
case 'DELETE_PLANT':
return {
plants: state.plants.filter((p) => p._id !== action.payload._id)
}
case 'UPDATE_PLANT':
return {
plants: state.plants.map((p) => p._id === action.payload._id ? action.payload : p )
}
default:
return state
}
}
export const PlantsContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(plantsReducer, {
plants: null
})
return (
<PlantsContext.Provider value={{...state, dispatch}}>
{ children }
</PlantsContext.Provider>
)
}
My plantController (update)
const updatePlant = async (req, res) => {
const { id } = req.params
if(!mongoose.Types.ObjectId.isValid(id)) {
return res.status(404).json({ error: "No plant" })
}
const plant = await Plant.findByIdAndUpdate({ _id: id }, {
...req.body
})
if (!plant) {
return res.status(400).json({ error: "No plant" })
}
res.status(200)
.json(plant)
}
Home component
import { useEffect } from "react";
import PlantDetails from "../components/PlantDetails";
import PlantForm from "../components/PlantForm";
import CalendarComponent from "../components/CalendarComponent";
import { usePlantsContext } from "../hooks/usePlantsContext";
const Home = () => {
const { plants, dispatch } = usePlantsContext();
useEffect(() => {
const fetchPlants = async () => {
console.log("called");
// ONLY FOR DEVELOPMENT!
const response = await fetch("/api/plants");
const json = await response.json();
if (response.ok) {
dispatch({ type: "SET_PLANTS", payload: json });
}
};
fetchPlants();
}, [dispatch]);
return (
<div className="home">
<div className="plants">
{plants &&
plants.map((plant) => <PlantDetails key={plant._id} plant={plant} />)}
</div>
<PlantForm />
</div>
);
};
export default Home;
Any help would be greatly appreciated.
My patch requests were going through smoothly but my state would not update until I reloaded my page. It was not returning the document after the update was applied.
https://mongoosejs.com/docs/tutorials/findoneandupdate.html#:~:text=%3B%20//%2059-,You,-should%20set%20the

When uploading a file to react I'm able get the progress of the upload but unable to pass it back to the progress bar. Would a custom hook work here?

I have a material-ui LinearDeterminate progress bar, and I would like to pass on how far along the upload is.
const LinearDeterminate = ({ uploadPercentage, setuploadPercentage }) => {
const classes = useStyles();
const [uploadPercentage, setuploadPercentage] = useState("");
console.log(uploadPercentage);
return (
<div className={classes.root}>
<LinearProgress variant="determinate" value={uploadPercentage} />
</div>
);
};
...
<UploadInput
path={`customer_creatives/assets/${customer_id}/${new Date().getTime()}`}
onChange={(value) =>
updateFieldHandler("link_to_assets")({ target: { value } })
}
value={submissionData["link_to_assets"] || ""}
label="Link to Assets"
sublabel="*Zip files before uploading"
isImage={false}
/>
<LinearDeterminate />
...
UploadInput is a custom input component that links to DropZone (Where the upload happens)
import React, { useState } from "react";
import ReactDropzone from "react-dropzone";
import axios from "axios";
import { noop } from "lodash";
import HelpDialog from "components/HelpDialog";
import { API_URL } from "config";
const Dropzone = ({
path,
onChange = noop,
children,
multiple = false,
maxSize,
sizeHelper,
...props
}) => {
const [url, setUrl] = useState("");
const [loading, setLoading] = useState("");
const [uploadPercentage, setuploadPercentage] = useState("");
const [sizeHelperOpen, setSizeHelperOpen] = useState(false);
const onDrop = ([file]) => {
const contentType = file.type; // eg. image/jpeg or image/svg+xml
console.log(file);
if (maxSize && maxSize < file.size) {
setSizeHelperOpen(true);
return;
}
const generatePutUrl = `${API_URL}/generate-put-url`;
const generateGetUrl = `${API_URL}/generate-get-url`;
const options = {
onUploadProgress: (progressEvent) => {
//console.log("progressEvent.loaded " + progressEvent.loaded)
//console.log("progressEvent.total " + progressEvent.total)
let percent = Math.round(
(progressEvent.loaded / progressEvent.total) * 100
);
setuploadPercentage({
uploadPercentage: percent,
});
console.log(uploadPercentage);
},
params: {
Key: path,
ContentType: contentType,
},
headers: {
"Content-Type": contentType,
},
};
setUrl(URL.createObjectURL(file));
setLoading(true);
axios.get(generatePutUrl, options).then((res) => {
const {
data: { putURL },
} = res;
axios
.put(putURL, file, options)
.then(() => {
axios.get(generateGetUrl, options).then((res) => {
const { data: getURL } = res;
onChange(getURL);
setLoading(false);
});
})
.catch(() => {
setLoading(false);
});
});
};
return (
<ReactDropzone onDrop={onDrop} multiple={multiple} {...props}>
{({ getRootProps, getInputProps }) => (
<>
<div {...getRootProps()}>
<input {...getInputProps()} />
{children({ url, loading })}
</div>
<HelpDialog
open={sizeHelperOpen}
onClose={() => setSizeHelperOpen(false)}
>
{sizeHelper}
</HelpDialog>
</>
)}
</ReactDropzone>
);
};
export default Dropzone;
I'm trying to get the results from the onUploadProgress function into my progress bar. Can I use a custom hook for that? My problem with that is Dropzone already has an export. Thanks for any advice!
It looks as simple as lifting the state up. You actually already have the { uploadPercentage, setuploadPercentage } props on the LinearDeterminate component. Just put that state in the common parent of the UploadInput and the LinearDeterminate components, and then keep passing down the handler to the DropZone component
Remove the state from the LinearDeterminate component
const LinearDeterminate = ({ uploadPercentage }) => {
const classes = useStyles();
return (
<div className={classes.root}>
<LinearProgress variant="determinate" value={uploadPercentage} />
</div>
);
};
Move it to the common parent
const [uploadPercentage, setuploadPercentage] = useState("");
...
<UploadInput
path={`customer_creatives/assets/${customer_id}/${new Date().getTime()}`}
onChange={(value) =>
updateFieldHandler("link_to_assets")({ target: { value } })
}
value={submissionData["link_to_assets"] || ""}
label="Link to Assets"
sublabel="*Zip files before uploading"
isImage={false}
setuploadPercentage={setuploadPercentage}
/>
<LinearDeterminate uploadPercentage={uploadPercentage}/>
...
UploadInput component
const UploadInput = ({ setuploadPercentage, ...allOtherCrazyProps }) => {
...
return (
<DropZone setuploadPercentage={setuploadPercentage} {...moreCrazyProps} />
);
};
And finally, in the DropZone
const Dropzone = ({
path,
onChange = noop,
children,
multiple = false,
maxSize,
sizeHelper,
setuploadPercentage, // this is the new prop
...props
}) => {
...
const options = {
onUploadProgress: (progressEvent) => {
...
setuploadPercentage(percent); // is a string?
console.log(uploadPercentage);
},
...
};
...
};
If you find it cumbersome passing the handler all the way down, you could use a Context to manage that state, but anywhere you use the UserInput you'll need to wrap it on the context provider.
I'd also say to move all the uploading logic and build something similar to downshift-js: a hook that returns all necessary props to turn an element into a droppable uploader, but you already depend on the ReactDropzone component, so I don't think can be done unless you try this other pattern https://blog.bitsrc.io/new-react-design-pattern-return-component-from-hooks-79215c3eac00
You can set the upload precent value after each axios successful call.
axios.get(generatePutUrl, options).then(res => {
const {
data: { putURL }
} = res;
/* Set precent to 33% here */
axios
.put(putURL, file, options)
.then(() => {
/* Set precent to 66% */
axios.get(generateGetUrl, options).then(res => {
/* Set precent to 99% here */
const { data: getURL } = res;
onChange(getURL);
setLoading(false);
});
})
.catch(() => {
setLoading(false);
});
});
Hope that helps.

In react how to call API while select dropdown value

I need to call Get method API. While select the dropdown value. If I select dropdown options="A" value=1 that value will set to API URL "http://localhost:12345/api/GetProfile/Get_MyPostDetails?id=${inputValue}". Please share any idea in react js. Dropdown values are set manually.
This one I tried
import React, { useState } from 'react';
import AsyncSelect from 'react-select/async';
function App() {
const [inputValue, setValue] = useState('');
const [selectedValue, setSelectedValue] = useState(null);
// handle input change event
const handleInputChange = value => {
setValue(value);
};
// handle selection
const handleChange = value => {
setSelectedValue(value);
}
const category=() =>
[
{
value:1,
label:"2017"
},
{
value:2,
label:"2018"
},
{
value:3,
label:"2019"
},
{
value:4,
label:"2020"
},
{
value:5,
label:"2021"
},
{
value:6,
label:"2022"
}
];
// load options using API call
const loadOptions = (inputValue) => {
let user = JSON.parse(localStorage.getItem('user'));
const accessToken=user;
fetch(`http://localhost:12345/api/GetProfile/Get_MyPostDetails?id=${inputValue}`, {
method: 'get',
headers:{
Accept: 'application/json',
Authorization: "Bearer " +accessToken
},
}).then(res => res.json());
};
return (
<div className="App">
<h3>React-Select Async Dropdown - Clue Mediator</h3>
<pre>Input Value: "{inputValue}"</pre>
<AsyncSelect
cacheOptions
defaultOptions
value={selectedValue}
options={category}
loadOptions={loadOptions}
onInputChange={handleInputChange}
onChange={handleChange}
/>
<pre>Selected Value: {JSON.stringify(selectedValue || [{}], null, 2)}</pre>
</div>
);
}
export default App;
I tried this one but it didn't work because I need get array of objects9multiple records) it display single records and also it display the option value from API URL I want to add it manually
Thank you in advance

How to implement rtk's createApi query for debouncing

Can someone help me in implementing the debounce functionality using creatApi with query implementation from redux toolkit.
Thanks in advance.
I personally didn't find any debounce implementation in RTK Query out-of-the-box. But you can implement it yourself.
Define an api. I'm using an openlibrary's one:
import { createApi, fetchBaseQuery } from '#reduxjs/toolkit/query/react';
type BooksSearchResult = {
docs: Book[];
};
type Book = {
key: string;
title: string;
author_name: string;
first_publish_year: number;
};
export const booksApi = createApi({
reducerPath: 'booksApi',
baseQuery: fetchBaseQuery({ baseUrl: 'http://openlibrary.org/' }),
endpoints: builder => ({
searchBooks: builder.query<BooksSearchResult, string>({
query: term => `search.json?q=${encodeURIComponent(term)}`,
}),
}),
});
export const { useSearchBooksQuery } = booksApi;
Next thing you need is debounce hook, which guarantees that some value changes only after specified delay:
function useDebounce(value: string, delay: number): string {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
Use debounce hook on your search form:
import React, { useEffect, useState } from "react";
import BookSearchResults from "./BookSearchResults";
function useDebounce(value: string, delay: number): string {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
const DebounceExample: React.FC = () => {
const [searchTerm, setSearchTerm] = useState("");
const debouncedSearchTerm = useDebounce(searchTerm, 500);
return (
<React.Fragment>
<h1>Debounce example</h1>
<p>Start typing some book name. Search starts at length 5</p>
<input
className="search-input"
type="text"
placeholder="Search books"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<BookSearchResults searchTerm={debouncedSearchTerm}></BookSearchResults>
</React.Fragment>
);
};
export default DebounceExample;
Use the search query hook in search results component. It uses its own state for search term value, which is very convenient if you want to add extra "filters" for debounced value (for example, start query only when search term's length is greater than some value).
import React, { useState, useEffect } from "react";
import { useSearchBooksQuery } from "./booksApi";
type BookSearchResultsProps = {
searchTerm: string;
};
const BookSearchResults: React.FC<BookSearchResultsProps> = ({
searchTerm
}: BookSearchResultsProps) => {
const [filteredSearchTerm, setFilteredSearchTerm] = useState(searchTerm);
const { data, error, isLoading, isFetching } = useSearchBooksQuery(
filteredSearchTerm
);
const books = data?.docs ?? [];
useEffect(() => {
if (searchTerm.length === 0 || searchTerm.length > 4) {
setFilteredSearchTerm(searchTerm);
}
}, [searchTerm]);
if (error) {
return <div className="text-hint">Error while fetching books</div>;
}
if (isLoading) {
return <div className="text-hint">Loading books...</div>;
}
if (isFetching) {
return <div className="text-hint">Fetching books...</div>;
}
if (books.length === 0) {
return <div className="text-hint">No books found</div>;
}
return (
<ul>
{books.map(({ key, title, author_name, first_publish_year }) => (
<li key={key}>
{author_name}: {title}, {first_publish_year}
</li>
))}
</ul>
);
};
export default BookSearchResults;
Full example is available here.
In my case the following solution worked well.
I used component way of debouncing. Use any debounce fn, in my way:
npm i debounce
And then in component
import debounce from 'debounce';
const Component = () => {
const { data, refetch } = useYourQuery();
const [mutate] = useYourMutation();
const handleDebouncedRequest = debouce(async () => {
try {
// you can use refetch or laze query way
await refetch();
await mutate();
} catch {}
}, 2000);
// ...other component logic
}
after the first render our request hook will try to send the request we can bypass this with the skipToken
the request will not be sent until searchTerm returns some value
import { useDebounce } from 'use-debounce'
import { skipToken } from '#reduxjs/toolkit/query'
const [storeName, setStoreName] = useState('')
const [searchTerm] = useDebounce(storeName, 1500)
const { data } = useSearchStoresRequestQuery(searchTerm || skipToken)
more about skipToken: https://redux-toolkit.js.org/rtk-query/usage/conditional-fetching
and also inside my useSearchStoresRequestQuery
endpoints: (builder) => ({
getStoresWithSearchRequest: builder.query({
query: ({searchTerm}) => {
return {
url: `admin/v1/stores?searchTerm?${searchTerm}`,
method: 'GET',
}
},

Resources