Passing the state of a Child component to a Parent? - reactjs

I think I know what I need to do to make my searchLocationChange function work, but I'm not sure quite how to do it. Please forgive the indentation, was grappling a fair bit with StackOverflow's WYSIWYG!
Here's my Parent component setup:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
forecasts: [],
location: {
city: '',
country: '',
},
selectedDate: 0,
searchText: '',
};
this.handleForecastSelect = this.handleForecastSelect.bind(this);
this.searchLocationChange = this.searchLocationChange.bind(this);
}
}
With this specific function I want to make work:
searchLocationChange() {
console.log(this.state.searchText);
Axios.get('https://mcr-codes-weather.herokuapp.com/forecast', {
params: {
city: this.state.searchText,
},
})
.then((response) => {
this.setState({
forecasts: response.data.forecasts,
location: {
city: response.data.location.city,
country: response.data.location.country,
}
});
});
}
And in my Child component, the logic is:
class SearchForm extends React.Component {
constructor(props) {
super(props);
this.state = {
searchText: '',
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const enteredText = event.target.value;
this.setState({
searchText: enteredText
});
}
render() {
return (
<span className="search-form">
<div className="search-form__input"><input type="text" value={this.state.searchText} onChange={this.handleInputChange} /></div>
<div className="search-form__submit"><button onClick={this.props.searchLocationChange}>Search</button></div>
</span>
);
}
}
I realise I'm trying to update the Parent component with searchText from the state of the Child component, but I can't figure out what I need to change to make this work. I have a sneaking suspicion I'm 99% of the way there, and it's only a few more lines I need, but I could be way off?

You're already passing down searchLocationChange from your parent.
in parent component:
searchLocationChange(searchedText) {
console.log(searchText);
Axios.get('https://mcr-codes-weather.herokuapp.com/forecast', {
params: {
city: searchText,
},
})
.then((response) => {
this.setState({
forecasts: response.data.forecasts,
location: {
city: response.data.location.city,
country: response.data.location.country,
},
});
});
}
in child:
render() {
const { searchText } = this.state;
return (
<span className="search-form">
<div className="search-form__input"><input type="text" value={this.state.searchText} onChange={this.handleInputChange} /></div>
<div className="search-form__submit"><button onClick={()=>{this.props.searchLocationChange(searchText)}}>Search</button></div>
</span>
);
}

You should call the function like that this.props.searchLocationChange(this.state.searchText)
You can do something like below
<div className="search-form__submit"><button onClick={() => {this.props.searchLocationChange(this.state.searchText)}}>Search</button></div>
and function definition should be
searchLocationChange(searchText) {

You are mixing controlled and uncontrolled. Either do controlled or uncontrolled. So take the search Text from parent only. Above solutiion is one way of doing this . Another way is to pass searchTxt from parent to child.
<SearchForm
searchTxt={this.state.searchTxt}
handleInputChange={this.handleInputChange}
searchLocationChange={this. searchLocationChange}
/>
Move your handleInputChange in parent:
handleInputChange = (event) => {
const enteredText = event.target.value;
this.setState({ searchText: enteredText });
}
Then change your child component respective line to
<div className="search-form__input"><input type="text" value={this.props.searchText} onChange={this.props.handleInputChange} /></div>
Now when you try the above code it should work. Now you are keeping your searchTxt in the parent component. your SearchForm component is Completely controlled now.

Related

How can I pass my state to this class component in ReactJs/.Net?

I followed a tutorial to make an Asp.Net Core MVC app with a ReactJs front end (https://reactjs.net/tutorials/aspnetcore.html). I've been adding additional functionality to the project after completing the tutorial to see what else I can do with it.
My <AddColourForm> component assembles a <Colour> object and posts it off via an XmlHttpRequest to my API controller which in turn persists it to local storage. The submitUrl for the controller is passed in through the props. This works.
I've since tried to add the <SoftDeleteColour> component to each colourNode rendered in the <ColourList> which I intend to behave in more-or-less the same manner as the <AddColourForm> component. Each colourNode rendered in the <ColourList> has it's own delete button and I want the <SoftDeleteColour> component to take the colour.id from the selected colour and pass it to the softDelete action on the API controller so that can be handled in turn (it'll find the colour by id and append a DateDeleted to it, the API will then ignore any colours where DateDeleted != null) and the <SoftDeleteColour> component can then call loadColoursFromServer() to bring back the refreshed list from the storage. I want <SoftDeleteColour> to receive the softDeleteUrl from props in the same way that the add form does.
When I run the project in debug the softDeleteUrl is coming in as undefined and when I inspect the props in the browser it doesn't contain the softDeleteUrl. Also the "colour" is undefined so I feel like my <SoftDeleteColour> component isn't receiving the props or state. I'm new to React and struggling conceptually with props/state binding a little bit so I suspect this is the source of my problem.
How can I pass the softDeleteUrl and the properties of the colour from the <ColourList> that I am selecting for deletion to the <SoftDeleteColour> component? Do I need to call something like <SoftDeleteColour HandleDeletion=this.HandleDeletion.bind(this) /> or something?
class ColourDisplay extends React.Component {
constructor(props) {
super(props);
this.state = { data: [] };
this.handleColourSubmit = this.handleColourSubmit.bind(this);
}
loadColoursFromServer() {
const xhr = new XMLHttpRequest();
xhr.open('get', this.props.url, true);
xhr.onload = () => {
const data = JSON.parse(xhr.responseText);
this.setState({ data: data });
};
xhr.send();
}
handleColourSubmit(colour) {
const data = new FormData();
data.append('name', colour.name);
data.append('brand', colour.brand);
data.append('expiry', colour.expiry);
data.append('serialNumber', colour.serialNumber);
const xhr = new XMLHttpRequest();
xhr.open('post', this.props.submitUrl, true);
xhr.onload = () => this.loadColoursFromServer();
xhr.send(data);
}
componentDidMount() {
this.loadColoursFromServer();
}
render() {
return (
<div className="colourDisplay">
<h1>Colours</h1>
<ColourList data={this.state.data}/>
<AddColourForm onColourSubmit={this.handleColourSubmit}/>
</div>
);
}
}
class ColourList extends React.Component {
render() {
const colourNodes = this.props.data.map(colour => (
<Colour name={colour.name} key={colour.id}>
<div>Brand: {colour.brand}</div>
<div>Exp: {colour.expiry}</div>
<div>Serial #: {colour.serialNumber}</div>
<div>Date Added: {colour.dateAdded}</div>
<SoftDeleteColour />
</Colour>
));
return <div className="colourList">{colourNodes}</div>;
}
}
class SoftDeleteColour extends React.Component {
constructor(props) {
super(props)
this.state = {
colour: this.props.colour
};
}
HandleDeletion(colour) {
var xhr = new XMLHttpRequest();
var url = this.props.softDeleteUrl + colour.id;
xhr.open('DELETE', url, true);
xhr.onreadystatechange = () => {
if (xhr.status == 204) {
this.loadColoursFromServer();
}
}
xhr.send();
}
render() {
return (
<button onClick={() => { this.HandleDeletion(this.state.colour); }}>Delete</button>
)
}
}
class AddColourForm extends React.Component {
constructor(props) {
super(props);
this.state = { name: '', brand: '', expiry: '', serialNumber: '' };
this.handleNameChange = this.handleNameChange.bind(this);
this.handleBrandChange = this.handleBrandChange.bind(this);
this.handleExpiryChange = this.handleExpiryChange.bind(this);
this.handleSerialNumberChange = this.handleSerialNumberChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleNameChange(e) {
this.setState({ name: e.target.value });
}
handleBrandChange(e) {
this.setState({ brand: e.target.value });
}
handleExpiryChange(e) {
this.setState({ expiry: e.target.value });
}
handleSerialNumberChange(e) {
this.setState({ serialNumber: e.target.value })
}
handleSubmit(e) {
e.preventDefault();
const name = this.state.name.trim();
const brand = this.state.brand.trim();
const expiry = this.state.expiry.trim();
const serialNumber = this.state.serialNumber.trim();
if (!name || !brand || !expiry || !serialNumber) {
return;
}
this.props.onColourSubmit({
name: name,
brand: brand,
expiry: expiry,
serialNumber: serialNumber
})
this.setState({
name: '',
brand: '',
expiry: '',
serialNumber: ''
});
}
render() {
return (
<form className="addColourForm" onSubmit={this.handleSubmit}>
<h2>Add a colour to your list</h2>
<div>
<input
type="text"
placeholder="Colour"
value={this.state.name}
onChange={this.handleNameChange}
/>
</div>
<div>
<input
type="text"
placeholder="Brand"
value={this.state.brand}
onChange={this.handleBrandChange}
/>
</div>
<div>
<input
type="text"
placeholder="Expiry MM/YY"
value={this.state.expiry}
onChange={this.handleExpiryChange}
/>
</div>
<div>
<input
type="text"
placeholder="Serial #"
value={this.state.serialNumber}
onChange={this.handleSerialNumberChange}
/>
</div>
<input type="submit" value="Post" />
</form>
);
}
}
class Colour extends React.Component {
render() {
return (
<div className="colour">
<h2 className="colourName">{this.props.name}</h2>
{this.props.children}
</div>
);
}
}
ReactDOM.render(
<ColourDisplay
url="/colours"
submitUrl="/colours/new"
softDeleteUrl="/colours/softDelete"
/>,
document.getElementById('content')
);

onChange or onKeyUp event takes previous value in ReactJS

I've created form in ReactJS. I am fetching value in common method in {key : value} paired. but I am getting previous value in method.
constructor(props) {
super(props);
{
this.state = { name: "", age: 0 };
}
}
inputChange = (key, value) => {
this.setState({ [key] : value });
console.log(this.state);
}
render() {
return (
<form>
<div>
Name : <input type="text" name="name" onKeyUp={(e) => this.inputChange('name', e.target.value)}></input>
</div>
<div>
Age : <input type="text" name="age" onKeyUp={(e) => this.inputChange('age', e.target.value)}></input>
</div>
</form>
)
}
I've attached the screenshot for better understanding.
setState enqueues a change to the state, but it doesn't happen immediately. If you need to do something after the state has changed, you can pass a second callback argument to setState:
inputChange = (key, value) => {
this.setState({ [key] : value }, () => {
console.log(this.state);
});
}
This will do exactly what you need.
class App extends Component {
constructor(props){
super(props)
this.state = {
name: '',
age: ''
}
}
handleInput(option, event){
if (option === 'name') {
this.setState({
name: event.target.value
}, () => {
console.log("Name: ", this.state.name)
});
}
else if (option === 'age'){
this.setState({
age: event.target.value
}, () => {
console.log("Age: ", this.state.age)
});
}
}
render() {
return (
<div>
<div>
<header>Name: </header>
<input type="text" onChange={this.handleInput.bind(this, 'name')}/>
<header>Age: </header>
<input type="text" onChange={this.handleInput.bind(this, 'age')}/>
</div>
</div>
);
}
}
update 2022
with functional components using useState hook this does not work longer, you have to use it in useEffect to update after rendering.
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().

Correct way to create a editable component fetching data from server?

I have a component called EditProfile where the user can update the info of their profile. Below is simplified version of component.
class EditProfile extends Component {
state = {
editable: false,
//main form data goes below here
username: "",
fullname: "",
country: "",
gender: "",
description: "",
};
setGender = e => {
this.setState({ gender: e.target.value });
};
onInputChange = e => {
this.setState({ errors: {} });
this.setState({ [e.target.name]: e.target.value });
};
static getDerivedStateFromProps = (nextProps, prevState) => {
if (nextProps.profile.username !== prevState.username) {
return { username: nextProps.profile.username };
}
};
render() {
return (
<div className="ProfileSignUp">
<h2 className="ProfileSignUp__header">Personal Information</h2>
<input
type="text"
className="ProfileSignUp__username"
placeholder="Username"
name="username"
value={this.state.username}
onChange={this.onInputChange}
/>
{this.state.errors.username}
<input
type="text"
className="ProfileSignUp__fullname"
placeholder="Fullname"
name="fullname"
value={this.state.fullname}
onChange={this.onInputChange}
/>
{this.state.errors.fullname}
<div className="ProfileSignUp__save-btn" onClick={this.saveProfile}>
Save Profile
</div>
</div>
);
}
}
const mapStateToProps = state => ({
profile: state.profile
});
As you can see the value on state is set from getDerivedStateFromProps.I want to wait till the profile is received from server in mapStateToProps.After that I want to update the value in state with the props from profile.Once that is done I want to switch to editable mode. The program should not care about the props and I can freely update the value in state. But with the current way the value in state is set correctly once the data is received from the server but I cannot update it. Can anyone suggest me correct pattern to do that?
I figured out a way to do this. You can do something like this in getDerivedStateFromProps
static getDerivedStateFromProps = (nextProps, prevState) => {
//After the data is fetched once from the server
//editable is set to true and this function never runs
//It just sets the value of state from props one time
if (
nextProps.profile &&
nextProps.profile.loggedIn &&
!prevState.editable
) {
return {
username: nextProps.profile.username,
fullname: nextProps.profile.fullname,
gender: nextProps.profile.gender,
description: nextProps.profile.description,
userImage: nextProps.profile.userImage,
editable: true
};
}
};

React: Updating one state property removes other states properties in the state

I have two text boxes with id set dynamically as en which can change. Every time the onchange is emittted from input field, the state should update. The problem is, if I type something into one of the inputs, the other input state seems to disappear. For example, If I type test into title text field, the state becomes:
this.state = {
translation: {
en: {
title: 'test'
}
}
};
If I move on to typing into the content text box, it seems to replace the title state. like so,
this.state = {
translation: {
en: {
content: 'content'
}
}
};
They should update the state independently without affecting each other. Here is my intended state
this.state = {
translation: {
en: {
title: 'title-text',
content: 'content-text'
}
}
};
Component
import React from 'react';
export default class Demo extends React.Component
{
constructor(props)
{
super(props);
// default state
this.state = {
translation: {}
};
}
onSubmit(e)
{
e.preventDefault();
console.log(this.state);
}
render()
{
return (
<form onSubmit={(event) => this.onSubmit(event)}>
<input id="en" name="title"
onChange={(event) => this.setState({
translation: {
[event.target.id]: {
title: event.target.value
}
}
})} />
<input id="en" name="content"
onChange={(event) => this.setState({
translation: {
[event.target.id]: {
content: event.target.value
}
}
})} />
<button type="submit">Login</button>
</form>
);
}
}
setState does not deeply merge current state and updates. You should spread your translation state prop.
this.setState({
translation: {
...this.state.translation,
[event.target.id]: {
content: event.target.value
}
}
})
The behaviour is correct.
e.g: var obj = {a:1,b:3};
obj = {a:4};//Obj updated. Now obj.b will be undefined.This is what you worried for.
In your case, you can do something like this. It is not one of the best solution.
onSubmit(e)
{
e.preventDefault();
let state = this.state.translation;
state.en.content = 'content';
this.setState(state);
console.log(this.state);
}
As was mentioned, setState doesn't deeply merge values, however what you really need is to do is this:
this.setState({
translation: {
[event.target.id]: {
...this.state.translation[event.target.id],
content: event.target.value
}
})
And same for title.
You forgot about immutability. Just add to your code ...this.state for import all the properties that have been there before.
this.state = {
...this.state,
..
}

Why is this.refs undefined?

Why is this.refs undefined in the code below?
class NewItem extends React.Component {
handleClick() {
console.log(this.refs) //prints out undefined
const name = this.refs.name.value;
const description = this.refs.description.value;
$.ajax({
url: 'api/v1/items',
type: 'POST',
data: {
item: {
name: name,
description: description
}
},
success: (item) => {
this.props.handleSubmit(item);
}
});
}
render() {
return (
<div>
<input ref='name' placeholder='Enter the name of the item' />
<input ref='description' placeholder='Enter the description of the item' />
<button onClick={this.handleClick}>Submit</button>
</div>
)
}
}
The method is not bound to this when used as a function:
<button onClick={this.handleClick.bind(this)}>Submit</button>
or
<button onClick={event => this.handleClick(event)}>Submit</button>
or bind it in constructor:
constructor(props, context) {
super(props, context);
this.handleClick = this.handleClick.bind(this);
}
You need to bind this to your handleClick() function, like this:
<button onClick={this.handleClick.bind(this)}>Submit</button>
or through the constructor, like this:
constructor(props) {
...
this.handleClick = this.handleClick.bind(this);
}
Though you should avoid using string literals in refs. This approach is deprecated.
Legacy API: String Refs
If you worked with React before, you might be
familiar with an older API where the ref attribute is a string, like
"textInput", and the DOM node is accessed as this.refs.textInput. We
advise against it because string refs have some issues, are considered
legacy, and are likely to be removed in one of the future releases. If
you're currently using this.refs.textInput to access refs, we
recommend the callback pattern instead.
Instead do:
constructor(props) {
this.nameInputRef;
this.descriptionInputRef;
}
...
<input ref={(el) => {this.nameInputRef = el;} placeholder='Enter the name of the item' />
<input ref={(el) => {this.descriptionInputRef = el;} placeholder='Enter the description of the item' />
You havens bind the functions. it should be done like this
class NewItem extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log(this.refs) //prints out undefined
const name = this.refs.name.value;
const description = this.refs.description.value;
$.ajax({
url: 'api/v1/items',
type: 'POST',
data: {
item: {
name: name,
description: description
}
},
success: (item) => {
this.props.handleSubmit(item);
}
});
}
render() {
return (
<div>
<input ref='name' placeholder='Enter the name of the item' />
<input ref='description' placeholder='Enter the description of the item' />
<button onClick={this.handleClick}>Submit</button>
</div>
)
}
}
Try using the ES6 features, The arrow functions to avoid this binding issue.
like this.
handleClick =()=> {
console.log(this.refs) //prints out undefined
const name = this.refs.name.value;
const description = this.refs.description.value;
$.ajax({
url: 'api/v1/items',
type: 'POST',
data: {
item: {
name: name,
description: description
}
},
success: (item) => {
this.props.handleSubmit(item);
}
});
}

Resources