React Context API and component methods [duplicate] - javascript

This question already has answers here:
Access React Context outside of render function
(5 answers)
Closed 3 years ago.
I've followed a few online examples, where they have a counter and an increment function in Context, and on a distant component, call the increment method and the results shows. All great, but ... I am trying to expand on this and create a login box, that sets an isAthenticated flag.
I have a very basic context:
import React from 'react';
const Context = React.createContext();
export class Provider extends React.Component {
state = {
isAuthenticated: false,
user: {
name: "Craig",
email: "craig#here.com"
},
changeEmail: (newEmail) => {
let user = this.state.user;
user.email = newEmail;
console.log(user);
this.setState({ user: user})
},
changeAuthenticated: () => {
this.setState ({ isAuthenticated: !this.state.isAuthenticated });
}
}
render() {
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
)
}
}
export const Consumer = Context.Consumer;
In it I allow the user to change email, and change isAuthenticated state.
My component (Remove style stuff) looks like this:
import React from 'react';
import { Input, Label, Button, Container } from 'reactstrap';
import { Consumer } from '../context';
class Login extends React.Component {
render() {
return (
<Consumer>
{value => {
return (
<Container style={containerStyle} >
<div style={loginBoxStyle}>
<div>
<h3>Login</h3>
</div>
<div style={loginBoxFieldsStyle}>
<div style={loginBoxFieldStyle}>
<div style={loginBoxLabelStyle}>
<Label for="email">Email:</Label>
</div>
<div style={loginBoxLabelStyle}>
<Input type="email" name="email" id="email" placeholder="Your Email" value={value.user.email} onChange={e=>value.changeEmail(e.target.value)} />
</div>
</div>
</div>
<div style={loginBoxFieldsStyle}>
<div style={loginBoxFieldStyle}>
<div style={loginBoxLabelStyle}>
<Label for="password">Password:</Label>
</div>
<div style={loginBoxLabelStyle}>
<Input type="password" name="password" id="password" placeholder="Your Password" />
</div>
</div>
</div>
<div style={loginBoxButtonStyle}>
<Button color="info" onClick={value.changeAuthenticated}>Login</Button>
</div>
</div>
</Container>
)}
}
</Consumer>
)
}
}
export default Login;
So when I change the email, the Context state is updated. And when I click the Login button, for now, it simply toggles IsAuthenticated.
I don't want the state to update as I type in the email box. I'd prefer to update the state when the Login button is clicked. So I feel I need a local component state, or something, which updates that state when I edit the data in the text boxes. And then updates the Context when I click Login.
But... How do I set up the state? 'values' (from context) is only available inside the Render. I need to set my component state outside of the render. So how would I go about achieving this?
My login button onClick should also fire a local method which has all the validation etc, and then update my route to redirect to a page on success. But then it needs access to the Context.UpdateMethod - from outside of the tags. Not sure how to achieve this.

You should probably just create a sub-component and then use the props to initialize the state.
class Login extends React.Component {
render() {
return (
<Consumer>
{value => (
<Container style={containerStyle}>
<SubComponent
changeAuthenticated={value.changeAuthenticated}
// ...etc

Related

Is there a way to get a React component's internal values when I click a button on the parent?

Suppose I have a component like this -
const MyForm = ({ formId }) => (
<div>
<input type="text" placeholder="Full name"></input>
<input type="text" placeholder="Email"></input>
</div>
)
export default MyForm;
And then I have my App.js like so -
import React from "react";
import MyForm from "./MyForm";
const App = () => (
<div id="app">
<MyForm formId="formOne"></MyForm>
<MyForm formId="formTwo"></MyForm>
<button onClick={
() => {
// Here, when the user clicks the button,
// I want to get values of both the textboxes,
// from both the component instances
}
}>Submit</button>
</div>
)
export default App;
So basically, what I want is - when the button is clicked, I want to be able to retrieve the values of the textboxes. One way to do this is to raise an event from inside MyForm.js so that every text change is bubbled up to the parent via a callback function prop, but that feels too cumbersome, especially if the form has a lot of fields. Is there any simple or direct way to do this? Do I need to involve global state management tools like Redux?
State inside a component is specific only to that component, the parent , children or sibling of a component have no idea of the state. The only way to communicate the value from one component to another component is via props . In your case, what we need is a state to reside at the App which can then be passed as a prop to both the MyForm Components.
App.js
const [ formState, setFormState ] = useState({ formOne: {fullName: '', Email: ''}, formTwo: '' })
const updateFormValues = (formId, key, value) => {
const stateCopy = JSON.parse(JSON.stringify(formState));
const formToUpdate = stateCopy[formId];
formToUpdate[key] = value;
setFormState(stateCopy)
}
<MyForm formId="formOne" values={formState.formOne} updateFormValues={updateFormValues}></MyForm>
<MyForm formId="formTwo" values={formState.formTwo} updateFormValues={updateFormValues}></MyForm>
MyForm.js
const MyForm = ({ formId, values, updateFormValues }) => {
const onInputChange = (e, key) => {
updateFormValues(formId, key, e.target.value)
}
return(
<div>
<input type="text" onChange={(e) => onInputChange(e, 'fullName'} value={values.fullName} placeholder="Full name"></input>
<input type="text" onChange={(e) => onInputChange(e, 'email'} value={values.email} placeholder="Email"></input>
</div>
)}
export default MyForm;
To have access to data inside children components you need to lift the state to the parent component.
One-way data flow
Identify every component that renders something based on that state.
Find a common owner component (a single component above all the components that need the state in the hierarchy).
Either the common owner or another component higher up in the hierarchy should own the state.
If you can’t find a component where it makes sense to own the state, create a new component solely for holding the state and add it somewhere in the hierarchy above the common owner component.
One way to do this:
import React, { useState } from "react";
function MyForm(props) {
const { handleChange, values } = props;
return (
<div>
<label htmlFor="name">Your name</label>
<input
type="text"
placeholder="Full name"
onChange={handleChange}
value={values.name}
id="name"
name="name"
/>
<label htmlFor="email">Your email</label>
<input
type="email"
placeholder="Email"
onChange={handleChange}
value={values.email}
id="email"
name="email"
/>
</div>
);
}
function App() {
const [values, setValues] = useState({ name: "", email: "" });
const handleChange = (event) => {
const updatedForm = { ...values, [event.target.name]: event.target.value };
setValues(updatedForm);
};
return (
<div id="app">
<MyForm
formId="formOne"
values={values}
handleChange={handleChange}
></MyForm>
<button
onClick={() => {
console.log(values);
}}
>
Submit
</button>
</div>
);
}
export default App;

React - How to get state data from a child component?

I am just starting to learn react and I'm currently building a form, so far I've created a parent component 'Form' and I've separated the rest of the inputs as components and each component has its own state. My question is how to get that state data from the children's components and use it in the parent component 'Form' when submitting the form?
Here is my parent component
import React, { Component } from "react";
import Name from "components/Name";
import Email from "components/Email";
import Select from "components/Select";
import Bio from "components/Bio";
class Form extends Component {
handleSubmit = event => {
event.preventDefault();
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<div className="shape rectangle"></div>
<div className="shape triangle"></div>
<div className="shape circle"></div>
<Name />
<Email />
<Select />
<Bio />
<button type="submit" className="btn">
Submit
</button>
</form>
);
}
}
export default Form;
And one of the child component
import React, { Component } from "react";
// Create Name component for name && email inputs
class Name extends Component {
constructor(props) {
super(props);
this.state = {
firstName: "",
lastName: ""
};
}
// Handle first name input on change event
handleFirstNameChange = event => {
this.setState({
firstName: event.target.value
});
};
// Handle last name input on change event
handleLastNameChange = event => {
this.setState({
lastName: event.target.value
});
};
// Render labels and name inputs
render() {
const { firstName, lastName } = this.state;
return (
<div className="form-names">
<label htmlFor="firstName">Name</label>
<br/>
<input
type="text"
name="firstName"
value={firstName}
placeholder="First Name"
id="firstName"
onChange={this.handleFirstNameChange}
/>
<input
type="text"
name="lastName"
value={lastName}
placeholder="Last Name"
id="lastName"
onChange={this.handleLastNameChange}
/>
</div>
);
}
}
export default Name;
To accomplish this you would need to "Lift the state" up to a common parent component (known as ancestor component), in your case, this would be the <Form> component. Then you would pass down the values to each corresponding child component as props.
It would look something like this:
import React, { Component } from "react";
import Name from "./Name";
// More imports go here..
class Form extends Component {
state = {
firstName: "",
lastName: ""
};
handleSubmit = event => {
event.preventDefault();
};
// Handle first name input on change event
handleFirstNameChange = event => {
this.setState({
firstName: event.target.value
});
};
// Handle last name input on change event
handleLastNameChange = event => {
this.setState({
lastName: event.target.value
});
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<Name
firstName={this.state.firstname}
lastName={this.state.lastName}
handleFirstNameChange={this.handleFirstNameChange}
handleLastNameChange={this.handleLastNameChange}
/>
{/* More components go here.. */}
<p>Current state:</p>
{JSON.stringify(this.state)}
</form>
);
}
}
export default Form;
Working example
More info: Lifting state up from the official React docs.
you can use a single state in the parent component , and pass the firstname and lastname in the props, your code will became:
import React, { Component } from "react";
import Name from "components/Name";
import Email from "components/Email";
import Select from "components/Select";
import Bio from "components/Bio";
class Form extends Component {
state = {
firstName: '',
lastName: '',
}
onChange = (e) => {
this.setState({[e.target.name] : e.target.value})
}
handleSubmit = event => {
event.preventDefault()
}
render() {
return (
...
<Name onChange={this.onChange} firstName={this.state.firstName} lastName={this.state.lastName} />
...
<button type="submit" className="btn">
Submit
</button>
</form>
)
}
}
export default Form;
and your child component became:
import React, { Component } from "react";
// Create Name component for name && email inputs
class Name extends Component {
render() {
const { firstName, lastName , onChange} = this.props;
return (
<div className="form-names">
<label htmlFor="firstName">Name</label>
<br/>
<input
type="text"
name="firstName"
value={firstName}
placeholder="First Name"
id="firstName"
onChange={onChange}
/>
<input
type="text"
name="lastName"
value={lastName}
placeholder="Last Name"
id="lastName"
onChange={onChange}
/>
</div>
);
}
}
export default Name;
My thought is to do sth like the following, passing the data as props to the component. But it doesn't make sense to me as when you click the submit button, the form gather data from the parent component. It may better keep the form in the parent component and group all the input data in state.
import React, { Component } from "react";
import Name from "components/Name";
import Email from "components/Email";
import Select from "components/Select";
import Bio from "components/Bio";
class Form extends Component {
constructor(props){
super(props)
this.state = {
firstName = "",
lastName = "",
(and other input data you need)...
}
}
handleSubmit = event => {
event.preventDefault();
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<div className="shape rectangle"></div>
<div className="shape triangle"></div>
<div className="shape circle"></div>
<Name firstName={this.state.firstName} lastName={this.state.lastName}/>
<Email (inputData as props)/>
<Select (inputData as props)/>
<Bio (inputData as props)/>
<button type="submit" className="btn">
Submit
</button>
</form>
);
}
}
export default Form;
You could either use something like Context or Redux, which would allow you to create a universal store and retrieve state from any component or create methods in your parent component, pass them down to it's children, and invoke them from within that child component. For example, you could have handleFirstNameChange in Form and pass it down to Name. In doing so you'd now keep props like name in Form as well and pass that down as well.

Component not re-rendered after updating the state (mapStateToProps called)

I'm having a trouble updating the state. I'm using redux so the state is updated on the reducer level. After updating the state from another component a new state is returned with new data. mapStateToProps is called but the component is not re-rendering.
This is my component
class Users extends Component {
render() {
console.log("RENDERING ------");
const usernames= this.props.usernames.map((username, key) => {
return (<div key={key} className="card mt-2">
<div className="card-body">
{username}
</div>
</div>)
})
return (
<div data-spy="scroll" data-target="#navbar-example3" data-offset="0">
{usernames}
</div>
)
}
}
const mapStateToProps = state => {
console.log("STATE", state);
return {
usernames: state.usernames.data
}
}
export default connect(mapStateToProps, null)(Users);
when loading the component the usernames are displayed. but adding a new username from another component the mapStateToProps is called but the component is not re-rendered.
Parent Component
class Parent extends Component {
render() {
return (
<div className="container">
<div className="row">
<div className="col">
<Editor />
</div>
<div className="col">
<Users />
</div>
</div>
</div>
)
}
This is the editor component where I'm dispatching the action
class Editor extends Component {
state = {
user: ""
}
handleChange = event => {
this.setState({ user: event.target.value });
}
onSubmit = e => {
e.preventDefault();
this.props.addUser(this.state.user);
}
render() {
return (
<form onSubmit={this.onSubmit}>
<div className="form-group">
<label htmlFor="exampleFormControlTextarea1">User</label>
<textarea className="form-control" id="exampleFormControlTextarea1" rows="3" value={this.state.user} onChange={this.handleChange} ></textarea>
</div>
<button type="submit" className="btn btn-primary mb-2">Submit</button>
</form>
)
}
}
const mapDispatchToProps = dispatch => {
return {
addUser: (user) =>
dispatch(addUser(user))
}
}
export default connect(null, mapDispatchToProps)(Editor);
After trying and searching online. I discovered that the problem was that the component re-render if mapStateToProps returns a different value from the last call. For my case I was handling my state on a mutable way. I was using push to add new user to the state on the reducer. The solution is to do it in the immutable way using concat:
const us = [];
us.push(action.results.user);
return { ...state, users: state.users.concat(us) };
You are mapping the usernames prop in your mapStateToProps, but not making use of it in your component. You are in fact creating a new variable called usernames and assigning it to the result of a map on this.props.messages.
Forgive me if I'm not understanding what you're trying to do, but it looks like you should just make use of this.props.usernames, and then when you update it through a dispatch, the prop will update and in turn the component will re-render.

Reusable form fields in React

If i have the following dialog/modal:
<Modal
open={this.state.createAccountModalOpen}
trigger={<Link size="m" theme="bare" href="#" className="main-menu-item" onClick={this.handleOpenModalCreateAccount}>Create account</Link>}
closeIcon
onClose={() => { this.setState({
createAccountModalOpen: false,
}); }}
>
<Header icon='add user' content='Create account' />
<Modal.Content>
<Form />
</Modal.Content>
<Modal.Actions>
<Button color='green' onClick={this.handleSubmit}>
<Icon name='add user' /> Create account
</Button>
</Modal.Actions>
</Modal>
Basically this is a React Semantic-ui Modal/Dialog. Now What i want to do is make Form reusable (the Form component contains 4 input fields), so i can use it in other modals or components. What would be the best way so that when I click on Create account, it gathers the data from the form and then submits it?
Do I have to pass functions to the Form to try store the data in the main Modal component? or is there a better way to get the validated data from the form?
I’m on my phone so I’m limited.
You want to define your custom function in the parent component where you call your Modal. Then pass that function to it as a prop modal onComplete={this.submitEmail}
Then in your modal component call this.props.onComplete in your handleSubmit.
Then from here out you can define the custom function you want to use wiTh the model and pass it through with onComplete={whateverFunction}
In order to only show the inputs that you want you could set up a series of render if statements. Then when you call your Modal you can pass through renderIfText={“email”} and in your model if this.props.renderIfText=email render email input.
import React from 'react';
class ReusableModalForm extends React.Component {
constructor(props){
super(props);
this.state ={
};
}
handleChange(e) {
let {name, value} = e.target;
this.setState({
[name]: value,
usernameError: name === 'username' && !value ? 'username must have a value' : null,
emailError: name === 'email' && !value ? 'email must have a value' : null,
passwordError: name === 'password' && !value ? 'password must have a value' : null,
});
}
handleSubmit(e) {
e.preventDefault();
this.props.onComplete(this.state)
}
render() {
return (
<Modal
open={this.state.createAccountModalOpen}
trigger={<Link size="m" theme="bare" href="#" className="main-menu-item" onClick={this.handleSubmit}>{this.props.buttonText}</Link>}
closeIcon
onClose={() => { this.setState({
createAccountModalOpen: false,
}); }}
>
<Header icon='add user' content='Create account' />
<Modal.Content>
<Form />
</Modal.Content>
<Modal.Actions>
<Button color='green' onClick={this.handleSubmit}>
<Icon name='add user' /> {this.props.buttonText}
</Button>
</Modal.Actions>
</Modal>
);
}
}
export default ReusableModalForm;
In order to make your <Form /> reusable you need to determine what are the inputs/outputs to your Form and allow any potential parent component to access/manipulate it via props.
Perhaps something like:
<CreateAccountForm
input1DefaultValue={...}
input2DefaultValue={...}
onSubmit={yourCreateAccountFormHandler}
/>
Do I have to pass functions to the Form to try store the data in the main Modal component? or is there a better way to get the validated data from the form?
It depends on how you implement Form and your input fields.
I would recommend react-form library or, if you want to have your own implementation - using redux state and wire your inputs/form to redux.
If no redux then you will need to store the state of inputs in the modal.
Whenever you compose components, you share data between them using props. I will be passing "name and label" props to stateless functional component named;
input.js
import React from "react";
const Input = ({name,label}) => {
return (
<div className="form-group">
<label htmlFor={name}>{label}</label>
<input
autoFocus
name={name}
id={name}
className="form-control"
aria-describedby="emailHelp"
/>
);
};
export default Input;
form.js
import React, { Component } from "react";
import Input from "./common/input";
class RegisterForm extends Form {
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<input name="username" label="username" />
<input name="email" label="email" />
<input name="password" label="password" />
</form>
</div> ); } }

Keypress is failing to record all keystrokes

I'm trying to capture keystrokes from a textarea and have tried using attributes onKeyUp, onKeyPress, onKeyPressCapture, onKeyDown, onKeyDownCapture. All of them seem to miss some key entries:
When I enter a new key, one of the ones that was not displaying before then shows, in order.
Because of that queued delay, I'm thinking I might need to put a delay on the console log. But that doesn't actually solve the underlying issue. Does anyone know why this behavior is happening?
Here is the parent (App) and child component (TypeArea)
Parent
class App extends React.Component {
constructor (props) {
super(props)
// sets up this.props to function
this.state = {
textbox_in_parent_state: 'string passed from state of Parent(App)',
someVar: 'parent_constructor_state',
text_from_textarea: ''
}
this.handler = this.handler.bind(this)
this.text_capture_from_parent = this.text_capture_from_parent.bind(this)
}
text_capture_from_parent(eventObject) {
this.setState({
text_from_textarea: eventObject.target.value
})
console.log(this.state.text_from_textarea)
}
render() {
return (
<div>
<div className="container">
<Header />
<div className="row">
<div className="col-sm-6">
<TypeArea textcapture={this.text_capture_from_parent}
/>
</div>
<div className="col-sm-6">
<MarkdownPreview />
</div>
</div>
</div>
{/* <div style={{textAlign: 'center'}}>*/}
{/* <h1>App component written in client/components/App.jsx</h1>*/}
{/* </div>*/}
</div>
)
}
}
export default App
Child
import React from 'react';
class TypeArea extends React.Component {
constructor(props) {
super(props)
this.state = {
textbox_text: 'string from state of child "TypeArea"'
}
console.log(this.state.textbox_text)
}
render() {
return (
<div>
<h1>Typing Area</h1>
<div className="form-group">
<textarea className="form-control" id="textbox" rows="25" placeholder="Type Here" onKeyPressCapture={this.props.textcapture}>
</textarea>
<button onClick={this.props.passdown}>Click me</button>
</div>
</div>);
}
}
export default TypeArea
text_capture_from_parent(eventObject) {
this.setState({
text_from_textarea: eventObject.target.value
})
console.log(this.state.text_from_textarea)
}
this.setState() is async, and you are calling directly console.log(..) after setting the state, at this moment the state maybe didn't successfully changed already. but luckily this.setState(..)is providing a callback when it finished setting the new state. so you can call it like this:
this.setState({
text_from_textarea: eventObject.target.value
}), () => {
console.log(this.state.text_from_textarea);
});
and you should see the actual value.

Categories

Resources