I'm using global giving API to make a charity finder app.
I have two dropdowns and a search button in the CharityFinderPage.js component. Now on clicking the search button, I want to fetch the charities using the themeId. The endpoint is https://api.globalgiving.org/api/public/projectservice/themes/{themeId}/projects
I know that on handleClick I should fetch the charities, but how do I get the value of themeId in the handleClick of CharityFinderPage.js component.
What I want is to show a new card component when the button clicks like showing a charity card with the fields populated on it from the data of the API, but first I need to be able to get the data from the API, then I can render a new component.
Here's the code:
CharityFinderPage.js
const CharityFinderPage = () => {
const handleClick = () => {
console.log("inside handleclick")
}
return (
<div style={containerStyle}>
<h1>Charity Finder ❤️</h1>
<h3>Search for charity</h3>
<h4>
Filter charities by personal search conditions. Use the dropdown below
to see charities matching your criteria.
</h4>
<Themes />
<Regions />
<button onClick={handleClick}>Search</button>
</div>
)
}
export default CharityFinderPage
Themes.js
import React, { useEffect, useState } from "react"
import axios from "axios"
const url = `https://api.globalgiving.org/api/public/projectservice/themes.json?api_key=${process.env.REACT_APP_api_key}`
const Themes = () => {
const [isLoading, setIsLoading] = useState(false)
const [selectValue, setSelectValue] = useState("")
const [themes, setThemes] = useState([])
useEffect(() => {
const fetchThemes = async () => {
try {
setIsLoading(true)
const result = await axios.get(url)
setThemes(result.data.themes.theme)
setIsLoading(false)
} catch (err) {
console.log(err)
}
}
fetchThemes()
}, [])
const handleChange = (event) => {
console.log("inside handleChange", event.target.value)
setSelectValue(event.target.value)
}
return (
<div>
{isLoading ? (
<h4>Loading......</h4>
) : (
<div>
<label>Select theme: </label>
<select onChange={handleChange} value={selectValue}>
{themes.map((theme, id) => {
return <option key={id}>{theme.name}</option> //{id} is the `themeId`
})}
</select>
</div>
)}
</div>
)
}
export default Themes
Regions component is exactly similar to Themes.
So the thing that you need to do here is called lifting the state up.
You need to move your states of theme component to CharityFinder component
I am lifting only selectedValue because that is all that you need
CharityFinderPage.js
const CharityFinderPage = () => {
const [selectValue, setSelectValue] = useState("")
const handleClick = () => {
console.log(`inside handleclick with ${selectValue}`)
}
return (
<div style={containerStyle}>
<h1>Charity Finder ❤️</h1>
<h3>Search for charity</h3>
<h4>
Filter charities by personal search conditions. Use the dropdown below
to see charities matching your criteria.
</h4>
// you can pass the setSelectValue as prop to Themes component
<Themes setSelectValue={setSelectValue} selectValue={selectValue} />
<Regions />
<button onClick={handleClick}>Search</button>
</div>
)
}
export default CharityFinderPage
Theme.js
import React, { useEffect, useState } from "react"
import axios from "axios"
const url = `https://api.globalgiving.org/api/public/projectservice/themes.json?api_key=${process.env.REACT_APP_api_key}`
const Themes = ({ selectValue, setSelectValue }) => {
const [isLoading, setIsLoading] = useState(false)
const [themes, setThemes] = useState([])
useEffect(() => {
const fetchThemes = async () => {
try {
setIsLoading(true)
const result = await axios.get(url)
setThemes(result.data.themes.theme)
setIsLoading(false)
} catch (err) {
console.log(err)
}
}
fetchThemes()
}, [])
const handleChange = (event) => {
console.log("inside handleChange", event.target.value)
setSelectValue(event.target.value)
}
return (
<div>
{isLoading ? (
<h4>Loading......</h4>
) : (
<div>
<label>Select theme: </label>
<select onChange={handleChange} value={selectValue}>
{themes.map((theme, id) => {
return <option key={id}>{theme.name}</option> //{id} is the `themeId`
})}
</select>
</div>
)}
</div>
)
}
export default Themes
You can do this.
const CharityFinderPage = () => {
const [themeId, setThemeId] = useState();
const handleClick = () => {
console.log("inside handleclick")
// make call to endpoint with themeId
}
return (
<div style={containerStyle}>
<h1>Charity Finder ❤️</h1>
<h3>Search for charity</h3>
<h4>
Filter charities by personal search conditions. Use the dropdown below
to see charities matching your criteria.
</h4>
<Themes setThemeId={setThemeId} />
<Regions />
<button onClick={handleClick}>Search</button>
</div>
)
}
export default CharityFinderPage
Then in Themes.js:
...
const handleChange = (event) => {
console.log("inside handleChange", event.target.value)
props.setThemeId(event.target.value);
setSelectValue(event.target.value)
}
...
Related
I have a form in a page, when the user inputs the name of a new student and clicks submit, I want the content of that component (the form) to be completely replaced by the submitted name. How can I achieve this (Replace the form with the list onsubmit)?
I have read that I can use conditional rendering to toggle components, but it's not really clear to me how i can apply it here.
StudentListResult.Jsx
import React, { useState } from "react";
import StudentForm from "./StudentForm";
import StudentList from "./StudentList";
const StudentListResult = () => {
const [newStudent, setNewStudent] = useState("");
const [students, setStudentsList] = useState([]);
return (
<div>
<div>
<StudentForm
newStudent={newStudent}
setNewStudent={setNewStudent}
students={students}
setStudentsList={setStudentsList}
/>
</div>
<div>
<StudentList students={students} setStudentsList={setStudentsList} />
</div>
</div>
);
};
export default StudentListResult;
StudentListForm
import React from "react";
import { v4 as uuidv4 } from "uuid";
const StudentListForm = ({
newStudent,
setNewStudent,
students,
setStudentsList,
}) => {
const addStudent = (event) => {
event.preventDefault();
setStudentsList([...students, { id: uuidv4(), name: newStudent }]);
setNewStudent("");
};
return (
<form onSubmit={addStudent}>
<div>
<input
value={newStudent}
type="text"
placeholder="Student Name"
onChange={(e) => setNewStudent(e.target.value)}
/>
</div>
<div>
<button>Submit</button>
</div>
</form>
);
};
export default StudentListForm;
StudentList.jsx
import React from "react";
const StudentList = ({ students = [], setStudentsList }) => {
return (
<div>
{students.map((student) => (
<ul key={student.id}>
<li>
<p>{student.name}</p>
</li>
</ul>
))}
</div>
);
};
export default StudentList;
So you want to show the form if not submitted and show the list if submitted? You can add a piece of state called submitted and do simple conditional rendering.
const StudentListResult = () => {
const [submitted, setSubmitted] = useState(false)
return (
{submitted ? <StudentList /> : <StudentListForm />}
);
};
And then in your addStudent function, set submitted.
const addStudent = (event) => {
// ...
setSubmitted(true)
}
If you want change form and list visibility state, you need pass custom function to form component:
StudentListResult.jsx:
const StudentListResult = () => {
const [newStudent, setNewStudent] = useState("");
const [students, setStudentsList] = useState([]);
const [getFormSubmitted, setFormSubmitted] = useState(false);
const setCompletedForm = () => {
setFormSubmitted(!getFormSubmitted);
};
return (
<div>
{getFormSubmitted ? (
<div>
<StudentList students={students} setStudentsList={setStudentsList} />
</div>
) : (
<div>
<StudentForm
newStudent={newStudent}
setNewStudent={setNewStudent}
students={students}
setStudentsList={setStudentsList}
onComplete={setCompletedForm}
/>
</div>
)}
</div>
);
};
Then call this function if form is submitted and all conditions is true
StudentListForm.tsx:
const StudentListForm = ({
newStudent,
setNewStudent,
students,
setStudentsList,
onComplete
}) => {
const addStudent = (event) => {
event.preventDefault();
setStudentsList([...students, { id: uuidv4(), name: newStudent }]);
setNewStudent("");
onComplete();
};
I don't understand why my page can't recognize other pages when I click (for example on page 2, the same page appears again and again)
This is in MealNew.js component:
import React, {useEffect, useState } from "react";
import './MealNew.css';
import Card from "../UI/Card";
import AppPagination from "./AppPagination";
const MealNew = () => {
const [data, setData] = useState([]);
const [showData, setShowData] = useState(false);
const [query,setQuery] = useState('');
const[page,setPage] = useState(9);
const[numberOfPages,setNumberOfPages]= useState(10);
const handleClick = () => {
setShowData(true);
const link = `https://api.spoonacular.com/recipes/complexSearch?query=${query}&apiKey=991fbfc719c743a5896bebbd98dfe996&page=${page}`;
fetch (link)
.then ((response)=> response.json())
.then ((data) => {
setData(data.results)
setNumberOfPages(data.total_pages)
const elementFood = data?.map((meal,key) => {
return (<div key={key}>
<h1>{meal.title}</h1>
<img src={meal.image}
alt='e-meal'/>
</div> )
})
const handleSubmit = (e) => {
e.preventDefault();
handleClick();
}
useEffect(()=> {
handleClick();
},[page])
return (
<Card className="meal">
<form onSubmit={handleSubmit}>
<input
className="search"
placeholder="Search..."
value={query}
onChange={(e)=>setQuery(e.target.value)}/>
<input type='submit' value='Search'/>
</form>
<li className="meal">
<div className = 'meal-text'>
<h5>{showData && elementFood}</h5>
<AppPagination
setPage={setPage}
pageNumber={numberOfPages}
/>
</div>
</li>
</Card>
) }
export default MealNew;
This is in AppPagination.js component:
import React from "react";
import { Pagination } from "#mui/material";
const AppPagination = ({setPage,pageNumber}) => {
const handleChange = (page)=> {
setPage(page)
window.scroll(0,0)
console.log (page)
}
return (
<div >
<div >
<Pagination
onChange={(e)=>handleChange(e.target.textContent)}
variant="outlined"
count={pageNumber}/>
</div>
</div>
)
}
export default AppPagination;
Thanks in advance, I would appreciate it a lot
The only error I am getting in Console is this:
Line 64:3: React Hook useEffect has a missing dependency: 'handleClick'. Either include it or remove the dependency array react-hooks/exhaustive-deps
You are not following the spoonacular api.
Your link looks like this:
https://api.spoonacular.com/recipes/complexSearch?query=${query}&apiKey=<API_KEY>&page=${page}
I checked the spoonacular Search Recipes Api and there's no page parameter you can pass. You have to used number instead of page.
When you receive response from the api, it returns the following keys: offset, number, results and totalResults.
You are storing totalResults as totalNumberOfPages in state which is wrong. MUI Pagination count takes total number of pages not the total number of records. You can calculate the total number of pages by:
Math.ceil(totalRecords / recordsPerPage). Let say you want to display 10 records per page and you have total 105 records.
Total No. of Pages = Math.ceil(105/10)= 11
Also i pass page as prop to AppPagination component to make it as controlled component.
Follow the documentation:
Search Recipes
Pagination API
Complete Code
import { useEffect, useState } from "react";
import { Card, Pagination } from "#mui/material";
const RECORDS_PER_PAGE = 10;
const MealNew = () => {
const [data, setData] = useState([]);
const [showData, setShowData] = useState(false);
const [query, setQuery] = useState("");
const [page, setPage] = useState(1);
const [numberOfPages, setNumberOfPages] = useState();
const handleClick = () => {
setShowData(true);
const link = `https://api.spoonacular.com/recipes/complexSearch?query=${query}&apiKey=<API_KEY>&number=${page}`;
fetch(link)
.then((response) => response.json())
.then((data) => {
setData(data.results);
const totalPages = Math.ceil(data.totalResults / RECORDS_PER_PAGE);
setNumberOfPages(totalPages);
});
};
const elementFood = data?.map((meal, key) => {
return (
<div key={key}>
<h1>{meal.title}</h1>
<img src={meal.image} alt='e-meal' />
</div>
);
});
const handleSubmit = (e) => {
e.preventDefault();
handleClick();
};
useEffect(() => {
handleClick();
console.log("first");
}, [page]);
return (
<Card className='meal'>
<form onSubmit={handleSubmit}>
<input className='search' placeholder='Search...' value={query} onChange={(e) => setQuery(e.target.value)} />
<input type='submit' value='Search' />
</form>
<li className='meal'>
<div className='meal-text'>
<h5>{showData && elementFood}</h5>
<AppPagination setPage={setPage} pageNumber={numberOfPages} page={page} />
</div>
</li>
</Card>
);
};
const AppPagination = ({ setPage, pageNumber, page }) => {
const handleChange = (page) => {
setPage(page);
window.scroll(0, 0);
console.log(page);
};
console.log("numberOfPages", pageNumber);
return (
<div>
<div>
<Pagination
page={page}
onChange={(e) => handleChange(e.target.textContent)}
variant='outlined'
count={pageNumber}
/>
</div>
</div>
);
};
export default MealNew;
I'm trying to use a search bar component in my React project to search/filter through an api list of movies by title. Right now my search term is console logging, but i'm trying to filter the movie list to only show the titles that match the term. I'm having issues with updating my movies state with the term and displaying the new array.
App
import SearchBar from "../Search/SearchBar"
export default function Movies() {
const [movies, setMovies] = useState([]);
async function getMovies() {
const movieData = await fetchMovies();
console.log(movieData);
setMovies(
movieData.data.data.sort((a, b) => a.title.localeCompare(b.title))
);
}
useEffect(() => {
getMovies();
}, []);
async function onSearchSubmit(term) {
console.log(term)
let fill = []
movies.filter((movie) => {
if(movie.title === term) {
fill.push(movie.title)
}
setMovies(fill)
})
}
return (
<>
<Nav
movies={movies}
setMovies={setMovies}/>
<SearchBar
onSubmit={onSearchSubmit}/>
{movies ? (
<div>
<div>
{movies.map((m, idx) => {
return <div key={idx}>{m.title}</div>;
})}{" "}
</div>
</div>
) : (
"loading..."
)}
</>
);
}
Search Bar component
import React,{useState} from 'react';
const SearchBar = ({onSubmit}) => {
const [term, setTerm] = useState("")
function onFormSubmit(event){
event.preventDefault()
onSubmit(term)
}
return (
<div className="ui segment">
<form onSubmit={onFormSubmit} className="ui form">
<div className="field">
<label>Movie Search</label>
<input
type="text"
value={term}
onChange={(e) => setTerm( e.target.value)}
/>
</div>
</form>
</div>
);
}
export default SearchBar;
First of all additional state is needed to record the loaded moves list:
const movies = useRef([]);
const [filteredMovies, setFilteredMovies] = useState([]);
It is better to declare handlers with useCallback and avoid the mixture of declarative and imperative styles. For example:
const onSearchSubmit = useCallback(async (term) => {
if (term) {
const _ = movies.current.filter(({ title }) => (title === term));
setFilteredMovies(_);
} else {
setFilteredMovies(movies.current);
}
}, [movies]);
https://jsfiddle.net/pq9xkewz/2/
I was trying to set my value in the input value! but after that, I cannot write anything in the input field! I wanted to set values from the back end in value!
We are writing an admin channel to edit the article for that we need already existing article values to edit the article! What am I doing wrong! or Maybe you can suggest a better way to edit the article in the admin channel!
here is the code:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { useParams } from 'react-router';
const EditArticle = (props) => {
const [editValues, setEditValues] = useState([]);
const [changedValues, setChangedValues] = useState('');
console.log('values', editValues);
console.log('changed', changedValues);
const params = useParams();
console.log(params);
const resultsId = params.id;
console.log('string', resultsId);
const [authTokens, setAuthTokens] = useState(
localStorage.getItem('token') || ''
);
const setTokens = (data) => {
localStorage.setItem('token', JSON.stringify(data));
setAuthTokens(data);
// setToken(data['dataValues']['token']);
};
useEffect(() => {
const fetchData = async () => {
try {
const res = await axios.get(
`${process.env.REACT_APP_API_URL}/article/${resultsId}`
);
setEditValues(res.data);
} catch (err) {}
};
fetchData();
}, [resultsId]);
const inputValue = editValues;
const userToken = props.token;
return (
<div>
<form value={{ authTokens, setAuthTokens: setTokens }}>
<input
value={editValues.title || ''}
onChange={(input) => setChangedValues(input.target.value)}
type='text'
/>
<input
// ref={editValues.shortDesc}
value={editValues.shortDesc}
onChange={(input) => setChangedValues(input.target.value)}
type='text'
/>
<button type='submit'>send</button>
</form>
</div>
);
};
export default EditArticle;
your onChange handler is updating a different state property than what is being used as the value on the input (editValues vs changedValues).
Also you can pass a defaultValue to input that will get used as the default value only.
See more here https://reactjs.org/docs/uncontrolled-components.html
you can use just do it just using editValues. try this:
I just reproduced it without the api call to run the code.
import React, { useState, useEffect } from "react";
const EditArticle = (props) => {
const [editValues, setEditValues] = useState([]);
console.log("values", editValues);
const [authTokens, setAuthTokens] = useState(
localStorage.getItem("token") || ""
);
const setTokens = (data) => {
localStorage.setItem("token", JSON.stringify(data));
setAuthTokens(data);
// setToken(data['dataValues']['token']);
};
useEffect(() => {
const fetchData = async () => {
try {
//here get the data from api and setstate
setEditValues({ title: "title", shortDesc: "shortDesc" });
} catch (err) {}
};
fetchData();
}, []);
return (
<div>
<form value={{ authTokens, setAuthTokens: setTokens }}>
<input
value={editValues.title || ""}
onChange={(input) => setEditValues({title: input.target.value})}
type="text"
/>
<input
value={editValues.shortDesc}
onChange={(input) => setEditValues({shortDesc: input.target.value})}
type="text"
/>
<button type="submit">send</button>
</form>
</div>
);
};
export default EditArticle;
Hi guys trying to make an mini application in react JS , in that application i want to display data in the my body part when i do search on my textbar , so anyone tell me how can i do it or what should i do to display data?
App.js This is my main component
import './App.css';
import Star from './Star';
import People from './People';
import Planet from './Planet';
function App(props) {
const {people,planet} = props;
return (
<div className="App">
<Star />
<People data={people}/>
<Planet data={planet}/>
</div>
);
}
export default App;
Star.js
This is my star component where i fetch my all star war apis
import React, { useState, useEffect } from 'react';
import './Star.css';
const Star = () => {
const [search, setSearch] = useState('');
const [people, setPeople] = useState([]);
const [planet, setPlanet] = useState([]);
const onSubmit = (e) => {
e.preventDefault();
if (search === "") {
alert("please Enter some value");
}
}
useEffect(() => {
async function fetchPeople() {
let result = await fetch("https://swapi.dev/api/people/?format=json");
let data = await result.json();
setPeople(data.results);
}
async function fetchPlanet() {
let result = await fetch("https://swapi.dev/api/planets/?format=json");
let data = await result.json();
setPlanet(data.results);
}
fetchPeople();
fetchPlanet();
}, [])
console.log("people", people);
console.log("planet", planet);
return (
<div>
<div className='container'>
<h2>Star War</h2>
<div className='jumbotron'>
<input type="text"
className="form-control"
placeholder='Search...'
value={search}
onChange={(e) => setSearch(e.target.value)} />
<span><button className='btn btn-secondary' onClick={onSubmit}>Search</button></span>
</div>
</div>
</div>
)
}
export default Star;
people.js
This is my people that i want to display in the my body part
import React from 'react';
const People = (props) => {
const { data } = props;
return (
<div className="row">
{data && data.map((people, i) => {
return (
<div className="col-md-3" key={i}>
<div className="card">
<div className="card-body">
<h4>{people.name}</h4>
</div>
</div>
</div>
)
})}
</div>
);
};
export default People;
If I understand correctly your issue, you want to display the People component and the issue is how to pass it the data fetched in the Star component.
The solution is to move the state of the data in the parent component so that it can be passed easily to all its children.
function App(props) {
const [people, setPeople] = useState([]);
const [planet, setPlanet] = useState([]);
useEffect(() => {
async function fetchPeople() {
let result = await fetch("https://swapi.dev/api/people/?format=json");
let data = await result.json();
setPeople(data.results);
}
async function fetchPlanet() {
let result = await fetch("https://swapi.dev/api/planets/?format=json");
let data = await result.json();
setPlanet(data.results);
}
fetchPeople();
fetchPlanet();
}, [])
return (
<div className="App">
<Star
people={people} //if you need these in the Star component
planet={planet} //if you need these in the Star component
/>
<People data={people}/>
<Planet data={planet}/>
</div>
);
}