My search bar component redirects the user to the search page like so
const handleSearch = () => {
goto(`/movies/search/${searchValue}`);
};
src/components/searchBar
<script>
import { goto } from '$app/navigation';
import searchIcon from '../assets/icon-search.svg';
export let placeholder;
let searchValue = '';
const handleSearch = () => {
goto(`/movies/search/${searchValue}`);
};
</script>
<form class=" ml-5% mt-6 pb-6 flex " on:submit|preventDefault={handleSearch}>
<label class=" relative ">
<input
class=" w-full py-2 pl-10 pr-2 bg-[transparent] border-none text-text text-lg font-light caret-primary placeholder:font-light focus:outline-none "
type="text"
{placeholder}
bind:value={searchValue}
/>
<div class=" absolute top-[50%] left-0 translate-y-[-50%] ">
<img src={searchIcon} alt="" loading="lazy" />
</div>
</label>
</form>
Then the movie gets fetched on +page.server.jsand rendered in +page.svelte. Everything is fine.
movies/search/[search_params]/+page.server.js
export const load = async ({ params }) => {
const { search_params } = params;
const fetchSearchedMovies = async () => {
const resp = await fetch(`https://api.themoviedb.org/3/search/movie? api_key=${TMDB_API_KEY}&language=en- US&query=${search_params}&page=1&include_adult=false`
);
const data = await resp.json();
return data.results;
};
return {
searchResults: fetchSearchedMovies()
};
};
/movies/search/[search_params]/+page.svelte
<script>
export let data;
const { searchResults } = data;
</script>
<SearchBar placeholder="Search for Movies" />
<CardGrid title="Search Results">
{#each searchResults as movie (movie.id)}
<Card {movie} />
{/each}
</CardGrid>
When I trigger another search, hit enter, it changes the url movies/search/movieYourSearchingFor, but the content doesn't get updated, i guess because the load function doesn't get re-triggered. When I refresh the page, it then gets loaded.
So my question is how can I make the load function run again, when the search_params change?
Can I use invalidate for that? I read in the docs, but Im not sure Im understanding it right.
Thanks for any help.
It should automatically update, the problem is probably this line:
const { searchResults } = data;
This causes the property to be read just once; change it to:
$: ({ searchResults } = data);
That way the results update when data changes.
Related
I need to fetch data from the MongoDB collection after the user clicks the id properties of the document
[frontend][1]
[1]: https://i.stack.imgur.com/fmW1N.jpg
import { useState, useEffect } from "react";
import { Link } from "react-router-dom";
const ViewVehicles = () => {
const [vehicles, setVehicles] = useState(null);
useEffect(() => {
const fetchvehicles = async () => {
const response = await fetch("/headofDeployement/registerVehicle");
const json = await response.json();
if (response.ok) {
setVehicles(json);
}
};
fetchvehicles();
}, []);
return (
<div className="container ">
<div className="row ">
<div className="col justify-content-center align-center">
<h4>Vehicles Registered</h4>
{vehicles &&
vehicles.map((vehicle) => (
<ul key={vehicle._id}>
<a href="" className="text-danger">
{vehicle._id}
</a>
</ul>
))}
</div>
</div>
</div>
);
};
export default ViewVehicles;
I'm not sure if I understand correctly but you should use the Link component you imported from react-router-dom.
For further reading and methodology of how this use case can be handled, check: https://v5.reactrouter.com/web/example/url-params
I have created a project in Next js and rendering the component using getServerSideProps.
It has an index page like this with two component, side and Hero
export default function HeroPage({data, data1, data2, data3}) {
return (
<div className="h-full flex flex-row">
<Side years={data2} chapters={data3} />
<MyMap districts = {data} mis={data1}/>
</div>
);
}
//using Serversideprops and fetching the data from api
// and passing the data to respective components
export async function getServerSideProps() {
const response = await fetch("https://dev.ksrsac.in/testjson/district.json");
const data = await response.json();
const response1 = await fetch("http://localhost:3000/api/mis");
const data1 = await response1.json();
const response2 = await fetch("http://localhost:3000/api/year");
const data2= await response2.json();
const response3 = await fetch("http://localhost:3000/api/sidebar");
const data3 = await response3.json();
return {
props: { data:data, data1:data1, data2:data2, data3:data3 }
};
}
and it is rendering properly.
The side bar component code is as below
export default function Side({ years, chapters }) {
const [yr, setYr] = useState(years);
const [ch, setCh] = useState(chapters);
const [showTab, setShowTab] = useState(false);
function clickHandler(){
setShowTab(true);
}
return (
<div className="w-1/5 bg-blue-200">
{/* year component */}
<div className="pt-2 px-4 m-4 bg-blue-900 rounded-lg">
<label className="text-white"> Year : </label>
<select className="px-10 text-white bg-blue-900">
{yr.map((y) => {
return <option key={y.id}>{y.year}</option>;
})}
</select>
</div>
{/* chapter component */}
<ul className="p-2 px-4">
{ch.map((c) => {
return (
<li key={c.id} onClick={clickHandler} className="bg-blue-900 text-white border border-white-800 rounded-lg p-2 hover:bg-sky-700 hover:text-yellow-300">
{c.id}. {c.cName}
</li>
);
})}
</ul>
{showTab && <Tab handler = {setShowTab}/>}
</div>
);
}
Side bar having a child component called Tab, if user clicks on chapter-> I am showing the Tab
export default function Tab({handler}) {
return (
<div className="absolute z-40 right-4 top-28 w-62 border border-blue-500">
<div className="absolute top-0 right-0">
<button onClick={handleClick=>handler(false)}>X</button>
</div>
<main className="mt-3 underline underline-offset-1">
it contains min/max/avg
</main>
<div className="overflow-y-scroll">
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
</ul>
</div>
</div>
);
}
In the Tab component, I have created a closing button. If user clicks on this, I am closing the tab component by changing the parent setShowTab status to false.
The challenge is
As the user clicks on ay list item - One, Two, Three
I have to call an api, which return array of object like below
One=[{id:1, femalePopCount:200}, {id:2, femalePopCount:300}, {id:3, femalePopCount:400}]
And I have to pass this data to MyMap component, which I am rendering in the index page.
How to do this ?
MyMap component code is as below - in map component I am joining two array i.e.,
district =[{id:1, name:’bidar’},{id:2, name:’dharwad’},{id:3, name:’yadgir’}]
mis =[{id:1, malePopCount:200}, {id:2, malePopCount:300}, {id:3, malePopCount:400}]
which i have received from the getServerSideProps
In MyMap component, as user click different list item from Tab component, I have to change the mis data and render the MyMap component.
Fairly new to react and trying to build a clone of The Movie Database site. I want this toggle switch to change my api call from movies to tv. It starts working after a couple clicks, but then it throws everything off and it's not displaying the correct items anyway. Not really sure what's going on here...or even why it starts working after two clicks. Anyone know whats up with this?
import React, { useState, useEffect } from "react";
import axios from "axios";
import API_KEY from "../../config";
const Popular = ({ imageUri }) => {
// GET POPULAR MOVIES
const [popularMovies, setPopularMovies] = useState("");
const [genre, setGenre] = useState("movie");
console.log(genre);
const getPopular = async () => {
const response = await axios.get(
`https://api.themoviedb.org/3/discover/${genre}?sort_by=popularity.desc&api_key=${API_KEY}`
);
setPopularMovies(response.data.results);
};
useEffect(() => {
getPopular();
}, []);
const listOptions = document.querySelectorAll(".switch--option");
const background = document.querySelector(".background");
const changeOption = (el) => {
let getGenre = el.target.dataset.genre;
setGenre(getGenre);
getPopular();
listOptions.forEach((option) => {
option.classList.remove("selected");
});
el = el.target.parentElement.parentElement;
let getStartingLeft = Math.floor(
listOptions[0].getBoundingClientRect().left
);
let getLeft = Math.floor(el.getBoundingClientRect().left);
let getWidth = Math.floor(el.getBoundingClientRect().width);
let leftPos = getLeft - getStartingLeft;
background.setAttribute(
"style",
`left: ${leftPos}px; width: ${getWidth}px`
);
el.classList.add("selected");
};
return (
<section className="container movie-list">
<div className="flex">
<div className="movie-list__header">
<h3>What's Popular</h3>
</div>
<div className="switch flex">
<div className="switch--option selected">
<h3>
<a
data-genre="movie"
onClick={(e) => changeOption(e)}
className="switch--anchor"
>
In Theaters
</a>
</h3>
<div className="background"></div>
</div>
<div className="switch--option">
<h3>
<a
data-genre="tv"
onClick={(e) => changeOption(e)}
className="switch--anchor"
>
On TV
</a>
</h3>
</div>
</div>
</div>
<div className="scroller">
<div className="flex flex--justify-center">
<div className="flex flex--nowrap container u-overScroll">
{popularMovies &&
popularMovies.map((movie, idX) => (
<div key={idX} className="card">
<div className="image">
<img src={imageUri + "w500" + movie.poster_path} />
</div>
<p>{movie.title}</p>
</div>
))}
</div>
</div>
</div>
</section>
);
};
export default Popular;
You're using the array index as your key prop when you're mapping your array.
You should use an id that is specific to the data that you're rendering.
React uses the key prop to know which items have changed since the last render.
In your case you should use the movie id in your key prop instead of the array index.
popularMovies.map((movie) => (
<div key={movie.id} className="card">
<div className="image">
<img src={imageUri + 'w500' + movie.poster_path} />
</div>
<p>{movie.title}</p>
</div>
));
Also
You're calling the api directly after setGenre. However state changes aren't immediate. So when you're making your api call you're still sending the last movie genre.
Two ways of fixing this:
You could call your function with the genre directly, and change your function so it handles this case:
getPopular('movie');
Or you could not call the function at all and add genre as a dependency of your useEffect. That way the useEffect will run each time the genre change.
useEffect(() => {
getPopular();
}, [genre]);
PS: You should consider splitting your code into more component and not interacting with the DOM directly.
To give you an idea of what it could look like, I refactored a bit, but more improvements could be made:
const Popular = ({ imageUri }) => {
const [popularMovies, setPopularMovies] = useState('');
const [genre, setGenre] = useState('movie');
const getPopular = async (movieGenre) => {
const response = await axios.get(
`https://api.themoviedb.org/3/discover/${movieGenre}?sort_by=popularity.desc&api_key=${API_KEY}`
);
setPopularMovies(response.data.results);
};
useEffect(() => {
getPopular();
}, [genre]);
const changeHandler = (el) => {
let getGenre = el.target.dataset.genre;
setGenre(getGenre);
};
const isMovieSelected = genre === 'movie';
const isTvSelected = genre === 'tv';
return (
<section className="container movie-list">
<div className="flex">
<MovieHeader>What's Popular</MovieHeader>
<div className="switch flex">
<Toggle onChange={changeHandler} selected={isMovieSelected}>
In Theaters
</Toggle>
<Toggle onChange={changeHandler} selected={isTvSelected}>
On TV
</Toggle>
</div>
</div>
<div className="scroller">
<div className="flex flex--justify-center">
<div className="flex flex--nowrap container u-overScroll">
{popularMovies.map((movie) => {
const { title, id, poster_path } = movie;
return (
<MovieItem
title={title}
imageUri={imageUri}
key={id}
poster_path={poster_path}
/>
);
})}
</div>
</div>
</div>
</section>
);
};
export default Popular;
const Toggle = (props) => {
const { children, onChange, selected } = props;
const className = selected ? 'switch--option selected' : 'switch--option';
return (
<div className={className}>
<h3>
<a
data-genre="movie"
onClick={onChange}
className="switch--anchor"
>
{children}
</a>
</h3>
<div className="background"></div>
</div>
);
};
const MovieHeader = (props) => {
const { children } = props;
return (
<div className="movie-list__header">
<h3>{children}</h3>
</div>
);
};
const MovieItem = (props) => {
const { title, imageUri, poster_path } = props;
return (
<div key={idX} className="card">
<div className="image">
<img src={imageUri + 'w500' + poster_path} />
</div>
<p>{title}</p>
</div>
);
};
In my Next.js app, the component is getting re-rendered when I change the browser tab and then get back to the tab in which the app is already opened. e.g. app is open tab 1 and when I switch to tab 2 and then come back to tab 1.
Actually, I have a page on which listing of records appears, so when I do local filter using text match it is working fine. But when I change the tab and get back to the app tab, it resets the listing again.
When I filter the location with text then it does the filter.
But when I switch the tab it resets the result.
I am using useSwr for data fetching and display listing. Here below is code of component:
import useSWR from 'swr'
import Link from 'next/link'
import Httpservice from '#/services/Httpservice'
import { useState, useEffect, useCallback } from 'react'
import NavBar from '#/components/NavBar'
import Alert from 'react-bootstrap/Alert'
import Router, { useRouter } from 'next/router'
import NoDataFound from '#/components/NoDataFound'
import nextConfig from 'next.config'
import { useTranslation, useLanguageQuery, LanguageSwitcher } from 'next-export-i18n'
export default function Locations({...props}) {
const router = useRouter()
const { t } = useTranslation()
const [queryLanguage] = useLanguageQuery()
const httpService = new Httpservice
const pageLimit = nextConfig.PAGE_LIMIT
const [loading,setLoading] = useState(true)
const [pageIndex, setPageIndex] = useState(1)
const [locations, setLocations] = useState([])
const [searchText, setSearchText] = useState('')
const [locationId, setLocationId] = useState(null)
const [isExpanding, setIsExpending] = useState(null)
const [loadMoreBtn, setLoadMoreBtn] = useState(true)
const [locationName, setLocationName] = useState(null)
const [errorMessage, setErrorMessage] = useState(null)
const [tempLocations, setTempLocations] = useState([])
const [deleteMessage, setDeleteMessage] = useState(null)
const [successMessage, setSuccessMessage] = useState(null)
const [displayConfirmationModal, setDisplayConfirmationModal] = useState(false)
const showDeleteModal = (locationName, locationId) => {
setLocationName(locationName)
setLocationId(locationId)
setSuccessMessage(null)
setErrorMessage(null)
setDeleteMessage(`Are you sure you want to delete the '${locationName}'?`)
setDisplayConfirmationModal(true)
}
const hideConfirmationModal = () => {
setDisplayConfirmationModal(false)
}
const locationsFetcher = async() => {
try{
await httpService.get(`/v1/locations?page=${pageIndex}&limit=${pageLimit}`).then((response) => {
if(response.status == 200 && response.data) {
let data = response.data.results
setLocations([...new Set([...locations,...data])])
setTempLocations([...new Set([...locations,...data])])
if(response.data.next == undefined && response.data.results.length == 0) {
setLoadMoreBtn(false)
}
setLoading(false)
setIsExpending(null)
return data
} else {
setLoading(false)
setIsExpending(null)
const error = new Error('An error occurred while fetching the data.')
error.info = response.json()
error.status = response.status
throw error
}
}).catch((error) => {
setLoading(false)
setIsExpending(null)
})
} catch (error) {
setLoading(false)
setIsExpending(null)
}
}
const {data, error} = useSWR(`/v1/locations?page=${pageIndex}&limit=${pageLimit}`, locationsFetcher,{
onErrorRetry: (error, key, config, revalidate, { retryCount }) => {
if (error.status === 404) return
if (retryCount >= 10) return
setTimeout(() => revalidate({ retryCount }), 5000)
}
})
const loadMore = () => {
setPageIndex(pageIndex + 1)
setIsExpending(true)
}
const handleSearch = (e) => {
let searchKey = e.target.value
setSearchText(e.target.value)
if(searchKey.length > 0) {
console.log(tempLocations)
let foundValue = tempLocations.filter(location => location.name.toLowerCase().includes(searchText.toLowerCase()))
if(foundValue) {
setLoadMoreBtn(false)
setLocations(foundValue)
} else {
setLoadMoreBtn(true)
setLocations(tempLocations)
}
} else {
setLoadMoreBtn(true)
setLocations(tempLocations)
}
}
return (
<>
<NavBar />
<div className="app-wrapper">
<div className="app-content pt-3 p-md-3 p-lg-4">
<div className="container-xl">
<div className="row gy-4 mb-2">
<div className="col-12 col-lg-8">
<h1 className="page-head-title"> {t('locations')} </h1>
</div>
</div>
<div className="summary_col">
<div className="row gy-4">
<div className="col-12 col-lg-12">
<div className="dotted float-end">
<a href="javascript:void(0)">
<img src="/images/icons/dotted.png" width="16" height="4" alt="" />
</a>
</div>
</div>
</div>
<div className="row gy-4 mt-2">
<div className="col-6 col-lg-3 col-md-4">
<div className="input-group search_col">
<div className="form-outline ">
<input type="search" className="form-control" placeholder={t('search')} value={searchText} onChange={handleSearch} />
</div>
<button type="button" className="btn">
<img src="/images/icons/search.png" width="19" height="19" alt="" />
</button>
</div>
</div>
<div className="col-6 col-lg-9 col-md-8 ">
<Link href={{ pathname: '/settings/locations/add', query: (router.query.lang) ? 'lang='+router.query.lang : null }}>
<a className="btn btn-primary float-end">{t('location_page.add_location')}</a>
</Link>
</div>
</div>
<div className="row gy-4 mt-2">
<div className="col-12 col-lg-12">
<div className="vehicles_col table-responsive">
<table className="table" width="100%" cellPadding="0" cellSpacing="0">
<thead>
<tr>
<th>{t('location_page.name')}</th>
<th>{t('location_page.company')}</th>
<th>{t('location_page.contact')}</th>
<th>{t('location_page.email')}</th>
<th>{t('location_page.phone')}</th>
<th>{t('location_page.address')}</th>
<th>{t('detail')}</th>
</tr>
</thead>
<tbody>
{error && <tr><td><p>{t('error_in_loading')}</p></td></tr>}
{(loading) ? <tr><td colSpan="6"><p>{t('loading')}</p></td></tr> :
(locations && locations.length > 0) ? (locations.map((location, index) => (
<tr index={index} key={index}>
<td>{location.name}</td>
<td>
<a href="javascript:void(0)">
{(location.links && location.links.Company) ? location.links.Company : '-'}
</a>
</td>
<td>{location.contact}</td>
<td>{location.email}</td>
<td>{location.phone}</td>
<td>
{(location.address1) ? location.address1 : ''}
{(location.address2) ? ','+location.address2 : ''}
{(location.address3) ? ','+location.address3 : ''}
<br />
{(location.city) ? location.city : ''}
{(location.state) ? ','+location.state : ''}
{(location.country) ? ','+location.country : ''}
{(location.zip) ? ','+location.zip : ''}
</td>
<td>
<Link href={{ pathname: '/settings/locations/edit/'+ location.UUID, query: (router.query.lang) ? 'lang='+router.query.lang : null }}>
{t('view')}
</Link>
</td>
</tr>
))) : (<tr><td><NoDataFound /></td></tr>)}
</tbody>
</table>
<div className="click_btn">
{(loadMoreBtn) ? (isExpanding) ? t('loading') : <a href="javascript:void(0)" onClick={() => loadMore()}>
<span>{t('expand_table')}</span>
</a> : t('no_more_data_avail')}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</>
)
}
By default useSWR will automatically revalidate data when you re-focus a page or switch between tabs. This is what's causing the re-renders.
You can disable this behaviour through the options object in your useSWR call, by setting the revalidateOnFocus field to false.
useSWR(`/v1/locations?page=${pageIndex}&limit=${pageLimit}`, locationsFetcher, {
onErrorRetry: (error, key, config, revalidate, { retryCount }) => {
if (error.status === 404) return
if (retryCount >= 10) return
setTimeout(() => revalidate({ retryCount }), 5000)
},
revalidateOnFocus: false
})
Alternatively, you can use useSWRImmutable (rather than useSWR) to disable all kinds of automatic revalidations done by SWR.
import useSWRImmutable from 'swr/immutable'
// ...
useSWRImmutable(key, fetcher, options)
Which is essentially the same as calling:
useSWR(key, fetcher, {
// other options here
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false
})
There are two debug points that i suggest to try first, although I believe the problem isn't caused by this component.
export default function Locations({...props}) {
console.log('Render')
and
const locationsFetcher = async() => {
console.log('Fetch')
The above are to confirm when switching tabs,
if the Locations component repaints
if the locationsFetcher has refired
The above questions will help you to dig further. My guts feeling is that you have another piece in your code that detects the tab switching, ex. listening to the page active or not. Because by default this Locations component shouldn't repaint by itself.
googlemapapiI'm having issues fetching google map, it says the page can't load correctly, I also have some errors on my console. I don't understand what I'm doing wrong, I should be able to make a query and have the places showing in the suggestions, but I'm doing something wrong. here is my component, I have also attached a photo. All help will be welcome [
import React, { Component } from "react";
import { Map, Marker, GoogleApiWrapper } from "google-maps-react";
const apiKey = process.env.REACT_APP_GOOGLE_API_KEY;
const center = {
lat: 51.5074,
lng: 0.1278,
};
let service = null;
export class MapContainer extends Component {
constructor(props) {
super(props);
this.state = {
input: "",
suggestions: [],
places: [],
};
}
savePlace = (place) => {
this.setState({ places: [...this.state.places, place] });
};
handleChange = (e) => {
this.setState({ input: e.target.value });
};
handleKeyPress = (event) => {
if (event.key === "Enter") {
this.search();
}
};
search = () => {
const {input} = this.state;
service.textSearch({query: input}, (suggestions) => {
this.setState({suggestions});
})
};
initPlaces(mapProps, map) {
const { google } = mapProps;
service = new google.maps.places.PlacesService(map);
}
render() {
const { suggestions, places } = this.state;
return (
<div className="container">
<div className="row">
<div className="col">
<div className="form-inline d-flex justify-content-between mb-4">
<input
type="text"
value={this.state.input}
onChange={this.handleChange}
className="form-control flex-grow-1"
placeholder="Search for places on Google Maps"
onKeyPress={this.handleKeyPress}
/>
<button onClick={this.search} className="btn btn-primary ml-2">
Search
</button>
</div>
<h3>Suggestions</h3>
<ul className="list-group">
{suggestions.map((place, i) => (
<li
key={i}
className="list-group-item d-flex justify-content-between align-items-center"
>
<div>
<div>
<strong>{place.name}</strong>
</div>
<span className="text-muted">
{place.formatted_address}
</span>
</div>
<button
className="btn btn-outline-primary"
onClick={() => this.savePlace(place)}
>
Show
</button>
</li>
))}
</ul>
</div>
<div className="col">
<Map google={this.props.google} zoom={14} initialCenter={center} onReady={this.initPlaces}></Map>
</div>
</div>
</div>
);
}
}
export default GoogleApiWrapper({
apiKey,
})(MapContainer);
]2
I checked your code and if you directly put your API key in your
const apiKey = "PUT_YOUR_API_KEY_HERE"; , it will properly show your map.
It seems that you are putting your variables in the .env file (refer here on how to add custom environment variables). Make sure that you put your .env file outside the src folder and set this inside your .env file :
REACT_APP_GOOGLE_API_KEY=API_KEY_VALUE_HERE. This works for me.
You can find the sample code in this link.
Make sure to change the value of the REACT_APP_GOOGLE_API_KEY in the .env file to your API key.
Hope this helps!