I am using react select async creatable,
the data from api loads correctly and can select it also can create new values
but after selecting a choice or if not, creating a new value, seems like my titleState becomes empty, I tried consoling the state but gives me blank/empty value on console
const [titleState,setTitleState] = useState('')
const loadOptions = (inputValue, callback) => {
axios.post(`sample/api`, {
data: inputValue
})
.then(res => {
callback(res.data.map(i => ({
label: i.title,
value: i.title,
})))
console.log(res)
})
.catch(err => console.log(err))
}
const handleInputChange = (newValue) => {
setTitleState(newValue)
}
const logState = () => {
console.log(titleState) // logs empty in the devtools
}
<AsyncCreatableSelect
menuPortalTarget={document.body}
styles={{ menuPortal: base => ({ ...base, zIndex: 9999 }) }}
loadOptions={loadOptions}
onInputChange={handleInputChange}
placeholder="Title Trainings"
/>
<Button onClick={logState}>Click</Button>
then I have some button when click will console the titleState, but it doesn't log it.
You should use onChange={handleInputChange} instead of onInputChange={handleInputChange}
onChange triggers when a new option is selected / created.
onInputChange triggers when writing a new option.
Related
How to pass data to another select after clicking on the first one. I have a select of countries, when clicking on them, regions are loaded into the second select loadOptions.
import dummy from './dummy';
import AsyncSelect from "react-select/async";
//This is country LoadOptions
const loadOptions = (inputValue, callback) => {
dummy.get(`country?CountrySearch[query]=${inputValue}`)
.then((response) => {
const options = []
response.data.data.forEach((permission) => {
options.push({
label: permission.name,
value: permission.id
})
})
callback(options);
})
}
Hooks with options Array(regions)
const [a, b] = useState([''])
let loadOptions2 = async (a) => {
return a
};
After onChange country select, get new data for regions
const handleCountry = (value) =>{
dummy.get(`region?filter[country_id]=${value.value}`)
.then((response) => {
const options = [];
response.data.data.forEach((permission) => {
options.push({
label: permission.name,
value: permission.country_id
})
})
b(options)
loadOptions2 = () => {
return options
}
console.log(a)
return options
})
}
Country Select
<AsyncSelect
cacheOptions
defaultOptions
onChange={handleCountry}
loadOptions={loadOptions}
/>
Region Select
<AsyncSelect
cacheOptions
defaultOptions
value={selectedCity}
onChange={handleCity}
loadOptions={loadOptions2}
/>
Console.log is show regions array, but loadOptions is empty
I'm trying to help my friend figure out why Autocomplete isn't showing anything.
Below is the code:
var names = [];
const schoolList = async () => ( await axios.get("http://localhost:5000/api/grabUnivNames/")
.then((res) => {
// schoolList = JSON.stringify(res.data.msg)
names = res.data.msg.map(user => user.school_name);;
console.log(names)
// return res.data.msg.map(user => user.school_name);
})
.catch((error) => {
console.log("ERROR");
console.log(error);
})
);
schoolList();
return() with Autocomplete:
<Autocomplete
options={names}
sx={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="School Name" />}
/>
What names contains:
What shows:
I only started learning about Autocomplete today but I think the problem may be in how he is obtaining names or how names is formatted but I am also very unfamiliar with Autocomplete.
How can I get names to display on the dropdown?
first of all i am assuming that your data fetching is done correctly and you use react functional based components.
You will need 2 main requirements to achieve what you want
first of all replace normal variable names with useState hook of
names array and loading boolean, cause normal variables will not have dynamic values over multiple renders
MUI Autocomplete supports async operation , so you will attach the getSchoolList handler to onOpen prop, and loading prop so let the component show progress while loading
const [names,setNames] = React.useState([])
const [loading, setLoading] = React.useState(false)
const getSchoolList = () => {
setLoading(true)
axios.get("http://localhost:5000/api/grabUnivNames/")
.then((res) => {
// schoolList = JSON.stringify(res.data.msg)
const namesArr = res.data.msg.map(user => user.school_name)
setNames(namesArr)
// return res.data.msg.map(user => user.school_name);
})
.catch((error) => {
console.log("ERROR");
console.log(error);
}).finally(() => setLoading(false))
}
<Autocomplete
options={names}
onOpen={() => getSchoolList()}
loading={loading}
sx={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="School Name" />}
/>
Isn't the hook useCallback supposed to return an updated function every time a dependency change?
I wrote this code sandbox trying to reduce the problem I'm facing in my real app to the minimum reproducible example.
import { useCallback, useState } from "react";
const fields = [
{
name: "first_name",
onSubmitTransformer: (x) => "",
defaultValue: ""
},
{
name: "last_name",
onSubmitTransformer: (x) => x.replace("0", ""),
defaultValue: ""
}
];
export default function App() {
const [instance, setInstance] = useState(
fields.reduce(
(acc, { name, defaultValue }) => ({ ...acc, [name]: defaultValue }),
{}
)
);
const onChange = (name, e) =>
setInstance((instance) => ({ ...instance, [name]: e.target.value }));
const validate = useCallback(() => {
Object.entries(instance).forEach(([k, v]) => {
if (v === "") {
console.log("error while validating", k, "value cannot be empty");
}
});
}, [instance]);
const onSubmit = useCallback(
(e) => {
e.preventDefault();
e.stopPropagation();
setInstance((instance) =>
fields.reduce(
(acc, { name, onSubmitTransformer }) => ({
...acc,
[name]: onSubmitTransformer(acc[name])
}),
instance
)
);
validate();
},
[validate]
);
return (
<div className="App">
<form onSubmit={onSubmit}>
{fields.map(({ name }) => (
<input
key={`field_${name}`}
placeholder={name}
value={instance[name]}
onChange={(e) => onChange(name, e)}
/>
))}
<button type="submit">Create object</button>
</form>
</div>
);
}
This is my code. Basically it renders a form based on fields. Fields is a list of objects containing characteristics of the field. Among characteristic there one called onSubmitTransformer that is applied when user submit the form. When user submit the form after tranforming values, a validation is performed. I wrapped validate inside a useCallback hook because it uses instance value that is changed right before by transform function.
To test the code sandbox example please type something is first_name input field and submit.
Expected behaviour would be to see in the console the error log statement for first_name as transformer is going to change it to ''.
Problem is validate seems to not update properly.
This seems like an issue with understanding how React lifecycle works. Calling setInstance will not update instance immediately, instead instance will be updated on the next render. Similarly, validate will not update until the next render. So within your onSubmit function, you trigger a rerender by calling setInstance, but then run validate using the value of instance at the beginning of this render (before the onSubmitTransformer functions have run).
A simple way to fix this is to refactor validate so that it accepts a value for instance instead of using the one from state directly. Then transform the values on instance outside of setInstance.
Here's an example:
function App() {
// setup
const validate = useCallback((instance) => {
// validate as usual
}, []);
const onSubmit = useCallback((e) => {
e.preventDefault();
e.stopPropagation();
const transformedInstance = fields.reduce((acc, {name, onSubmitTransformer}) => ({
...acc,
[name]: onSubmitTransformer(acc[name]),
}), instance);
setInstance(transformedInstance);
validate(transformedInstance);
}, [instance, validate]);
// rest of component
}
Now the only worry might be using a stale version of instance (which could happen if instance is updated and onSubmit is called in the same render). If you're concerned about this, you could add a ref value for instance and use that for submission and validation. This way would be a bit closer to your current code.
Here's an alternate example using that approach:
function App() {
const [instance, setInstance] = useState(/* ... */);
const instanceRef = useRef(instance);
useEffect(() => {
instanceRef.current = instance;
}, [instance]);
const validate = useCallback(() => {
Object.entries(instanceRef.current).forEach(([k, v]) => {
if (v === "") {
console.log("error while validating", k, "value cannot be empty");
}
});
}, []);
const onSubmit = useCallback((e) => {
e.preventDefault();
e.stopPropagation();
const transformedInstance = fields.reduce((acc, {name, onSubmitTransformer}) => ({
...acc,
[name]: onSubmitTransformer(acc[name]),
}), instanceRef.current);
setInstance(transformedInstance);
validate(transformedInstance);
}, [validate]);
// rest of component
}
I am working on a React application and have been using the react-admin framework.
I need to pre-process the data coming from a form given that I need separate tables for a new employee and their address but don't want to split it into two screens.
I found the Using onSave To Alter the Form Submission Behavior section in the react-admin's Create/Edit View documentation and I applied it to my code (sample below) in hopes that it would allow me to process the data before getting into the dataProvider. Unfortunately, I can't seem to get the data out of the form and into the callback for the CreateEntity button module.
Create View
const CreateActions = props => (
<TopToolbar {...props}>
<CreateEntityButton props={props} variant="contained" label={"Create"}/>
</TopToolbar>
);
const EmployeeCreate = props => (
<Create {...props} >
<TabbedForm toolbar={<CreateActions record={props.record} redirect="show" />}>
<FormTab label="Identity">
<span >
<PersonInput />
</span>
</FormTab>
<FormTab label="Address">
<span >
<AddressInput />
</span>
</FormTab>
</TabbedForm>
</Create>
)
export default TequitiEmployeeCreate;
When I step through the logic in the browser, the callback function in the handleSave method (below) passes down undefined for both the values and the redirect parameters.
I expected the values object to contain all the input values from the TabbedForm so that it could be parsed and then passed over to my dataProvider module.
CreateEntityButton logic:
const CreateEntityButton = ({ ...props}) => {
const [create] = useCreate(props.props.resource);
const redirectTo = useRedirect();
const notify = useNotify();
const { basePath } = props;
const handleSave = useCallback(
(values, redirect) => { // <-------- undefined all the time
console.log(values);
console.log(redirect);
create(
{
payload: { data: { ...values } },
},
{
onSuccess: ({ data: newRecord }) => {
notify('ra.notification.created', 'info', {
smart_count: 1,
});
redirectTo(redirect, basePath, newRecord.id, newRecord);
},
}
);
},
[create, notify, redirectTo, basePath]
);
return <SaveButton
label={props.label}
variant={props.variant}
handleSubmitWithRedirect={handleSave}
/>;
};
I thought that perhaps having separate modules for PersonInput and AddressInput was to blame for this, but even consolidating all those components into a single one, didn't help.
Any help/thoughts would be helpful.
Turns out, I was mixing the example and was using handleSubmiutWithRedirect instead of the onSave action in the SaveButton.
const CreateEntityButton = ({ ...props}) => {
const resource = props.props.resource;
const redirectTo = useRedirect();
const notify = useNotify();
const { basePath } = props.props;
const dataProvider = useDataProvider();
const handleSave = useCallback(
(values) => {
const createPerson = new PersonAddressCreate(dataProvider);
createPerson.create(values, resource)
.then((data)=>{
notify('ra.notification.created', 'info', { smart_count: 1 });
redirectTo("show", basePath, data.id, data)
})
.catch((error)=>{
notify(error, 'error', { smart_count: 1 });
})
},
[notify, redirectTo, basePath]
);
return <SaveButton
{...props.props}
label={props.label}
variant={props.variant}
onSave={handleSave}
/>;
};
I am loading data on the initial load. When they user clicks the button to add a recognition, the api call adds it, and returns it. I add the new post to the array, but the new update doesn't render. The return object, and the array of objects are the same object type. When I reload the page, the new post is rendered, just not on the add function. Is there something that I am missing?
const [recognitions, setRecognitions] = useState([]);
useEffect(() => {
Api.GetRecognitions(params)
.then(response => {
const items = response || [];
setRecognitions(recognitions => [...recognitions, ...items]);
})
}, [setRecognitions]);
const handleAddPost = () => {
Api.AddRecognition(params)
.then(response => {
const newPost = response;
setRecognitions(recognitions=> [...recognitions, newPost])
});
}
<Form.Group>
<Form.Field>
<Button basic color='blue' onClick={handleAddPost}>Add</Button>
</Form.Field>
</Form.Group>
<Form.Group>
<Form.Field>
{recognitions.map(recognition => (
<RecogWallPost
key={recognition.recogStagingId}
recognition={recognition}
participantId={participantId}
/>
)
)}
</Form.Field>
</Form.Group>
Instead of passing [setRecognitions] as the second argument to useEffect, you want to pass [recognitions]. This tells the useEffect hook to run every time recognitions changes, which it does inside handleAddPost.
You have to create an async function, and then use it as follow:
useEffect(() => {
async function initData() {
Api.GetRecognitions(params)
.then(response => {
const items = response || [];
setRecognitions(recognitions => [...recognitions, ...items]);
})
}
initData()
}, [setRecognitions]);