Update the state from child to parent component in React - javascript

I have this constructor in my parent component
constructor (props) {
super(props)
this.state = {
owner,
name: '',
projectToAdd: this.props.defaultProject,
}
}
And the property projectToAdd is set to the value passed by props, I need to change this property of the state with the following function located in my child component:
handleProjectSelection = (project) => () => {
this.setState({
projectToAdd: project.get('id')
})
}
This function is called when I click an element of a <li> tag in my child component:
renderDropdown () {
const { projects } = this.props
const projectsList = projects.map((project) => (
<li className='u-cursor--pointer u-padding-tiny u-font-size--12px'
key={project.get('id')}
onClick={this.handleProjectSelection(project)} >
{project.get('name')}
</li>
))
return (
<div>
<ul className='c-folder-dropdown'
name='projectList'
form='start-retro-form'>
{projectsList}
</ul>
</div>
)
}
How can I change the state from the child component to the parent component?

Pass a change handler function prop from your parent to the child.
class Parent{
handleChange = () => {
this.setState({
foo: 'bar'
})
}
render(){
return <Child onChange={this.handleChange} {...this.state}/>
}
}

Related

React context reset consumer state

I m trying to use React Context API to pass state data from a Parent component to a deep nested Child.
The parent is a class component and the child is a function.
When from the child i update the parent state with a function passed through the Context the parent state is updated successfully but then the local state of the child is reset to the initial value.
Parent:
export class ObjectLinking extends Component {
constructor(props) {
super(props);
this.state = {
setCurrentlyDisplayed: this.setCurrentlyDisplayed,
currentlyDisplayed: []
};
}
[...]
render() {
return (
<ObjectLinkingContext.Provider value={this.state}>
{//Panel body will contain deep nested child that is connected to context}
<PanelBody />
</ObjectLinkingContext.Provider>
);
}
}
Child:
import ObjectLinkingContext from '../../context/ObjectLinkingContext';
const AssetListFilters = ({ assets, filtersModel }) => {
const [searchByNameVal, setSearchByNameVal] = useState([]);
const panelContext = useContext(ObjectLinkingContext);
useEffect(() => {
filterBySearchNameVal();
}, [searchByNameVal]);
const filterBySearchNameVal = () => {
if (searchByNameVal.length) {
const { setCurrentlyDisplayed } = panelContext;
const { value: searchedId } = searchByNameVal[0];
const searchedAsset = assets.filter(asset => asset.assetId === searchedId) || [];
setCurrentlyDisplayed(searchedAsset);
}
};
return (
<Autocomplete
onChange={val => setSearchByNameVal(val)}
/>
);
};
Find here the full Parent.js and VeryDeepChild.js component

How to get react element from event.target

I have attached an event listener to the parent element to listen for a non-synthetic-event and I wonder if there is a nice way to get reference to the component which triggers the event to use it's properties
I need to postpone the rendering of item.component until the nonSyntheticEvent occurs
const items = [
{
name: "click me",
component: function First() {
return <strong>asd</strong>;
}
},
{
name: "click me 2",
component: function Second() {
return <b>oasd</b>;
}
}
];
class Component extends React.Component {
componentDidMount() {
this.el.addEventListener("nonSyntheticEvent", event =>
this.nonSyntheticEventHandler(event)
);
}
nonSyntheticEventHandler(event) {
// how to get reference to item
// from event.target to render it's item.component
const el = React.createElement(item.component);
ReactDOM.render(el, event.target);
}
render() {
return (
<div ref={ref => { this.el = ref; }}>
{this.props.items.map(item => <Child {...item} />)}
</div>
);
}
}
<Component items={items} />
With React 16.3 React.createRef() is introduced which can be used in Component to create reference before the Child component is rendered.
for example in Component.constructor a reference to each child can be created in the state
this.state = {
items: items.map(item => ({
...item,
reference: React.createRef()
}))
};
and then in the Child component can be used from props:
function Child(props){
return (
<div ref={props.reference}>
<span>{props.name}</span>
</div>
);
}
and then in the nonSyntheticEventHandler the item can be obtained like so:
const found = this.state.items.find(item => {
return item.reference.current === event.target;
});
working example in Codesandbox.io

How to update an arrays state with onChange through Child component's input field

I am trying to update an array which is within my Parent component's state. Each child component is created through an element from the Parent component's state array. Right now I have an onChange handler that is within my Parent component that I pass down to my Child component.
Here are some snippets that have to do with this:
Parent
class PropertyBar extends Component {
state = {
selectedCell: graph.selectedCell,
cellProperties: []
}
onChangeHandler(e) {
let cellProperties = [...this.state.cellProperties];
cellProperties[e.target.name] = e.target.value;
this.setState({cellProperties});
}
renderPropertyList() {
return this.state.cellProperties.map(key => {
return <PropertyBarItem name={key.nodeName} value={key.nodeValue} onChange={this.onChangeHandler}/>
})
}
}
Child
class PropertyBarItem extends Component {
constructor(props) {
super(props);
}
onChangeHandler(e) {
if (this.props.onChangeHandler) {
this.props.onChangeHandler(e);
}
}
render() {
return (
<ListItem divider={true} className="property-bar-item">
<TextField
id="property"
label={this.props.name}
value={this.props.value}
margin="dense"
onChange={(e) => {this.onChangeHandler(e)}}
/>
</ListItem>
)
}
}
I don't think I am handling the onChangeHandler passed down correctly within my Child component, but I might have the logic wrong too.
In your child component, you are not using the onChangeHandler function correctly because you are passing it as a prop of onChange so it should be like this code below
class PropertyBarItem extends Component {
constructor(props) {
super(props);
this.state = {
input: props.value
}
}
onChange = (event) =>{
this.setState({ input: event.target.value },()=>{ // callback of setState
this.props.onChange(this.state.input) // passing the state to parent component
})
}
render() {
return (
<ListItem divider={true} className="property-bar-item">
<TextField
id="property"
label={this.props.name}
value={this.state.input}
margin="dense"
onChange={this.onChange}
/>
</ListItem>
)
}
}
As well you need to bind onChangeHandle in your parent constructor or use arrow function like
onChangeHandler = (e) => { // arrow function
console.log(e.target.value) // check
//let cellProperties = [...this.state.cellProperties];
//cellProperties[e.target.name] = e.target.value;
//this.setState({cellProperties});
}
In your parent component, just onChange replaced with clicked = { (e) => {this.onChangeHandler(e)} and in your chlild component us as onChange= {this.props.clicked}
and in your parent component
onChangeHandler =(e)=> {
let cellProperties = [...this.state.cellProperties];
cellProperties[e.target.name] = e.target.value;
this.setState({cellProperties});}

Unmount component on click in child component button // React

I am struggling with successfully removing component on clicking in button. I found similar topics on the internet however, most of them describe how to do it if everything is rendered in the same component. In my case I fire the function to delete in the child component and pass this information to parent so the state can be changed. However I have no idea how to lift up the index of particular component and this is causing a problem - I believe.
There is a code
PARENT COMPONENT
export class BroadcastForm extends React.Component {
constructor (props) {
super(props)
this.state = {
numberOfComponents: [],
textMessage: ''
}
this.UnmountComponent = this.UnmountComponent.bind(this)
this.MountComponent = this.MountComponent.bind(this)
this.handleTextChange = this.handleTextChange.bind(this)
}
MountComponent () {
const numberOfComponents = this.state.numberOfComponents
this.setState({
numberOfComponents: numberOfComponents.concat(
<BroadcastTextMessageForm key={numberOfComponents.length} selectedFanpage={this.props.selectedFanpage}
components={this.state.numberOfComponents}
onTextChange={this.handleTextChange} dismissComponent={this.UnmountComponent} />)
})
}
UnmountComponent (index) {
this.setState({
numberOfComponents: this.state.numberOfComponents.filter(function (e, i) {
return i !== index
})
})
}
handleTextChange (textMessage) {
this.setState({textMessage})
}
render () {
console.log(this.state)
let components = this.state.numberOfComponents
for (let i = 0; i < components; i++) {
components.push(<BroadcastTextMessageForm key={i} />)
}
return (
<div>
<BroadcastPreferencesForm selectedFanpage={this.props.selectedFanpage}
addComponent={this.MountComponent}
textMessage={this.state.textMessage} />
{this.state.numberOfComponents.map(function (component) {
return component
})}
</div>
)
}
}
export default withRouter(createContainer(props => ({
...props
}), BroadcastForm))
CHILD COMPONENT
import React from 'react'
import { createContainer } from 'react-meteor-data'
import { withRouter } from 'react-router'
import { BroadcastFormSceleton } from './BroadcastForm'
import './BroadcastTextMessageForm.scss'
export class BroadcastTextMessageForm extends React.Component {
constructor (props) {
super(props)
this.handleChange = this.handleChange.bind(this)
this.unmountComponent = this.unmountComponent.bind(this)
}
handleChange (e) {
this.props.onTextChange(e.target.value)
}
unmountComponent (id) {
this.props.dismissComponent(id)
}
render () {
console.log(this.props, this.state)
const textMessage = this.props.textMessage
return (
<BroadcastFormSceleton>
<div className='textarea-container p-3'>
<textarea id='broadcast-message' className='form-control' value={textMessage}
onChange={this.handleChange} />
</div>
<div className='float-right'>
<button type='button'
onClick={this.unmountComponent}
className='btn btn-danger btn-outline-danger button-danger btn-small mr-3 mt-3'>
DELETE
</button>
</div>
</BroadcastFormSceleton>
)
}
}
export default withRouter(createContainer(props => ({
...props
}), BroadcastTextMessageForm))
I am having problem with access correct component and delete it by changing state. Any thoughts how to achieve it?
Please fix the following issues in your code.
Do not mutate the state of the component. Use setState to immutably change the state.
Do not use array index as the key for your component. Try to use an id field which is unique for the component. This will also help with identifying the component that you would need to unmount.
Try something like this. As mentioned before, you don't want to use array index as the key.
class ParentComponent extends React.Component {
constructor() {
this.state = {
// keep your data in state, as a plain object
textMessages: [
{
message: 'hello',
id: '2342334',
},
{
message: 'goodbye!',
id: '1254534',
},
]
};
this.handleDeleteMessage = this.handleDeleteMessage.bind(this);
}
handleDeleteMessage(messageId) {
// filter by Id, not index
this.setState({
textMessages: this.state.textMessages.filter(message => message.id !== messageId)
})
}
render() {
return (
<div>
{this.state.textMessages.map(message => (
// Use id for key. If your data doesn't come with unique ids, generate them.
<ChildComponent
key={message.id}
message={message}
handleDeleteMessage={this.handleDeleteMessage}
/>
))}
</div>
)
}
}
function ChildComponent({message, handleDeleteMessage}) {
function handleClick() {
handleDeleteMessage(message.id)
}
return (
<div>
{message.message}
<button
onClick={handleClick}
>
Delete
</button>
</div>
);
}

React: Calling two event handlers onClick and changing style of child component

I have a parent class-based component A and a child functional component B. Inside B I map over a list of names and render them as li elements, which onClick call the onLanguageUpdate handler declared in the parent component, and what this handler does is update the state to reflect the selected name.
Question then:
I need to call a second event handler in the same onClick, this time to change the color of the name the user has clicked on. I added a new property to the state, color, to represent a className that I can then toggle with the handleStyleColorChange handler. But how do I get the li elements in the child component to update their className (or style) based on the result of this handler? If I was doing all of this inside component A's render method, I could do style={language === this.state.selectedLanguage ? {color: 'red'} : null} on the li and call it a day.
// Component A
import React, { Component } from 'react';
import B from './B';
class A extends Component {
constructor(props) {
super(props);
this.state = {
selectedLanguage: 'All',
color: 'lang-black-text'
};
}
handleUpdateLanguage = (language) => {
return this.setState({ selectedLanguage: language });
}
handleStyleColorChange = (language) => {
if (language === this.state.selectedLanguage) {
return this.setState({ color: 'lang-red-text' });
} else {
return this.setState({ color: 'lang-black-text' });
}
}
handleClick = (language) => {
this.handleUpdateLanguage(language);
this.handleStyleColorChange(language);
}
render() {
return (
<LanguageList onLanguageUpdate={this.handleClick} />
);
}
}
export default A;
// Component B
import React from 'react';
const B = (props) => {
const languages = ['English', 'Spanish', 'Japanese', 'Italian'];
const languageListFormatted = languages.map(language => {
return (
<li
key={language}
onClick={() => props.onLanguageUpdate(language)}>{language}
</li>
);
});
return (
<ul className="languages">{languageListFormatted}</ul>
);
}
export default B;
You can't manage the color from the parent comp, it needs to be done from the child comp. Then, send the selectedLanguage to the child and you are good.
class A extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedLanguage: 'All',
color: 'lang-black-text'
};
}
handleUpdateLanguage = (language) => {
return this.setState({ selectedLanguage: language });
}
handleStyleColorChange = (language) => {
if (language === this.state.selectedLanguage) {
return this.setState({ color: 'lang-red-text' });
} else {
return this.setState({ color: 'lang-black-text' });
}
}
handleClick = (language) => {
this.handleUpdateLanguage(language);
this.handleStyleColorChange(language);
}
render() {
return (
<B
onLanguageUpdate={this.handleClick}
selectedLanguage={this.state.selectedLanguage}
/>
);
}
}
const B = (props) => {
const languages = ['English', 'Spanish', 'Japanese', 'Italian'];
const languageListFormatted = languages.map(language => {
return (
<li
key={language}
style={props.selectedLanguage === language ? {background: 'yellow'} : {}}
onClick={() => props.onLanguageUpdate(language)}>{language}
</li>
);
});
return (
<ul className="languages">{languageListFormatted}</ul>
);
}
ReactDOM.render(
<A />,
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>

Categories