I'm building a new project with React. I have a component that defines several child components like this:
class TimeStepDisplayGraph extends React.Component {
render () {
return <div id={ this.props.id }>
<TimeStepDisplayGraphNode class="graphNodeStyles"/>
<TimeStepDisplayGraphNode class="graphNodeStyles"/>
<TimeStepDisplayGraphNode class="graphNodeStyles"/>
<TimeStepDisplayGraphNode class="graphNodeStyles"/>
<TimeStepDisplayGraphNode class="graphNodeStyles"/>
<TimeStepDisplayGraphNode class="graphNodeStyles"/>
</div>;
}
}
Now ideally what I would like to do is not have the number of nodes created defined explicitly, but rather through a call like:
function createGraphNode() {
return React.createElement( <TimeStepDisplayGraphNode class="graphNodeStyles"/>);
}
That I call x number of times. It seems like something really simple to do, and I'm sure I'll be kicking myself later for being so stupid, but at the moment I'm really at a loss for how to do this. Any help would be appreciated.
There are many ways to do this. The simplest would be to simply create an array of size n and map over it, returning a React component for each one:
render () {
const arr = new Array(6);
return (
<div id={ this.props.id }>
{arr.map((ea, i) => {
return <TimeStepDisplayGraphNode key={i} class="graphNodeStyles"/>
}}
</div>
);
}
Note that you need to add the key prop to each created node to uniquely identify it between renders. Using i is normally not ideal (because it does not actually uniquely identify which node is which) but in the absence of any other identifying data it will do.
This is a very common pattern in React - see the official docs for more info.
You can just put it in a function, like you are thinking, but you don't need the React.createElement. Something like this would suffice:
class TimeStepDisplayGraph extends React.Component {
...
render () {
return (
<div id={ this.props.id }>
{createGraphNode()}
</div>
)
}
}
...
}
function createGraphNode() {
return <TimeStepDisplayGraphNode class="graphNodeStyles"/>;
}
Or to add it n times, something like:
class TimeStepDisplayGraph extends React.Component {
...
render () {
return (
<div id={ this.props.id }>
{Array.from({ length: n }, createGraphNode)}
</div>
)
}
}
...
}
function createGraphNode(_, index) {
return <TimeStepDisplayGraphNode key={index} class="graphNodeStyles"/>;
}
Is that what you had in mind?
Yep, Muhammad is right. This is how to use a loop to do what you're asking:
class outerComponent extends React.Component {
constructor (props) {
super(props);
this.state = {
numberOfTimestamps: 100000 // (just kidding, make that smaller!)
}
}
render () {
let myTimestamps = []
for (let i=0; i < this.state.numberOfTimestamps; i++) {
myTimestamps.push(<TimeStepDisplayGraphNode class="graphNodeStyles"/>)
}
return (
<div>
{myTimestamps}
</div>
)
}
}
Edit: Changed to for loop rather than forEach. Cannot forEach a number!
Related
I have a small part of my new React app which contains a block of text, AllLines, split into line-by-line components called Line. I want to make it work so that when one line is clicked, it will be selected and editable and all other lines will appear as <p> elements. How can I best manage the state here such that only one of the lines is selected at any given time? The part I am struggling with is determining which Line element has been clicked in a way that the parent can change its state.
I know ways that I can make this work, but I'm relatively new to React and trying to get my head into 'thinking in React' by doing things properly so I'm keen to find out what is the best practice in this situation.
class AllLines extends Component {
state = {
selectedLine: 0,
lines: []
};
handleClick = (e) => {
console.log("click");
};
render() {
return (
<Container>
{
this.state.lines.map((subtitle, index) => {
if (index === this.state.selectedLine) {
return (
<div id={"text-line-" + index}>
<TranscriptionLine
lineContent={subtitle.text}
selected={true}
/>
</div>
)
}
return (
<div id={"text-line-" + index}>
<Line
lineContent={subtitle.text}
handleClick={this.handleClick}
/>
</div>
)
})
}
</Container>
);
}
}
class Line extends Component {
render() {
if (this.props.selected === true) {
return (
<input type="text" value={this.props.lineContent} />
)
}
return (
<p id={} onClick={this.props.handleClick}>{this.props.lineContent}</p>
);
}
}
In your case, there is no really simpler way. State of current selected Line is "above" line collection (parent), which is correct (for case where siblings need to know).
However, you could simplify your code a lot:
<Container>
{this.state.lines.map((subtitle, index) => (
<div id={"text-line-" + index}>
<Line
handleClick={this.handleClick}
lineContent={subtitle.text}
selected={index === this.state.selectedLine}
/>
</div>
))}
</Container>
and for Line component, it is good practice to use functional component, since it is stateless and even doesn't use any lifecycle method.
Edit: Added missing close bracket
'Thinking in React' you would want to give up your habit to grab DOM elements by their unique id ;)
From what I see, there're few parts missing from your codebase:
smart click handler that will keep only one line selected at a time
edit line handler that will stick to the callback that will modify line contents within parent state
preferably two separate components for the line capable of editing and line being actually edited as those behave in a different way and appear as different DOM elements
To wrap up the above, I'd slightly rephrase your code into the following:
const { Component } = React,
{ render } = ReactDOM
const linesData = Array.from(
{length:10},
(_,i) => `There goes the line number ${i}`
)
class Line extends Component {
render(){
return (
<p onClick={this.props.onSelect}>{this.props.lineContent}</p>
)
}
}
class TranscriptionLine extends Component {
constructor(props){
super(props)
this.state = {
content: this.props.lineContent
}
this.onEdit = this.onEdit.bind(this)
}
onEdit(value){
this.setState({content:value})
this.props.pushEditUp(value, this.props.lineIndex)
}
render(){
return (
<input
style={{width:200}}
value={this.state.content}
onChange={({target:{value}}) => this.onEdit(value)}
/>
)
}
}
class AllLines extends Component {
constructor (props) {
super(props)
this.state = {
selectedLine: null,
lines: this.props.lines
}
this.handleSelect = this.handleSelect.bind(this)
this.handleEdit = this.handleEdit.bind(this)
}
handleSelect(idx){
this.setState({selectedLine:idx})
}
handleEdit(newLineValue, lineIdx){
const linesShallowCopy = [...this.state.lines]
linesShallowCopy.splice(lineIdx,1,newLineValue)
this.setState({
lines: linesShallowCopy
})
}
render() {
return (
<div>
{
this.state.lines.map((text, index) => {
if(index === this.state.selectedLine) {
return (
<TranscriptionLine
lineContent={text}
lineIndex={index}
pushEditUp={this.handleEdit}
/>
)
}
else
return (
<Line
lineContent={text}
lineIndex={index}
onSelect={() => this.handleSelect(index)}
/>
)
})
}
</div>
)
}
}
render (
<AllLines lines={linesData} />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
This is my actually code:
class Notifications extends React.Component {
constructor(props) {
super(props);
this.state = {
newNotifications: null, //this always must be a null in constructor.
};
}
componentDidUpdate(prevProps, prevState) {
if (this.props.isNewNotification !== prevProps.isNewNotification) {
this.setState({
newNotifications: prevProps.newNotifications,
});
}
}
...
My prevProps.newNotifications is an array, for example:
[{"date_time":"Wednesday, 19:42","amount_money":"2,10 USD","sender_name":"John Polaszek"}]
I would like to merge array my prevProps.newNotifications in prevState.newNotifications when prevState.newNotifications isn't a null. In render() i have this method:
{newNotifications ? (
<div className="no-test">
{newNotifications.map((newNotification, id) => (
<Fragment>
<span>
{newNotification.sender_name}
</span>
<span className="notification_amount">
{newNotification.amount_money}
</span>
</span>
<br />
<span>
{newNotification.date_time}
</span>
</Fragment>
))}
</div>
)...
How can I do this? I hope my question is understandable.
In your componentDidUpdate method, you have the right idea, you just need to either set, or Array.concat(), depending on the condition.
componentDidUpdate(prevProps, prevState) {
if (this.props.isNewNotification !== prevProps.isNewNotification) {
let newNotifications;
if (prevState.newNotifications !== null) {
newNotifications = prevState.newNotifications.concat(prevProps.newNotifications);
} else {
// otherwise, set newNotifications to your other condition
}
this.setState({ newNotifications }); // computed property since the var is the same as the state name
}
}
You can keep it as an empty array instead of null. You can just use the spread operator.
class Notifications extends React.Component {
constructor(props) {
super(props);
this.state = {
newNotifications: [], // maintain it as an array instead.
};
}
componentDidUpdate(prevProps, prevState) {
if (this.props.isNewNotification !== prevProps.isNewNotification) {
const newNotifications = [...prevState.newNotifications, ...prevProps.newNotifications];
this.setState({
newNotifications,
});
}
}
This does not remove duplicates, you can try to use underscore or lodash or write a function yourself to remove them.
union(prevProps.newNotifications, prevState.newNotifications);
union or uniq or uniqBy can do the job for you.
http://underscorejs.org
http://lodash.com/docs
And in the render function, you can change your ternary operator to check for length, if length is 0 (as in the initialisation), the else part will be executed.
{newNotifications.length ? (
<div className="no-test">
{newNotifications.map((newNotification, id) => (
<Fragment>
<span>
{newNotification.sender_name}
</span>
<span className="notification_amount">
{newNotification.amount_money}
</span>
</span>
<br />
<span>
{newNotification.date_time}
</span>
</Fragment>
))}
</div>
)...
I was going to add this as a comment, but it's too long.
If I'm understanding your question correctly, the others have answered your question, however, you shouldn't be doing what you're asking to do.
I can tell you're aiming to be declarative and handle things through props, but there's remnants of imperative thinking that are complicating things. From what I can tell, when the parent component has new notifications it will pass both the newNotifications and isNewNotification. Then it would flip isNewNotification back to false. Whenever you have a sequence to things, you're thinking imperatively, not declaratively. Reaching for lifecycle methods is also an indication that you're thinking imperatively (though this is sometimes necessary).
You're essentially trying to replicate a function call with props. I think you'd actually be better off exposing an addNotifications method, getting a ref to the component and calling the method, rather than this declarative-looking-but-really-imperative api.
But really, we don't want to expose an imperative api for components to communicate. If the parent is responsible for adding the new notifications, it's probably in a better position to maintain the notifications list. I'm guessing these notifications are coming from an api or a websocket. When they are received, this is the place to concat the new notifications and store that back in the parent's state. The child then can become just a stateless component.
class NotificationParent extends React.Component {
constructor(props) {
super(props);
this.state = {
notifications: null
};
}
componentDidMount() {
api.on("notifications", newNotifications => {
this.setState(({ notifications } => {
if (!notifications) {
return {
notifications: newNotifications
};
} else {
return {
notifications: notifications.concat(newNotifications)
};
}
});
})
}
render() {
return (
<Notifications notifications={this.state.notifications}/>
);
}
}
function Notification({ notifications }) {
return (
newNotifications ? (
<div className="no-test">
{newNotifications.map((newNotification, id) => (
<Fragment>
<span>
{newNotification.sender_name}
</span>
<span className="notification_amount">
{newNotification.amount_money}
</span>
<br />
<span>
{newNotification.date_time}
</span>
</Fragment>
))}
</div>
) : (
/* ... */
)
)
}
import React, {Component} from 'react';
import "./DisplayCard.css";
class DisplayCard extends Component {
runArray = (array) => {
for (var i = 0; i<array.length; i++) {
return <div>{array[i].task}</div>
}
}
renderElements = (savedTasks) =>{
if (savedTasks.length === 0) {
return <div className="noTasks"> <p>You have no saved tasks.</p> </div>
} else {
return this.runArray(savedTasks)
}
}
render() {
return (
<div className="DisplayCardContainer">
{this.renderElements(this.props.saved)}
</div>
)
}
}
export default DisplayCard;
Hey guys,
I am new to react, so this is my child component that takes state from its parent component. My goal is to re-render component every time the array this.props.saved is changed.
This component renders: <p>You have no saved tasks.</p> when the this.props.saved.length === 0 and it renders <div>{array[0].task}</div> when i enter the first task, but it keeps it at <div>{array[0].task}</div> after that. I do see that the state keeps changing and this.props.saved keeps getting bigger, but my component doesn't change anymore.
Here's your problem:
runArray = (array) => {
for (var i = 0; i<array.length; i++) {
//the first time we get here, it immediately ends the function!
return <div>{array[i].task}</div>
}
}
This loop only ever goes through once (at i=0) and then returns, exiting the runArray function and cancelling the rest of the loop. You probably wanted to return an array of elements, one for each of the tasks. I recommend using Array.map() for this, which takes an array and transforms each element, creating a new array:
runArray = (array) => {
return array.map(arrayElement => <div>arrayElement.task</div>);
}
This should do the trick. Note that React may complain about the fact that your elements lack the key property - see the documentation for more info: https://reactjs.org/docs/lists-and-keys.html
The problem is in your runArray function. Inside your loop, you are returning the first element and that's it. My guess is, you see only the first entry?
When you are trying to render all your tasks, I would suggest to map your tasks, e.g.
runArray = (array) => array.map(entry => <div>{entry.task}</div>)
It is because you write wrong the runArray function. You make a return in the for loop so it breaks after the first iteration. It will not iterate over the full array.
You need to transform your for loop to a map : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
runArray = (array) => {
return array.map(v => <div>{v.task}</div>)
}
Does it fix your issue ?
You have to update state of the component to trigger render function. Your render function is not triggered because you did not update the state when the props changed. There are many ways to update state when props updated. One method may be the following:
componentWillReceiveProps(nextProps){
if (nextProps.saved !== this.props.saved) {
this.setState({ saved: nextProps.saved })
}
}
Also change yoour render function to use state of the component as below:
renderElements = () =>{
if (this.state.savedTasks.length === 0) {
return <div className="noTasks"> <p>You have no saved tasks.</p> </div>
} else {
return this.runArray(this.state.savedTasks)
}
}
Use .map so that it renders your task correctly. You can remove runArray and rely entirely on props so you don't need to pass arguments across functions as it can get messy quickly. Here's a quick running example of how to create a parent component where you can add a task and pass them into a component so that it renders your data when props are changed, therefore making it reactive.
class App extends React.Component {
state = {
taskLabel: "",
tasks: [
{
id: 1,
label: "Do something"
},
{
id: 2,
label: "Learn sometihng"
}
]
};
handleInput = evt => {
this.setState({
[evt.target.name]: evt.target.value
});
};
handleSubmit = evt => {
evt.preventDefault();
this.setState(prevState => ({
taskLabel: "",
tasks: [
...prevState.tasks,
{
id: prevState.tasks.length + 1,
label: this.state.taskLabel
}
]
}));
};
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<input
name="taskLabel"
type="text"
placeholder="Task label"
value={this.state.taskLabel}
onChange={this.handleInput}
/>
<button>Create task</button>
</form>
<DisplayCard tasks={this.state.tasks} />
</div>
);
}
}
class DisplayCard extends React.Component {
renderTasks = () => {
if (this.props.tasks.length !== 0) {
return this.props.tasks.map(task => (
<div key={task.id}>{task.label}</div>
));
} else {
return <div>No tasks</div>;
}
};
render() {
return <div>{this.renderTasks()}</div>;
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<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="root"></div>
(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>
);
}
}
I have the following code. It doesn't render the fullRecipe items at all but i see nothing wrong in here. I have trouble learning this framework and after asking nobody knows what's happening... Do you see what's wrong?
Thanks
class Index extends React.Component {
constructor() {
super();
}
render() {
var list_results = this.props.recipes.map(function(recipe, index){
console.log(index); //Happens
console.log(recipe); //Happens
return (<fullRecipe recipe={recipe}></fullRecipe>);
});
return (<ul>{this.list_results}</ul>)
}
}
function fullRecipe(props) {
console.log(props || "no props"); // Doesnt happen
return (<li><div class="delete-button">Delete</div>{props.name} - {props.ingredients}</li>);
}
fullRecipe needs to either be part of the Index class or made into another component.
You're also using this.list_results, which should be just list_results. this is the context of the whole class, whereas your var is local to render().
The simplest method would be:
class Index extends React.Component {
constructor() {
super();
}
fullRecipe() {
return (<li><div class="delete-button">Delete</div>{this.props.name} - {this.props.ingredients}</li>);
}
render() {
var list_results = this.props.recipes.map((recipe, index) => this.fullRecipe(recipe));
return (<ul>{list_results}</ul>)
}
}
EDIT
I'm not sure what I was thinking with the above. Really, it should be two components, and neither one needs to be stateful.
//Index.js
export default const Index = ({ recipes }) => {
return (
<ul>
{
recipes.map( ({ ingredients, name }, index) => {
return <Recipe key={index} ingredients={ingredients} name={name} />
})
}
</ul>
);
}
//Recipe.js
export default const Recipe = ({ ingredients, name }) => {
return (
<li>
<button className="delete-button">Delete</button>
{name} - {ingredients}
</li>
);
}
Incorrect use of function/component
You can either create a Component called fullRecipe to display the information, or bring the function fullRecipe to Index component.
check this link https://facebook.github.io/react/docs/multiple-components.html