ReactJS - How to change a component's state based on another component - javascript

Im trying to update a component's state when a different component's state changes.
Here is the main component that contains the form:
class IpsumForm extends React.Component {
state = {
character: 'All',
paragraphs: 1,
ipsumDisplayed: false
};
handleInputChange = (e) => {
const target = e.target;
this.setState({ paragraphs: target.value });
};
handleSelectChange = (e) => {
const target = e.target;
this.setState({ character: target.value });
};
handleReset = (e) => {
this.setState({
character: 'All',
paragraphs: 1,
ipsumDisplayed: false
});
};
handleSubmit = (e) => {
e.preventDefault();
this.setState({
ipsumDisplayed: true
});
};
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<p>Select Your Character:</p>
<select
value={this.state.character}
onChange={this.handleSelectChange}
name="characterPick"
>
<option value="All">All</option>
<option value="Michael">Michael</option>
<option value="Dwight">Dwight</option>
<option value="Jim">Jim</option>
<option value="Andy">Andy</option>
<option value="Creed">Creed</option>
</select>
<div className="length">
<p>How Many Paragraphs?</p>
<input
onChange={this.handleInputChange}
name="paragraphLength"
type="text"
/>
</div>
<hr />
<input
id="submit"
type="submit"
value="Bibity Boppity Give Me The Zoppity"
/>
<button onClick={this.handleReset}>Reset</button>
</form>
<br />
<IpsumText
person={this.state.character}
length={this.state.paragraphs}
displayed={this.state.ipsumDisplayed}
/>
</div>
);
}
}
export default IpsumForm;
And here is the component that I would like to return the ipsum within a textbox:
class IpsumText extends React.Component {
state = {
value: ''
};
handleValueChange = (e) => {
console.log(data.quote1);
this.setState({
value: data.quote1
});
};
render() {
let character = this.props.person;
let paragraphs = this.props.length;
let displayed = this.props.displayed;
return (
<div className="returned-ipsum">
<textarea value={this.state.value} onChange={this.handleValueChange} />
</div>
);
}
}
export default IpsumText;
The code I have now doesn't work because a state change from the IpsumForm doesn't cause the textarea onChange handler to run in the IpsumText
I'm wondering if there is a way to do this other than using a lifecycle method because it seems like i'm just overthinking it

Here's a very simple way of looking at it:
class App extends React.Component {
state = {
firstState: 1,
}
handleStateUpdate = () => {
this.setState(prev => ({
firstState: prev.firstState + 1
}))
}
render() {
console.log(this.state.firstState)
return (
<Foo handleStateUpdate={this.handleStateUpdate} />
)
}
}
class Foo extends React.Component {
state = {
otherState: 1,
}
handleLocalStateUpdate = () => {
this.props.handleStateUpdate()
this.setState(prev => ({
otherState: prev.otherState + 1
}))
}
render() {
return <button onClick={() => this.handleLocalStateUpdate()}>Click here!</button>
}
}
ReactDOM.render(<App />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='app'></div>
What this is doing is passing down a function from the parent that updates the parent state, down to the child component. Once you define a function that updates the state of the child component, you can call that passed down function to also update the parent state.
Hopefully, the principle of doing this helps with the issue you're facing.
Here's a link to the code in case you wanna mess with it:
CodePen

Related

React - Adding an item to a list from an input by clicking submit button

I'm practicing react and I'm trying to add an item to a list from input by clicking submit button.
I prefer to use state and setState
I'd LOVE some help.
I don't think my code is needed but here is it anyway:
class App extends Component {
state = {
userInput: ""
}
inputChangeHandler = (e) => {
this.setState({userInput: e.target.value})
}
listAddHandler = () => {
var listElement = document.createElement('li')
listElement.appendChild("ul")
}
render() {
return (
<div className="checklist">
<h1>This is an inventory</h1>
<input type="text" value={this.state.userInput} onChange={this.inputChangeHandler} placeholder="Insert item"/>
<button onClick={this.listAddHandler}>Submit</button>
<ul>
<li>
</li>
</ul>
</div>
)
}
}
You need to keep track of listed items within your state as an array:
const { Component } = React,
{ render } = ReactDOM,
rootNode = document.getElementById('root')
class App extends Component {
state = {
listItems: [],
userInput: '',
}
inputChangeHandler = ({target:{value}}) => this.setState({
userInput: value
})
submitHandler = e =>{
e.preventDefault()
this.setState({
listItems: [...this.state.listItems, this.state.userInput],
userInput: ''
})
}
render() {
return (
<div>
<ul>
{
this.state.listItems.map((li,key) => <li {...{key}}>{li}</li>)
}
</ul>
<form onSubmit={this.submitHandler}>
<input value={this.state.userInput} onChange={this.inputChangeHandler} />
<input type="submit" value="Submit" />
</form>
</div>
)
}
}
render (
<App />,
rootNode
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>

Lifting message up in REACT

I want to do a real time searching in React. How can I lift the message up from child to parent? Or how can I pass a parent handler to children through props to handle the event?
parent class:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
activities: activities,
filteredActivities: activities,
};
this.handleSearchChange = this.handleSearchChange.bind(this);
}
filterActivity = searchText => {
return this.state.activities
.filter(activity => {
if(activity.content.toLowerCase().includes(
searchText.toLowerCase())
){
return true;
}
return false;
});
}
handleSearchChange = event => {
this.setState({
filteredActivities: this.filterActivity(event.target.value)
});
};
render() {
const filteredActivities = this.props.filteredActivities;
return(
<div className="notificationsFrame">
<div className="panel">
<Header name={this.props.name} />
<SearchBar onChange={this.handleSearchChange} />
<Content activities={this.state.filteredActivities} />
</div>
</div>
);
}
}
Child class:
class SearchBar extends React.Component {
onChangeHandler = event => {
console.log(event.target.value);
}
render() {
return (
<div className="search-bar" >
<input type="text" onChange={this.onChangeHandler} />
</div>
);
}
}
I want to pass the event.target.value to handleSearchChange. if I put the code of class Searchbar to class App, I can perform a real time searching very good. But I can't put them into two components. I want to make them into two components. Thanks a lot.
Should be as simple as this:-
Child class:
class SearchBar extends React.Component {
onChangeHandler = event => {
this.props.inputChanged(event.target.value);
}
render() {
return (
<div className="search-bar" >
<input type="text" onChange={this.onChangeHandler} />
</div>
);
}
}
Parent class:
handleSearchChange = inputValue => {
this.setState({
filteredActivities: this.filterActivity(inputValue)
});
};
JSX of parent class:
<SearchBar inputChanged={this.handleSearchChange} />
since you're already passing the function's reference as a prop you can access it using this.props.propName and call it.
class SearchBar extends React.Component {
/* onChangeHandler = event => {
console.log(event.target.value);
} */
render() {
const { onChange } = this.props;
return (
<div className="search-bar" >
<input type="text" onChange={onChange} />
</div>
);
}
}
You can try this, as you already took event as a parameter in parent class for handleSearchChange method

how to Uncheck Checkbox.Group in Antd React.js

I have the following Code using https://ant.design/components/checkbox/, and Trying to uncheck when a checkbox has been checked.
I don't want to check all if button is click, just Uncheck all or the one checkbox selected.
constructor(props) {
super(props);
this.state = {
checked: true,
}
this.onChange = this.onChange.bind(this);
}
onChange(value) {
this.props.onChangeSession(value)
}
onChangeBox = e => {
this.setState({
checked: e.target.checked,
});
};
unChecked = () => {
this.props.onChangeSession([])
this.setState({
checked: false
});
};
render () {
const {
data,
} = this.props
return (
<div>
<Button type="primary" size="small" onClick={this.unChecked}>
Uncheck
</Button>
<Checkbox.Group
style={{ width: '100%' }}
onChange={this.onChange}>
<div>
<div className="filter-subhead">Track</div>
{data.map(i =>
<div className="filter-item">
<Checkbox
checked={this.state.checked}
onChange={this.onChangeBox}
value={i}>{i}</Checkbox>
</div>
)}
</div>
</Checkbox.Group>
</div>
)
}
Any help will be appreciate!
Working Link
The toggle on the checkbox wasn't working due to Checkbox.Group, you can simply use Checkbox
Regarding Checkbox State:
You can't have a single state for all the checkboxes, so you will need to have an array of bool which will serve as the state for each checkbox item.
In the example, I have initialized checkbox state on componentDidMount and it creates an array ([false,false,false,...]) and the exact same thing is used for resetting on Uncheck. (Possibility of refactoring in my code)
User assigned state will decide whether to check the checkbox or not.
import React from "react";
import ReactDOM from "react-dom";
import { Button, Checkbox } from "antd";
import "antd/dist/antd.css";
import "./index.css";
let data = [3423, 3231, 334234, 55345, 65446, 45237];
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
checkboxArray: []
};
// this.onChange = this.onChange.bind(this);
}
componentDidMount() {
let createEmptyArray = new Array(data.length).fill(false);
this.setState({ checkboxArray: createEmptyArray });
}
onChange(e) {
console.log(e.target);
}
onChangeBox = (e, i) => {
let checkBoxCurrentState = this.state.checkboxArray;
checkBoxCurrentState[i] = !checkBoxCurrentState[i];
this.setState({
checkboxArray: checkBoxCurrentState
});
};
unChecked = e => {
let resetArray = new Array(data.length).fill(false);
this.setState({
checkboxArray: resetArray
});
};
render() {
const { data } = this.props;
return (
<div>
<Button type="primary" size="small" onClick={this.unChecked}>
Uncheck
</Button>
<div>
<div className="filter-subhead">Track</div>
{data.map((i, index) => (
<div className="filter-item">
<Checkbox
checked={this.state.checkboxArray[index] ? true : false}
onChange={e => this.onChangeBox(e, index)}
value={index}
>
{JSON.stringify(this.state.checkboxArray[index])}
</Checkbox>
</div>
))}
</div>
{JSON.stringify(this.state.checkboxArray)}
</div>
);
}
}
ReactDOM.render(<App data={data} />, document.getElementById("root"));
Simple copy and paste the above code and add props where required.
And if you want to user Checkbox.Group, will need to update the onChange method of CheckBox.Group
let data = ['Apple', 'Pancakes', 'Butter', 'Tea', 'Coffee'];
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
checkboxArray: []
};
// this.onChange = this.onChange.bind(this);
}
componentDidMount() {
let createEmptyArray = new Array(this.props.data.length).fill(false);
this.setState({ checkboxArray: createEmptyArray });
}
onChangeBox = (e, i) => {
let checkBoxCurrentState = this.state.checkboxArray;
checkBoxCurrentState[i] = !checkBoxCurrentState[i];
this.setState({
checkboxArray: checkBoxCurrentState
});
};
unChecked = () => {
let resetArray = new Array(data.length).fill(false);
this.setState({
checkboxArray: resetArray
});
};
render() {
const { data } = this.props;
return (
<div>
<button onClick={this.unChecked}>Clear All</button>
{this.props.data.map((single, index) => (
<div>
<input
type="checkbox"
id={index}
name="scales"
checked={this.state.checkboxArray[index]}
onChange={e => this.onChangeBox(e, index)}
/>
<label for={index}>{single}</label>
</div>
))}
</div>
);
}
}
ReactDOM.render(<App data={data} />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
If you're going to use a map to dynamically generate the checkboxes, using state to keep track of checked value will be tricky. You should not do this.
What you should do is not include the checked prop at all in the component.
<Checkbox
onChange={this.onChangeBox}
value={i}>{i}
</Checkbox>
The checkbox should still check even if you do not include the checked prop. It's a little strange, I know.
Instead just pass the value into the onChangeBox function and handle all your logic there and set a state value on change.
I just tested it out and it works.

Reactjs button in Parent component which submits Redux Form in Child Component

I have 4 nested components, <CompD> is nested within <CompC> which is nested within <CompB> which is nested within <CompA>.
<CompD> contains a Redux Form with onSubmit={onSubmit} with a bunch of inputs and a Save button with type="submit"
A snippet of <CompD> currently with the button:
const propTypes = {
firstName: PropTypes.string,
secondName: PropTypes.string
};
const defaultTypes = {
firstName = " ",
secondName = " "
};
const PollForm = ({
firstName,
secondName
}) => (
<Form onSubmit={onSubmit}>
.
.
<button type="submit">
'Save'
</button>
</Form>
);
PollForm.propTypes = propTypes;
PollForm.defaultProps = defaultProps;
export default PollForm;
I want to move this button to <CompA> instead, so a button in <CompA> which behaves the exact same way as the button in <CompD> and submits the Form.
A snippet of <CompA> with a new button added:
const propTypes = {
onSubmit: PropTypes.func, ...
};
const defaultTypes = {
onSubmit: () = {}, ...
};
class Config extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this); ...
}
handleSubmit(data) {
this.props.onSubmit(data);
}
render() {
const {
} = this.props;
return (
.
.
<button onClick={onSubmit}>
'Save'
</button>
)
}
}
How do I pass the handleSubmit(data) function in <CompA the data from the form
Any ideas how I could do this?
You can lift up the form-submission state and logics from <CompD> to <CompA>, and may use the React Context to provide the form states and handlers deep-down to <CompD>, such as this:
import React from 'react';
import ReactDOM from 'react-dom';
const FormContext = React.createContext();
function CompB({ children }) {
return <div id="B">{children}</div>;
}
function CompC({ children }) {
return <div id="C">{children}</div>;
}
function CompD() {
return (
<FormContext.Consumer>
{({ onSubmit, onChange, name }) => {
return (
<form onSubmit={onSubmit}>
<label>
Name:
<input type="text" value={name} onChange={onChange} />
</label>
<input type="submit" value="submit-input" />
</form>
);
}}
</FormContext.Consumer>
);
}
class CompA extends React.Component {
onChange = ({ target: { value } }) => {
this.setState({ name: value });
};
onSubmit = event => {
event.preventDefault();
console.log('Submitting name: ', this.state.name);
};
state = {
name: 'defaultName',
onSubmit: this.onSubmit,
onChange: this.onChange,
};
render() {
return (
<FormContext.Provider value={this.state}>
<CompB>
<CompC>
<CompD />
</CompC>
</CompB>
<button name="submit" onClick={this.onSubmit}>submit-btn<button/>
</FormContext.Provider>
);
}
}
ReactDOM.render(
<CompA />,
document.getElementById('root'),
);
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>React App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
But it does looks verbose to have form separated from submit button. Not sure why you have such a need but normally it's better to keep them together in one component.
React Components use props to communicate with each other. Parent -> Child goes via props, Child -> Parent goes via callback props. Another way is to use new Context API. It would be better if you have a really deep nested structure.
Bellow I shown how it could be done via props.
class CompB extends React.Component {
state = {
name: "John Doe",
email: "john#doe.com"
}
handleChange = e => {
const {name, value} = e.target;
this.setState({[name]: value})
}
handleSubmit = e => {
e.preventDefault();
this.submit(this.state)
}
submit = (data) => {
console.log("submitted", data, +new Date());
this.props.onSubmit(data);
}
componentDidUpdate (oldProps) {
if (!oldProps.shouldSubmit && this.props.shouldSubmit){
this.submit(this.state);
}
}
render () {
const { name, email } = this.state;
return (
<form onChange={this.handleChange} onSubmit={this.handleSubmit}>
<div>
<label>
Name
<input name="name" type="text" value={name} />
</label>
</div>
<div>
<label>
Email
<input name="email" type="email" value={email} />
</label>
</div>
</form>
)
}
}
class CompA extends React.Component {
state = {
shouldSubmit: false,
}
submit = () => {
this.setState({shouldSubmit: true})
}
handleSubmit = () => {
this.setState({shouldSubmit: false})
}
render () {
const {shouldSubmit} = this.state;
return (
<div>
<CompB
shouldSubmit={shouldSubmit}
onSubmit={this.handleSubmit}
/>
<button
type="button"
onClick={this.submit}
>
Submit
</button>
</div>
)
}
}
ReactDOM.render(<CompA />, document.querySelector("#root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

React doesn´t rerender ComponentDidUpdate after react-select Input changed state

I am really lost on this one:
import React, { Component } from "react";
import Select from "react-select";
class selectDemo extends Component {
state = {
selectedOption: "",
data: [{ Model: "Option1" }, { Model: "Option2" }]
};
handleChange = selectedOption => {
this.setState({ selectedOption }, () =>
console.log(this.state.selectedOption.Model)
);
};
renderButton = () => {
return (
<button type="button" class="btn btn-primary">
{this.state.selectedOption.Model}
</button>
);
};
componentDidUpdate(prevProps, prevState) {
if (this.state.selectedOption !== prevState.selectedOption) {
this.renderButton();
}
}
render() {
const { selectedOption } = this.state;
const value = selectedOption && selectedOption.Model;
return (
<div>
<div name="selectedOption" className="section">
<Select
className="form-control"
placeholder="Select Option"
name="selectedOption"
value={value}
onChange={this.handleChange}
labelKey="Model"
valueKey="Model"
optionClassName="dropdown-item"
options={this.state.data}
/>
</div>
<div className="form-group">{this.renderButton}</div>
</div>
);
}
}
export default selectDemo;
All I want to achieve is that after I made a Selection from a dropDown List created with the react-select package a button gets rendered on the page. Everything is working fine except for the fact that the button does not get rendered when a selection has been made. Anyone has an idea where I am wrong?
You have to call renderButton explicitly this.renderButton()
here is the right way :
class SelectDemo extends React.Component {
state = {
selectedOption: "",
data: [{ Model: "Option1" }, { Model: "Option2" }]
};
handleChange = selectedOption => {
this.setState({ selectedOption }, () =>
console.log(this.state.selectedOption.Model)
);
};
renderButton = () => {
return (
<button type="button" className="btn btn-primary">
{this.state.selectedOption.Model}
</button>
);
};
render() {
const { selectedOption } = this.state;
const value = selectedOption && selectedOption.Model;
return (
<div>
<div name="selectedOption" className="section">
<Select
className="form-control"
placeholder="Select Option"
name="selectedOption"
value={value}
onChange={this.handleChange}
labelKey="Model"
valueKey="Model"
optionClassName="dropdown-item"
options={this.state.data}
/>
</div>
{this.state.selectedOption && <div className="form-group">{this.renderButton}</div>}
<div className="form-group">{this.renderButton()}</div>
</div>
);
}
}
You are missing the function call to your this.renderButton in your main render function. It should look like:
<div className="form-group">{this.renderButton()}</div>
Also, in your componentDidUpdate, you are calling this.renderButton() but that will not cause it to render the returned markup. The state update from your handleChange will trigger the re-render of your component without you needing to check in componentDidUpdate.

Categories

Resources