Updating parent component state from child component - javascript

I have one parent component that holds state of clicking : if file is clicked or not.
Files come from child component.
I know that I can use props and call function from parent, but doing that, I get this.props.handleStateChange is not a function
export class Parent extends Component {
constructor(props) {
super(props);
this.state = {
clickable: false
};
this.handleStateChange = this.handleStateChange.bind(this);
}
handleStateChange = (val) => {
this.setState({ clickable: val })
}
render() {
return (
<Child handleStateChange={this.handleStateChange} />
);
}
}
class Child extends Component {
constructor(props) {
super(props);
this.state = {
clickable: false
};
}
handleClick = () => {
this.state.clickable ? this.setState({ clickable: false }) :
this.setState({ clickable: true });
this.props.handleStateChange(this.state.clickable)
}
render() {
return (
<div className={this.state.clickable ? 'clickable' : null}
>
<img className="item" src={file} alt="file" onClick=
{this.handleClick} />
</div>
);
}
}
Any ideas what am I missing there?

Here everything is working fine, no errors.
If the code you add in your question isn't the real code you are working with, maybe try checking for typos.
Probably you are passing the prop with the wrong/different name.
Some tips that aren't related to the question
Instead of
this.state.clickable
? this.setState({ clickable: false })
: this.setState({ clickable: true });
You should do
this.setState(prevState => ({clickable: !prevState.clickable}))

setState is asynchronous, so using your newly set state immediately after isn't guaranteed to work. Instead, try this for handleClick:
handleClick = () => {
this.setState(prevState => {
this.props.handleStateChange({ !prevState.clickable });
return { clickable: !prevState.clickable };
})
}
That said, you're maintaining the same state in the parent and child. Probably better to set it in the parent (from the child) and pass it to the child as a prop.
Also also, since you're using an arrow function, you don't need to bind any of your functions in the constructor.

Related

if else react one-liner

I want to change the state of a Nav.Toggle in react-bootstrap. c
Currently, I have got it to open when clicked but I would like it to close i.e. this.setState({ expanded: false}) when clicked if the current state is "expanded".
My onClick handler looks as such:
onClick={() => (this.state.expanded ? false : this.setState({ expanded: "expanded" }))}
How do I make it say else if this.state.expanded ? "expanded" : this.setState({ expanded: false })?
I assume I should move this logic above the render method so bonus points if you can show me how to do it under this line as well:
constructor(props) {
super(props);
this.state = {
expanded: false
};
}
handleNavToggle => ???
Thanks!
setState has a second form you can use if you want the state to depend on the previous state.
I suppose this is something like what you're after:
constructor(props) {
super(props);
this.state = {
expanded: false
};
}
toggleExpanded = () => {
this.setState((prevState) => ({
expanded: !prevState.expanded ? 'expanded' : false
}));
};
render() {
return (
<Nav.Toggle
onClick={this.toggleExpanded}
/>
);
}
However, I'd recommend you to stick to one type. Either boolean or string. With a boolean, the setState callback would simply be as (prevState) => ({ expanded: !prevState.expanded })
So if it's a boolean initially, you can just put exclamation mark in front of it.
onClick={() => this.setState(() => ({expanded: !this.state.expanded}))}
this way it will become true or false back and forth.
Edit: Don't forget to return as an object so you need open to normal paranthesis before curly braces.

React doesn't render all components of array

I want to dynamically add Components, after clicking the "add" button.
For that, I created an array that consists of all the components, and add them on click.
My problem is, that it only renders one component, even though it consists of several ones.
My code looks like this:
class QuestionBlock extends React.Component {
constructor(props) {
super(props);
this.state = {answersArray: []};
}
addPossibleAnswer() {
this.state.answersArray.push(
<PossibleAnswers id={this.state.answersArray.length + 1}/>
)
this.forceUpdate();
}
componentWillMount() {
this.state.answersArray.push(
<PossibleAnswers id={this.state.answersArray.length + 1}/>
)
}
render() {
console.log(this.state.answersArray) // Grows after adding componenets, but they are not rendered.
return (
<div>
{this.state.answersArray}
<AddPossibleAnswer addPossibleAnswer={() => this.addPossibleAnswer()} />
</div>
);
}
}
If you see what I did wrong, I'd be really glad if you could help me out!
Instead of mutating state directly and adding JSX to it, you can instead keep raw data in your state and derive the JSX from that in the render method instead.
Example
class QuestionBlock extends React.Component {
state = { answers: 1 };
addPossibleAnswer = () => {
this.setState(({ answers }) => ({ answers: answers + 1 }));
};
render() {
return (
<div>
{Array.from({ length: this.state.answers }, (_, index) => (
<PossibleAnswers key={index} id={index} />
))}
<AddPossibleAnswer addPossibleAnswer={this.addPossibleAnswer} />
</div>
);
}
}
You don't interact with state like you do. Never mutate the state field. You need to use this.setState:
this.setState(prevState => ({answersArray: prevState.answersArray.concat([
<PossibleAnswers id={prevState.answersArray.length + 1}])}));
Having said that, it is also strange that you store components in state. Usually, you would store data and create the components based on the data in the render method.
You are directly pushing elements to the array without setState so the component won't re-render
Also avoid using tthis.forceUpdate() as much as you can in your application because this is not recommended much
You need to change your code like below. The recommended approach for dealing with arrays in react is using previous state and push to an array
addPossibleAnswer() {
this.setState(prevState => ({
answersArray: [...prevState.answersArray, <PossibleAnswers id={prevState.answersArray.length + 1}/>]
}));
}
componentWillMount() {
this.setState(prevState => ({
answersArray: [...prevState.answersArray, <PossibleAnswers id={prevState.answersArray.length + 1}/>]
}));
}
Also keep in mind that componentWillMount life cycle method is deprecated in react 16. So move the code to componentDidMount instead
Here is the corrected code
class QuestionBlock extends React.Component {
constructor(props) {
super(props);
this.state = {answersArray: []};
}
addPossibleAnswer() {
this.setState(prevState => ({
answersArray: [...prevState.answersArray, <PossibleAnswers id={prevState.answersArray.length + 1}/>]
}));
}
componentDidMount() {
this.setState(prevState => ({
answersArray: [...prevState.answersArray, <PossibleAnswers id={prevState.answersArray.length + 1}/>]
}));
}
render() {
const { answersArray } = this.state;
return (
<div>
{answersArray}
<AddPossibleAnswer addPossibleAnswer={() => this.addPossibleAnswer()} />
</div>
);
}
}

How to remove an instance of a React component class instantiated by its parent's state?

(Pardon the verbose question. I'm brand new to React and ES6, and I'm probably overly-convoluting this.)
I am writing an app that contains a button component. This button calls a method onAddChild that creates another component of class ColorModule by adding a value to an array stored in the App's state.
In each newly created ColorModule, I want to include another button that will remove the module. Since this component is created by an array.map method, my thought is that if I can find the index of the array item that corresponds with the component and use that index in array.splice then perhaps that component will be removed (untested theory). That said, I'm not really sure how to find the index where I would use this in my onRemoveModule method.
Two part question: 1) How would I go about finding the index of the array item in my state, and 2) if I'm completely off base or there's a better way to do this altogether, what does that solution look like?
imports...
class App extends Component {
static propTypes = {
children: PropTypes.node,
};
constructor(props) {
super(props);
this.state = {
// Here's the array in question...
moduleList: [1],
};
this.onAddChild = this.onAddChild.bind(this);
this.onRemoveModule = this.onRemoveModule.bind(this);
this.className = bemClassName.bind(null, this.constructor.name);
}
onAddChild(module) {
const moduleList = this.state.moduleList;
this.setState({ moduleList: moduleList.concat(1) });
}
onRemoveModule( e ) {
e.preventDefault();
...¯\_(ツ)_/¯
}
render() {
const { className } = this;
return (
<div className={className('container')}>
<Header onAddChild={this.onAddChild} /> /* Add module button lives here */
<div className="cf">
{this.state.moduleList.map(
( delta, index ) => {
return (
<ColorModule
className="cf"
onRemove={this.onRemoveModule}
key={index}
moduleId={'colorModule' + index}
/>
); /* Remove module button would live in the module itself */
}
)}
</div>
</div>
);
}
}
export default App;
Well this part is pretty easy, all you need to do is pass the index as prop to the ColorModule component and when calling the onRemove method in it you could pass it back to the onRemoveModule. However react optimizes based on keys and its a really good idea to have a unique id given to each module instance.
class App extends Component {
static propTypes = {
children: PropTypes.node,
};
constructor(props) {
super(props);
this.state = {
// Here's the array in question...
moduleList: [1],
};
this.onAddChild = this.onAddChild.bind(this);
this.onRemoveModule = this.onRemoveModule.bind(this);
this.className = bemClassName.bind(null, this.constructor.name);
}
onAddChild(module) {
const moduleList = this.state.moduleList;
this.setState({ moduleList: moduleList.concat(uuid()) }); //uuid must return a unique id everytime to be used as component key
}
onRemoveModule( index ) {
// now with this index you can update the moduleList
}
render() {
const { className } = this;
return (
<div className="cf">
{this.state.moduleList.map(
( delta, index ) => {
return (
<ColorModule
className="cf"
index={index}
onRemove={this.onRemoveModule}
key={delta}
moduleId={'colorModule' + delta}
/>
);
}
)}
</div>
);
}
}
Now in ColorModule component
class ColorModule extends React.Component {
onRemoveClick=() => {
this.props.onRemove(this.props.index);
}
}
Check this answer for more details on how to pass data from Child component to Parent
I ended up solving this problem using some of the guidance here from #ShubhamKhatri (didn't know about unique ID generation!), but I took a slightly different approach and handled the solution using state manipulation in App without needing a new method in my ColorModule component. I also never knew about currying in ES6, so that discovery made passing in the index values needed to manipulate my state array possible
If I'm off-base here or being inefficient, I'm definitely still open to feedback on a better way!
class App extends Component {
constructor(props) {
super(props);
this.state = {
moduleList: [{ id: UniqId(), removeModule: false }],
};
this.onAddChild = this.onAddChild.bind(this);
this.className = bemClassName.bind(null, this.constructor.name);
}
onAddChild(module) {
const moduleList = this.state.moduleList;
this.setState({
moduleList: moduleList.concat({
id: UniqId(),
removeModule: false,
}),
});
}
onRemoveModule = ( i, arr ) => (e) => {
const moduleList = this.state.moduleList;
e.preventDefault();
moduleList[i].removeModule = true;
this.setState({ moduleList: moduleList });
}
render() {
const { className } = this;
return (
<div className={className('container')}>
<Header onAddChild={this.onAddChild} />
<div className="cf">
{this.state.moduleList.map(
( delta, index ) => {
if ( !this.state.moduleList[index].removeModule ) {
return (
<ColorModule
className="cf"
onRemove={this.onRemoveModule( index, this.state.moduleList )}
index={index}
key={delta.id}
moduleId={'colorModule' + delta}
/>
);
}
}
)}
</div>
</div>
);
}
}

Focus a certain input on a list of dynamically generated inputs

I have a list of dynamically generated inputs.
input --> onClick new Input beneath
[dynamically added]input
input
How can give just this dynamically added input focus?
The input has the textInput ref. This partly works:
componentWillUpdate(){
this.textInput.focus();
}
Yet, just works or the first new Input. Then it seems like the logic breaks.
the inputs are .map() from an array. Is there a way to either say, if the current rendered element has el.isActive to focus it. Or just say focus the input with the index 5?
CODE
Inputsgenerating file/component
import React from 'react';
import ReactDOM from 'react';
import _ from 'lodash'
class SeveralInputs extends React.Component {
constructor(props) {
super(props);
this.state = {
value: ' '
}
this.showIndex = this
.showIndex
.bind(this)
this.map = this
.map
.bind(this)
this.handleChange = this
.handleChange
.bind(this);
}
componentWillUpdate() {
this.textinput && this
.textInput
.focus();
}
render() {
return (
<ul>
{this.map()}
</ul>
)
}
map() {
{
return this
.props
.data
.map((name, index) => <li
onKeyPress={this
.showIndex
.bind(this, index)}
key={index}><input
onChange={this
.handleChange
.bind(this, index)}
task={this.task}
value={name.value}
ref={(input) => {
this.textInput = input;
}}
type="text"/>{name.value}</li>)
}
}
handleChange(index, e) {
let data = this
.props
.data
.splice(index, 1, {
value: e.target.value,
isActive: true
})
this
.props
.refreshState(data);
}
showIndex(index, e) {
if (e.which === 13 || e.keyPress === 13) {
let data = this.props.data[index].isActive = false
data = this
.props
.data
.splice(index + 1, 0, {
value: ' ',
isActive: true
})
this
.props
.refreshState(data);
} else {
return null
}
}
}
export default SeveralInputs
The data that lives in the parent component
const data = [
{
value: 0,
isActive: true
}, {
value: 2,
isActive: false
}
]
The parents state:
this.state = {
error: null,
data
};
The parents render
render() {
return (
<div>
{/* <Input/> */}
{/* <SeveralItems refreshState={this.refreshState} data={this.state.data.value}/> */}
<SeveralInputs refreshState={this.refreshState} data={this.state.data}/> {/* <SeveralInputsNested refreshState={this.refreshState} data={this.state.data}/> {this.items()} */}
</div>
);
}
refreshState(data) {
this.setState({data: this.state.data})
console.log(this.state.data)
}
The first issue I see is that in refreshState you pass some data that you do not handle, try this:
refreshState(newData) {
this.setState({data: newData})
}
And trying to log this.state right after won't work because :
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied.

Focusing div elements with React

Is it possible to focus div (or any other elements) using the focus() method?
I've set a tabIndex to a div element:
<div ref="dropdown" tabIndex="1"></div>
And I can see it gets focused when I click on it, however, I'm trying to dynamically focus the element like this:
setActive(state) {
ReactDOM.findDOMNode(this.refs.dropdown).focus();
}
Or like this:
this.refs.dropdown.focus();
But the component doesn't get focus when the event is triggered. How can I do this? Is there any other (not input) element I can use for this?
EDIT:
Well, It seems this it actually possible to do: https://jsfiddle.net/69z2wepo/54201/
But it is not working for me, this is my full code:
class ColorPicker extends React.Component {
constructor(props) {
super(props);
this.state = {
active: false,
value: ""
};
}
selectItem(color) {
this.setState({ value: color, active: false });
}
setActive(state) {
this.setState({ active: state });
this.refs.dropdown.focus();
}
render() {
const { colors, styles, inputName } = this.props;
const pickerClasses = classNames('colorpicker-dropdown', { 'active': this.state.active });
const colorFields = colors.map((color, index) => {
const colorClasses = classNames('colorpicker-item', [`colorpicker-item-${color}`]);
return (
<div onClick={() => { this.selectItem(color) }} key={index} className="colorpicker-item-container">
<div className={colorClasses}></div>
</div>
);
});
return (
<div className="colorpicker">
<input type="text" className={styles} name={inputName} ref="component" value={this.state.value} onFocus={() => { this.setActive(true) }} />
<div onBlur={() => this.setActive(false) } onFocus={() => console.log('focus')} tabIndex="1" ref="dropdown" className={pickerClasses}>
{colorFields}
</div>
</div>
);
}
}
React redraws the component every time you set the state, meaning that the component loses focus. In this kind of instances it is convenient to use the componentDidUpdate or componentDidMount methods if you want to focus the element based on a prop, or state element.
Keep in mind that as per React Lifecycle documentation, componentDidMount will only happen after rendering the component for the first time on the screen, and in this call componentDidUpdate will not occur, then for each new setState, forceUpdate call or the component receiving new props the componentDidUpdate call will occur.
componentDidMount() {
this.focusDiv();
},
componentDidUpdate() {
if(this.state.active)
this.focusDiv();
},
focusDiv() {
ReactDOM.findDOMNode(this.refs.theDiv).focus();
}
Here is a JS fiddle you can play around with.
This is the problem:
this.setState({ active: state });
this.refs.component.focus();
Set state is rerendering your component and the call is asynchronous, so you are focusing, it's just immediately rerendering after it focuses and you lose focus. Try using the setState callback
this.setState({ active: state }, () => {
this.refs.component.focus();
});
A little late to answer but the reason why your event handler is not working is probably because you are not binding your functions and so 'this' used inside the function would be undefined when you pass it as eg: "this.selectItem(color)"
In the constructor do:
this.selectItem = this.selectItem.bind(this)
this.setActive = this.setActive.bind(this)
This worked in my case
render: function(){
if(this.props.edit){
setTimeout(()=>{ this.divElement.focus() },0)
}
return <div ref={ divElement => this.divElement = divElement}
contentEditable={props.edit}/>
}

Categories