Issue with Saving a Value to the Component State - javascript

While attempting to add the functionality to my project of saving the term in the Search Bar through refreshing your browser, I got it to where things were being saved correctly. The only issue came up with me saving the term in the "handleTermChange" method of the component, so it would pass undefined if you do not change the term in any way before searching. I attempt to bypass this in the "search" method by checking if the state for the term is empty (as it's live updated with the handleTermChange method). The conditional checking if the term in the state is empty works fine, as you enter it when you search without changing anything in the SearchBar. The first console.log prints out the variable fine, but the second console log still prints out an empty string, I'm probably missing something small, but I can't tell, I'm too tired to see little things.
search() {
if (this.state.term === '') {
const savedTerm = sessionStorage.getItem("inputValue");
console.log(savedTerm);
this.setState({term: savedTerm});
console.log(this.state.term);
}
this.props.onSearch(this.state.term);
}

Does this work ? Because of the state updates could be asynchronous, you can't be sure that term will be savedItem without the callback.
Doc here.
search() {
if (this.state.term === '') {
const savedTerm = sessionStorage.getItem("inputValue");
console.log(savedTerm);
this.setState({term: savedTerm}, () => {
console.log(this.state.term);
this.props.onSearch(this.state.term);
});
} else {
this.props.onSearch(this.state.term);
}
}

This call is asynchronous, try adding a callback function before accessing the property like:
search() {
if (this.state.term === '') {
const savedTerm = sessionStorage.getItem("inputValue");
console.log(savedTerm);
var thisState = this;
this.setState({term: savedTerm}, function () {
console.log(thisState.state.term);
});
}
this.props.onSearch(this.state.term);
}

Related

How to make sure a React state using useState() hook has been updated?

I had a class component named <BasicForm> that I used to build forms with. It handles validation and all the form state. It provides all the necessary functions (onChange, onSubmit, etc) to the inputs (rendered as children of BasicForm) via React context.
It works just as intended. The problem is that now that I'm converting it to use React Hooks, I'm having doubts when trying to replicate the following behavior that I did when it was a class:
class BasicForm extends React.Component {
...other code...
touchAllInputsValidateAndSubmit() {
// CREATE DEEP COPY OF THE STATE'S INPUTS OBJECT
let inputs = {};
for (let inputName in this.state.inputs) {
inputs = Object.assign(inputs, {[inputName]:{...this.state.inputs[inputName]}});
}
// TOUCH ALL INPUTS
for (let inputName in inputs) {
inputs[inputName].touched = true;
}
// UPDATE STATE AND CALL VALIDATION
this.setState({
inputs
}, () => this.validateAllFields()); // <---- SECOND CALLBACK ARGUMENT
}
... more code ...
}
When the user clicks the submit button, BasicForm should 'touch' all inputs and only then call validateAllFields(), because validation errors will only show if an input has been touched. So if the user hasn't touched any, BasicForm needs to make sure to 'touch' every input before calling the validateAllFields() function.
And when I was using classes, the way I did this, was by using the second callback argument on the setState() function as you can see from the code above. And that made sure that validateAllField() only got called after the state update (the one that touches all fields).
But when I try to use that second callback parameter with state hooks useState(), I get this error:
const [inputs, setInputs] = useState({});
... some other code ...
setInputs(auxInputs, () => console.log('Inputs updated!'));
Warning: State updates from the useState() and useReducer() Hooks
don't support the second callback argument. To execute a side effect
after rendering, declare it in the component body with useEffect().
So, according to the error message above, I'm trying to do this with the useEffect() hook. But this makes me a little bit confused, because as far as I know, useEffect() is not based on state updates, but in render execution. It executes after every render. And I know React can queue some state updates before re-rendering, so I feel like I don't have full control of exactly when my useEffect() hook will be executed as I did have when I was using classes and the setState() second callback argument.
What I got so far is (it seems to be working):
function BasicForm(props) {
const [inputs, setInputs] = useState({});
const [submitted, setSubmitted] = useState(false);
... other code ...
function touchAllInputsValidateAndSubmit() {
const shouldSubmit = true;
// CREATE DEEP COPY OF THE STATE'S INPUTS OBJECT
let auxInputs = {};
for (let inputName in inputs) {
auxInputs = Object.assign(auxInputs, {[inputName]:{...inputs[inputName]}});
}
// TOUCH ALL INPUTS
for (let inputName in auxInputs) {
auxInputs[inputName].touched = true;
}
// UPDATE STATE
setInputs(auxInputs);
setSubmitted(true);
}
// EFFECT HOOK TO CALL VALIDATE ALL WHEN SUBMITTED = 'TRUE'
useEffect(() => {
if (submitted) {
validateAllFields();
}
setSubmitted(false);
});
... some more code ...
}
I'm using the useEffect() hook to call the validateAllFields() function. And since useEffect() is executed on every render I needed a way to know when to call validateAllFields() since I don't want it on every render. Thus, I created the submitted state variable so I can know when I need that effect.
Is this a good solution? What other possible solutions you might think of? It just feels really weird.
Imagine that validateAllFields() is a function that CANNOT be called twice under no circunstances. How do I know that on the next render my submitted state will be already 'false' 100% sure?
Can I rely on React performing every queued state update before the next render? Is this guaranteed?
I encountered something like this recently (SO question here), and it seems like what you've come up with is a decent approach.
You can add an arg to useEffect() that should do what you want:
e.g.
useEffect(() => { ... }, [submitted])
to watch for changes in submitted.
Another approach could be to modify hooks to use a callback, something like:
import React, { useState, useCallback } from 'react';
const useStateful = initial => {
const [value, setValue] = useState(initial);
return {
value,
setValue
};
};
const useSetState = initialValue => {
const { value, setValue } = useStateful(initialValue);
return {
setState: useCallback(v => {
return setValue(oldValue => ({
...oldValue,
...(typeof v === 'function' ? v(oldValue) : v)
}));
}, []),
state: value
};
};
In this way you can emulate the behavior of the 'classic' setState().
I have tried to solve it using the useEffect() hook but it didn't quite solve my problem. It kind of worked, but I ended up finding it a little too complicated for a simple task like that and I also wasn't feeling sure enough about how many times my function was being executed, and if it was being executed after the state change of not.
The docs on useEffect() mention some use cases for the effect hook and none of them are the use that I was trying to do.
useEffect API reference
Using the effect hook
I got rid of the useEffect() hook completely and made use of the functional form of the setState((prevState) => {...}) function that assures that you'll get a current version of your state when you use it like that. So the code sequence became the following:
// ==========================================================================
// FUNCTION TO HANDLE ON SUBMIT
// ==========================================================================
function onSubmit(event){
event.preventDefault();
touchAllInputsValidateAndSubmit();
return;
}
// ==========================================================================
// FUNCTION TO TOUCH ALL INPUTS WHEN BEGIN SUBMITING
// ==========================================================================
function touchAllInputsValidateAndSubmit() {
let auxInputs = {};
const shouldSubmit = true;
setInputs((prevState) => {
// CREATE DEEP COPY OF THE STATE'S INPUTS OBJECT
for (let inputName in prevState) {
auxInputs = Object.assign(auxInputs, {[inputName]:{...prevState[inputName]}});
}
// TOUCH ALL INPUTS
for (let inputName in auxInputs) {
auxInputs[inputName].touched = true;
}
return({
...auxInputs
});
});
validateAllFields(shouldSubmit);
}
// ==========================================================================
// FUNCTION TO VALIDATE ALL INPUT FIELDS
// ==========================================================================
function validateAllFields(shouldSubmit = false) {
// CREATE DEEP COPY OF THE STATE'S INPUTS OBJECT
let auxInputs = {};
setInputs((prevState) => {
// CREATE DEEP COPY OF THE STATE'S INPUTS OBJECT
for (let inputName in prevState) {
auxInputs =
Object.assign(auxInputs, {[inputName]:{...prevState[inputName]}});
}
// ... all the validation code goes here
return auxInputs; // RETURNS THE UPDATED STATE
}); // END OF SETINPUTS
if (shouldSubmit) {
checkValidationAndSubmit();
}
}
See from the validationAllFields() declaration that I'm performing all my code for that function inside a call of setInputs( (prevState) => {...}) and that makes sure that I'll be working on an updated current version of my inputs state, i.e: I'm sure that all inputs have been touched by the touchAllInputsValidateAndSubmit() because I'm inside the setInputs() with the functional argument form.
// ==========================================================================
// FUNCTION TO CHECK VALIDATION BEFORE CALLING SUBMITACTION
// ==========================================================================
function checkValidationAndSubmit() {
let valid = true;
// THIS IS JUST TO MAKE SURE IT GETS THE MOST RECENT STATE VERSION
setInputs((prevState) => {
for (let inputName in prevState) {
if (inputs[inputName].valid === false) {
valid = false;
}
}
if (valid) {
props.submitAction(prevState);
}
return prevState;
});
}
See that I use that same pattern of the setState() with functional argument call inside the checkValidationAndSubmit() function. In there, I also need to make sure that I'm get the current, validated state before I can submit.
This is working without issues so far.

Unable to compare props on componentWillRecieveProps dispatch

I have an infinite loop, while its doing what I want it to, fetches my updated data without refresh its extremely slow as its processing over and over.
I have read a few things and tried a check to see if props have updated but cant get it right...
My infinite loop...
componentWillReceiveProps(nextProps) {
whenGapiReady(() => {
const { Id } = this.props;
this.props.fetchFileUploads(Id)
});
}
tried to change it to:
componentWillReceiveProps(nextProps){
if(nextProps.Id !== this.props.Id)
dispatch(this.props.fetchPlayerFileUploads(Id))
}
without success, its not doing anything now. I've missed out the gapi as I could add this back in either.
Any assistance of what this should be to stop the loop! Also saw componentDidUpdate should be used as componentWillReceiveProps is being depreciated but I will take anything at the moment.
EDIT
componentWillReceiveProps(nextProps) {
whenGapiReady(() => {
const { Id } = this.props;
if(nextProps.Id === this.props.Id) {
this.props.fetchFileUploads(this.props.Id)
}
});
}
The above still loops endlessly...?
You need to spell Receive correctly in the refactored code:
componentWillReceiveProps(nextProps){
// ^^
if(nextProps.Id !== this.props.Id)
dispatch(this.props.fetchPlayerFileUploads(Id))
}
if(nextProps.Id === this.props.Id) {
this.props.fetchFileUploads(this.props.Id)
}
componentWillReceiveProps will be invoked everytime, and nextProps.Id will always equals to this.props.Id and hence the infinite loop, you can instead store the Id to state and improve your comparison from there instead

Reactjs: Checkbox state is update, then reverted when callback function terminates

I'm pretty new to react and I ran into a weird issue today. I have a function called handleCheckBoxClick()
handleCheckboxClick : function (e) {
this.setState({isChecked : e.target.checked},
function () {
if (this.state.isChecked) {
this.props.addToTransactionSubmissions(
this.props.submissionGuid,
this.props.paymentInfo
);
} else {
this.props.removeFromTransactionSubmissions(
this.props.submissionGuid
);
}
}
);
}
This particular function calls a function passed down through a parent called addTo/removeFromTransactionSubmission. The code for both is as follows:
addToTransactionSubmissions : function (guid, paymentInfo) {
if (Object.keys(this.state.transactionSubmissions).length === 0) {
this.setState({submissionType : paymentInfo.status_description.toLowerCase()},
function () {
console.log('it set the state though');
console.log(this.state.transactionSubmissions);
this.toggleButtons(this.state.submissionType);
}
);
}
var newTansactionSubmissions = update(this.state.transactionSubmissions,
{
$merge : {[guid] : paymentInfo}
});
this.setState({transactionSubmissions : newTansactionSubmissions},
function () {
console.log('state is now', this.state.transactionSubmissions);
}
);
},
removeFromTransactionSubmissions : function (guid) {
if (Object.keys(this.state.transactionSubmissions).length === 0) {
this.setState({submissionType : undefined},
function () {
this.toggleButtons(this.state.submissionType);
}
);
}
var newTransactionSubmission = update(this.state.transactionSubmissions,
{
[guid] : {$apply: function (x) {return undefined}}
});
this.setState({transactionSubmissions : newTransactionSubmission},
function () {
console.log('here in remove Transaction');
});
}
The problem I run into is that when addTo/removeFromTransactionSubmissions is called, the checkbox does not changes states, even though the state is changed before addTo/removeFromTransactionSubmissions is called. Through further debugging using Firebug, I discovered that 1) the functions are all being called properly, 2) if I do not set state in addTo/removeFromTransactionSubmissions everything runs without a hitch, and 3) the checkbox becomes unchecked after handleCheckboxClick completely finishes.
I suspect that for whatever reason, the state is being lost when Reactjs is trying to update the DOM. However, I do not know why this is the case and don't know how to further debug. And for clarification, the checkbox is in the child component whereas the transactionSubmissions state is in a parent, and on the click of a checkbox, transactionSubmissions is modified (Child Action modifies Parent state). If this is the wrong way to go about the problem, please tell me.
Basically what I want to do is every time I click/unclick a box, it removes the corresponding object to/from a map of ids to the object. Am I doing something incorrectly?
Thanks for the help!
I think you should use another aproach.
handleCheckboxClick : function (e) {
this.setState({isChecked : e.target.checked})
}
Add a method componentWillUpdate(https://facebook.github.io/react/docs/component-specs.html#updating-componentwillupdate) or componentDidUpdate(https://facebook.github.io/react/docs/component-specs.html#updating-componentdidupdate) and handle there the changes that must occur after state change
componentWillUpdate()
{
if (this.state.isChecked)
{
this.props.addToTransactionSubmissions(
this.props.submissionGuid,
this.props.paymentInfo);
} else {
this.props.removeFromTransactionSubmissions(this.props.submissionGuid);
}
}
Also you should not call setState sequencially, it can throw some errors by trying to mutate a component while it was updating.
Every time your code calls setState, react goes trought all the dom, check what has changed and render the changes again, saving processing. Ref:https://facebook.github.io/react/docs/component-api.html#setstate

sportsBasketballChange another place its not setting state properly

I tried to say is I am not able to set the value using setState in sportsBasketballChange function but I am able to set it in sportsSoccerChange function
i am new to react.js
i am trying to set the value using setState.
in sportsSoccerChange function its correctly setting setState.
but sportsBasketballChange another place its not setting state properly.
can you guys tell me how to fix it.
providing my code below.
part of code
sportsSoccerChange(value) {
this.props.onChange();
let processedValue = value;
// sportsMessages sportsAirFalling
processedValue = sportsAirFallBALL(processedValue, this.props.sportsAirFall);
// sportsSuceessOnTime-ation
let sportsSuceessOnTime-ationResult = sportsSuceessOnTime-ateBALL(processedValue, this.props.sportsDrive);
if (sportsSuceessOnTime-ationResult === true) {
this.setState({ sportsOutcome: 'sportsSuceessOnTime-' });
///here i get value as sportsSuceessOnTime-
}
//this.setState({ isBALLValid: sportsSuceessOnTime-ationResult });
// formatting
processedValue = formatBALL(processedValue, this.props.sportsLongJump);
// set value in local component state
this.setState({ sportsMessagesValue: processedValue });
},
sportsBasketballChange() {
if (this.state.sportsOutcome === 'female') {
this.setState({ sportsOutcome: 'sportsSuceessOnTime-' });
///here i don't get value as sportsSuceessOnTime-
}
},
whole code here
https://gist.github.com/js08/e20c02bf21242201c1525577d55dedbc
I'm assuming that you are checking the value of this.state at those commented lines, either using logging or debugging.
setState is asynchronous. This means that there is no guarantee that the changes have occurred by the time you reach the next line of code. However, setState allows you to provide a callback function to be run after the state has finished updating. That is where you should be checking the updated value.
sportsBasketballChange() {
if (this.state.sportsOutcome === 'female') {
this.setState({ sportsOutcome: 'sportsSuceessOnTime-' },
function(){
console.log(this.state.sportsOutcome); // == 'sportsSuceessOnTime-'
}
);
console.log(this.state.sportsOutcome); // untrustworthy
}
},

Meteor.js Collection empty on Client

Why is it that myCollection.find().fetch() returns an empty array [] even though the call is made within if(data){...}? Doesn't the if statement ensure that the collection has been retrieved before executing the console.log()?
Template.chart.rendered = function() {
var data = myCollection.find().fetch();
if(data) {
console.log(data);
}
$('#chart').render();
}
This returns [] in the browser Javascript console.
You could use count() instead which returns the number of results. data itself would be an empty array, [] which isn't falsey ( [] == true ).
Also don't use fetch() unless you're going to use the raw data for it because its quite taxing. You can loop through it with .forEach if you need to.
var data = myCollection.find();
if(data.count())
console.log(data);
//If you need it for something/Not sure if this is right but just an example
$('#chart').render(data.fetch())
The problem is that you have to wait for data from the server. When you just use Template.name.rendered function it is immediately invoked. You have to use Template.name.helpers function to wait for data from the server. Everything is described in the documentation.
It seems when you "remove autopublish" you have to also subscribe on the client.
if(Meteor.isClient) {
Meteor.startup(function() {
Myvars = new Mongo.Collection("myvars");
Meteor.subscribe('myvars')
});
}
and enable allow and publish on the server
if(Meteor.isServer) {
Meteor.startup(function () {
Myvars = new Mongo.Collection("myvars");
Myvars.allow({
insert: function () {
return true;
},
update: function () {
return true;
},
remove: function () {
return true;
}
});
if (Myvars.find().count() == 0) {
Myvars.insert({myvalue:'annoyed'});
}
Meteor.publish("myvars", function() {
return Myvars.find();
});
});
}
I'm new to this as well. I was just looking to have a global value that all clients could share. Seems like a useful idea (from a beginner's perspective) and a complete oversight on the Meteor teams behalf, it was nowhere clearly documented in this way. I also still have no idea what allow fetch is, that too is completely unclear in the official documentation.
It does, but in javascript you have the following strange behaviour
if ([]){
console.log('Oops it goes inside the if')
} // and it will output this, nontheless it is counter-intuitive
This happens because JS engine casts Boolean([]) to true. You can how different types are casted to Boolean here.
Check if your array is not empty in the beginning.
a = [];
if (a.length){
//do your thing
}

Categories

Resources