React isn't updating a variable from a function - javascript

I'm using axios to return a map to my main app where it will be distributed to other values in the program. I am having an issue though. When I use 'onClick' on a drop down select, I want it to call that external function to return the JSON string and save it to a variable but it won't do it. I have console logged it and it says my variable is use undefined. Here is my axios code
import axios from "axios";
// ** when you launch server. Make sure 'express stuff' server is working and that it is posting to 5000/loadCycle
function Parent() {
let data = null;
console.log("called");
const url = "http://localhost:5000/";
axios
.get(`${url}loadCycle`)
.then((response) => {
data = response.data.workflow;
data = JSON.stringify(data);
data = JSON.parse(data);
//console.log(data);
const map = new Map(Object.entries(data));
console.log(map);
return map;
})
.catch((error) => console.error(`Error: ${error}`));
}
export default Parent;
and here is the code I want to format
function App() {
let dataCollection = null;
return (
<div>
<Box
sx={{ display: "flex", width: "40%", justifyContent: "space-between" }}
>
<Box sx={{ display: "flex" }}>
{/* <Typography sx={{ paddingTop: "6%" }}>Cycle</Typography> */}
{/* Cycle dropdown menu */}
{/* // MAKE CHANGES ON BRANCH // */}
<FormControl
sx={{ m: 1, minWidth: 200 }}
size="small"
variant="standard"
>
<InputLabel>Cycles</InputLabel>
<Select>
<MenuItem value="">
<em>None</em>
</MenuItem>
<MenuItem value={10} onClick={dataCollection=Parent()}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
</FormControl>
{/* cycle dropdown menu end */}
</Box>
</div>
)
Why won't selecting 'one' from my select update dataCollection from 'null' to the map I am trying to return to it. Console logging it shows that the 'map' data in Parent is correct but the log for dataCollection is 'undefined'

<MenuItem value={10} onClick={dataCollection=Parent()}>Ten</MenuItem>
First of all you didn't define function (you are tried to do it like in a vainilla js, but react don't work in this way)
So, let's define separate function:
const handleSave = () => {
dataCollection=Parent()
}
// ...
<MenuItem value={10} onClick={handleSave}>Ten</MenuItem>
Next problem that's what your Parent function isn't synchronous, you should return your axios promise and after that use this function like that:
Parent().then(data => {
dataCollection = data;
})
That's not all, we can't save data at dataCollection, because your functional component this is like render function and you will lose your data on next render, so you shoud save your data to ref or state (depending on the purpose of use), let's use state:
const [dataCollection, setDataCollection] = React.useState();
// ...
const handleSave = () => {
Parent().then(data => {
setDataCollection(data);
})
}
Besides this I can see some style issues. And looks like you haven't read react doc attentively, please read againg "state and props" and "lifecycle" subjects from docs.

You have a couple of issues with your approach. I'm not sure what the other components: Box, FormControl, InputLabel, Select, and MenuItem are doing, so it makes it harder to discern if they are functioning correctly. I would simplify the code and just use regular HTML select and option tags. The select tag receives a change event and with React all events can be prefixed with "on", so it would be onChange on the select tag.
Create a prototype, a simpler project, that just focuses on that functionality until you understand it for your needs. Also, practice naming constructs a bit better, as Parent doesn't convey what it is doing. Aim to be succinct and general.

Related

Trigger api call on setting the default value of MUI Select

In Short : Execute the onChange event when default value is set in Material-UI Select Component.
When you will run this react component, you will find a Material UI Select Component with a defaut selected option as 10 which is being set using useState hook.How to make an API call at the same time when the default value is being set.
The onChange props will only execute when we change the dropdown menu item.If we are loading our page with some Select Component and setting some default menu item, then how can we make an API call and get some Data.
Use the CodeSand Box link. The alert message does not executes even though the menu-item/ option in the Select is getting changed from empty string to 10.
Assume that the default value is coming from some API call.
export default function SelectVariants() {
const [age, setAge] = React.useState("10");
//This should execute by default or on Page Load
const handleChange = (event: SelectChangeEvent) => {
setAge(event.target.value);
alert("Run on load"); // Can be an API Call on the Page load And also on Subsequent onChange Events
};
return (
<div>
<FormControl variant="standard" sx={{ m: 1, minWidth: 120 }}>
<InputLabel id="demo-simple-select-standard-label">Age</InputLabel>
<Select
labelId="demo-simple-select-standard-label"
id="demo-simple-select-standard"
value={age}
onChange={handleChange}
label="Age"
>
<MenuItem value={""}>None</MenuItem>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
</FormControl>
</div>
);
}
Code Pen Example
Following this requirement:
Set a default value on the Select which is coming from an API call
(API-1) -> then using the same default value, trigger another API call
(API-2) -> then at every onChange of Select menu, also make API call
(API-2) only.
const [age, setAge] = React.useState("");
useEffect(() => {
api1().then((defaultValue) => {
setAge(defaultValue);
api2(defaultValue);
});
}, []);
const handleChange = (event) => {
setAge(event.target.value);
api2(event.target.value);
};
Working example
Hope this helps.

How can get the current state of the material UI component without clicking on any event in react functional component?

I am trying to get Material Ui component current values after page reloads or DOM change without clicking on any event. The values are showing from the database.
Basically, I am checking after clicking on the save button if the values are updated or not, if the values are previous values I will return nothing. My function is ready but I am not able to get the current values when the page reloads, I am getting status state null.
My UI, the data is showing from the database:
Select component:
const [status, setStatus] = useState<string | null>(null); // My state
data.map(details=> {
<FormControl sx={{ m: 1, minWidth: 120 }}>
<InputLabel id="demo-controlled-open-select-label">Status</InputLabel>
<Select
labelId="demo-controlled-open-select-label"
id="demo-controlled-open-select"
open={open}
onClose={handleClose}
onOpen={handleOpen}
value={details.status || status} // value from DB
label="Status"
onChange={(e) => setStatus(details.status || e.target.value)}
>
<MenuItem value={'Placed'}>
<em>Placed</em>
</MenuItem>
<MenuItem value={'Packed'}>Packed</MenuItem>
<MenuItem value={'Shipped'}>Shipped</MenuItem>
<MenuItem value={'Delivered'}>Delivered</MenuItem>
<MenuItem value={'Cancel'}>Cancel</MenuItem>
</Select>
</FormControl>
<button onClick={()=>validate (details)}>save</button>
})
My function:
const validate = (details)=>{
if (details.status !== status) {
console.log('Do somthing')
} else {
console.log('None')
}
}
why did you give initial value null to state , try changing that initial value to what you want

Load Material UI AutoComplete suggestions after user input

I have an Autocomplete component that is required to load a massive data list (up to 6000 elements) and show suggestions accordingly to the user's input.
As the data options have so many elements, whenever the user starts typing in a slow computer, it slows down and requires some time to load everything. I have to prevent it, so I came with an idea to show the user suggestions after they typed the third character. It's even giving me this error whenever the user clicks on the input box:
Warning: React instrumentation encountered an error: RangeError: Maximum call stack size exceeded console.
I need to show the suggestions after the third character input. I have tried to use the getOptionDisabled suggestion and the limitTags, but they did not work.
Here is the code:
const NameSelect = (props) => {
return (
<Dialog>
<React.Fragment>
<DialogTitle id="search-name-dialog-title">
Search name
</DialogTitle>
<DialogContent>
<Autocomplete
id="combo-box-client-select"
options={props.NameList}
value={props.preSelectedName}
getOptionLabel={(option) =>
option.Name_name +
", " +
option.country +
", " +
option.city
}
onChange={(object, value) => {
props.preSelectedNameSet(value);
}}
renderInput={(params) => (
<TextField
{...params}
label="Search name"
variant="outlined"
fullWidth
/>
)}
/>
.
.
.
</Dialog>
);
};
Can someone please help me with that approach, or suggest a better one? Thanks!
Try something like this:
<Autocomplete
inputValue={inputValue}
onInputChange={(e) => setinputValue(event.target.value)}
id="combo-box-demo"
options={values}
getOptionLabel={(option) => option}
style={{ width: 300 }}
renderInput={(params) => (
<TextField {...params} label="Combo box" variant="outlined" />
)}
open={inputValue.length > 2}
/>
Use InputValue prop to trigger the auto complete drop down.
Example : auto complete dropdown
My idea is to add a state for Autocomplete current value to watch for its autoComplete property. That state will look something like this:
const [currentValue, useCurrentValue] = useState(props.preSelectedName);
so that your component will look something like this:
<Autocomplete
autoComplete={currentValue.length >= 3 ? true : false}
onChange={useCurrentValue}
...your other properties
/>
Another idea: you might wanna use setTimeout in your onChange method to slow down the re-render. But don't forget to clearTimeout before you set them.
The feature that you require is known as "Debouncing" and it is used whenever time consuming tasks occur frequently. In your case it, everytime you type the key, the suggestions are computed and this will definetely lead to lagging.
Lodash's debounce function will implement this for you.
As far as my knowledge, I am not sure whether you can implement this with MUI Autocomplete, but a custom solution you can do something like this:-
import React, { useState, useCallback } from "react";
import { _ } from "lodash";
function AutoComplete() {
const [input, setInput] = useState("");
const [suggestions, setSuggestions] = useState([]);
const updateInput = (input) => {
setInput(input);
/*_.debounce will fire the setSuggestions
and fetchSuggestions only after a gap of 3000ms */
_.debounce((input) => setSuggestions(fetchSuggestions(input), 3000));
};
return (
<div>
<input
value={input}
class="input"
onChange={(event) => updateInput(event.target.value)}
/>
<div class="suggestions">
<ul>
{suggestions?.length > 0 &&
suggestions?.map((val, idx) => (
<li class="suggestion" key={idx}>
{val}
</li>
))}
</ul>
</div>
</div>
);
}
export default AutoComplete;
You can style the components using the appropriate styles and materialize.css so that you get a functional replica of the Autocomplete component of MUI.

React autocomplete component, call endpoint every time a letter is typed

I am working with react and the component autocomplete of material-ui, I need help with the following problem.
In the examples of autocomplete I saw you need to have all elements of the list in the frontend to use the autocomplete, in my case I get the list from a web service and it could be huge, so instead of searching for the whole list I want that every time a letter is typed in the autocomplete it generates a search to the web service filtering names according to the input that is being written and with a max results of 10 elements. The endpoint of the webservice already has a filter property where you can pass the quantity of results you want and the letters you want of the name.The only thing that the autocomplete has to do is everytime you type a letter it hits the endpoint (filtering with the word that is being typed) and updates the list of elements of the autocomplete.
Right now I have the following code, the problem is that it searches the whole list when you click the autocomplete but when you type each letter it doesn't do anything.
import Autocomplete from '#material-ui/lab/Autocomplete';
import TextField from '#material-ui/core/TextField';
import CircularProgress from '#material-ui/core/CircularProgress';
const [open, setOpen] = React.useState(false);
const [organizationList, setOrganizationList] = React.useState([]);
const loading = open && organizationList.length === 0;
React.useEffect(() => {
let active = true;
if (!loading) {
return undefined;
}
(async () => {
if (active) {
try {
setOrganizationList(await api.post('/v1/organizations/search', {maxResults:10}));
} catch (error) {
snackbar.showMessage(error, "error");
}
}
})();
return () => {
active = false;
};
}, [loading]);
React.useEffect(() => {
if (!open) {
setOrganizationList([]);
}
}, [open]);
The definition of the autocomplete:
<Autocomplete
id="asynchronous-demo"
style={{ width: 300 }}
open={open}
onOpen={() => {
setOpen(true);
}}
onClose={() => {
setOpen(false);
}}
getOptionSelected={(option, value) => option.orgName === value.orgName}
getOptionLabel={(option) => option.orgName}
options={organizationList}
loading={loading}
renderInput={(params) => (
<TextField
{...params}
label="Asynchronous"
variant="outlined"
InputProps={{
...params.InputProps,
endAdornment: (
<React.Fragment>
{loading ? <CircularProgress color="inherit" size={20} /> : null}
{params.InputProps.endAdornment}
</React.Fragment>
),
}}
/>
)}
/>
To hit the endpoint I have this:
setOrganizationList(await api.post('/v1/organizations/search', {maxResults:10}));
I need to pass the input of the autocomplete every time a letter is typed, like this:
setOrganizationList(await api.post('/v1/organizations/search', {name:inputAutocomplete,maxResults:10}));
Thanks a lot for the help.
Im new to react by the way.
In material-ui library Autocomplete component has a props onChange that can be used like this.
onChange={(event, newValue) => {
setValue(newValue);
}}
You should be interested in the second parameter newValue. Thus, you will receive a new input value every time a letter is typed.
Therefore, just move the logic for getting the list into this callback.
You can read more about controllable state in the material-ui documentation
Implementing the onChange function and giving it the function you already made should give you the solution you want.

Component event handler receiving props of different component in list

I have the following component called PrivateReview, that has event handlers for updating it's content.
export default function PrivateReview(props) {
const classes = useStyles();
const removeReviewAndReload = async () => {
await fetch(`/rest/review/${props.movieId}`, deleteHeaders);
props.onChange();
};
const updateRating = async (event, newRating) => {
event.preventDefault();
const data = {
rating: newRating,
};
await fetch(`/rest/rating/${props.movieId}`, {...updateHeaders, body: JSON.stringify(data)});
props.onChange();
};
const updateReview = async (event, newComment) => {
event.preventDefault();
const data = {
comment: newComment,
};
await fetch(`/rest/review/${props.movieId}`, {...updateHeaders, body: JSON.stringify(data)});
props.onChange();
};
return (
<Grid item xs={12}>
<Card style={{width: 1150, margin: 10}}>
<CardHeader
title={<Link href={`/Movie/${props.movieId}`} className={classes.title} style={{ fontSize: '30px' }}>{props.title}</Link>}
action={
<Box component="fieldset" mb={-1} borderColor="transparent" marginTop={5}>
<Rating name="read-only" precision={0.5} value={props.rating} onChange={updateRating} />
</Box>
}
/>
<CardContent>
{props.text}
</CardContent>
<CardActions className={classes.right}>
<EditReviewButton movieId={props.movieId} oldReview={props.text} onSubmit={updateReview}/>
<IconButton color="primary" component="span" className={classes.control} onClick={removeReviewAndReload}>
<DeleteIcon />
</IconButton>
<Button disabled color="secondary">
{props.postDate}
</Button>
</CardActions>
</Card>
</Grid>
);
}
I map the review data that I receive from a parent component and try to generate a list of rendered components.
const Reviews = reviews.map(({ movieName, movieId, comment, rating, post_date, user }) => {
return <PrivateReview
key={movieId}
title={movieName}
text={comment}
rating={rating}
postDate={post_date}
user={user.userId}
movieId={movieId}
onChange={props.reloadDashboardData}
/>;
});
My problem is that when updateRating gets called, props.movieId is set to the movieId of the first item in the list inside updateRating, and hence the first item in the list gets modified instead of the item that I want.
Outside of this particular function however, when I console.log(props.movieId) in the function body, props.movieId is set correctly. This is the case for the other update methods as well, such as removeReviewAndReload and updateReview.
I suspect that this has something to do with either closing over the wrong value, or the bare asynchronous fetch calls. Could anyone point out the incorrectness or what I'm doing wrong here?
Thanks
EDIT:
As suggested in the comments below, I've changed the key property used in the list, and created a somewhat minimal running version here:
https://codesandbox.io/s/purple-framework-382hq
There are 3 event handlers in PrivateReview.js, removeReview, updateReview, and updateRating. removeReview and updateReview correctly log the corresponding movieId of the review that they are associated with, but updateRating logs the movieId of the review at the top of the rendered list.
I've removed the async/await things in the linked sandbox, but the bug is still there, so I think can rule out problems with fetch and async stuff. I've updated the tags accordingly.
Thanks very much for your help so far!
EDIT 2:
Since I've super simplified the event handlers in the sandbox to the point where they are only console logging, I think it might have something to do with material-ui, or my component layout structure (the DOM thing but for components). I've added the material-ui tag to this question in case it has something to do with that.
Cheers
Thanks very much for your help and comments guys!
After minifying my example into the sandbox, and removing all the extraneous stuff, I found out the problem.
In my example, the name was set as such:
<Rating name="read-only" precision={0.5} value={props.rating} onChange={updateRating} />
However as stated here: https://material-ui.com/pt/api/rating/
The name attribute of the radio input elements. If readOnly is false,
the prop is required, this input name`should be unique within the
parent form.
Changing that to this:
<Rating name={`${props.movieId}`} precision={0.5} value={props.rating} onChange={updateRating} />
And everything works! Therefore I just needed to set the rating "name" attribute to a uniqueId associated with the review much in the same way as we need to set the key for components that are part of a list.
Cheers for your feedback!

Categories