React child components won't unmount (reappear) - javascript

I'm running stuck on a problem in React (files provided below). I'm trying to generate a set of child components and would like to be able to remove these. I have a state parameter this.state.lipids which contains an array of objects, using these I generate several <DataListCoupledInput /> components inside <RatioCoupledDataLists />. The components are generated and do what their supposed to do with the exception of refusing to be removed.
When I alter my this.state.lipids array by removing an element via removeLipid(index) it triggers a re-render that for some reason magically generates the same <DataListCoupledInput /> component that I just removed.
I tried moving the responsibility up to the parent level of <RatioCoupledDataLists /> but the problem remains.
Please help me.
RatioCoupledDataLists.jsx
class RatioCoupledDataLists extends React.Component {
constructor(props) {
super(props)
this.state = {
lipids: [{value: null, upperLeaf: 1, lowerLeaf: 1}]
}
this.registerLipid = this.registerLipid.bind(this);
this.addLipid = this.addLipid.bind(this);
this.removeLipid = this.removeLipid.bind(this);
}
registerLipid(index, lipid) {
var lipids = [];
lipids = lipids.concat(this.state.lipids);
lipids[index] = lipid;
this.setState({lipids: lipids});
this.state.lipids[index] = lipid;
}
addLipid() {
var mapping = {value: null, upperLeaf: 1, lowerLeaf: 1};
this.setState({lipids: this.state.lipids.concat(mapping)});
}
removeLipid(index) {
var lipids = [];
lipids = lipids.concat(this.state.lipids);
lipids.splice(index, 1);
this.setState({lipids: lipids});
}
render() {
var itemIsLast = function(index, len) {
if (index + 1 === len) {
// item is last
return true;
}
return false;
}
var makeLipidSelects = function() {
var allLipids = [];
for (var i = 0; i < this.state.lipids.length; i++) {
// Add RatioCoupledDataList
allLipids.push(
<DataListCoupledInput
fr={this.registerLipid}
list="lipids"
description="Membrane lipids"
add={this.addLipid}
remove={this.removeLipid}
key={i}
id={i}
isFinal={itemIsLast(i, this.state.lipids.length)}
lipid={this.state.lipids[i]} />);
}
return allLipids;
}
makeLipidSelects = makeLipidSelects.bind(this);
return <div className="category">
<div className="title">Custom Membrane</div>
<DataList data={lipids} id="lipids" />
{makeLipidSelects()}
</div>
}
}
DataListCoupledInput.jsx
class DataListCoupledInput extends React.Component {
constructor(props) {
super(props);
this.state = {
value: this.props.lipid.value,
active: this.props.isFinal,
upperLeaf: this.props.lipid.upperLeaf,
lowerLeaf: this.props.lipid.lowerLeaf
}
this.handleChange = this.handleChange.bind(this);
this.registerLeafletValue = this.registerLeafletValue.bind(this);
this.registerLipid = this.registerLipid.bind(this);
}
registerLipid(lipid) {
this.props.fr(this.props.id, lipid);
}
handleChange(event) {
this.registerLipid({value: event.target.value, upperLeaf: this.state.upperLeaf, lowerLeaf: this.state.lowerLeaf});
this.setState({value: event.target.value});
}
registerLeafletValue(leaflet) {
if (leaflet.name === "upperRatio") {
this.registerLipid({value: this.state.value, upperLeaf: leaflet.value, lowerLeaf: this.state.lowerLeaf});
this.setState({upperLeaf: leaflet.value});
}
if (leaflet.name === "lowerRatio") {
this.registerLipid({value: this.state.value, upperLeaf: this.state.upperLeaf, lowerLeaf: leaflet.value});
this.setState({lowerLeaf: leaflet.value});
}
}
render() {
var canEdit = function() {
if (this.props.isFinal === true) {
return <input onBlur={this.handleChange} list={this.props.list} />;
} else {
return <div className="description">{this.state.value}</div>
}
}
canEdit = canEdit.bind(this);
var addOrRemove = function() {
if (this.props.isFinal !== true) {
return <div className="remove" onClick={() => {this.props.remove(this.props.id)}}><div>-</div></div>;
} else {
return <div className="add" onClick={this.props.add}><div>+</div></div>;
}
}
addOrRemove = addOrRemove.bind(this);
return (
<div className="input-wrap">
<div className="input">
{canEdit()}
</div>
<div className="abundance">
<DynamicNumber
min={this.props.min}
max={this.props.max}
step={this.props.step}
name="lowerRatio"
value={this.state.upperLeaf}
fr={this.registerLeafletValue}
type="linked" />
<div className="to">:</div>
<DynamicNumber
min={this.props.min}
max={this.props.max}
step={this.props.step}
name="upperRatio"
value={this.state.lowerLeaf}
fr={this.registerLeafletValue}
type="linked" />
</div>
{addOrRemove()}
<div className="description">{this.props.description}</div>
</div>
)
}
}

Related

React component does not update on set state

I am trying to conditionally render a component based on toggling of flag inside state. It looks like the state is getting updated but the component is not getting rendered. Can some one tell what is wring here. renderTree function updates the state, but render is not called then.
import React from "react";
import CheckboxTree from "react-checkbox-tree";
import "react-checkbox-tree/lib/react-checkbox-tree.css";
import { build } from "../data";
import { Input, Dropdown } from "semantic-ui-react";
import _ from "lodash";
class Widget extends React.Component {
constructor(props) {
super(props);
this.state = {
nodes: build(),
checked: [],
expanded: [],
isDropdownExpanded: false,
keyword: ""
};
}
onCheck = checked => {
this.setState({ checked }, () => {
console.log(this.state.checked);
});
};
onExpand = expanded => {
this.setState({ expanded }, () => {
console.log(this.state.expanded);
});
};
renderTree = () => {
this.setState(
prevState => {
return {
...prevState,
isDropdownExpanded: !prevState.isDropdownExpanded
};
},
() => {
console.log(this.state);
}
);
};
onSearchInputChange = (event, data, searchedNodes) => {
this.setState(prevState => {
if (prevState.keyword.trim() && !data.value.trim()) {
return {
expanded: [],
keyword: data.value
};
}
return {
expanded: this.getAllValuesFromNodes(searchedNodes, true),
keyword: data.value
};
});
};
shouldComponentUpdate(nextProps, nextState) {
if (this.state.keyword !== nextState.keyword) {
return true;
}
if (!_.isEqual(this.state.checked, nextState.checked)) {
return true;
}
if (_.isEqual(this.state.expanded, nextState.expanded)) {
return false;
}
return true;
}
getAllValuesFromNodes = (nodes, firstLevel) => {
if (firstLevel) {
const values = [];
for (let n of nodes) {
values.push(n.value);
if (n.children) {
values.push(...this.getAllValuesFromNodes(n.children, false));
}
}
return values;
} else {
const values = [];
for (let n of nodes) {
values.push(n.value);
if (n.children) {
values.push(...this.getAllValuesFromNodes(n.children, false));
}
}
return values;
}
};
keywordFilter = (nodes, keyword) => {
let newNodes = [];
for (let n of nodes) {
if (n.children) {
const nextNodes = this.keywordFilter(n.children, keyword);
if (nextNodes.length > 0) {
n.children = nextNodes;
} else if (n.label.toLowerCase().includes(keyword.toLowerCase())) {
n.children = nextNodes.length > 0 ? nextNodes : [];
}
if (
nextNodes.length > 0 ||
n.label.toLowerCase().includes(keyword.toLowerCase())
) {
n.label = this.getHighlightText(n.label, keyword);
newNodes.push(n);
}
} else {
if (n.label.toLowerCase().includes(keyword.toLowerCase())) {
n.label = this.getHighlightText(n.label, keyword);
newNodes.push(n);
}
}
}
return newNodes;
};
getHighlightText = (text, keyword) => {
const startIndex = text.indexOf(keyword);
return startIndex !== -1 ? (
<span>
{text.substring(0, startIndex)}
<span style={{ color: "red" }}>
{text.substring(startIndex, startIndex + keyword.length)}
</span>
{text.substring(startIndex + keyword.length)}
</span>
) : (
<span>{text}</span>
);
};
render() {
const { checked, expanded, nodes, isDropdownExpanded } = this.state;
let searchedNodes = this.state.keyword.trim()
? this.keywordFilter(_.cloneDeep(nodes), this.state.keyword)
: nodes;
return (
<div>
<Dropdown fluid selection options={[]} onClick={this.renderTree} />
{isDropdownExpanded && (
<div>
<Input
style={{ marginBottom: "20px" }}
fluid
icon="search"
placeholder="Search"
iconPosition="left"
onChange={(event, data) => {
this.onSearchInputChange(event, data, searchedNodes);
}}
/>
<CheckboxTree
nodes={searchedNodes}
checked={checked}
expanded={expanded}
onCheck={this.onCheck}
onExpand={this.onExpand}
showNodeIcon={true}
/>
</div>
)}
</div>
);
}
}
export default Widget;
Problem is in your shouldComponentUpdate method:
shouldComponentUpdate(nextProps, nextState) {
if (this.state.keyword !== nextState.keyword) {
return true;
}
if (!_.isEqual(this.state.checked, nextState.checked)) {
return true;
}
if (_.isEqual(this.state.expanded, nextState.expanded)) {
return false;
}
return true;
}
Since renderTree only changes isDropdownExpanded value, shouldComponentUpdate always returns false
If shouldComponenetUpdate returns true then your component re-renders, otherwise it dosen't.
In your code sandbox, it can be seen that every time you click on the dropdown, the shouldComponenetUpdate returns false for this condition
if (_.isEqual(this.state.expanded, nextState.expanded)) {
return false;
}
Either you need to change the state of this variable in your renderTree function or you need to re-write this condition as
if (_.isEqual(this.state.isDropdownExpanded, nextState.isDropdownExpanded)) {
return false;
}
Ciao, to force a re-render in React you have to use shouldComponentUpdate(nextProps, nextState) function. Something like:
shouldComponentUpdate(nextProps, nextState) {
return this.state.isDropdownExpanded !== nextState.isDropdownExpanded;
}
When you change isDropdownExpanded value, shouldComponentUpdate will be triggered and in case return is equal to true, component will be re-rendered. Here working example (based on your codesandbox).

ReactJS - Buttons not Re-Rendering on State Change?

React Newbie here,
import React, { Component } from "react";
class AudioList extends Component {
constructor(props) {
super(props);
this.audios = [];
this.buttonText = [];
for (let i = 0; i < this.props.songs.length; i++) {
this.audios.push(new Audio(this.props.songs[i].song_url));
this.buttonText.push(String(i));
}
this.state = {
songs: "",
buttonText: this.buttonText
};
}
componentWillMount() {
const songs = [];
for (let i = 0; i < this.props.songs.length; i++) {
this.audios[i].addEventListener("play", () => {
let stateArray = [...this.state.buttonText];
let stateArrayElement = { ...stateArray[i] };
stateArrayElement = "playing";
stateArray[i] = stateArrayElement;
console.log(stateArray);
this.setState({ buttonText: stateArray });
console.log(this.state.buttonText[i]);
});
songs.push(
<div className="song-preview">
<button
className="preview"
onClick={() => this.toggle(this.audios[i])}
>
{this.state.buttonText[i]}
</button>
</div>
);
}
this.setState({
songs: songs
});
}
componentWillUnmount() {
for (let i = 0; i < this.props.songs.length; i++) {
this.audios[i].pause();
}
}
getCurrentAudio() {
return this.audios.find(audio => false === audio.paused);
}
toggle(nextAudio) {
const currentAudio = this.getCurrentAudio();
if (currentAudio && currentAudio !== nextAudio) {
currentAudio.pause();
nextAudio.play();
}
nextAudio.paused ? nextAudio.play() : nextAudio.pause();
}
render() {
if (this.state.songs) {
return <div className="song-list">{this.state.songs}</div>;
} else {
return <div className="song-list"></div>;
}
}
}
export default AudioList;
I am using this code from a previous solution that I found on Stackoverflow (https://stackoverflow.com/a/50595639). I was able to implement this solution to solve my own challenge of needing to have multiple audio sources with one audio player and multiple buttons. However, I am now faced with a new challenge - I want a specific button's text to change when an event is fired up.
I came up with this implementation where the button text is based on an array in the state called buttonText. The buttons are rendered correctly on startup, but when the event listener picks up the event and changes the state, the text in the button is not re-rendering or changing, even though it is based on an element in an array in the state that is changing.
Does anyone have any suggestions about why it may be failing to re-render?
EDIT: Changing an individual array element in the state is based on React: how to update state.item[1] in state using setState?
I have restructured your code a bit (but it's untested it's tested now):
const songs = [
{
title: "small airplane long flyby - Mike_Koenig",
song_url: "http://soundbible.com/mp3/small_airplane_long_flyby-Mike_Koenig-806755389.mp3"
},
{
title: "Female Counts To Ten",
song_url: "http://soundbible.com/mp3/Female%20Counts%20To%20Ten-SoundBible.com-1947090250.mp3"
},
];
class AudioList extends React.Component {
audios = new Map();
state = {
audio: null,
song: null
};
componentDidUpdate() {
// stop playing if the song has been removed from props.songs
if (this.state.song && !this.props.songs.some(song => song.song_url === this.state.song.song_url)) {
this.toggle(this.state.audio, this.state.song);
}
}
componentWillUnmount() {
if (this.state.audio) {
this.state.audio.pause();
}
this.audios.clear();
}
toggle(audio, song) {
this.setState(state => {
if (audio !== state.audio) {
if (state.audio) {
state.audio.pause();
}
audio.play();
return { audio, song };
}
audio.pause();
return { audio: null, song: null };
});
}
getAudio(song) {
let audio = this.audios.get(song.song_url);
if (!audio) {
this.audios.set(song.song_url, audio = new Audio(song.song_url));
}
return audio;
}
render() {
return <div className="song-list">{
this.props.songs.map((song, i) => {
const audio = this.getAudio(song);
const playing = audio === this.state.audio;
return <div className="song-preview">
<button
className="preview"
onClick={this.toggle.bind(this, audio, song)}
>
{playing ? "playing" : (song.title || i)}
</button>
</div>
})
}</div>;
}
}
ReactDOM.render(<AudioList songs={songs} />, document.body);
<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>
Edit: added a title to the song-objects and display them on the buttons
I simply moved all of the code from componentWillMount() to render(). I also removed 'songs' as a state variable and set it to a variable that exists only in render as songs is simply just a set of divs.
import React, { Component } from "react";
const audio1 =
"http://soundbible.com/mp3/small_airplane_long_flyby-Mike_Koenig-806755389.mp3";
const audio2 =
"http://soundbible.com/mp3/Female%20Counts%20To%20Ten-SoundBible.com-1947090250.mp3";
class AudioList extends Component {
constructor(props) {
super(props);
this.audios = [];
this.buttonText = [];
for (let i = 0; i < this.props.songs.length; i++) {
this.audios.push(new Audio(this.props.songs[i]));
this.buttonText.push(String(i));
}
this.state = {
buttonText: this.buttonText
};
}
componentWillUnmount() {
for (let i = 0; i < this.props.songs.length; i++) {
this.audios[i].pause();
}
}
getCurrentAudio() {
return this.audios.find(audio => false === audio.paused);
}
toggle(nextAudio) {
const currentAudio = this.getCurrentAudio();
if (currentAudio && currentAudio !== nextAudio) {
currentAudio.pause();
nextAudio.play();
}
nextAudio.paused ? nextAudio.play() : nextAudio.pause();
}
render() {
const songs = [];
for (let i = 0; i < this.props.songs.length; i++) {
this.audios[i].addEventListener("play", () => {
console.log("playing");
let stateArray = [...this.state.buttonText];
let stateArrayElement = { ...stateArray[i] };
stateArrayElement = "playing";
stateArray[i] = stateArrayElement;
console.log(stateArray);
this.setState({ buttonText: stateArray });
console.log(this.state.buttonText);
});
songs.push(
<div className="song-preview">
<button
className="preview"
onClick={() => this.toggle(this.audios[i])}
>
{this.state.buttonText[i]}
</button>
</div>
);
}
return (
<div>{songs}</div>
)
}
}
export default () => <AudioList songs={[audio1, audio2]} />;
The code now runs as expected.

Does a change in a React components props cause a re-render?

I am building a simple timer as React practice. Right now I am just focusing on getting the seconds to work. When a user inputs a selection in baseSeconds, the timer will stop at that second.
It works if you hardcode a number as a prop instead of passing the state. and I know the props are changing in the component based on the {this.props.baseSeconds} I've outputted as a test. But when I put this.state.baseSeconds as props, the timer keeps on going.
Parent component of Settings:
const baseMin = [];
for (var i=0; i <= 60; i++) {
baseMin.push(i);
}
const baseSec = [];
for (var i=0; i <= 60; i++) {
baseSec.push(i);
}
const displayMinutes = baseMin.map((minute) =>
<option value={minute}>{minute}</option>
)
const displaySeconds = baseSec.map((second) =>
<option value={second}>{second}</option>
)
class Settings extends React.Component {
constructor(props) {
super();
this.state = {
baseMinutes: 0,
baseSeconds: 0,
varMinutes: 0,
varSeconds: 0
};
this.updateBaseMin = this.updateBaseMin.bind(this);
this.updateBaseSec = this.updateBaseSec.bind(this);
this.updateVarMin = this.updateVarMin.bind(this);
this.updateVarSec = this.updateVarSec.bind(this);
this.renderTimer = this.renderTimer.bind(this);
}
updateBaseMin(event) {
this.setState({ baseMinutes: event.target.value });
}
updateBaseSec(event) {
this.setState({ baseSeconds: event.target.value });
}
updateVarMin(event) {
this.setState({ varMinutes: event.target.value });
}
updateVarSec(event) {
this.setState({ varSeconds: event.target.value });
}
renderTimer(e) {
e.preventDefault();
const { baseMinutes, baseSeconds, varMinutes, varSeconds } = this.state;
return(
<Timer baseMinutes={baseMinutes} baseSeconds={baseSeconds} varMinutes={varMinutes} varSeconds={varSeconds}/>
)
}
render() {
const varMin = [];
for (var i=0; i <= this.state.baseMinutes; i++) {
varMin.push(i);
}
const displayBaseMin = varMin.map((minute) =>
<option value={minute}>{minute}</option>
)
const varSec = [];
for (var i=0; i <= this.state.baseSeconds; i++) {
varSec.push(i);
}
const displayBaseSec = varSec.map((second) =>
<option value={second}>{second}</option>
)
const { baseMinutes, baseSeconds, varMinutes, varSeconds } = this.state;
return (
<Container>
Settings
<form onSubmit={this.renderTimer}>BaseTime
<select onChange={this.updateBaseMin}>
{displayMinutes}
</select>
:
<select onChange={this.updateBaseSec}>
{displaySeconds}
</select>
VarTime +-
<select onChange={this.updateVarMin}>
{displayBaseMin}
</select>
:
<select onChange={this.updateVarSec}>
{displayBaseSec}
</select>
<input type="submit" value="Submit" />
</form>
<p>{this.state.baseMinutes}, {this.state.baseSeconds}, {this.state.varMinutes}, {this.state.varSeconds}</p>
<div>{this.renderTimer}</div>
<Timer baseMinutes={baseMinutes} baseSeconds={this.state.baseSeconds} varMinutes={varMinutes} varSeconds={varSeconds}/>
</Container >
)
}
}
export default Settings
child component of Timer:
class Timer extends React.Component {
constructor(props) {
super(props);
this.state = {
minutes: 0,
seconds: 0,
baseSeconds: this.props.baseSeconds
}
this.go = this.go.bind(this);
this.stop = this.stop.bind(this);
this.reset = this.reset.bind(this);
}
go = () => {
this.timer = setInterval(() => {
if ((this.state.seconds) === (this.props.baseSeconds)) {
clearInterval(this.timer);
} else {
this.setState({ seconds: this.state.seconds + 1 })
console.log(this.state.baseSeconds)
}
}, 1000)
}
stop = () => {
clearInterval(this.timer);
}
reset = () => {
this.setState({ minutes: 0, seconds: 0 })
}
render() {
return (
<div>
<button onClick={this.go}>start</button>
<button onClick={this.stop}>stop</button>
<button onClick={this.reset}>reset</button>
<p>{this.props.baseMinutes}:{this.props.baseSeconds}</p>
<p>{this.state.minutes}:{this.state.seconds}</p>
</div>
)
}
}
export default Timer
Yes, A change in props causes a re-render by default. BUT in your case, in the child component, the initial state (baseSeconds) is based on a prop (this.props.baseSeconds) which is not recommended. The constructor runs only once ( when the component mounts ) and any update on the baseSeconds props after that won't be detected as a result. You can use the props directly inside render without using the local state. if you must update the local state using props, the recommended approach would be to use getDerivedStateFromProps lifecycle method.

Render function inside component and display in container on button click

I am newbie in react and javascript and recently start developed an app. I want to render the renderBoard array inside the boardContainer div after i click button . Thx for any answers. This is the code for my component:
class Main extends Component {
constructor(props) {
this.state = {
size: [2, 2],
renderBoard: false,
}
this.renderBoard = this.renderBoard.bind(this);
}
renderBoard() {
var newWorld = [];
var cellRow = [];
for(var i = 0; i < this.state.size[0]; i++) {
for (var j = 0; j < this.state.size[1]; j++){
cellRow.push(<Cell key={[i, j]} />);
}
newWorld.push(<div className="row" key={i}>{cellRow}</div>);
cellRow = [];
}
return newWorld;
}
render() {
return (
<div>
<div className="headerButtons">
<button className="submit" onClick= {this.renderBoard()}>Start</button>
</div>
Generation:
<div className="boardContainer">
{this.renderBoard()}
</div>
</div>
);
}
}
class Cell extends Component {
render() {
return (
<div className="cellContainer"></div>
);
}
}
export default Main;
You need to set the state when you click the button instead of calling your render function. Do something like this:
class Main extends Component {
constructor(props) {
super(props);
this.state = {
size: [2, 2],
renderBoard: false,
}
this.renderBoard = this.renderBoard.bind(this);
}
renderBoard() {
....
}
showBoard => () => {
this.setState({ renderBoard: true });
}
render() {
return (
<div>
<div className="headerButtons">
<button className="submit" onClick={this.showBoard}>Start</button>
</div>
Generation:
<div className="boardContainer">
{this.state.renderBoard ? this.renderBoard() : null}
</div>
</div>
);
}
}
or you could change your renderBoard function instead of using a ternary in your JSX like this:
renderBoard = () => {
if (this.state.renderBoard) {
....
} else {
return null;
}
}
I would also suggest renaming your state variable to be clearer. So maybe instead of renderBoard it would be showBoard. Then you'd use it like if (this.state.showBoard). But that isn't required to make it work.

React js state management

class Todo extends React.Component {
constructor(props) {
super(props);
this.state = {
saveText: '',
}
this.handleSaveText = this.handleSaveText.bind(this);
this.displayText = this.displayText.bind(this);
}
handleSaveText(saveText) {
this.setState({
saveText: saveText
})
}
render() {
return (
<div>
<Save saveText = {this.state.saveText}
onSaveTextChange = {this.handleSaveText}
/>
<Display saveText = {this.state.saveText}
/> </div>
);
}
}
class Save extends React.Component {
constructor(props) {
super(props);
this.handleSaveText = this.handleSaveText.bind(this);
}
handleSaveText(e) {
this.props.onSaveTextChange(e.target.value);
}
render() {
return ( <div>
<input type = "text"
value = {
this.props.saveText
}
onChange = {
this.handleSaveText
}
/> <input type = "button"
value = "save"
onClick = {
this.displayText
}
/> </div>
);
}
}
class Display extends React.Component {
render() {
var todos = [];
var todo = this.props.saveText;
//todos.push(todo);
return ( <div> {
todos
} </div>
);
}
}
ReactDOM.render( <Todo / > ,
document.getElementById('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>
I am new to react still trying to figure out how state works.I am trying to implement a simple todo app which takes in an input and displays an output on the screen after click of a button.
According to the minimal UI representation I broke the UI into two parts, the first contains the Save class which has an input box and a button. The second contains a display class which will display the contents of the input box.
I am storing the value of input box in state.
How can I pass that state into Display class and display the values on the screen?
Codepen Link
This will do it:
class Todo extends React.Component {
constructor(props) {
super(props);
this.state = {
saveText: '',
displayText: []
}
this.handleSaveText = this.handleSaveText.bind(this);
this.displayText = this.displayText.bind(this);
}
handleSaveText(saveText) {
this.setState({
saveText: saveText
})
}
displayText(text) {
let newDisplay = this.state.displayText;
newDisplay.push(text);
this.setState({displayText: newDisplay});
}
render() {
return (
<div>
<Save saveText = {this.state.saveText}
onSaveTextChange = {this.handleSaveText}
displayText={this.displayText}
/>
<Display displayText = {this.state.displayText}
/> </div>
);
}
}
class Save extends React.Component {
constructor(props) {
super(props);
this.handleSaveText = this.handleSaveText.bind(this);
this.displayText = this.displayText.bind(this);
}
handleSaveText(e) {
this.props.onSaveTextChange(e.target.value);
}
displayText() {
this.props.displayText(this.props.saveText);
}
render() {
return ( <div>
<input type = "text"
value = {
this.props.saveText
}
onChange = {
this.handleSaveText
}
/> <input type = "button"
value = "save"
onClick = {
this.displayText
}
/> </div>
);
}
}
class Display extends React.Component {
render() {
return ( <div> {
this.props.displayText
} </div>
);
}
}
ReactDOM.render( <Todo / > ,
document.getElementById('root')
)
You can't push to the array in the render method because that won't exist anymore after it re-renders when it receives new props from you clicking the button again. My method saves an array of previous responses as "displayText" and sends that to the display component. Note that this method will display the entire array as a single line with no spaces. In practice you'll want to map it by doing this:
this.props.displayText.map((text, idx) => (<div key={idx}>{text}</div>));
Here is working example of todo list.
class Todo extends React.Component {
constructor(props) {
super(props);
this.state = {
text: '',
list: []
}
// this.handleSaveText = this.handleSaveText.bind(this);
this.addTodo = this.addTodo.bind(this);
}
handleSaveText(text) {
this.setState({
text: text
})
}
addTodo(saveText) {
var list = this.state.list;
list.push(saveText);
this.setState({
list: list
});
// to save to localstorage, uncomment below line
// window.localStorage.setItem('todos', list);
}
render() {
return ( <
div >
<
Save text = {
this.state.text
}
onClick = {
this.addTodo
}
/> <
Display list = {
this.state.list
}
/> < /
div >
);
}
}
class Save extends React.Component {
constructor(props) {
super(props);
this.state = {
input: this.props.text || '',
}
this.onChange = this.onChange.bind(this);
this.addToTodo = this.addToTodo.bind(this);
}
onChange(e) {
this.setState({
input: e.target.value
});
}
addToTodo() {
this.props.onClick(this.state.input);
this.setState({
input: ''
});
}
render() {
return ( < div >
<
input type = "text"
value = {
this.state.input
}
onChange = {
this.onChange
}
/> <input type = "button"
value = "save"
onClick = {
this.addToTodo
}
/> </div >
);
}
}
class Display extends React.Component {
constructor(props) {
super(props);
this.state = {
todos: []
}
}
componentWillReceiveProps(nextProps) {
this.setState({
todos: nextProps.list
});
}
render() {
var i = 1;
var renderList = this.state.todos.map((name) => {
return <div key = {
i++
} > {
name
} < /div>;
});
return ( < div > {
renderList
} < /div>);
}
}
ReactDOM.render( < Todo / > ,
document.getElementById('root')
)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My React Project on CodePen</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/4.1.0/sanitize.css">
<link rel="stylesheet" href="css/style.processed.css">
</head>
<body>
<div id="root"></div>
<script src="https://npmcdn.com/react#15.3.0/dist/react.min.js"></script>
<script src="https://npmcdn.com/react-dom#15.3.0/dist/react-dom.min.js"></script>
</body>
</html>
If you are trying create a todo list, you can tweak a little bit by adding array list in the main TODO component, and it down to display component.
save component you just have to handle the input change and on click function.
simple enough.
You must use:
componentWillReceiveProps(nextProps)
in your Display Component.
This is a working example:
class Todo extends React.Component {
constructor(props) {
super(props);
this.state = {
todos: []
}
this.handleSaveText = this.handleSaveText.bind(this);
}
handleSaveText(saveText) {
let todos = this.state.todos;
todos.push(saveText);
this.setState({
todos: todos
});
}
render() {
return (
<div>
<Save
onSaveTextClick = {this.handleSaveText}
/>
<Display todos = {this.state.todos}
/> </div>
);
}
}
class Save extends React.Component {
constructor(props) {
super(props);
this.state = {
saveText: ''
}
this.handleSaveText = this.handleSaveText.bind(this);
this.handleChangeText = this.handleChangeText.bind(this);
}
handleChangeText(e){
this.setState({saveText: e.target.value});
}
handleSaveText(e) {
this.props.onSaveTextClick(this.state.saveText);
}
render() {
return ( <div>
<input type = "text"
onChange = {
this.handleChangeText
}
/> <input type = "button"
value = "save"
onClick = {
this.handleSaveText
}
/> </div>
);
}
}
class Display extends React.Component {
constructor(props){
super(props);
this.state = {
todos: []
}
}
componentWillReceiveProps(nextProps){
this.setState({todos: nextProps.todos});
}
render() {
let todos = this.state.todos.map((todo)=>{return <div>{todo}</div>});
return ( <div> {
todos
} </div>
);
}
}
ReactDOM.render( <Todo / > ,
document.getElementById('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>

Categories