I try to append a component dynamically, here it's Boards that i try to add to MainComponent.
The function addChild work and the list boards is filling up when the loop for is running, but nothing else happen, nothing is displayed on the screen
const MainComponent = props => (
<div className={props.className}>
<p>
<a href="#" onClick={props.addChild}>Add Another Child Component</a>
</p>
{props.boards}
</div>
);
class MainPage extends React.Component {
state = {
numChildren: 0
}
onAddChild = () => {
this.setState({
numChildren: this.state.numChildren + 1
});
}
render () {
const boards = [];
for (var i = 0; i < this.state.numChildren; i += 1) {
var _id = "board-" + i;
console.log
boards.push(<Board id={_id} className="board" />);
};
return (
<div className="test">
<Title/>
<MainComponent addChild={this.onAddChild} className="flexbox">
{boards}
</MainComponent>
</div>
);
}
}
export default MainPage```
Try to add key for each ChildComponent
const boards = [];
for (var i = 0; i < this.state.numChildren; i += 1) {
var _id = "board-" + i;
console.log
boards.push(<Board id={_id} key={_id} className="board" />);
};
because react depends on the key for rendering the component that is rendered inside loops
so react considers that all child that you are rendering is one component unless you give them a different key
Related
I am extremely new to react and am building a simple todo list app. I am trying to edit data from my child component and send it back to my parent. When I am printing console logs the parent state seems to be getting set correctly, but the child elements are not refreshing. Am I doing something conceptually wrong here?
I have tried to share the entire code as I am not sure whether it is correct conceptually. I am new to JS. When the handleSave() and handleComplete() are called i can see correct values getting returned and set to my PArent State, but there is no refresh of the child components.
Below is my Parent class code.
class App extends Component {
state = {
taskList: [
]
};
saveEventHandler = data => {
console.log("I am in saveEventHandler");
var uniqid = Date.now();
const taskList = [...this.state.taskList];
taskList.push({
id: uniqid,
taskDescText: data,
isFinished: false
});
console.log(taskList);
this.setState({'taskList':taskList});
};
deleteEventHandler = (index) => {
const taskList = [...this.state.taskList];
taskList.splice(index,1)
this.setState({'taskList':taskList});
}
editEventHandler = (index,data) => {
var uniqid = Date.now();
console.log("In edit event handler")
console.log(data)
console.log(index)
const taskList = [...this.state.taskList];
taskList[index] = {
id: uniqid,
taskDescText: data,
isFinished: false
}
this.setState({'taskList':taskList});
console.log(this.state.taskList)
}
handleComplete = (index) => {
console.log("In complete event handler")
const taskList = [...this.state.taskList];
const taskDescriptionOnEditIndex = taskList[index]
taskDescriptionOnEditIndex.isFinished = true
taskList[index] = taskDescriptionOnEditIndex
this.setState({'taskList':taskList});
console.log(this.state.taskList)
}
render() {
return (
<div className="App">
<h1>A Basic Task Listing App </h1>
<CreateTask taskDescription={this.saveEventHandler} />
{this.state.taskList.map((task, index) => {
return (
<Task
taskDescText={task.taskDescText}
taskCompleted={task.isFinished}
deleteTask={() => this.deleteEventHandler(index)}
editTask={(editTask) => this.editEventHandler(index,editTask)}
handleComplete={() => this.handleComplete(index)}
editing='false'
/>
);
})}
</div>
);
}
}
export default App;
and my child class code
export default class Task extends React.Component {
state = {
editing : false
}
notCompleted = {color: 'red'}
completed = {color: 'green'}
textInput = React.createRef();
constructor(props) {
super(props);
this.state = props
}
handleEdit = () => {
this.setState({editing:true});
}
handleSave = () => {
this.props.editTask(this.textInput.current.value);
this.setState({editing:false});
};
editingDiv = (<div className = 'DisplayTask'>
<span className='TaskDisplayText' style={!this.props.taskCompleted ? this.notCompleted: this.completed}>{this.props.taskDescText} </span>
<button label='Complete' className='TaskButton' onClick={this.props.handleComplete}> Complete</button>
<button label='Edit' className='TaskButton' onClick={this.handleEdit}> Edit Task</button>
<button label='Delete' className='TaskButton' onClick={this.props.deleteTask}> Delete Task</button>
</div> );
nonEditingDiv = ( <div className = 'DisplayTask'>
<input className='TaskDescEditInput' ref={this.textInput}/>
<button label='Save' className='TaskButton' onClick={this.handleSave} > Save Task</button>
<button label='Delete' className='TaskButton' onClick={this.props.deleteTask}> Delete Task</button>
</div>);
render() {
return (
!this.state.editing ? this.editingDiv : this.nonEditingDiv
)
};
}
Move your editingDiv and nonEditingDiv definitions inside render() method. Since you're defining them as instance variables, they're initialized once, and never get re-rendered again with new prop values.
By moving them to render() method, render() which is called every time when there's a prop update will pick up the new prop values.
I am creating an app, where users should compose a correct sentence from shuffled one. Like on the following picture. The problem is that I am not implementing the app in a right way. For example, I do not change inputs through state. And my main question is how to clear input of a particular line (a bunch of inputs) ? I need it when a user types something wrong and needs to start again. I know that input field should be controlled through state, but it is impossible since all inputs are generated according to number of words. And in one line there maybe more than one input. Let me explain how my code works, so that you have an idea my implementation.
This is where I get data from.
export default {
id:'1',
parts:[
{
speaker:'Speaker1',
words:'Hello how are you?'
},
{ speaker:'Speaker2',
words:'I am OK, thanks'
},
{ speaker:'Speaker1',
words:'What are your plans for Saturday?'
}
]
}
import React, { Component } from 'react';
import {CopyToClipboard} from 'react-copy-to-clipboard';
import { MdDoneAll } from "react-icons/md";
// This is the array where data from inputs is pushed to
var pushArr = [];
// Initial points
var points = 0;
class DialogueShuffleFrame extends Component {
constructor(props) {
super(props)
this.state = {
// Variable to show correct or incorrect notification
showCorrect:false
}
this.writeSometihng = this.writeSometihng.bind(this)
}
// Function that is triggered when a tick is clicked
// Pushes value to the pushArr array when onBlur function is triggered
writeSometihng(e) {
e.preventDefault()
pushArr.push(e.target.value)
console.log(pushArr)
}
// Function check if array of value from input matches to an array from
// initial data source
checkLines(arr, lines) {
let joinedStr = arr.join(' ');
//console.log(joinedStr);
lines[0].parts.map((obj) => {
let line = obj.words
if (joinedStr === line) {
this.setState({
showCorrect:true
})
pushArr = [];
points += 80;
} else {
pushArr = [];
}
})
}
// Resets pushArr array
reset() {
pushArr.length = 0
console.log('clicked')
}
// Shuffles words
formatWords(words) {
const splittedWords = words.split(' ')
const shuffledArray = this.shuffle(splittedWords)
return (
shuffledArray.map((word, index) => (
<>
<input className="word-to-drop-input" id={index} onBlur={this.writeSometihng} size={2} />
<CopyToClipboard text={word}>
<span key={uid(word)} value={word} className="word-to-drop">{word}</span>
</CopyToClipboard>
</>
))
)
}
shuffle(a) {
var j, x, i;
for (i = a.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1));
x = a[i];
a[i] = a[j];
a[j] = x;
}
return a;
}
render() {
const {lines} = this.props
const shuffles = lines[0].parts && (
lines[0].parts.map((element,i) => (
<>
<li className="line" key={i}><span>{element.speaker}{": "}</span><span>{this.formatWords(element.words)}</span></li>
<MdDoneAll type="button" onClick={() => {this.checkLines(pushArr, lines)}} style={{color:'white'}}/>
</>
))
)
return (
<>
<h1 className="centered" style={{color:'white'}}>Dialogue shuffle frame</h1>
<ul className="lines-container">
{shuffles}
</ul>
{<div className="reactangular">{this.state.showCorrect ? 'Correct' : 'Incorrect'}</div>}
<div>{points}</div>
<div className="reactangular" onClick={() => this.reset()}>Reset</div>
</>
)
}
}
export default DialogueShuffleFrame;
I'd recommend to use proper data structure with state management, but that's a different story.
To solve your specific issue of clearing input elements, you can use ReactDOM.findDOMNode to access the DOM node and traverse the input elements and set the value to empty string.
Something like this:
class App extends React.Component {
check = (ref) => {
const inputElements = ReactDOM.findDOMNode(ref.target).parentNode.getElementsByTagName('input');
[...inputElements].forEach(el => el.value = '')
}
render() {
return (
<div>
<ul>
<li>
<input />
<input />
<button onClick={this.check}>Check</button>
</li>
<li>
<input />
<input />
<input />
<button onClick={this.check}>Check</button>
</li>
</ul>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
<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="app"></div>
I am trying to get scrollPosition using getSnapshotBeforeUpdate lifecycle method. I went through below React official site to understand about new life cycle method getSnapshotBeforeUpdate
But I am not able to find any scroll position. I console.log all the output, but didn't find anything in console.
This is my code file:
import React from 'react';
class ScrollingList extends React.Component {
constructor(props){
super(props);
this.listRef = React.createRef();
}
getStaticContent() {
const result = [];
for( let i= 1; i <=100; i++){
result.push(<li key={i}>{i} > This is paragraph line-{i}</li>);
}
return result;
}
getSnapshotBeforeUpdate(preProps, preState) {
// const list = this.listRef.current;
// console.log(list);
if(preProps.list.length > this.props.list.length) {
const list = this.listRef.current;
console.log(list);
return list.ScrollingHeight - list.ScrollTop;
}
return null;
}
componentDidUpdate(preProps, preState, snapshot) {
console.log(snapshot);
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return(
<div ref={this.listRef}>
{/* <div className="content-wrap">
<ul>
{this.getStaticContent()}
</ul>
</div> */}
</div>
);
}
}
export default ScrollingList;
Error: TypeError: Cannot read property 'length' of undefined
How I can get scroll position using this life cyle method?
You have use OffsetHeight and scrollTop , please try this modified code.
getSnapshotBeforeUpdate(preProps, preState) {
if(preProps.list.length > this.props.list.length) {
const list = this.listRef.current;
console.log(list);
const { current } = this.listRef;
const isScrolledToBottom =
current.scrollTop + current.offsetHeight >= current.scrollHeight;
return { isScrolledToBottom };
}
return null;
}
I'm trying to do something very simple but its not playing well with my code. I can see it render but only 3 times and not 9
const renderTempBoxes = () => {
for (var i = 0; i < 10; i++) {
console.log('i = ', i);
return <div className={styles.box} key={i} />;
}
};
const Component = () => {
return (
{renderTempBoxes()}
)
}
This doesn't even work, which is overkill to use an array when I just want 9 boxes to render.
UPDATE:
const Component = () => {
return (
<div>
{
[...Array(10)].map((x, i) => {
console.log('i = ', i);
return <div className={styles.box} key={i} />;
})
}
</div>
)
}
The first issue is that you simply cannot return individual elements from within the for loop like that. This is not specific to React, this is simply a JavaScript issue. Instead you can try something like this using Array.from to map an array of elements:
const renderTempBoxes = () => Array.from({ length: 10 }).map((v, i) =>
<div className={styles.box} key={i}>{i}</div>
);
Or simply the for loop with Array.prototype.push to generate an array of elements and return it:
const renderTempBoxes = () => {
let els = [];
for (let i = 0; i < 10; i++) {
els.push(<div className={styles.box} key={i}>{i}</div>);
}
return els;
};
Rendering the elements:
const Component = () => {
return (
<div>
{renderTempBoxes()}
</div>
)
}
Or with React.Fragment to forgo the wrapping extra node:
const Component = () => {
return (
<React.Fragment>
{renderTempBoxes()}
</React.Fragment>
)
}
The second issue with your example is that <div /> isn't going to really render anything, it's not a void/self-closing element such as <meta />. Instead you would need to do return the div element as <div className={styles.box} key={i}>{whatever}</div>.
Regarding the syntax [...Array(10)], there must be an Webpack in terms of how it handles/transpiles Array(10), [...Array(10)], [...new Array(10)], or even `[...new Array(10).keys()]. Either of the approaches described in the answer should solve your issue.
I've created a StackBlitz to demonstrate the functionality.
When trying to render multiple times the same components use an array an map over it.
export default class MyComp extends Component {
constructor(props) {
super(props)
this.state = {
array: [{key: 1, props: {...}}, {key: 2, props: {...}, ...]
}
}
render () {
return (
<div>
{this.state.array.map((element) => {
return <div key={element.key} {...element.props}>
})}
</div>
)
}
}
Remember to always set a unique key to every component you render
I am trying to create a dynamic element. whatever user input in an input box, the same number of LI element will create.
I get this output when I enter 2 in input box.
<li key=0>input 0</li><li key=1>input 1</li>
where I want two li elements.
import React, { Component } from "react";
import logo from "./logo.svg";
import "./App.css";
class App extends Component {
constructor(props) {
super(props);
this.state = {
times: 0
};
}
createList = event => {
let times = event.target.value;
this.setState({
times
});
};
getData = times => {
let str = "";
for (let i = 0; i < times; i++) {
str += "<li key=" + i + ">input " + i + "</li>";
}
return <ul>{str}</ul>;
};
render() {
return (
<div className="App">
<p className="App-intro">
<input type="number" onChange={this.createList} />
{this.getData(this.state.times)}
</p>
</div>
);
}
}
export default App;
This is because you are treating them as string instead of DOM node elements. Use an array and push all the elements inside that and return the final result.
Don't forgot you are writing JSX, and <li> input: {i+1} </li> will get converted into:
React.createElement(
"li",
null,
"input: ",
i+1
);
But if you write: "<li>input: {i+1}</li>", it will be treated as a string.
Check the complied code result of these two cases by Babel REPL.
Solution:
Write it like this:
getData = times => {
let str = [];
for (let i = 0; i < times; i++) {
str.push(<li key={i}> input: {i+1} </li>);
}
return <ul>{str}</ul>;
};