Accessing state from another component - reactjs

I am attempting to access state from another component in the correct way. From what I understand is you cannot access state directly but the state should be lifted. For my purpose I am trying to access the name of the user from my profile component in my form component with localStorage.
My Profile Component ....
import React, { Component } from 'react';
import NavigationBar from './NavigationBar'
export default class Profile extends Component {
documentData;
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleFormSubmit = this.handleFormSubmit.bind(this);
this.state = {
name: '',
email: '',
phone: '',
address1: '',
address2: '',
addresscitystate: '',
addresszip: ''
}
}
handleChange= (e)=> {
this.setState({[e.target.name]:e.target.value});
}
// on form submit...
handleFormSubmit(e) {
e.preventDefault()
localStorage.setItem('document',JSON.stringify(this.state));
alert ('Profile has been updated')
}
// React Life Cycle
componentDidMount() {
this.documentData = JSON.parse(localStorage.getItem('document'));
if (localStorage.getItem('document')) {
this.setState({
name: this.documentData.name,
phone: this.documentData.phone,
email: this.documentData.email,
address1: this.documentData.address1,
address2: this.documentData.address2,
addresscitystate: this.documentData.addresscitystate,
addresszip: this.documentData.addresszip,
})
} else {
this.setState({
name: '',
phone: '',
email: '',
address1: '',
address2: '',
addresscitystate: '',
addresszip: ''
})
}
}
render() {
return (
<div className="container">
<NavigationBar />
<div>
<h1 className='profile-title'> Profile </h1>
</div>
<form onSubmit={this.handleFormSubmit}>
<div className="form-group">
**<input type="text" name="name" className="form-control" value={this.state.name} onChange={this.handleChange} />
<label>Name</label>**
</div>
....
My Form Component....
import {useState, useEffect} from 'react'
const Form = () => {
//initial state
const [transaction, setTransaction] = useState({
description: '',
amount: ''
})
const [list, setList] = useState(
JSON.parse(localStorage.getItem('list')) || []
)
const [balance, setBalance] = useState('')
const [income, setIncome] = useState(
JSON.parse(localStorage.getItem('income'))
)
const [expense, setExpense] = useState(JSON.parse(localStorage.getItem('expense')))
//updates based onChange value
const updateBalance = (e) => {
setTransaction({
...transaction,
[e.target.name]:
e.target.type === 'number' ? parseInt(e.target.value) : e.target.value
})
}
//identify if transaction is income/expense
const plusMinus = () => {
transaction.amount > 0
? setIncome(income + transaction.amount)
: setExpense(expense + transaction.amount)
}
// updates balance after transaction is added
const getBalance = () => {
const amounts = list.map(i => i.amount);
const money = amounts.reduce((acc, item) => (acc += item), 0).toFixed(2);
setBalance(money)
}
useEffect(() => {
getBalance()
localStorage.setItem('list', JSON.stringify(list))
localStorage.setItem('income', JSON.stringify(income))
localStorage.setItem('expense', JSON.stringify(expense))
}, [])
//clear transaction list
const clearBudget = () => {
localStorage.clear();
alert ('Balance has been cleared, Please Refresh Page')
}
const onSubmit = e => {
e.preventDefault();
setList([transaction, ...list])
plusMinus()
setTransaction({ description: '', amount: ''})
}
return (
**<div>
<div className='totals'>
<h2 className='balance'> Hello User, Your Current Balance </h2>
<h3> ${balance} </h3>
</div>**
...
Any advise on how to start would be appreciated!
Thanks.

Since you want to make your user accessible within your whole app passing it down to your component is not advisable as it'll cause prop drilling which is not a good practise. To avoid that problem then you should consider using a built-in feature of react called ContextAPI or any other state management library like Redux of Flux. But since your app is not complex then, ContextApi would be a good choice.

When you need to access state from multiple components you should either use Context API or a State Management Library such as Redux. Please note that Redux is used mostly in bigger applications, the ContextAPI should be more than enough for your use case.

Related

Warning: A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined

When I add more than two inputs i get an warning about controlled component changing to uncontrolled component. Also when you add value on the second input the first input also gets inputted.
Warning :A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component.
import MyForm from './Components/MyForm';
import uniqid from "uniqid";
class App extends Component {
constructor (props) {
super(props);
this.state = {
person: {
firstName : '',
secondName : '',
profession : '',
city: '',
state: '',
phone:'',
email: '',
jobTitle: '',
employer: '',
startDate: '',
endDate: '',
schoolName: '',
schoolLocation: '',
gradStartDate: '',
gradEndDate: '',
id: uniqid()
},
resume:[]
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange = (event) => {
this.setState({
person : {
firstName: event.target.value,
seconfName: event.target.value,
}
});
}
handleSubmit = (event) => {
event.preventDefault();
return alert(this.state.person.firstName + this.state.person.secondName)
}
render () {
const { person, resume} = this.state;
return (
<div>
<MyForm person = {person} resume = {resume} handleChange={this.handleChange} handleSubmit={this.handleSubmit}/>
</div>
)
}
}
class MyForm extends React.Component {
render() {
const {person, handleChange,handleSubmit } = this.props;
console.log(person,handleChange)
return (
<div>
<form onSubmit = { handleSubmit }>
<div>
<label>
First Name:
<input type="text" value = { person.firstName }
onChange = { handleChange } name="firstName" />
</label>
</div>
<div>
<label>
Second Name:
<input type="text" value = { person.secondName }
onChange = { handleChange } name="secondName" />
</label>
</div>
<input type ="submit" value = "Submit" />
</form>
</div>
)
}
}
export default MyForm;
The problem is that you are affecting the same value to both firstName and secondName, you should handle the change of the first name separatly from the second name, and also you have a mistake of naming in handleChange, you are writing seconfName instead of secondName.
You can try this:
import MyForm from './Components/MyForm';
import uniqid from "uniqid";
class App extends Component {
constructor (props) {
super(props);
this.state = {
person: {
firstName : '',
secondName : '',
profession : '',
city: '',
state: '',
phone:'',
email: '',
jobTitle: '',
employer: '',
startDate: '',
endDate: '',
schoolName: '',
schoolLocation: '',
gradStartDate: '',
gradEndDate: '',
id: uniqid()
},
resume:[]
}
this.handleChangeFirstName = this.handleChangeFirstName.bind(this);
this.handleChangeSecondName = this.handleChangeSecondName.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChangeFirstName = (event) => {
this.setState({
person : {
...this.state.person,
firstName: event.target.value,
}
});
}
handleChangeSecondName = (event) => {
this.setState({
person : {
...this.state.person,
secondName: event.target.value,
}
});
}
handleSubmit = (event) => {
event.preventDefault();
return alert(this.state.person.firstName + this.state.person.secondName)
}
render () {
const { person, resume} = this.state;
return (
<div>
<MyForm person = {person} resume = {resume} handleChangeFirstName={this.handleChangeFirstName} handleChangeSecondName={this.handleChangeSecondName} handleSubmit={this.handleSubmit}/>
</div>
)
}
}
class MyForm extends React.Component {
render() {
const {person, handleChangeFirstName, handleChangeSecondName,handleSubmit } = this.props;
console.log(person,handleChange)
return (
<div>
<form onSubmit = { handleSubmit }>
<div>
<label>
First Name:
<input type="text" value = { person.firstName }
onChange = { handleChangeFirstName } name="firstName" />
</label>
</div>
<div>
<label>
Second Name:
<input type="text" value = { person.secondName }
onChange = { handleChangeSecondName } name="secondName" />
</label>
</div>
<input type ="submit" value = "Submit" />
</form>
</div>
)
}
}
export default MyForm;

How to update deep objects in a nest react

I need help making a handler that will update the nested object state.
Here is the sample code
const [state, setState] = useState({
firstName: '',
lastName: '',
permanentAddress: {
street: '',
city: ''
},
currentAddress: {
street: '',
city: ''
}
});
const handler = (e) => {
setState({...state, [e.target.name]: e.target.value})
}
Then I import a custom textfield
export const CustomTextField = (props) => {
const { handler, ...rest } = props
return (
<TextField {...rest} onChange={handler} />
)
}
Now I know this will only create a new key named street with the value instead of updating the one already in the state. I have also thought of just adding a prop that will indicate whether it is nested, something like
<CustomTextField cat='permanentAddress' name='street' handler={handler} />
Then I will change the handler to something like this, passing along cat in the parameter
const handler = (cat, e) => {
if (cat) {
const category = {...state[cat]}
category[e.target.name] = e.target.value
setState({...state, [cat]: category})
} else {
//non-nested update here
}
}
Is there a better way to do this? Thank you!

How to manage state in a signup flow (ReactJS)

So, I'm building a signup and payment flow using React/NextJS, AWS Cognito, and Stripe. I have created a class component to manage state and the overall flow with child components handling the individual steps. The problem I am having is managing the state so that each component can access the state and make changes to it as well as use functions that are in the parent component and not within the child components. Keep note that the child components are functional and not classes. Here is the flow step by step:
Parent Component
import React, { Component } from "react";
import Layout from "../../components/layouts/mainlayout/mainlayout";
//import { handleSubmit } from "../../components/auth/awshelper";
import { Auth } from "aws-amplify";
import FlowStep1 from "../../components/auth/clubflow/step1";
import FlowStep2 from "../../components/auth/clubflow/step2";
import FlowStep3 from "../../components/auth/clubflow/step3";
//import { Router } from "next/router";
const meta = {
title: "Join the club!",
description: "Sign up and enjoy the best travel experiences ever."
};
class ClubFlow extends Component {
state = {
firstname: "",
lastname: "",
username: "",
phonenumber: "",
password: "",
confirmpassword: "",
step: 1,
go: false,
plan: null,
member: false,
errors: {
cognito: null,
blankfield: false,
passwordmatch: false
}
};
/*Unnecessary functions:
getStripeFunc = func => {
return func;
};
*/
clearErrorState = () => {
this.setState({
errors: {
cognito: null,
blankfield: false,
passwordmatch: false
}
});
};
choosePlan = e => {
setState({ plan: e.target.value });
};
onInputChange = event => {
//const [firstname, setFirstname] = useState("");
this.setState({
[event.target.id]: event.target.value
});
document.getElementById(event.target.id).classList.remove("is-danger");
};
handleSubmit = async event => {
event.preventDefault();
// Form validation
this.clearErrorState();
const error = null; /*Validate(event, this.state)*/
if (error) {
console.log("an validation erreor was fired");
this.setState({
errors: { ...this.state.errors, ...error }
});
}
// AWS Cognito integration here
const { firstname, lastname, phonenumber, username, password } = this.state;
try {
const signUpResponse = await Auth.signUp({
username,
password,
attributes: {
name: firstname,
family_name: lastname,
phone_number: phonenumber
}
});
if (signUpResponse) {
this.setState({ step: 2, go: true });
}
} catch (error) {
let err = null;
!error.message ? (err = { message: error }) : (err = error);
this.setState({
errors: { ...this.state.errors, cognito: error }
});
}
};
//This function sets which plan the user selects. Eventually this value will be passed down to the checkout form as a prop.
goNext = async () => {
try {
if (this.state.step === 1) {
this.handleSubmit(event);
if (
this.state.step <
3 /*&&
this
.signUpResponse /*Remove the previous comment in order for the next button to work correctly.*/
) {
this.setState(state => ({
step: state.step + 1
}));
}
} else if (this.state.step === 2) {
this.setState(state => ({
step: state.step + 1
}));
} else {
this.getStripeFunc();
}
} catch (error) {
console.log("You have a problem");
}
};
goBack = () => {
if (this.state.step > 1) {
this.setState(state => ({
step: state.step - 1
}));
}
};
render() {
let stage;
if (this.state.step === 1) {
stage = (
<FlowStep1 state={this.state} onInputChange={this.onInputChange} />
);
} else if (this.state.step === 2) {
stage = <FlowStep2 state={this.state} choosePlan={this.choosePlan} />;
} else {
stage = <FlowStep3 state={this.state} />;
}
//console.log(this.state.plan);
return (
<Layout class={"ovh"} meta={meta}>
A user is able to signup and creates an account by entering their name, email, phone, and password.
import FormErrors from "../FormErrors";
import { Auth } from "aws-amplify";
import React, { Component } from "react";
/*import {
backspacerUP,
backspacerDOWN
} from "../../utilities/phonenumbervalidator";
const onKeyUp = backspacerUP;
const onKeyDown = backspacerDOWN;
Might need to use a plugin: https://www.npmjs.com/package/react-phone-input-auto-format*/
class FlowStep1 extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<section className="section auth">
<div className="container">
<h1>Register</h1>
<FormErrors /*formerrors={state.errors}*/ />
A user is able to choose a plan which needs to pass the plan that the user chooses onto step 3:
import React, { useState } from "react";
const FlowStep2 = (props) => {
const plan
const [plan, setPlan] = useState(0);
return (
<div className="mb-4">
<h2>this is what is in state:{plan}</h2>
<input
type="radio"
name="plan"
value="1"
readOnly
//checked={this.state.plan === 1}
onChange={() => setPlan(1)}
/>
<input
type="radio"
name="plan"
value="2"
readOnly
onChange={() => setPlan(2)}
className="ml-3"
/>
<input
type="radio"
name="plan"
value="3"
//checked={true}
onChange={() => setPlan(3)}
className="ml-3"
/>
</div>
);
};
export default FlowStep2;
A user is able to enter their credit card info and submit it. Once the charge is okay'd by stripe and charged based on the plan the user choosed in step two they will get a confirmation email and will be navigated to the welcome page. The selected plan will need to be eventually passed on to an express server that will handle the charge and pass it on to stripe for processing charging based on what plan they selected.
import ClubPayWrap from "../../payments/clubpaywrap";
const FlowStep3 = props => {
return (
<div>
<h2>Payment</h2>
<ClubPayWrap flowstate={props.state} />
</div>
);
};
export default FlowStep3;
Please, let me know if I need to give more detail.
For managing states in react based application there are different approaches. You need to store the logged in user data in a global store in which you can access in different routes and components.
The most used store manager to react is Redux.In the latest version, it provided some hooks. The redux hooks made it so easy to access to your store.
The next one is mobX. I personally haven't used it yet and I can not help you with that!
The other one is Context Api which is provided by react itself. React added some hooks to make it easy to use.
There is another technology provided for managing states in React called Rxjs which is a little bit different and maybe is not good for your case.
In the end I think number one is the perfect solution for you and then number 3.

React. How to populate the form after the login is completed

I have two react components(SignupDetails.js, BasicInformation.js). SignupDetails is obviously responsible to signup the user, it will get the user information from the form and submit it to the server through axios.
On the server side(backend), if the user already exist, the server will then collect the remain information, such as first name, middle name, etc and then send it back to be collected on the client side.
Back to the client side, now the remain user information which has been returned is then stored in the central store using the redux-reducers and the page is then "redirected" to BasicInformation.js
Now on the BasicInformation.js, mapStateToPros is then executed so I will do have access to the information that has been stored on the central store but then it becomes the problem: I am struggling to show the remain information. This sounds pretty simple but I tried many things on the componentDidUpdate and on the render method but without being successful.
Please find the code below and let me know if you any ideas.
import React, {Component} from 'react';
import classes from './SignUp.module.css';
import {connect} from 'react-redux'
import * as actions from '../../store/actions/index';
import Spinner from '../../components/UI/Spinner/Spinner'
class SignupDetails extends Component {
constructor(props) {
super(props)
this.state = {
email: '',
password: ''
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
postDataHandler = () => {
const data = {
username: this.state.email,
email: this.state.email,
password: this.state.password
};
this.props.onSignupDetails(data);
}
componentDidMount() {
this.props.history.push({pathname: '/signup_details/'})
}
componentDidUpdate() {
if (this.props.signup_details_success)
this.props.history.push({pathname: '/basic_information/'})
}
render() {
let errorMessage = null;
if (this.props.error) {
errorMessage = (
<p>{this.props.signup_details_response}</p>
);
}
return (
<div>
<Spinner show={this.props.loading}/>
<div className={classes.SignUpForm}>
{errorMessage}
<h3>Take your first step.</h3>
<div>
<input
key="email"
name="email"
value={this.state.email}
onChange={this.handleInputChange}/>
</div>
<div>
<input
key="password"
name="password"
value={this.state.password}
onChange={this.handleInputChange}/>
</div>
<button className={classes.OkButton} onClick={this.postDataHandler}>Next</button>
</div>
</div>
);
}
}
const mapStateToProps = state => {
return {
signup_details: state.signup.signup_details,
signup_details_success: state.signup.signup_details_success,
signup_details_response: state.signup.signup_details_response,
loading: state.signup.loading,
error: state.signup.error
};
};
const mapDispatchToProps = dispatch => {
return {
onSignupDetails: (signup_details) => dispatch(actions.signupDetails(signup_details))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(SignupDetails);
-----
import React, {Component} from 'react';
import classes from './SignUp.module.css';
import {connect} from 'react-redux'
import * as actions from '../../store/actions/index';
import Spinner from '../../components/UI/Spinner/Spinner'
class BasicInformation extends Component {
constructor(props) {
super(props)
this.state = {
first_name: '',
middle_name: '',
last_name: '',
mobile_number: '',
fund_balance: ''
}
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
postDataHandler = () => {
const data = {
username: this.state.email,
email: this.state.email,
first_name: this.state.first_name,
middle_name: this.state.middle_name,
last_name: this.state.last_name,
mobile_number: this.state.mobile_number,
sfunds: [{balance: this.state.fund_balance}]
};
this.props.onSignupBasicInformation(data);
}
componentDidUpdate() {
if (this.props.signup_basic_information_success)
this.props.history.push({pathname: '/personal_information/'})
}
render() {
let errorMessage = null;
if (this.props.error) {
errorMessage = (
<p>{this.props.signup_basic_information_response}</p>
);
}
return (
<div>
<Spinner show={this.props.loading}/>
<div className={classes.SignUpForm}>
{errorMessage}
<h3>First we need some basic information</h3>
<div><input
key="first_name"
name="first_name"
value={this.state.first_name}
onChange={this.handleInputChange}/></div>
<div><input
key="middle_name"
name="middle_name"
value={this.state.middle_name}
onChange={this.handleInputChange}/></div>
<div><input
key="last_name"
name="last_name"
value={this.state.last_name}
onChange={this.handleInputChange}/></div>
<div><input
key="mobile_number"
name="mobile_number"
value={this.state.mobile_number}
onChange={this.handleInputChange}/></div>
<div><input
key="fund_balance"
name="fund_balance"
value={this.state.fund_balance}
onChange={this.handleInputChange}/></div>
<button className={classes.OkButton} onClick={this.postDataHandler}>Next</button>
</div>
</div>
);
}
}
const mapStateToProps = state => {
return {
signup_details_success: state.signup.signup_details_success,
signup_details_response: state.signup.signup_details_response,
signup_basic_information: state.signup.signup_basic_information,
signup_basic_information_success: state.signup.signup_basic_information_success,
signup_basic_information_response: state.signup.signup_basic_information_response,
loading: state.signup.loading,
error: state.signup.error
};
};
const mapDispatchToProps = dispatch => {
return {
onSignupBasicInformation: (basic_information) => dispatch(actions.signupBasicInformation(basic_information))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(BasicInformation);
In case someone wonder, I managed to solve this problem through componentDidMount to set the local state.
componentDidMount(){
if(this.props.smsf_member_details) {
this.setState({
first_name: this.props.smsf_member_details.first_name,
last_name: this.props.smsf_member_details.last_name,
middle_name: this.props.smsf_member_details.middle_name,
});
}
}

Property not found in props of React element `form`

Hi i'm having problems with Flow and React.
I'm getting these errors and I want to get rid of them. What am I missing I have search everywhere. I understand that the props are missing but I can't seem to find where to define them.
Flow: property className. Property not found in props of React element form
Flow: property onSubmit. Property not found in props of React element form
export default class LoginForm extends React.Component {
_submitForm: Function;
constructor(props?: {}) {
super(props);
this.state = {
form: {
email: '',
password: '',
},
errors: {
_form: "",
email: "",
password: ""
}
};
this._submitForm = this._submitForm.bind(this);
}
_handleValues(param: string, value?: string) {
let obj = this.state;
obj['form'][param] = value;
this.setState(obj);
}
_submitForm(event: Event) {
this._clearErrors(event);
let form = this.state.form;
AxiosQueue
.post({
url: LINK.AUTHENTICATE,
data: form
})
.then(({data}) => {
if (!data.success) {
return;
}
})
.catch((response) => {
console.error(response);
});
}
render() {
const {errors, form} = this.state;
const user = UserStore.getUser();
const formText = FORM_TEXT[user.language || "en_AU"];
return (
<form className="form-inline" onSubmit={this._submitForm}>
{errors._form}
<InputEmail id="email" error={errors.email} value={form.email} callback={this._handleValues}/>
<InputPassword id="password" error={errors.password} value={form.password}
callback={this._handleValues}/>
<button type="submit" className="btn btn-default">{formText.LOGIN}</button>
</form>
);
}
}
There is conflict with your variable name form in Syntex const {errors, form} = this.state; and form component. Solution is to give some other name in this.state. Like
this.state = {
formValidation:{
//Validation properties
}
}
And consume so that will remove conflict
const {formValidation} = this.state

Resources