React App class won't render when a child Component changes - javascript

I just started to learn React. I'm trying to write a Todo list and so far it looks like:
However when I check the box of a Todo, the count of things left to do won't change even when the state of a list of Todos changes (the 'checked' property of a Todo that I just checked change to true)
My App.js:
import React, {Component} from 'react';
import TaskComponent from "./TaskComponent";
class App extends Component {
constructor(props) {
super(props)
this.state = {
taskList: [],
newTaskContent: ''
}
this.generateTask = this.generateTask.bind(this)
this.updateNewTaskContent = this.updateNewTaskContent.bind(this)
}
generateTask() {
if (this.state.newTaskContent) {
const joined = this.state.taskList.concat({
id: this.state.taskList.length + 1,
content: this.state.newTaskContent,
checked: false
})
this.setState({taskList: joined, newTaskContent: ''})
}
}
updateNewTaskContent({target: {value}}) {
this.setState({newTaskContent: value})
}
render() {
return (
<div>
<ul>{this.state.taskList.map(task => <TaskComponent key={task.id} task={task.content}
checked={task.checked}/>)}</ul>
<input type='text' placeholder='Type your new task'
onChange={this.updateNewTaskContent} value={this.state.newTaskContent}/>
<button name='generateTask' onClick={this.generateTask}>Generate task</button>
<div>There are {this.state.taskList.filter(task => !task.checked).length} things left to do!</div>
</div>
);
}
}
export default App;
My TaskComponent.js file:
import React, {Component} from 'react'
class TaskComponent extends Component {
constructor({task, checked}) {
super(undefined)
this.state = {
taskContent: task,
checkedState: checked
}
this.changeHandler = this.changeHandler.bind(this)
}
changeHandler({target: {checked}}) {
this.setState({checkedState: checked})
}
render() {
return (
<div>
<span>{this.state.taskContent}</span>
<input type="checkbox" checked={this.state.checkedState} onChange={this.changeHandler}/>
</div>
);
}
}
export default TaskComponent;
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
taskList: [],
newTaskContent: ''
}
this.generateTask = this.generateTask.bind(this)
this.updateNewTaskContent = this.updateNewTaskContent.bind(this)
}
generateTask() {
if (this.state.newTaskContent) {
const joined = this.state.taskList.concat({
id: this.state.taskList.length + 1,
content: this.state.newTaskContent,
checked: false
})
this.setState({taskList: joined, newTaskContent: ''})
}
}
updateNewTaskContent({target: {value}}) {
this.setState({newTaskContent: value})
}
render() {
return (
<div>
<ul>{this.state.taskList.map(task => <TaskComponent key={task.id} task={task.content}
checked={task.checked}/>)}</ul>
<input type='text' placeholder='Type your new task'
onChange={this.updateNewTaskContent} value={this.state.newTaskContent}/>
<button name='generateTask' onClick={this.generateTask}>Generate task</button>
<div>There are {this.state.taskList.filter(task => !task.checked).length} things left to do!</div>
</div>
);
}
}
class TaskComponent extends React.Component {
constructor({task, checked}) {
super(undefined)
this.state = {
taskContent: task,
checkedState: checked
}
this.changeHandler = this.changeHandler.bind(this)
}
changeHandler({target: {checked}}) {
this.setState({checkedState: checked})
}
render() {
return (
<div>
<span>{this.state.taskContent}</span>
<input type="checkbox" checked={this.state.checkedState} onChange={this.changeHandler}/>
</div>
);
}
}
ReactDOM.render(<App />, document.querySelector("#root"));
<script src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Inside the TaskComponent class I add the function for the event of changing the "checked" state of the check box but somehow the 'taskList' state in my App does not change at all when I try to console.log it. What is my problem here? Be gentle since I'm new to React and Javascript in general.

You are setting state in TaskComponent and expecting it to change the prop in App.js.
Instead of setting a TaskComponents's state when it is checked, I would recommend calling a function passed in as a prop when it is checked, which has its id and new value. Something along the lines of:
App.js:
// somewhere in your class:
handler(id, value) {
// set state to reflect changes
}
// in your render()
<ul>{this.state.taskList.map((task) => {
<TaskComponent onChange={this.handler} id={task.id} key={task.id} task={task.content} checked={task.checked} />})}</ul>
In TaskComponent.js:
changeHandler({target: {checked}}) {
this.props.onChange(this.props.id, checked);
}
I would also recommend making TaskComponent not have state at all, because it seems unnecessary to me.

Related

Reactjs pass state as props to child

I Started to learn React.
I've seen the tutorial of react website
https://reactjs.org/docs/hello-world.html
and still, sometimes all the props idea doesn't work for me.
my program is crashing and the error message is :
why its happening?
someone can help me, please?
TypeError: Cannot read property 'handleSetColor' of undefined
onSetColor
8 | class ColorDisplay extends Component {
9 |
10 | onSetColor(newColor) {
11 | this.handleSetColor(newColor);
12 | }
import React, { Component } from 'react';
import './App.css';
import _ from 'underscore';
import PropTypes from 'prop-types';
class ColorDisplay extends Component {
onSetColor(newColor) {
this.props.handleSetColor(newColor);
}
render() {
return (<p>
<input type="color" value={this.props.val} onChange={this.onSetColor} />
</p>);
}
}
ColorDisplay.propTypes = {
handleSetColor: PropTypes.func,
val: PropTypes.string,
};
function TextDisplay(props) {
return <input type="text" value={props.val} readOnly={true} />
}
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
showColorDisplay: true,
showTextDisplay: true,
val: '#ffff00',
}
}
randomizeColor = (e) => {
this.setState(oldState => ({
val: _.sample(['#ffff00', '#ff00ff', '#abff01', '#10f100', '#3030ff', '#ddcc10']),
}));
}
toggle(val) {
this.setState(oldState => ({ [val]: !oldState[val] }));
}
setColor(newColor) {
this.setState({ val: newColor });
}
toggleColorDisplay = (e) => {
this.toggle('showColorDisplay');
}
toggleTextDisplay = (e) => {
this.toggle('showTextDisplay');
}
render() {
return (
<div className="spaced">
<h1>HELLO</h1>
<button onClick={this.randomizeColor}>Shuffle</button><br />
<label>
<br />
<input type="checkbox" checked={this.state.showColorDisplay} onChange={this.toggle.bind(this, 'showColorDisplay')} />
Color Display
</label>
<label>
<input type="checkbox" checked={this.state.showTextDisplay} onChange={this.toggle.bind(this, 'showTextDisplay')} />
Text Display
</label>
<div className="spaced">
{this.state.showColorDisplay && <ColorDisplay val={this.state.val} handleSetColor={this.setColor} />}
</div>
<div className="spaced">
{this.state.showTextDisplay && <TextDisplay val={this.state.val} />}
</div>
</div>
);
}
}
You need to call the Component constructor and bind the scope of onSetColor:
class ColorDisplay extends Component {
constructor(props) {
// This calls the constructor for React.Component
super(props);
// you also need to explicitly bind the scope
this.onSetColor = this.onSetColor.bind(this);
}
onSetColor(newColor) {
this.props.handleSetColor(newColor);
}
render() {
return (<p>
<input type="color" value={this.props.val} onChange={this.onSetColor} />
</p>);
}
}
Let me know if you need any further explanation. Here's an example from the Reactjs site that has to do with your issue.
I can't replicate the problem right now, but i would try binding the method to Color Display, just in case...

React: How to display a default object and highlight li bold?

I am fairly new to React and was wondering if anybody could give me an insight on a problem I am stuck with.
Right now I have a parent(Hello.js) component and two children(Mixer.js and renderCont.js) at the same level.
I am trying to render a list in the Mixer.js and display its corresponding objects in the Hello.js through by passing the values into RenderCont.js. I've gotten to a point where nothing is displayed before I click on any of the list to pass on a object.
From here is where I am stuck: I want the first object of the list to be displayed as a default, at the same time bold the first in the list. And then execute the as I have below.
This is my first time posting a question on stackoverflow so I'm not sure if my question makes sense with the attached codes but I will greatly appreciate any kind of support.
Parent Hello.js:
import React, { Component } from 'react';
import RenderCont from './renderCont.js';
import Mixer from './Mixer';
class Hello extends Component{
constructor(props) {
super(props);
this.state = {
items: [{
id: 0,
name: "First",
background: "white"
}, {
id: 1,
name: "Second",
background: "yellow"
}, {
id: 2,
name: "Third",
background: "blue"
}],
selectedItem: 0
}
this.handle = this.handle.bind(this)
}
handle(value) {
// console.log(this.state.selectedItem);
this.setState({
selectedItem: value
})
}
render() {
const list = this.state.items.map((item) => {
return(item);
})
return (
<div>
<Mixer item={list} onClick={this.handle} selected={this.state.selectedItem}/>
<ul id = "todo" >
<RenderCont item={this.state.selectedItem}/>
</ul>
</div>
)
}
}
export default Hello;
Mixer.js Child1:
import React, { Component } from 'react';
class Mixer extends Component{
constructor(props) {
super(props);
this.state = {
}
this.handleClick = this.handleClick.bind(this);
}
handleClick(item){
this.props.onClick(item);
}
renderTodos(propItems) {
return (
<div>
{propItems.map((item) => (
<li className={this.props.selected === item ? 'media clicked' : 'media'}
key={item.id} onClick = {() => this.handleClick(item)}>
{item.name}
</li>
))}
</div>
)
}
render() {
return (
<div className="yoyoyo">
{this.renderTodos(this.props.item)}
</div>
)
}
}
export default Mixer;
Second Child Comp renderCont.js :
import React, { Component } from 'react';
class RenderCont extends Component{
constructor(props) {
super(props);
}
renderBox(item){
return(
<div style={{color:item.background}}>
{item.id}
{item.name}
</div>
)
}
render() {
return (
<div className="yoyo">
{this.renderBox(this.props.item)}
</div>
)
}
}
export default RenderCont;
and the CSS:
.yoyo{
left: 500px;
background-color:red;
width:500px;
height:500px;
}
.media{
color: black;
}
.clicked{
font-weight: 900;
}
.yoyoyo{
background-color:lightblue;
width:200px;
height:200px;
}
I think the problem is some mismatch between the initial and eventual value of this.props.selected in Mixer.js. You initially set this.state.selectedItem = 0, and this is what is initially passed as the selected prop to Mixer. But the test you apply in that component is
this.props.selected === item ?
While there is one item.id that === 0, there is never an item that === 0. So no items are highlighted at first. But then, once an item is clicked and selectedItem is actually set to an item, the entry is made bold.
So it looks like you need to either make your initial selection equal to the item.id === 0 reference, or consistently refer to items within your components by their id's.

Getting value from react component

I have a component InputArea with state = {input: ''}
Then I map several of these components in a container and write them in state = {inputAreas: []}
Now, how can I get inputs in the container? Logging this.state.inputAreas[0] returns object like this:
{$$typeof: Symbol(react.element), type: ƒ, key: "1", ref: null, props:
{…}, …}
In elements it shows like this:
<input type="text" class="form-control" name="input" value="abc">
Using this.state.prefooterArea[0].value gives undefined.
I also tried passing input from component to container as props, but it says getInput is not a function. From what I understood it has something to do with the fact I used map in the container. I can't use redux in this project.
Code of component
class PrefooterAreaInput extends Component {
state = {
input: ''
}
textChangedHandler = (event) => {
let newState = {};
newState[event.target.name] = event.target.value;
this.setState(newState);
}
render() {
return (
<div>
<input
className="form-control"
type="text"
name="input"
value = {this.state.input}
onChange={this.textChangedHandler}
/>
</div>
)
}
}
Code of container
class DescriptionFrame extends Component {
state = {,
prefooterArea: [<PrefooterAreaInput key={1}/>]
};
addFooterInputHandler = event => {
event.preventDefault();
if (this.state.prefooterArea.length < prefooterInputFieldsMax) {
var newPrefooterArea = this.state.prefooterArea.map(
inputField => inputField
);
newPrefooterArea.push(
<PrefooterAreaInput key={this.state.prefooterArea.length + 1} />
);
this.setState({ prefooterArea: newPrefooterArea });
}
};
removeFooterInputHandler = event => {
event.preventDefault();
if (this.state.prefooterArea.length > 1) {
var newPrefooterArea = this.state.prefooterArea.map(
inputField => inputField
);
newPrefooterArea.splice(newPrefooterArea.length - 1);
this.setState({ prefooterArea: newPrefooterArea });
}
render() {
// want to get this.state.prefooterArea[0]'s value
return (
<div>
{this.state.prefooterArea}
<a
className="nav-link"
href=""
onClick={this.addFooterInputHandler}
>
Add More
</a>
<a
className="nav-link"
href=""
onClick={this.removeFooterInputHandler}
>
Remove Last
</a>
</div>
);
}
}
Figured it out. This caused problem.
prefooterArea: [<PrefooterAreaInput key={1}/>]
I should have added that initial PrefooterAreaInput with lifecycle method instead. With that I was able to pass state just fine.
Are you trying to achieve something like this ?
child component :
export default class InputBox extends React.Component {
render() {
return (
<input onChange={event => this.props.onChange(event.target.value)} />
);
}}
parent component :
import InputBox from './InputBox';
class FilterBar extends React.Component {
constructor(props) {
super(props);
this.state = {
inputs: "" //get input value from state this input
};
this.updateFilters = this.updateFilters.bind(this);
}
updateFilters(i) {
this.setState({ inputs: i }); // this will print whatever input you type
}
render() {
return (
<div>
<InputBox onChange={(i) => this.updateFilters(i)} />
</div>
);
}
}

React - Unable to click or type in input form using react-tagsinput and react-mailcheck

I am using react-tagsinput, react-input-autosize, and react-mailcheck to create input tags that also suggests the right domain when the user misspell it in an email address.
I have gotten the react-tagsinput to work with react-input-autosize but when added with react-mailcheck my input form does not work at all, the form is un-clickable and unable to type and text into the field. I'm not getting any errors in the console and i'm not sure what is wrong with my code. I followed what they did in the react-mailcheck documentation: https://github.com/eligolding/react-mailcheck. I was hoping someone could look at it with a fresh pair of eyes to see what I am missing that is making it not work.
Thanks!
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import TagsInput from 'react-tagsinput';
import AutosizeInput from 'react-input-autosize';
import MailCheck from 'react-mailcheck';
class EmailInputTags extends Component {
static propTypes = {
label: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
};
constructor() {
super();
this.state = { tags: [], inputText: '' };
this.handleChange = this.handleChange.bind(this);
this.handleInputText = this.handleInputText.bind(this);
this.renderInput = this.renderInput.bind(this);
}
handleChange(tags) {
this.setState({ tags });
}
handleInputText(e) {
this.setState({ inputText: e.target.value });
}
renderInput({ addTag, ...props }) {
const { ...other } = props;
return (
<MailCheck email={this.state.inputText}>
{suggestion => (
<div>
<AutosizeInput
type="text"
value={this.state.inputText}
onChange={this.handleInputText}
{...other}
/>
{suggestion &&
<div>
Did you mean {suggestion.full}?
</div>
}
</div>
)}
</MailCheck>
);
}
render() {
const { label, name } = this.props;
return (
<div className="input-tag-field">
<TagsInput inputProps={{ placeholder: '', className: 'input-tag' }} renderInput={this.renderInput} value={this.state.tags} onChange={this.handleChange} />
<label htmlFor={name}>{label}</label>
</div>
);
}
}
export default EmailInputTags;
I have not tried this out.
Try passing as a prop to TagsInput the onChange function.
ie
{... onChange={(e) => {this.setState(inputText: e.target.value}}

Toggle active class on child components

I'm having a bit of a head ache trying to figure out the React way of implementing this.
I have a Searches component which houses SearchItems, when an item is clicked among other things I need to set it's state to active to that it gets the correct CSS, I managed to get this working fine but how would I go about removing the active state from the others?
I was thinking that I could pass down a function from the top level component that would take the ID of the search, when clicked it'd zip through SearchItems and change their state to either true/false depending on which ID it was?
Code below!
Top level component:
import React from "react";
import {Link} from "react-router";
import Search from "./Search";
export default class Searches extends React.Component {
constructor(){
super();
this.state = {
searches : [
{
id : "2178348216",
searchName: "searchName1",
matches: "5"
},
{
id : "10293840132",
searchName: "searchName2",
matches: "20"
}
]
};
}
render() {
const { searches } = this.state;
const SearchItems = searches.map((search) => {
return <Search key={search.id} {...search}/>
})
return (
<div> {SearchItems} </div>
);
}
}
Search items component
export default class Search extends React.Component {
constructor() {
super();
// Set the default panel style
this.state = {
panelStyle: { height: '90px', marginBottom: '6px', boxShadow: '' },
selected: false
}
}
isActive(){
return 'row panel panel-success ' + (this.state.selected ? 'active' : 'default');
}
viewNotifications(e){
this.setState({selected: true});
}
render() {
const { id, searchName, matches } = this.props;
const buttonStyle = {
height: '100%',
width: '93px',
backgroundColor: '#FFC600'
}
return (
<div style={this.state.panelStyle} className={this.isActive()}>
<div class="col-xs-10">
<div class="col-xs-7">
Search Name: {searchName}
</div>
<div class="col-xs-7">
Must Have: PHP, MySQL
</div>
<div class="col-xs-7">
Could Have: AngularJS
</div>
</div>
<button type="button" onClick={this.viewNotifications.bind(this)} style={buttonStyle} class="btn btn-default btn-lg"> {matches} </button>
</div>
);
}
}
I think you don't need the state in the child component at all. In fact is a good idea to avoid having state in most components so they are easy to reason and reuse.
I would leave all the state only on the parent component in this case.
TOP Component:
import React from "react";
import Search from "./search";
export default class Searches extends React.Component {
constructor(){
super();
this.state = {
searches : [
{
id : "2178348216",
searchName: "searchName1",
matches: "5"
},
{
id : "10293840132",
searchName: "searchName2",
matches: "20"
}
],
activeElement : null
};
}
_onSearchSelect(searchId) {
this.setState({'activeElement': searchId})
}
render() {
const { searches, activeSearchId } = this.state;
const SearchItems = searches.map((search) => {
return <Search key={search.id} {...search}
isActive={search.id === activeElement}
onSelect={this._onSearchSelect.bind(this)} />
})
return (
<div> {SearchItems} </div>
);
}
}
CHILD Component:
import React from "react";
export default class Search extends React.Component {
_getPanelClassNames() {
const { isActive } = this.props
return 'row panel panel-success ' + (isActive ? 'active' : 'default')
}
_onSelect() {
const { id, onSelect } = this.props;
onSelect(id)
}
render() {
const { searchName, matches } = this.props;
const panelStyle = { height: '90px', marginBottom: '6px', boxShadow: '' }
const buttonStyle = {
height: '100%',
width: '93px',
backgroundColor: '#FFC600'
}
return (
<div style={panelStyle} className={this._getPanelClassNames()}>
<div className="col-xs-4">
Search Name: {searchName}
</div>
<div className="col-xs-3">
Must Have: PHP, MySQL
</div>
<div className="col-xs-3">
Could Have: AngularJS
</div>
<div className="col-xs-2">
<button type="button" onClick={this._onSelect.bind(this)}
style={buttonStyle} className="btn btn-default btn-lg"
>
{matches}
</button>
</div>
</div>
);
}
}
You can also see it running in Plunker: https://plnkr.co/edit/sdWzFedsdFx4MpbOuPJD?p=preview
Ok it turns out this is simpler than I thought and is simply a case of understanding how react works(and not getting confused) .
When you have a top level component you pass it's state via props to children, when you update the state in the top level component it'll pass that down to the children and you can use componentWillReceiveProps to take action.
I added a function to my top level component called updateActiveSearch which simply sets the state of the TOP level component I then passed the activeElement state as a prop to the child Elements along with the function. When a child element calls this function to set itself as active all of them will fire componentWillReceiveProps, they simply just need to check their own ID against the one they've received, if it matches they're active, if it doesn't they're not!
So my top level component now looks like this:
export default class Searches extends React.Component {
constructor(){
super();
this.state = {
searches : [
{
id : "2178348216",
searchName: "searchName1",
matches: "5"
},
{
id : "10293840132",
searchName: "searchName2",
matches: "20"
}
],
activeElement : 0
};
}
// This function gets passed via a prop below
updateActiveSearch(id){
//console.log(id);
this.setState({activeElement : id});
}
render() {
const SearchItems = this.state.searches.map((search) => {
return <Search activeElement={this.state.activeElement} goFunction={this.updateActiveSearch.bind(this)} key={search.id} {...search}/>
})
return (
<div> {SearchItems} </div>
);
}
}
CHILD COMPONENTS
export default class Search extends React.Component {
constructor() {
super();
// Set the default panel style
this.state = {
panelStyle: { height: '90px', marginBottom: '6px', boxShadow: '' },
selected: false
}
}
// This happens right before the props get updated!
componentWillReceiveProps(incomingProps){
if(incomingProps.activeElement == this.props.id){
this.setState({selected: true});
} else {
this.setState({selected: false});
}
}
isActive(){
return 'row panel panel-success ' + (this.state.selected ? 'active' : 'default');
}
viewNotifications(e){
//this.state.panelStyle.boxShadow = '-2px 3px 20px 5px rgba(255,198,0,1)';
this.setState({selected: true});
this.props.goFunction(this.props.id);
}
render() {
const { id, searchName, matches } = this.props;
const buttonStyle = {
height: '100%',
width: '93px',
backgroundColor: '#FFC600'
}
return (
<div style={this.state.panelStyle} className={this.isActive()}>
<div class="col-xs-10">
<div class="col-xs-7">
Search Name: {searchName}
</div>
<div class="col-xs-7">
Must Have: PHP, MySQL
</div>
<div class="col-xs-7">
Could Have: AngularJS
</div>
</div>
<button type="button" onClick={this.viewNotifications.bind(this)} style={buttonStyle} class="btn btn-default btn-lg"> {matches} </button>
</div>
);
}
}

Categories

Resources