Autocomplete through form - reactjs

I was going to build autocomplete using remix.run, but then it occurred to me that would be relying too much on routing and forms to select/focus the input after each submit, this can not produce good UX. The user will input something into form, the form gets submitted, he awaits response, and then the input is focused again, and the ux here that this is instantsearch/autocomplete.

What you need is the useFetcher hook Remix exports. This hook let you fetch data from the loader of any route without causing a navigation, it was added for this kind of UIs.
import { Form, useFetcher } from "remix"
export default function Screen() {
let fetcher = useFetcher()
function handleChange(event) {
let value = event.currentTarget.value
// load data from a route with a loader
fetcher.load(`/api/autocomplete?query=${value}`)
}
return (
<Form>
<input type="text" onChange={handleChange} list="suggestions" />
<datalist id="suggestions">
{fetcher.data.map(item => {
return <option key={item.id} value={item.value} />
})}
</datalist>
</Form>
)
}
Something like that, and in the endpoint you load with the fetcher you export a loader
export async function loader({ request }) {
let url = new URL(request.url)
let query = url.searchParams.get("query") ?? "";
let data = await getData(query)
return json(data)
}

Related

Modify Axios request with inputs from React Modal

I'm creating a React web app which would show a modal on every POST request asking users to add a approval token. This approval token would be added to the request header, before sending the request to the server.
I'm using axios to make a request as follows:
axios({
url: '/backend/call',
method: 'POST'
})
I'm intercepting the request through axios interceptors, to show the modal here. The modal has a text box and a submit button.
axios.interceptors.request.use(function (config) {
if (config.method === 'POST') {
ApprovalPopup.useApprovalPopup.showPopup(); // This displays the modal which has text field and Submit button
const token = --------; // How do I get the token from the Modal here, after submit is clicked.
config.headers.approvalToken = token;
}
return config;
});
Modal code for reference:
export type IApprovalPopup = {
showPopup: () => void;
};
const ApprovalPopupContext = createContext<IApprovalPopup>({
showPopup: () => undefined,
});
export default function ApprovalPopupProvider({children}: { children: ReactNode }) {
const dialogRef = useRef<HTMLDialogElement>(null);
const [ticketID, setTickedID] = useState('');
function handleSubmit() {
// update state and make backend call
}
function showPopup() {
dialogRef.current?.show()
}
return (
<ApprovalPopupContext.Provider
value={{
showPopup
}}
>
<span slot="body">
<div className="ticket-entry">
<Label> Ticket ID</Label>
<TextField onTextFieldChange={(event) => handleTicketIDEntry(event.detail.value)}
label="Ticket-ID"
value={ticketID}>
</TextField>
</div>
<div className="submit-section">
<div className="button">
<Button className="button" onClick={handleSubmit}>
Submit
</Button>
</div>
</div>
</span>
{children}
</ApprovalPopupContext.Provider>
);
}
ApprovalPopupProvider.useApprovalPopup = () => useContext(ApprovalPopupContext);
How do I get the token from the Modal and append it in the header before making the call to the server?
I could technically let the first call fail, and pass the config to the Modal as well. The Modal would then attach the header to the config and make the required call. But in that case how would I make sure the response goes back to the actual calling component. The calling component is updating local state objects / handling custom errors etc, so it needs the promise to be resolved
I think for you case it would be better to set axios header in handleSubmit function
simply add this :
function handleSubmit() {
axios.defaults.headers.common['approvalToken'] = token;
// update state and make backend call
}

Why when using React useState is the payload data clearing on a onsubmit event? This is a POST request to the server side

Background: I am trying to get my frontend code to connect with my backend code.
Steps Taken:
--set a breakpoint for on click event for the submit button: this returns with the event being fired however no Promise function ran and no response in the console.
--tested sending data on Postman to backend and get status 200 and works (backend is set correctly and is hosted on heroku)
--cleaned up POST request and set it as a promise and stored in Const Variable
Steps the code is taking:
First taking in string in each input field (name, email, question).
Using react usestate hook to use the current state and set the event.target.value as those values.
Once that is stored, then it gets stored in a variable called custData
One submit button is clicked it calls on the Variable that stores the Promise function for POST request using Axios
(This is where I believe something else is occurring)
*the POST is set to the correct backend URL with the values of custData which should be a string. Then it should return with response.data in the console and does not.
Below is my frontend react code for this component:
import React from 'react';
import {useState} from 'react';
import Axios from 'axios'
//import { response } from 'express';
const QuoteForm = () => {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [question, setQuestion] = useState("");
//This is the promise version
const custData =
{
"name" :name ,
"email" :email ,
"question":question
} ;
const submitPromise= () => {
console.log(custData);
Axios.post('https://hookahsite-backend.herokuapp.com || https://localhost:8000 ' , custData)
.then( (axiosResponse)=> {
// here you can use the data
console.log(custData);
const submitQuestions = axiosResponse.data;
console.log(submitQuestions);
})
.catch((e)=> {console.log(e)})
}
//this uses try catch however the backend is not getting hit with any data
//tested this same request in Postman and it works
/*
function submitQuestion() {
try {
Axios.post('https://hookahsite-backend.herokuapp.com ' ,
{
name:name ,
email:email ,
question:question
},
)
}
catch (err) {console.error(err);}
}
*/
return(
<React.Fragment>
<form id="quoteForm"
>
<h1 id="quoteTitle">Quote Help Form</h1>
<p id="quotePar">Please provide your Name, Contact Email, and what products you would like more information about in this form :</p>
<label id="formName" className="Form">
Name:
<input type="text" name="name"
onChange={(event) => { setName(event.target.value);}}
/>
</label>
<label id="formEmail" className="Form">
Email:
<input type="text" name="email"
onChange={(event) => { setEmail(event.target.value);
}}/>
</label>
<br/>
<label id="formQuestion" className="Form" >
What products would you like to know more about:
<input type="text" name="help"
onChange={(event) => { setQuestion(event.target.value);
}}/>
</label>
<br/>
<br/>
<button id="quoteSubmit" type="submit"
onClick =
{
submitPromise
}
/*
old way
{()=>
submitQuestion()
}
*/
>Submit </button>
</form>
›
</React.Fragment>
)
};
export default QuoteForm;
(When I set the breakpoints this is a screen shot showing the promise is there and there seems to be an issue with data being sent as possible text and not json format)
**Any Further Help on this topic would greatly be appreciated. **
The problem I believe lies in your Axios post call.
Axios is used like this:
Axios.post('ENDPOINT_URL', BODY)
Here ENDPOINT_URL is the URL of your api endpoint that you want to send a post request to, however you are saying to axios that the endpoint url is:
'https://hookahsite-backend.herokuapp.com || https://localhost:8000'
As far as I am concerned there is no logic in axios so it is trying to hit that string literally with a post request.
You should move your OR logic to other place of the application.
For example using env variables to know that you are running locally or on heroku you could do something like:
let url;
if (process.env.SERVER_LOCATION === "LOCAL") {
url = "https://localhost:8000";
} else{
url = "https://hookahsite-backend.herokuapp.com";
}
axios.post(url, custData).then( // etc

Paypal button cannot read new React state. How to work with dynamic values and paypal in React?

I'm currently working on the checkout page of an application where a user can purchase up to three items at one of three prices chosen by the user (this is mostly being done as an experiment). When the user chooses a price by clicking a button this triggers a setState and a new price is stored to the state. When doing console.log I see the new state has been set, but upon checkout it appears the state resets to its initial value. I can't tell why and have no idea where to begin on this one. I imagine on initial render paypal is keeping the initial state it was passed and needs to be rerendered when the new state is set, but not sure how to go about this or even if this is the problem. Any help or guidance is appreciated.
I'm using the #paypal/react-paypal-js library for this paypal implementation, but am welcome to alternative suggestions.
Here is the code I'm using but cut down relevant sections:
import React, {useState, useRef, useEffect} from 'react';
import { PayPalButtons, usePayPalScriptReducer } from "#paypal/react-paypal-js";
import PriceButton from './PriceButton.jsx';
import NumberItemButton from './NumberItemButton';
import {priceOptions, amountItems} from './PriceOptions';
const PaymentPage = () => {
const [isLoading, setIsLoading] = useState(false);
const [payAmount, setPayAmount] = useState('5.00');
const [itemAmount, setItemAmount] = useState('1');
const payPalOptions = { //Note: This is used in the higher level component PayPalScriptProvider
"client-id": `${process.env.REACT_APP_PAYPAL_CLIENT_ID}`,
currency: "USD",
intent: "capture",
};
const createOrder = (data, actions) => { //This will show the initial state when triggered
return actions.order.create({
purchase_units : [
{
amount: {
value: payAmount //This stays at the initial State of '5.00' despite the newState being set
}
}
]
})
};
const onApprove = (data, actions) => {
return actions.order.capture().then(function(orderData) {
console.log('Capture result', orderData, JSON.stringify(orderData, null, 2));
let transaction = orderData.purchase_units[0].payments.captures[0];
alert('Transaction '+ transaction.status + ': ' + transaction.id + '\n\nSee console for all available details');
}
)};
const onError = (error) => {
console.log(error)
}
console.log(payAmount) //Note: This will show the new State
return (
<div>
<h1>Purchase</h1>
<label> Choose number of items
<div>
{amountItems.map((item, index) => {
return <NumberItemButton key={index} setItemAmount={setItemAmount} amount={item.amount} />
})}
</div>
</label>
<label> Pick a price
<div>
{priceOptions.map((item, index) => {
return <PriceButton key={index} itemAmount={itemAmount} setPayAmount={setPayAmount} price={item.price} />
})}
</div>
</label>
<PayPalButtons
createOrder={(data, actions) => createOrder(data, actions)}
onApprove={(data, actions) => onApprove(data, actions)}
onError={onError}
/>
</div>
);
}
export default PaymentPage;
I'll also add the price button component incase the issue is there
const PriceButton = ({itemAmount, setPayAmount, price}) => { //itemAmount is the amount customer buys, price is the value passed through on the mapping function
const multPrice = (itemAmount * price).toFixed(2);
const withTaxPrice = (parseInt(multPrice) + .5).toFixed(2).toString();
return (
<button onClick={() => setPayAmount(withTaxPrice)}>${multPrice}</button>
)
}
export default PriceButton;
Appreciate any help!
I came back to this with a fresh pair of eyes and found the solution (though I'm not sure if it's the best one).
The issue is when the Paypal button renders it pulls in the initial state that is passed through, but it needs to be rerendered when a new state is passed.
My solution to this was to pass a forceReRender={[payAmount]} within the PaypalButtons component. This rerenders the Paypal button upon update to the price state and allows me to pass an updated value.
Hope this helps others!
I found a better solution. Just use useRef and access the ref.current value!

How to pass value via `props` in export function in react-hooks

How to use props in export function in react-hooks. I would like to export a function to feed onSubmit() in Profile.js.
I would like to get the companyName value from CompanyDetails and passed to Profile and use in onSubmit() via props. I am getting 'companyName' undefined error now.
import CompanyDetails from "./CompanyDetails";
const Profile = () =>{
const onSubmit = (companyName) =>{
const profileData = async () => {
try {
const res = await axios.put('http://localhost:8000/service/company', companyName);
if (res.data.success) {
// push do the rest of code after save here...
}
else {
console.log(res.data.message);
}
} catch (e) {
console.log(e.response.data.message);
}
}
//return () => { profileData() }
profileData();
}
return (
<div className="profile_wrapper">
<div className="wrap">
<h1>Details:</h1>
</div>
<CompanyDetails
onSubmit={onSubmit}
/>
</div>
);
}
export default Profile;
export default function CompanyDetails ({onSubmit}) {
const [companyName, setCompanyName] = useState('');
const handleChange = (e) => {
e.persist();
const {value} = e.target.value
setCompanyName(value);
}
return (
<div className="container">
<Select defaultValue={'DEFAULT'} name="company" onChange={e =>handleChange(e)} id="select">
<option value="DEFAULT" disabled>Choose an option</option>
<option value="company1">Company 1</option>
<option value="company2">Company 2</option>
<Select>
<Button onClick={onSubmit(companyName)} color="primary">
Save
</Button>
</div>
);
}
Ok, so I finally got it working. Although I had to basically rewrite everything from scratch to try and catch all the weird little bugs that would come up. But I believe the crux of the problem was making several improper function calls. You can take a look at the changes and see where I did things differently.
Here is a Codesandbox with the working code.
I had to mock making an axios request, but since the mock returns a promise just like an axios request would, you should be able to replace it with a working API on your end.
If things still aren't working, then leave a public API endpoint in the comments and I will test everything without the mocks.
Also note that sometimes I encountered an error with importing the material-ui components. But this seems to be an issue with Codesandbox and not with the components themselves.
at your CompanyDetails you need a handler for onClick like:
const onClickHandler = () => onSubmit(companyName)
<Button onClick={onClickHandler} color="primary">Save</Button>
or pass as an arrow function:
<Button onClick={) => onSubmit(companyName)} color="primary">Save</Button>
there is also issues regarding your onSubmit function at Profile component. when you call onSubmit you return another function return () => { profileData() }, when you should have called return profileData(). though you could just mark your onSubmit function as async and avoid declaring other function inside it imho.

React redirect after login not working

I am trying using react-router and redux to build my react app. However, I am unable to get the url to route back to the dashboard post login. Can somebody please point out my mistakes?
const form = reduxForm({
form: 'login'
});
class Login extends Component {
handleFormSubmit(formProps) {
this.props.loginUser(formProps);
var token = cookie.load('token');
if(token !== undefined){
const location = this.props.location;
if (location.state && location.state.nextPathname) {
browserHistory.push(location.state.nextPathname)
} else {
browserHistory.push('/')
}
}
}
renderAlert() {
if(this.props.errorMessage) {
return (
<div>
<span><strong>Error!</strong> Authentication error</span>
</div>
);
}
}
render() {
const { handleSubmit } = this.props;
return (
<div>
<form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
{this.renderAlert()}
<div>
<label>Username</label>
<Field name="username" className="form-control" component="input" type="text" />
</div>
<div>
<label>Password</label>
<Field name="password" className="form-control" component="input" type="password" />
</div>
<button type="submit" className="btn btn-primary submitButton">Login</button>
</form>
</div>
);
}
}
function mapStateToProps(state) {
return {
errorMessage: state.auth.error,
message: state.auth.message
};
}
const mapDispatchToProps = dispatch => ({
loginUser: () =>
dispatch(loginUser);
});
export default connect(mapStateToProps, mapDispatchToProps)(form(Login));
My loginUser function is as below:
export function loginUser({ username, password }) {
return function(dispatch){
axios.post(`${AUTH_URL}/obtain-auth-token/`, { username, password}, {
headers: {
"X-CSRFToken": cookie.load('csrftoken')
}
})
.then(response => {
if('token' in response.data){
cookie.save('token', response.data.token, {path: '/'});
dispatch({type: AUTH_USER});
} else{
console.log("Error condiction: " + response);
errorHandler(dispatch, error.response, AUTH_ERROR);
}
})
.catch((error) => {
errorHandler(dispatch, error.response, AUTH_ERROR);
});
}
}
This is my first react-redux project so the mistake might be pretty elementary. Would really appreciate your help!
The root of your issue appears to be with handling your async call - in your handleFormSubmit(formProps) function you have the following two lines:
this.props.loginUser(formProps);
var token = cookie.load('token');
You are dispatching your action that will be running your async function (loginUser(formProps) does a post using axios), and then you immediately try to consume the results of this async function by loading the token that it should have stored in a cookie upon success. This does not work because immediately upon running an async function, JavaScript will not wait for the results but instead will return to your handleFormSubmit function and run the rest of it through completion. I am sure that if you console.log'd your token it will be undefined (assuming there were no cookies before running the app) - the function continued without waiting for your async function.
So I know two good options that you can use to fix this:
Simple, standard solution: You run dispatch({type: AUTH_USER}); upon the success of your async post - have this action creator result in a change in your state held by redux (ex: loginFlag). Include loginFlag as a prop in your Login component (include it in your mapStateToProps function). Finally, include a componentWillReceiveProps(nextProps) lifecycle function to your Login component and have it handle the route change. Include something like:
componetWillReceiveProps(nextProps) {
if (nextProps.loginFlag) {
var token = cookie.load('token');
if(token !== undefined){
const location = this.props.location;
if (location.state && location.state.nextPathname) {
browserHistory.push(location.state.nextPathname)
} else {
browserHistory.push('/')
}
}
}
}
Or:
Heavier solution using another package: You can use the npm package react-router-redux (https://github.com/reactjs/react-router-redux), to do a push to browserHistory within your redux action at the end of your async call - doing so will require you to setup its middleware and hook it up to your redux store. More info: https://github.com/reactjs/react-router-redux#api
I would definitely recommend looking into react-router-redux. If you do not want the browserHistory functions available in your redux actions, then it is very simple to setup in your project (don't need to make changes to the store or add its middleware) and it allows you to track your url path within your redux state which can be very helpful.

Resources