unable to set react-select-async-paginate initial value - reactjs

I am using a react-select-async-paginate. It's working on form submit. I am facing problem in edit form. When I click on edit button, I am able to show all the values in their tags except the select. I have made a few attempts to show the selected value but no luck.
export function UserDropdown(props) {
const [value, setValue] = useState(null);
const defaultAdditional = {
page: 1
};
const onChange = (e) => {
props.getUserEmail(e)
setValue(e)
}
<AsyncPaginate
additional={defaultAdditional}
defaultValue={props.value}
loadOptions={loadPageOptions}
onChange={ (e) => onChange(e)}
/>
}
import UserDropdown from './userDropdown'
export class User extends Component {
constructor(props) {
super(props);
this.state = {
userEmail: ''
}
}
onUserChange(e) {
this.setState({userEmail: e.value})
}
<UserDropdown getUserEmail = { (email) => this.onUserChange(email)} value={this.state.userEmail} />
}
in loadPageOptions I am using the API's response.

If you want to give a controlled select a default value you need to make sure that value initially is the default value. You can only use defaultValue if the select is uncontrolled meaning that it manages state of value internally:
const OPTIONS = [
{
value: 1,
label: "Audi"
},
{
value: 2,
label: "Mercedes"
},
{
value: 3,
label: "BMW"
}
];
const fakeData = async () => {
await new Promise(resolve => setTimeout(resolve, 1000));
return {
results: OPTIONS,
has_more: false
};
};
const loadOptions = async () => {
const response = await fakeData();
return {
options: response.results,
hasMore: response.has_more
};
};
export default function App() {
// this has to be initialized with the default value
const [value, setValue] = useState({ value: 1, label: "Audi" });
return (
<div className="App">
<AsyncPaginate
value={value}
loadOptions={loadOptions}
onChange={setValue}
/>
</div>
);
}

Related

React & Ant Design - Submit form before switching tabs

I have an ant design tab group with an ant design form in each tab. I would like that upon switching tabs, the user is prompted to submit their changes. If the user selects yes, then the data should be submitted. We switch tabs only if the response comes back as a success, or the user opted not to save.
The forms are all child components, which means the parent needs to somehow indicate that the tab is switching, and tell the child to submit their form.
I can achieve this with the useImperativeHandle hook and forwardRef but I'm wondering if there is some non-imperative way to achieve this?
Here is a stripped down example, not checking if the form is dirty, and just using the native confirm popup. There is also an async function to simulate submitting the form, which will randomly succeed or error.
Stackblitz: https://stackblitz.com/edit/react-ts-zw2cgi?file=my-form.tsx
The forms:
export type FormRef = { submit: (data?: Data) => Promise<boolean> };
export type Data = { someField: string };
const MyForm = (props: {}, ref: Ref<FormRef>) => {
const [form] = useForm<Data>();
useImperativeHandle(ref, () => ({ submit }));
async function submit(data?: Data): Promise<boolean> {
if (!data) data = form.getFieldsValue();
const res = await submitData(data);
return res;
}
return (
<Form form={form} onFinish={submit}>
<Form.Item name="someField">
<Input />
</Form.Item>
<Form.Item>
<Button htmlType="submit">Submit</Button>
</Form.Item>
</Form>
);
};
export default forwardRef(MyForm);
The parent component with the tabs:
const App: FC = () => {
const [activeKey, setActiveKey] = useState('tabOne');
const formOneRef = useRef<FormRef>();
const formTwoRef = useRef<FormRef>();
async function onChange(key: string) {
if (confirm('Save Changes?')) {
if (activeKey === 'tabOne' && (await formOneRef.current.submit()))
setActiveKey(key);
if (activeKey === 'tabTwo' && (await formTwoRef.current.submit()))
setActiveKey(key);
} else setActiveKey(key);
}
const tabs = [
{
label: 'Tab One',
key: 'tabOne',
children: <MyForm ref={formOneRef} />,
},
{
label: 'Tab Two',
key: 'tabTwo',
children: <MyForm ref={formTwoRef} />,
},
];
return <Tabs items={tabs} onChange={onChange} activeKey={activeKey} />;
};
The submit function
export default async function submitData(data: Data) {
console.log('Submitting...', data);
const res = await new Promise<boolean>((resolve) =>
setTimeout(
() => (Math.random() < 0.5 ? resolve(true) : resolve(false)),
1000
)
);
if (res) {
console.log('Success!', data);
return true;
}
if (!res) {
console.error('Fake Error', data);
return false;
}
}
Ant Design Tabs: https://ant.design/components/tabs/
Ant Design Form: https://ant.design/components/form/
I ended up making a state variable to store the submit function, and setting it in the child with useEffect.
A few caveats:
Had to set destroyInactiveTabPane to ensure forms were unmounted and remounted when navigating, so useEffect was called.
Had to wrap the form's submit function in useCallback as it was now a dependency of useEffect.
Had to make sure when calling setSubmitForm I had passed a function that returns the function, else the dispatch just calls submit immediately.
Stackblitz: https://stackblitz.com/edit/react-ts-n8y3kh?file=App.tsx
export type Data = { someField: string };
type Props = {
setSubmitForm: Dispatch<SetStateAction<() => Promise<boolean>>>;
};
const MyForm: FC<Props> = ({ setSubmitForm }) => {
const [form] = useForm<Data>();
const submit = useCallback(
async (data?: Data): Promise<boolean> => {
if (!data) data = form.getFieldsValue();
const res = await submitData(data);
return res;
},
[form]
);
useEffect(() => {
setSubmitForm(() => submit);
}, [setSubmitForm, submit]);
return (
<Form form={form} onFinish={submit}>
<Form.Item name="someField">
<Input />
</Form.Item>
<Form.Item>
<Button htmlType="submit">Submit</Button>
</Form.Item>
</Form>
);
};
const App: FC = () => {
const [activeKey, setActiveKey] = useState('tabOne');
const [submitForm, setSubmitForm] = useState<() => Promise<boolean>>(
async () => true
);
async function onChange(key: string) {
if (confirm('Save Changes?')) {
if (await submitForm()) setActiveKey(key);
} else setActiveKey(key);
}
const tabs = [
{
label: 'Tab One',
key: 'tabOne',
children: <MyForm setSubmitForm={setSubmitForm} />,
},
{
label: 'Tab Two',
key: 'tabTwo',
children: <MyForm setSubmitForm={setSubmitForm} />,
},
];
return (
<Tabs
items={tabs}
onChange={onChange}
activeKey={activeKey}
destroyInactiveTabPane
/>
);
};

I am trying to do a grocery basket application where I want to show an alert and want it to disappear after 2 seconds

If you see below, there is a component named TimeOut() in which I am using the useEffect() to set the timer for the alert.
And then I'm calling <TimeOut/> inside the handleSubmit() function inside if(!name). I get an error where I call <TimeOut/> "expected an assignment or function call and instead saw an expression".
import React, { useState, useEffect } from 'react'
import List from './List'
import Alert from './Alert'
function App() {
const [name, setName]= useState('');
const [list, setList] = useState([]);
const [isEditing, setIsEditing] = useState(false);
const [editID, setEditID] = useState(null);
const [alert, setAlert ] = useState({show: false, msg:'', type:''});
const TimeOut =()=>{
useEffect(() => {
setInterval(() => {
// type in message here
setAlert({show:true, msg:'testing this will be displayed with name', type:''})
}, 2000);
}, []);
return <div></div>;
}
const handleSubmit = (e) => {
e.preventDefault();
if (!name) {
// console.log('testing');
//setAlert({show:true, msg:'Value cannot be empty u idiot', type:''});
<TimeOut/>
} else if (name && isEditing) {
} else {
const newItem = { id: new Date().getTime().toString(), title: name };
setList([...list, newItem]);
setName('');
}
};
const forOnChange = (e) =>{
setName(e.target.value);
}
const showAlert = (show= false, type= '', msg= '')=>{
setAlert({show:show, msg:msg, type:type})
}
const removeAllItems = ()=>{
setAlert({show: true, msg: 'List is empty now', type:''});
setList([]);
}
const removeSpecificItem = (id)=>{
const newList = list.filter((item) => item.id !== id);
setList(newList);
}
return (
<section className="section-center">
<form action="" onSubmit={handleSubmit}>
{alert.show ? <Alert alert={alert} removeAlert={showAlert}/> : null}
<h4>Grocery basket</h4>
<input type="text" placeholder="chickets etc" onChange={(e) => setName(e.target.value)}/>
<button value={name} >{isEditing ? 'Edit' : 'Submit'}</button>
<button className="clear-btn" onClick={removeAllItems}>Clear all items</button>
</form>
{list.length > 0 ? <div className="grocery-container">
<List items={list} removeSpecificItem={removeSpecificItem}/>
</div> : null}
</section>
);
}
export default App
Error
Line 31:7: Expected an assignment or function call and instead saw an expression no-unused-expressions
Line 31:8: 'Timeout' is not defined react/jsx-no-undef
The error is at
try this
const handleSubmit = (e) => {
e.preventDefault();
if (!name) {
// console.log('testing');
// <TimeOut/> remove this, you cant use react components here
setAlert({show:true, msg:'Value cannot be empty u idiot', type:''});
setTimeout(function () {
setAlert({show:false, msg:'Value cannot be empty u idiot', type:''});
}, 2000)
} else if (name && isEditing) {
} else {
const newItem = { id: new Date().getTime().toString(), title: name };
setList([...list, newItem]);
setName('');
}
};

Cant pass array of objects to setPeople function in React

I have another file of data.js that i'm calling it in my index.js. I know I will get data if I pass it in useState directly like this below.
const [people, setPeople] = useState(data);
But I want to keep the above state null at the start rather than passing data.
const [people, setPeople] = useState([]);
But rather than accessing data this way I want to directly pass it in my setPeople. Is it possible to do that? Something like this. So that it can be accessed in people and can be iterated.
setPeople([...people,{data}])
Index.js
import React, { useState, useReducer } from 'react';
import Modal from './Modal';
import { data } from '../../../data';
// reducer function
const Index = () => {
const [name,setName]= useState('');
const [modal, showModal] = useState(false);
const [people, setPeople] = useState([]);
const handleSubmit = ((e)=>
{
e.preventDefault();
setPeople([...people,{data}])
})
return (
<div>
<form onSubmit={handleSubmit}>
<input type='text' value={name} onChange={((e)=>setName(e.target.value))} />
<button type='submit'>Add</button>
</form>
{
people.map((person,index)=>{
return(
<div key={index}>
<h2>{person.name}</h2>
</div>
)
})
}
</div>
)
};
export default Index;
data.js
export const data = [
{ id: 1, name: 'john' },
{ id: 2, name: 'peter' },
{ id: 3, name: 'susan' },
{ id: 4, name: 'anna' },
];
You can pass this additional data to setPeople in the same way which you pass current people - use spread syntax.
setPeople([ ...people, ...data ])
Also if you want to depend on current people state you should pass callback which always provide you correct value which sometimes can't not happen if you depend on people.
setPeople(( prevPeople ) => [ ...prevPeople, ...data ])
UPDATE
const Index = () => {
const [name,setName]= useState('');
const [modal, showModal] = useState(false);
const [people, setPeople] = useState([]);
useEffect( () => {
setState(data) // this will be added only once when component mounted
}, [])
const handleSubmit = ((e)=>{
e.preventDefault();
setPeople(( prevPeople ) => [ ...prevPeople, { id: ?, name: "Only one new person" } ])
})
return (
<div>
{ /* your jsx */ }
</div>
)
};
UPDATE 2
const handleSubmit = () => {
const peoplesIds = people.map( p => p.id )
const notIncludedData = data.filter( obj => ! peoplesIds.includes( obj.id ) )
setPeople( prev => [ ...prev, ...notIncludedData, { id: 100, name: "new guy" } ] )
}
You can try this at handleSubmit
setPeople([...people,...data])

how to add input field dynamically when user click on button in react.js

I have two Question ->
First one is I want to add user input field dynamically when user click "+" button in react.js. And if user click more times then more number of field add to the form. How to do this in react.js?
Second one when user change its value I want to store their corresponding value of each input field into my component state variable. Now here how I add their value, what data structure I use array or object or something else ? and how ?
function App() {
const inputArr = [
{
type: "text",
id: 1,
value: ""
}
];
const [arr, setArr] = useState(inputArr);
const addInput = () => {
setArr(s => {
return [
...s,
{
type: "text",
value: ""
}
];
});
};
const handleChange = e => {
e.preventDefault();
const index = e.target.id;
setArr(s => {
const newArr = s.slice();
newArr[index].value = e.target.value;
return newArr;
});
};
return (
<div>
<button onClick={addInput}>+</button>
{arr.map((item, i) => {
return (
<input
onChange={handleChange}
value={item.value}
id={i}
type={item.type}
size="40"
/>
);
})}
</div>
);
}
I would set a counter and increase it with each click of a button. I would map the counter numbers into an array and loop over creating new input elements.
import { useState } from "react";
import "./styles.css";
export default function App() {
const [counter, setCounter] = useState(0);
const handleClick = () => {
setCounter(counter + 1);
console.log(counter);
};
return (
<div className="App">
<button onClick={handleClick}>Hello</button>
{Array.from(Array(counter)).map((c, index) => {
return <input key={c} type="text"></input>;
})}
</div>
);
}
https://codesandbox.io/s/elastic-wave-36ous?fontsize=14&hidenavigation=1&theme=dark

Why is AsyncSelect only showing options once?

I'm trying to make use of <AsyncSelect /> to pull options from an API to populate a form.
It works find the first time I search for something, I can select the option and am then prompted to type again.
However when I type something else, I get No Options even though I can see the data show up in a console.log().
import React, { Component } from 'react';
import AsyncSelect from 'react-select/async';
type State = {
inputValue: string,
};
const promiseOptions = inputValue =>
new Promise(resolve => {
setTimeout(() => {
resolve(
fetch('https://restcountries.eu/rest/v2/all')
.then(r => r.json())
.then(rjson => {
return rjson.map(c => {
return { name: c.name, label: c.name}
})
})
);
}, 1000);
});
export default class AsyncMulti extends Component<*, State> {
state = { inputValue: '' };
handleInputChange = (newValue: string) => {
const inputValue = newValue.replace(/\W/g, '');
this.setState({ inputValue });
return inputValue;
};
render() {
return (
<AsyncSelect
isMulti
loadOptions={promiseOptions}
/>
);
}
}
I just realized that the object being returned in the Promise:
{ name: c.name, label: c.name }
had the wrong keys, and should have been:
{ value: c.name, label: c.name }
And it works now.

Resources