❌

Reading view

I built a social platform in about a month to learn more about React and Supabase

hey guys,

I built this website over the last month to learn more about making website and handling real time data, it uses React, Next.js and Supabase

You can try it out from here: https://project-s-nine-kappa.vercel.app

(Please just click the "Explore Demo" button on the login screen so you don't have to create an account!)

Source code is here: https://github.com/zzz-nv/s-social

Would love to hear any feedback!

submitted by /u/darkpickle_ to r/reactjs
[link] [comments]
  •  

Google map autocomplete cost issue

Hi everyone!

I'm currently implementing Google Places Autocomplete using the new Autocomplete Data API with React and @vis.gl/react-google-maps.

The implementation works, but I'm concerned about Google Maps API pricing, especially during development. I managed to hit my API usage limit within just two days, and I'm trying to understand whether my implementation is making unnecessary requests or whether this is expected behavior.

I'd appreciate some clarification from anyone who has experience with the Google Places API.

  1. My current implementation
    I'm using a 500ms debounce to avoid sending a request on every keystroke.

SearchBox component

```
import { MagnifyingGlassIcon, MapPinIcon } from '@phosphor-icons/react'
import { useState } from 'react'
import { useDebounce } from '@/hooks/useDebounce'
import { useAnimatedPlaceholder } from '@/features/search/hooks/useAnimatedPlaceholder'
import { useMapsLibrary } from '@vis.gl/react-google-maps'
import { useAutocompleteSuggestions } from '@/hooks/useAutoCompleteSuggestions'
import { useNavigate } from 'react-router-dom'

import styles from './styles.module.css'

const SearchBox = () => {
const [searchQuery, setSearchQuery] = useState('')
const debouncedSearchQuery = useDebounce(searchQuery, 500)
const inputRef = useAnimatedPlaceholder()

return (
<>
<div className={styles.searchBox}>
<MagnifyingGlassIcon size={24} />
<input placeholder="Search for " ref={inputRef} value={searchQuery} onChange={e => setSearchQuery(e.currentTarget.value)} />
</div>
<SearchSuggestions debouncedSearchQuery={debouncedSearchQuery} />
</>

)
}

export default SearchBox

type SearchSuggestions = {
debouncedSearchQuery: string
}

function SearchSuggestions({ debouncedSearchQuery }: SearchSuggestions) {
const navigate = useNavigate()
const places = useMapsLibrary('places');
const { suggestions, resetSession } = useAutocompleteSuggestions(debouncedSearchQuery, {
includedPrimaryTypes: ["locality"]
})

const handleSuggestionClick = async (suggestion: google.maps.places.AutocompleteSuggestion) => {
if (!places || !suggestion.placePrediction) return

const place = suggestion.placePrediction.toPlace()
await place.fetchFields({
fields: [
"addressComponents"
]
})
// calling fetchFields invalidates the session-token, so we now have to call
// resetSession() so a new one gets created for further search
resetSession()

const city = place.addressComponents && place.addressComponents[0].longText
const placeId = suggestion.placePrediction.placeId
if (city && placeId) return navigate(`${city}/${placeId}`)
}

return !!suggestions.length && (
<div className={styles.optionBox}>
{
suggestions.map(suggestion => <button key={suggestion.placePrediction?.placeId} className={styles.option} onClick={() => handleSuggestionClick(suggestion)}>
<div className={styles.iconWrapper}>
<MapPinIcon weight="fill" size={20} />
</div>
<span className={styles.suggestionText}>
{suggestion.placePrediction?.text.text}
</span>
</button>
)
}
</div >
)
}
```
useAutocompleteSuggestions hook
```import { useEffect, useRef, useState } from 'react';
import { useMapsLibrary } from '@vis.gl/react-google-maps';

export type UseAutocompleteSuggestionsReturn = {
suggestions: google.maps.places.AutocompleteSuggestion[];
isLoading: boolean;
resetSession: () => void;
};

/**
* A reusable hook that retrieves autocomplete suggestions from the Google Places API.
* The data is loaded from the new Autocomplete Data API.
* (https://developers.google.com/maps/documentation/javascript/place-autocomplete-data)
*
* @param inputString The input string for which to fetch autocomplete suggestions.
* @param requestOptions Additional options for the autocomplete request
* (See {@link https://developers.google.com/maps/documentation/javascript/reference/autocomplete-data#AutocompleteRequest}).
*
* @returns An object containing the suggestions, the current loading-status,
* and a function to reset the session.
*
* @example
* ```jsx
* const MyComponent = () => {
* const [input, setInput] = useState('');
* const { suggestions, isLoading, resetSession } = useAutocompleteSuggestions(input, {
* includedPrimaryTypes: ['restaurant']
* });
*
* return (
* <>
* <input value={input} onChange={(e) => setInput(e.target.value)} />
* <ul>
* {suggestions.map(({placePrediction}) => (
* <li key={placePrediction.placeId}>{placePrediction.text.text}</li>
* ))}
* </ul>
* </>
* );
* }
* ```
*/
export function useAutocompleteSuggestions(
inputString: string,
requestOptions: Partial<google.maps.places.AutocompleteRequest> = {}
): UseAutocompleteSuggestionsReturn {
const placesLib = useMapsLibrary('places');

// stores the current sessionToken
const sessionTokenRef =
useRef<google.maps.places.AutocompleteSessionToken | null>(null);

// the suggestions based on the specified input
const [suggestions, setSuggestions] = useState<
google.maps.places.AutocompleteSuggestion[]
>([]);

// indicates if there is currently an incomplete request to the places API
const [isLoading, setIsLoading] = useState(false);

// once the PlacesLibrary is loaded and whenever the input changes, a query
// is sent to the Autocomplete Data API.
useEffect(() => {
if (!placesLib) return;

const { AutocompleteSessionToken, AutocompleteSuggestion } = placesLib;

// Create a new session if one doesn't already exist. This has to be reset
// after `fetchFields` for one of the returned places is called by calling
// the `resetSession` function returned from this hook.
if (!sessionTokenRef.current) {
sessionTokenRef.current = new AutocompleteSessionToken();
}

const request: google.maps.places.AutocompleteRequest = {
...requestOptions,
input: inputString,
sessionToken: sessionTokenRef.current
};

if (inputString === '') {
if (suggestions.length > 0) setSuggestions([]);
return;
}

setIsLoading(true);
AutocompleteSuggestion.fetchAutocompleteSuggestions(request).then(res => {
setSuggestions(res.suggestions);
setIsLoading(false);
});
}, [placesLib, inputString]);

return {
suggestions,
isLoading,
resetSession: () => {
sessionTokenRef.current = null;
setSuggestions([]);
}
};
}
```
2. My main questions about pricing
1. How exactly do session tokens work?
Google documentation mentions session-based pricing, and I'm having trouble understanding what this means in practice.

Does one session token group multiple autocomplete requests into a single billing session?
Are autocomplete requests within the same session free, discounted, or individually billed?
Does the session only become complete when the user selects a prediction and requests Place Details?
What happens if the user types something but never selects a suggestion?
2. Does debouncing actually reduce API costs?
For example, suppose a user types TOKYO.

With a 500ms debounce, will Google receive:

One request after the user finishes typing?
Multiple requests if the user pauses between keystrokes for more than 500ms?
If the user types slowly, could this result in five separate autocomplete requests within the same session?

  1. What does Google mean by unlimited autocomplete requests?
    I've seen references to unlimited requests in the Google Maps pricing documentation.

Does this mean unlimited requests within a session, or does it mean something else?

I'm particularly confused about how this relates to actual billing.

  1. Is my implementation correct?
    I'd also appreciate feedback on whether my current implementation follows Google's recommended session-token lifecycle.

Some things I'm unsure about:

Am I creating and reusing session tokens correctly?
Is resetting the token after fetchFields() the correct approach?
Should I create a new token when the user clears the search input?
Should I reset the session when the user abandons a search without selecting anything?
Is there a better way to prevent unnecessary API requests?
4. Is Google Places Autocomplete expensive?
I'm building an application that will use city autocomplete as an important feature.

I'm concerned about the cost when the application has hundreds or thousands of users searching for cities.

During development alone, I seem to have hit my API usage limit within two days.

Is this normal, or does it indicate that something is wrong with my implementation?

Would you recommend any alternatives or optimization strategies for reducing Google Places API costs?

  1. My goal
    I want to implement Google Places Autocomplete properly while keeping API costs predictable.

I'm not looking for ways to bypass Google's billing. I just want to understand the pricing model, session tokens, and best practices

Any explanations, implementation suggestions, or real-world experiences would be greatly appreciated!

Thanks in advance!

submitted by /u/the_good_dev to r/reactjs
[link] [comments]
  •  

I built a hand-drawn React UI kit. The hard part was making "random" wobble SSR-safe.

PlopUI is a React component library where everything looks sketched: wobbly borders, tilted cards, sticker shadows. Some things that were more interesting to build than they look:

- No Math.random() in render. Every tilt, wobble and jitter comes from a seed function, so the server and the client draw the exact same crooked line and there are no hydration mismatches.

- Native elements first. Dialog is a real <dialog> with showModal(), the upcoming Accordion is <details>/<summary>. The wobble is decoration on top, not a replacement for the element.

- Interactive components are "use client". Static ones stay Server Components, so you don't pay for JS on a card that just sits there.

- Every component has a Vitest test for its keyboard behavior and a story for every state.

- The library never imports from next/*. It's plain React; Next.js only lives in the docs and demo apps.

npm i @plop-ui/react @plop-ui/fonts

Docs: https://plopui.com

Playground: https://plopui.com/playground

Code: https://github.com/Snorlark/plop-ui

I'd really like feedback on the API. If a prop name or pattern feels wrong, tell me now before 1.0 makes it permanent.

submitted by /u/Repulsive-Beach4801 to r/reactjs
[link] [comments]
  •  

I kept rebuilding the same "agent is thinking" UI, so I turned it into a small React + Tailwind library

Every AI app I worked on needed the same small pieces: a thinking indicator, a task checklist

that shows progress, a voice orb, a card that shows what a tool call is doing.

I kept rewriting them, so I collected them into one library.

What I focused on:

- Plain React + Tailwind. The code is copied into your project, no runtime package.

- Calm motion, and everything respects prefers-reduced-motion.

- Each component has a props playground so you can tweak it before copying.

The newest one is a tool-call card: the arguments stream in, a status pill runs,

then the result slides in. It takes a Vercel AI SDK tool part directly.

Still early, so I'd really like feedback: which part of an agent UI do you find hardest to get right?

(Link in the comments.)

submitted by /u/Slight_Care2031 to r/reactjs
[link] [comments]
  •  

X3Bun a Hono/TanStack Start/tRPC/Better-Auth Turborepo Starter App

X3Bun a Hono/TanStack Start/tRPC/Better-Auth Turborepo Starter App

This is simply a starter template that I use and I thought of sharing

  • Bun optimized
  • Front End: Tanstack Start, Tanstack Query, and Shadcn
  • Backend: Hono with tRPC
  • Authentication: Better-Auth
  • Repo Management: Turborepo

I encourage you to check and share your opinion

submitted by /u/youngsargon to r/reactjs
[link] [comments]
  •  
❌