trying to NOT get response one change late - reactjs

So I have an input :
onChange this input's value get's updated and stored in props for persistance but that's unrelated to the issue.
I'm going to add a checkmark next to the field if the entered value is found in the database.
I already have the call to API and answer set up (my redux reducer) :
import EventTypes from '../EventTypes';
const initialState = {
response: {},
};
export default (state = initialState, action) => {
switch (action.type) {
case EventTypes.EXISTS_FULFILLED.type:
console.log(action.payload.body().data());
return { ...state, response: action.payload.body().data() };
default:
return state;
}
};
Note that the above console log correctly prints the true/false value (+ sent value. I get the two back from the server).
but in the component that calls this :
const defaultProps = {
onChange: () => {
},
value: { type: '' },
provider: { type: '' },
};
class InputsComponent extends Component {
onChange = () => {
this.props.onChange(this.props.id);
};
updateFirstInput(event) {
const { onChange, exists, response } = this.props;
const payload = {
objectType: this.props.placeholder.toLowerCase(),
objectName: event.target.value,
};
exists(payload);
this.setState({ firstInput: event.target.value });
onChange({ value: {
...this.state,
firstInput: event.target.value,
} }, this.props.id);
setTimeout(() => {
this.setState({ exitsStore: response });
}, 200);
setTimeout(() => {
console.log(this.state.exitsStore);
}, 1000);
}
The console logs are empty the first time "updateFirstInput()" is called and then are always one "onChange" behind from being accurate.
that is for "hello" :
[input value] [ console log of value for which presence on the server is true/false]
"h" ""
"he" "h"
"hel" "he"
"hell" "hel"
"hello" "hell"
after awhile I figured out that this was happening.
I've tried this :
const defaultProps = {
onChange: () => {
},
value: { type: '' },
provider: { type: '' },
};
class InputsComponent extends Component {
onChange = () => {
this.props.onChange(this.props.id);
};
checkExists(object){
const { exists, response } = this.props;
const payload = {
objectType: this.props.placeholder.toLowerCase(),
objectName: object,
};
exists(payload);
return response;
}
updateFirstInput(event) {
const { onChange } = this.props;
this.setState({ firstInput: event.target.value });
onChange({ value: {
...this.state,
firstInput: event.target.value,
} }, this.props.id);
const response = await this.checkExists(event);
console.log(response);
this.setState({ exitsStore: response });
console.log('stored data :');
console.log(this.state.exitsStore);
}
but for some reason people online suggesting we use await are not providing context. the above is not even compilable. (npm start will fail)
and even before that ESLint indicates that "await is a reserved word expecting newline or semicolon".
but I'm quite sure the above even with correct syntaxe wouldn't work either.
it's probably not await but how do I manage to have a filled in response from server (ideally) within a single function, the same function that calls the input update.
Constrainst :
as far as I know you can't have both onBlur and onChange on the same input so since i'm already using onChange no onBlur.
and I can't call any of this within a parent or other : there are multiple input fields as children and I need to have the value check and checkmark appear actions happen within the child to be able to match the values together.
UPDATE :
and if I simply remove the timers :
const defaultProps = {
onChange: () => {
},
value: { type: '' },
provider: { type: '' },
};
class InputsComponent extends Component {
onChange = () => {
this.props.onChange(this.props.id);
};
updateFirstInput(event) {
const { onChange, exists, response } = this.props;
const payload = {
objectType: this.props.placeholder.toLowerCase(),
objectName: event.target.value,
};
exists(payload);
this.setState({ firstInput: event.target.value });
onChange({ value: {
...this.state,
firstInput: event.target.value,
} }, this.props.id);
this.setState({ exitsStore: response });
console.log(this.state.exitsStore);
}
...TWO calls behind.

setState is asynchronous. It looks like you are using timeouts to try to get around this fact, but that's a very crude and brute force way to "solve" the problem and it doesn't actually solve the problem (as shown by your results).
But setState has handlers available when you need to run code "after" it. You do this simply by passing in a callback function as the second argument. E.g.;
this.setState({
exitsStore: response
}, () => {
console.log(this.state.exitsStore);
});

The handy pre-existing methods of React to the rescue.
In this case : componentWillRecieveProps().
componentWillReceiveProps(newProps) {
const response = newProps.response;
const old = this.props.response;
console.log(response);
const id = this.props.id;
if (response !== old && response.objectIdentifier === id) {
if (response.activ) {
if (response.isthere) {
this.setState({ inputIcon: 4 });
} else {
this.setState({ inputIcon: 3 });
}
} else {
this.setState({ inputIcon: 2 });
}
}
}
onChange = () => {
this.props.onChange(this.props.id);
};
updateInput(event) {
const { onChange, exists, response } = this.props;
const inputValue = event.target.value;
this.setState({ input: inputValue });
onChange({ value: {
...this.state,
input: inputValue,
} }, this.props.id);
if (inputValue === '') {
this.setState({ inputIcon: 0 });
} else {
const placeHolder = this.props.placeholder.toLowerCase();
const objectIdentifier = this.props.id;
const payload = {
objectType: placeHolder,
objectName: inputValue,
objectIdentifier,
};
exists(payload);
this.setState({
existStore: response,
};
}
}
the reason why this works is that componentWillRecieveProps() by default receives a parameter which is the updated props state and this one is indeed the new one. you can check that there has indeed been an update by doing as I did : checking the object you have in props against the passed parameter and only perform your actions if they are different.
I need edits on this post please, because I'm terrible at expressing this with the correct developer terms!

Related

how to call reactQuery refetch in input onchange event in reactjs

In my React application, I need to call does exit API. API call should happen when change event in the input for that, I am using the reactQuery refetch to do that.
I had tried with below code
const [createObj, setCreateObj] = useState(mCreateObj);
const { data: doexit, refetch: doexitRefetch } = useQuery('getDoexit', () => api.doexitAPI(createObj.c_name), { enabled: false });
const handleInput = ({ target: { name, value } }) => { setCreateObj(state => ({ ...state, [name]: value }), []); }
export const doexitAPI= (value) => axios.get(/doexist/${value}, { headers: setHeader }).then(res => res);
useEffect(() => { console.log(createObj) doexitRefetch(); }, [createObj.mx_name])
How to call in input onchange event
You can invalidate your query and handle fetch data again with query keys.
https://react-query.tanstack.com/guides/query-keys#if-your-query-function-depends-on-a-variable-include-it-in-your-query-key
const { data: doexit, refetch: doexitRefetch } = useQuery(['getDoexit', createObj.mx_name], () => api.doexitAPI(createObj.c_name), { enabled: false });

"To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function." error But I'm not using it

Warning: Can't perform a React state update on an unmounted component.
This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in a useEffect
cleanup function.
I have tried everything to fix but nothing works. I didn't even use "useEffect". This happens when I login to account and navigate the user to search page.
loginScreen.js
const validationSchema = Yup.object().shape({
email: Yup.string()
.label("Email")
.email("Enter a valid email")
.required("Please enter a registered email"),
password: Yup.string()
.label("Password")
.required()
.min(6, "Password must have at least 6 characters "),
});
const { width, height } = Dimensions.get("window");
class loginScreen extends React.Component {
state = {
passwordVisibility: true,
rightIcon: "ios-eye",
};
goToSignup = () => this.props.navigation.navigate("Signup");
handlePasswordVisibility = () => {
this.setState((prevState) => ({
rightIcon: prevState.rightIcon === "ios-eye" ? "ios-eye-off" : "ios-eye",
passwordVisibility: !prevState.passwordVisibility,
}));
};
handleOnLogin = async (values, actions) => {
const { email, password } = values;
try {
const response = await this.props.firebase.loginWithEmail(
email,
password
);
if (response.user) {
this.props.navigation.navigate("App");
}
} catch (error) {
alert("Seems like there is no account like that. Try something else.");
} finally {
actions.setSubmitting(false);
}
};
searchScreen.js
class searchScreen extends Component {
apiurl = "";
_isMounted = false;
constructor(props) {
super(props);
this.state = {
searchText: "",
results: [],
isLoading: true,
};
}
showMovie = async (imdbID) => {
await axios(this.apiurl + "&i=" + imdbID).then(({ data }) => {
let result = data;
this.props.navigation.navigate("Movie", {
selected: result,
movieID: imdbID,
});
});
};
componentDidMount() {
this._isMounted = true;
this.initial();
}
componentWillUnmount() {
this._isMounted = false;
}
initial = async () => {
const user = await this.props.firebase.getUser(user);
try {
await AsyncStorage.setItem("useruid", user.uid);
} catch (error) {
console.log(error);
}
const expoToken = await UserPermissions.registerForPushNotificationsAsync();
if (expoToken) {
this.props.firebase.setExpoToken(expoToken);
}
if (this._isMounted) {
this.setState({ isLoading: false });
}
};
search = async () => {
Keyboard.dismiss();
await axios(this.apiurl + "&s=" + this.state.searchText).then(
({ data }) => {
let results = data.Search;
if (this._isMounted) {
this.setState((prevState) => {
return { ...prevState, results: results };
});
}
}
);
};
After a successful login, you call the navigate function. This navigates to a different component, which means the login component becomes unmounted. The handleLogin function still has more logic to execute though, in your finally statement, you are setting submission to false. When that finally runs there is no mounted component which means there is no state to set.
Moving your submission false state change to before the navigate call and before the alert will solve the problem.
I’d recommend not bothering with it in the case the user actually logs in, because the user is about to visually move to a completely different screen, changing the state doesn’t really help them.

React passing a variable object to setState vs. an anonymous object to setState

I was having issues rendering a chart after state has been updated by the data fetched from an API.
import React, { Component } from 'react';
import { Bar, Line, Pie } from 'react-chartjs-2';
export default class PopChart extends Component {
constructor(props) {
super(props);
this.state = {
chartData: {
labels: [],
datasets: [
{
label: 'AAA',
data: [],
backgroundColor: []
}
]
}
};
}
url = 'api_url';
componentDidMount() {
fetch(this.url)
.then(response => {
this.setState({
httpStatusCode: response.status,
httpStatusOk: response.ok
});
if (response.ok) {
return response.json();
} else if (response.status === 404) {
// Not found
throw Error('HTTP 404, Not found');
} else {
throw Error(`HTTP ${response.status}, ${response.statusText}`);
}
})
.then(responseData => {
console.log('Then function');
this.setChartProperties(responseData);
})
.then(() => {
this.setState({ loading: false });
})
.catch(error => {
console.log(error);
});
}
setChartProperties = data => {
this.setChartData(data);
// this.setChartXAxis(data);
};
setChartData = data => {
let hours = data.map(event => event.hour);
hours = hours.splice(0, 10);
const { chartData } = { ...this.state };
const chartDataUpdate = chartData;
chartDataUpdate['datasets'].data = hours;
// Working version
// this.setState({
// chartData: {
// labels: ['a', 'a', 'a', 'a', 'a', 'a', 'a'],
// datasets: [
// {
// label: 'AAA',
// data: hours,
// backgroundColor: []
// }
// ]
// }
// });
// Not working version
this.setState({ chartData: chartDataUpdate });
};
render() {
<Bar data={this.state.chartData} />;
}
}
At first I thought the issue was regarding the asynchronous nature of fetch() and the <Bar/> component from Chart.js was being rendered before the state got a chance to get updated. However, when I logged out the state, on the console it showed my state with the changed values. However, the chart did not display any of the values.
When I called setState() and passed an anonymous object (refer to the commented out working version of setState()), the chart displayed the values.
Why does it not work when I stored the fetched data into a variable and pass it as an argument for setState()?
One thing I noticed is that state.chartData.datasets is an array, but in setChartData you're setting data on the array not one of its elements.
This:
chartDataUpdate['datasets'].data = hours;
Should be:
chartDataUpdate['datasets'][0].data = hours;

Re-request on state change react

Im trying to make my component make a new request when state changes in react. I believe that i understand the lifecycle methods, but i cannot manage to make my component make a new request when the date state changes.
I have used componentWillUpdate with prevState, where prevState returns the updated state instead of the previous date.
Anything that can help me in the right direction is appriciated.
this.state = {
date: moment(),
bookings: []
}
componentWillUpdate(prevProps, prevState) {
// prevState returns updated state
}
componentWillMount() {
const { course } = this.props.match.params;
const payload = {
startDate: this.state.date.format('MM-DD-YYYY'),
endDate: this.state.date.add(1, 'd').format('MM-DD-YYYY')
}
axios.get(`/api/booking/find/${course}? startDate=${payload.startDate}&endDate=${payload.endDate}`).then(response => {
this.setState({
bookings: response.data
})
});
}
onNext = () => {
this.setState({
date: this.state.date.add(1, 'd')
})
}
onPrev = () => {
this.setState({
date: this.state.date.subtract(1, 'd')
})
}
According to React doc, UNSAFE_componentWillUpdate will soon be deprecated.
Btw, this function signature is UNSAFE_componentWillUpdate(nextProps, nextState), this is why you get the updated state.
You should consider extracting your axios get request inside a function, that could be called inside a componentDidUpdate lifecycle method :
this.state = {
date: moment(),
bookings: []
}
componentDidUpdate(prevProps, prevState) {
if (this.state.date.valueOf() !== prevState.date.valueOf()) {
this.getBookings();
}
}
componentWillMount() {
this.getBookings();
}
getBookings = () => {
const { course } = this.props.match.params;
const payload = {
startDate: this.state.date.format('MM-DD-YYYY'),
endDate: this.state.date.add(1, 'd').format('MM-DD-YYYY')
}
axios.get(`/api/booking/find/${course}? startDate=${payload.startDate}&endDate=${payload.endDate}`).then(response => {
this.setState({
bookings: response.data
})
});
}
onNext = () => {
this.setState({
date: this.state.date.add(1, 'd')
})
}
onPrev = () => {
this.setState({
date: this.state.date.subtract(1, 'd')
})
}
It sounds like you want componentDidUpdate
componentDidUpdate(prevProps, prevState) {
if (!this.state.date.isSame(prevState.date)) {
// do request
}
}

react Invalid attempt to spread non-iterable instance

I keep getting this error:
TypeError: Invalid attempt to spread non-iterable instance
while I'm trying to fetch some data in here :
export const genres = () => {
const apiUrl = "http://localhost:3000/api";
return fetch(apiUrl + "/genres")
.then(response => response.json())
.then(data => {
const res = Array.from(data.results);
return res;
});
};
console.log(genres)
export function getGenres() {
return genres().then(res => res.filter(g => g));
}
and updating the state of my component in here :
componentDidMount() {
const genres = [{ _id: "", name: "All Genres" }, ...getGenres()];
this.setState({ genres});
}
I'm aware that probleme comes from the fact that genres returns an object while the state should be an array but I'm not sure how to fixe it.
Thanks
getGenres returns a promise, so you need to wait for it to resolve before you try to put what is returned from it in state.
componentDidMount() {
getGenres().then(res => {
const genres = [{ _id: "", name: "All Genres" }, ...res];
this.setState({ genres });
})
}

Resources