React how to store state value onSubmit - reactjs

I am trying to store the value of one state property within another on submit, so that I can submit a URL friendly slug to my database.
Below is part of the function that is called when my form is submitted. Currently the form submits to a database (Firestore) and it works fine. However, I need to collect the value that a user inputs into streetAddress, slugify it, and then submit that to my database as its own slug field using the slug property of my state.
The problem I have, is I don't know how to do this. I've tried a few ways and slug is submitted to the database, but always with an empty value. Below is how I have attempted it.
onSubmit = event => {
const { reviewTitle, reviewContent, streetAddress, cityOrTown,
countyOrRegion, postcode, startDate, endDate, landlordOrAgent, rating, slug } = this.state;
this.setState({
slug: streetAddress
})
// Creating batch to submit to multiple Firebase collections in one operation
var batch = this.props.firebase.db.batch();
var propertyRef = this.props.firebase.db.collection("property").doc();
var reviewRef = this.props.firebase.db.collection("reviews").doc();
batch.set(propertyRef, { streetAddress, cityOrTown,
countyOrRegion, postcode, slug,
uid });
batch.set(reviewRef, { startDate, endDate,
reviewTitle, reviewContent, rating,
uid });
batch.commit().then(() => {
this.setState({ ...INITIAL_STATE });
});
event.preventDefault();
};
Can anyone point me in the right direction or tell me what I'm doing wrong?

this.setState is a async function. So what you can do is calling a callback function after the state is updated.
this.setState({
slug: streetAddress
}, () => {
// Creating batch to submit to multiple Firebase collections in one operation
var batch = this.props.firebase.db.batch();
var propertyRef = this.props.firebase.db.collection("property").doc();
var reviewRef = this.props.firebase.db.collection("reviews").doc();
batch.set(propertyRef, {
streetAddress, cityOrTown,
countyOrRegion, postcode, slug,
uid
});
batch.set(reviewRef, {
startDate, endDate,
reviewTitle, reviewContent, rating,
uid
});
batch.commit().then(() => {
this.setState({ ...INITIAL_STATE });
});
event.preventDefault();
})

Related

Object Values Undefined in Post Request from ReactJS

I am making a single object out of three independently populated subsections of a form. They are independently populated because the data comes from three different sources:
1) backend api for name and address
2) third party api for telephone number
3) user executing onChange on other fields in the form
what I have noticed is if I submit the form with onChange being the last function executed, ie if entering a note or updating an email address, then the other two elements have some of the data removed specifically the first field of each source.
I have remedied this by creating an effect that basically runs a form control operation (basically resets the phone number) and doing this fixes the problem, but clearly I would not like to not have to rely on a a useState method and call it something its not for the sake of fixing a problem I don't understand. Here is some code.
Thanks!
const leadContext = useContext(LeadContext);
const { clearLiens, lien, setLien, letCall, number, clearNumber, addLead, postLogics } = leadContext;
useEffect(() => {
if (lien !== null) {
setRecord(lien);
}else {
setRecord({
name: '',
address:'',
city:'',
state:'',
zip:'',
plaintiff:'',
amount:''
});
}
}, [lien, leadContext]);
useEffect (()=>{
if(number !== null){
setCall({phone:number});
}else {
setCall({phone:''});
}
},[number, leadContext]);
const [ record, setRecord ] = useState({
name: '',
address:'',
city:'',
state:'',
zip:'',
plaintiff:'',
amount:'',
lienid:''
});
const [ call, setCall ] = useState({
phone: ''});
const [ open, setOpen ] = useState({
email:'',
lexId:'',
compliant:'filed',
filingStatus:'married',
cpa: 'cpa',
ssn:'',
noteText:''
});
const onChange = e => {
setRecord({...name, address, city, state, zip, plaintiff, amount, [e.target.name]: e.target.value });
setCall({...phone, [e.target.name]: e.target.value });
setOpen({...email, lexId, compliant, filingStatus, cpa, ssn, noteText, [e.target.name]: e.target.value});
}
const { name, address, city, state, zip, plaintiff, amount, lienid } = record
const { phone } = call
const { email, lexId, compliant, filingStatus, cpa, ssn, noteText } = open
const lead = {phone, name, address, city, state, zip, plaintiff, amount, lienid, email, lexId, compliant, filingStatus, cpa, ssn, noteText }
const clearLead = () => {
clearNumber();
setLien('');
setRecord({
name: '',
address:'',
city:'',
state:'',
zip:'',
plaintiff:'',
amount:'',
});
setCall({
phone: ''});
setOpen({
email:'',
lexId:'',
compliant:'filed',
filingStatus:'m',
cpa: 'cpa',
noteText:'',
ssn:''
});
}
const onSubmit = e => {
e.preventDefault();
addLead(lead);
clearAll();
};
const clearAll = () => {
clearLiens();
clearLead();
};
const onClick = e => {
letCall(number);
}
the letCall(number) is my hot fix of basically calling set state on one of the form fields. I cannot stack this into my on submit either, so it has to be done as a separate function.
const addLead = async lead => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const { phone, name, address, city, state, zip, plaintiff, amount, lienid, email, lexId, compliant, filingStatus, cpa, ssn, noteText } = lead
const noteId = uuidv4();
const notes = [{ id : noteId,
note : noteText,
notePostedBy: ''
}]
const steve = {phone, name, address, city, state, zip, plaintiff, amount, lienid, email, lexId, compliant, filingStatus, cpa, ssn, notes }
console.log(lead,'1');
console.log(steve,'1');
const res = await axios.post('/api/leads/', steve, config);
dispatch({
type: POST_LEAD,
payload: res.data
});
};
Looks like this has to do with the way you update your state values in onChange. Specifically, you write:
setRecord({...name, address, city, state, zip, plaintiff, amount, [e.target.name]: e.target.value });
But since name is not spreadable, it becomes an empty value, so the updated record state will not have a value for name (unless name was the changed input).
To fix this you can simplify your updating with:
const onChange = e => {
setRecord(prev => ({...prev, [e.target.name]: e.target.value}));
setCall(prev => ({...prev, [e.target.name]: e.target.value}));
setOpen(prev => ({...prev, [e.target.name]: e.target.value}));
}
However you probably also want to add some sort of check to update the correct state object so that the keys don't get put into all state objects. Something like:
const onChange = e => {
if (Object.keys(record).includes(e.target.name) {
setRecord(prev => ({...prev, [e.target.name]: e.target.value}));
}
// and so on...
}
Edit:
When using the callback version of set[State], the event will become null since the callback is called asynchronously (specifically at another time). To fix this you can either use:
e.persist();
at the top of the onChange function (however, probably not optimal in this case).
Or get name and value from e.target and passing them directly to the callback. For example:
const onChange = e => {
const { name, value } = e.target;
if (Object.keys(record).includes(name) {
setRecord(prev => ({...prev, [name]: value}));
}
// and so on...
}
This is probably the most appropriate solution here.

How to update a state variable in an array in React?

I have this state and I want to update the properties name and location in a dynamic way.
this.state = {
players: [{
name: '',
location: ''
},
{
name: '',
location: ''
}]
}
The idea is that you can click a button to add another/remove a player. Per player, these input fields should appear (which I achieved), but I'm unable to update the state on change.
Preferably something like this (but I'm unable to make it work for this particular case). Unless there's a better way to achieve this of course (I'm rather new in React).
this.handleChange = (event) => { let obj = {...this.state.obj }; obj [event.target.name] = event.target.value; this.setState({obj }); }
Any help will be appreciated!
you can add temporary id value to the player's list and on change pass the target id and the field changes, and change the state accordingly
const onPlayerChange = ({target : { id , name , vlaue}}) =>{
const newPlayersState = this.state.players.map(player=>{
if(player.id === id) return {...player,[name]:value}
return player;
})
this.setState({players:newPlayersState})
}
addPlayer(name, location) {
const players = {...this.state.players};
players.push({
name,
location
});
this.setState({
players
});
}
removePlayer(name) {
const players = {...this.state.players};
this.setState({
players: players.filter(p => p.name != name)
});
}

pass table row object from table and edit it using Redx-Form

I'm trying to pass the table row object to Redux-Form to Edit the object values.
here some contents of ViewStudents.js file
handleEdit(e, student) {
e.preventDefault();
const { dispatch } = this.props;
dispatch(allActions.editStudents(this.state));
this.setState({
firstName: student.firstName,
lastName: student.lastName
});
this.props.history.push("/EditStudents");
}
<button onClick={e => this.handleEdit(e, student)}>Edit</button>
function mapStateToProps(state) {
const { students } = state.viewStudents;
return {
students
};
}
export default connect(mapStateToProps)(ViewStudents);
here some contents of EditStudents.js
constructor(student) {
super(student);
this.state = {
firstName: student.firstName,
lastName: student.lastName
};
}
handleSubmit(e) {
e.preventDefault();
const { dispatch } = this.props;
dispatch(allActions.editStudents(this.state));
this.setState({
firstName: "",
lastName: ""
});
this.props.history.push("/ViewStudents");
}
function mapStateToProps(state) {
const { student } = state.addStudents;
return {
initialValues: {
firstName: state.student.firstName,
lastName: state.student.lastName
}
};
}
export default reduxForm({
form: "EditForm",
validate,
mapStateToProps
})(EditStudents);
Problem is, this object values not passing to edit form, though I bind in mapStateToProps, initialValues and passed this object in constructor
how to bind this and pass properly the clicking object in a table row and edit/save that object
Few small issues I can see with this.
You shouldn't use state to select what item you wish to edit. Always think that a user will refresh the page at any item. Therefore you should use React Router Splat to pass a unique value/ID.
http://localhost:3000/EditStudents/1
To do this you need to add a unique id for each student when adding and then use the ID with the route.
<Route path="/EditStudents/:studentId" component={EditStudents} />
You can then read the ID and load on componentDidMount
componentDidMount() {
const { dispatch } = this.props;
var {studentId } = props.match.params;
dispatch( { type: "LOAD_STUDENT", studentId });
}
The use of refreshing the state (request, success) has the effect of clearing the data. Therefore any initial state is lost.
Note, you are also using a Service to load in data.
Really you should be using an async thunk to load data into redux.
You should only get data from and to redux and then use middleware to persist the data.
https://github.com/reduxjs/redux-thunk
https://codesandbox.io/s/MQnD536Km

Want to set a state object within a for loop: React+Typescript

I have a state object which is basically an array of objects. I am doing a fetch call, and the array of objects returned by it should be set to the state object. But It I am unable to do the same.
Any alternative?
Where am I going wrong?
Help would be appreciated
this.state = {
accounts: [{firstname:"",lastname:"",age:"",id:""}],
};
async componentDidMount(){
let useraccounts = await this.fetchAccounts();
if(useraccounts.length > 0)
{
for(let i=0;i<useraccounts.length;i++)
{
account = {firstname:useraccounts[i].firstname,lastname:useraccounts[i].lastname,age:useraccounts[i].age,id:useraccounts[i].id};
this.setState((prevState) => ({ accounts: [ ...prevState.accounts, account ]}));
}
}
}
fetchAccounts = async() => {
//fetch API call which will return all users accounts
}
You don't need to call setState for each account individually, just do a single call with all of the accounts:
async componentDidMount(){
try {
let useraccounts = await this.fetchAccounts();
let newAccounts = useraccounts.map(({firstname, lastname, age, id}) => ({firstname, lastname, age, id}));
this.setState(({accounts}) => ({accounts: [...accounts, ...newAccounts]}));
} catch (e) {
// Do something with the error
}
}
That gets the accounts, creates a new array with just the relevant properties (what you were doing in your for loop), then calls setState to add the new accounts.
Note that I'm doing destructuring in the parameter lists of the map callback and the setState callback to pick out only the parts of the objects they receive that I want. For instance, this:
let newAccounts = useraccounts.map(({firstname, lastname, age, id}) => ({firstname, lastname, age, id}));
is the same as this:
let newAccounts = useraccounts.map(account => {
return {
firstname: account.firstname,
lastname: account.lastname,
age: account.age,
id: account.id
};
});
It's just a bit more concise.
Of course, if you don't really need to copy the objects, you could just use the accounts you got from fetchAccounts directly:
async componentDidMount(){
try {
let useraccounts = await this.fetchAccounts();
this.setState(({accounts}) => ({accounts: [...accounts, ...useraccounts]}));
} catch (e) {
// Do something with the error
}
}
Some notes on your original code:
You're breaking one of the rules of promises by using an async function where nothing is going to handle the promise it returns: You need to handle any errors that occur, rather than ignoring them. That's why I added a try/catch.
If you're doing for(let i=0;i<useraccounts.length;i++), there's no need for if(useraccounts.length > 0) first. Your loop body won't run if there are no accounts.

Using componentWillUpdate with switch statements

I am using React-Native and React-Native-Firebase and am trying to have my Events component make a different Firebase query (and then update redux store) depending what the value of the activityType prop is.
Here is the parent component which is working just fine. It updates state.eventType when I change the dropdown value and passes the value into <Events />.
let eventTypes = [{value: 'My Activity'}, {value: 'Friend Activity'}, {value: 'All Activity'}];
state = {
showEventFormModal: false,
eventType: 'Friend Activity'
}
<View style={styles.container}>
<Dropdown
data={eventTypes}
value={this.state.eventType}
containerStyle={{minWidth: 200, marginBottom: 20}}
onChangeText={val => this.setState({eventType: val})}
/>
<Events activityType={this.state.eventType}/>
</View>
And here is Events component. Using a Switch statement to determine which activityType was passed into props. The issue I am having is an infinite loop because within each case statement I am dispatching the action to update the store which causes a rerender and the componentWillUpdate() to retrigger. What I am trying to understand is what the optimal way to handle this problem is? Because clearly my method now does not function properly. Is there a common react pattern to achieve this?
// GOAL: when this components props are updated
// update redux store via this.props.dispatch(updateEvents(events))
// depending on which type of activity was selected
componentWillUpdate() {
let events = [];
switch(this.props.activityType) {
case 'Friend Activity': // get events collections where the participants contains a friend
// first get current users' friend list
firebase.firestore().doc(`users/${this.props.currentUser.uid}`)
.get()
.then(doc => {
return doc.data().friends
})
// then search the participants sub collection of the event
.then(friends => {
firebase.firestore().collection('events')
.get()
.then(eventsSnapshot => {
eventsSnapshot.forEach(doc => {
const { type, date, event_author, comment } = doc.data();
let event = {
doc,
id: doc.id,
type,
event_author,
participants: [],
date,
comment,
}
firebase.firestore().collection('events').doc(doc.id).collection('participants')
.get()
.then(participantsSnapshot => {
for(let i=0; i<participantsSnapshot.size;i++) {
if(participantsSnapshot.docs[i].exists) {
// if participant uid is in friends array, add event to events array
if(friends.includes(participantsSnapshot.docs[i].data().uid)) {
// add participant to event
let { displayName, uid } = participantsSnapshot.docs[i].data();
let participant = { displayName, uid }
event['participants'].push(participant)
events.push(event)
break;
}
}
}
})
.then(() => {
console.log(events)
this.props.dispatch(updateEvents(events))
})
.catch(e => {console.error(e)})
})
})
.catch(e => {console.error(e)})
})
case 'My Activity': // get events collections where event_author is the user
let counter = 0;
firebase.firestore().collection('events').where("event_author", "==", this.props.currentUser.displayName)
.get()
.then(eventsSnapshot => {
eventsSnapshot.forEach(doc => {
const { type, date, event_author, comment } = doc.data();
let event = {
doc,
id: doc.id,
type,
event_author,
participants: [],
date,
comment,
}
// add participants sub collection to event object
firebase.firestore().collection('events').doc(event.id).collection('participants')
.get()
.then(participantsSnapshot => {
participantsSnapshot.forEach(doc => {
if(doc.exists) {
// add participant to event
let { displayName, uid } = doc.data();
let participant = { displayName, uid }
event['participants'].push(participant)
}
})
events.push(event);
counter++;
return counter;
})
.then((counter) => {
// if all events have been retrieved, call updateEvents(events)
if(counter === eventsSnapshot.size) {
this.props.dispatch(updateEvents(events))
}
})
})
})
case 'All Activity':
// TODO
// get events collections where event_author is the user
// OR a friend is a participant
}
}
Updating the store is best to do on the user action. So, I'd update the store in the Dropdown onChange event vs. in the componentWillUpdate function.
I've figured out what feels like a clean way to handle this after finding out I can access prevProps via componentDidUpdate. This way I can compare the previous activityType to the current and if they have changed, then on componentDidUpdate it should call fetchData(activityType).
class Events extends React.Component {
componentDidMount() {
// for initial load
this.fetchData('My Activity')
}
componentDidUpdate(prevProps) {
if(this.props.activityType !== prevProps.activityType) {
console.log(`prev and current activityType are NOT equal. Fetching data for ${this.props.activityType}`)
this.fetchData(this.props.activityType)
}
}
fetchData = (activityType) => {
//switch statements deciding which query to perform
...
//this.props.dispatch(updateEvents(events))
}
}
https://reactjs.org/docs/react-component.html#componentdidupdate

Resources