Copy Props From Parent in ReactJs? - reactjs

I am using reactjs 16.
I am making a wizard and I am trying to design it in away that I can reuse it anywhere on my site I need it.
Here is my code
export default class App extends Component {
constructor(props) {
super(props);
}
render() {
const steps = [ {name: 'Step 1', component: <Step1 /> },
{name: 'Step 2', component: <Step2 /> }];
return (
<React.Fragment>
<WizardComponent steps={steps}/>
</React.Fragment>
);
}
}
import React, { Component } from "react";
import ReactDOM from "react-dom";
import WizardStepCompoent from "./WizardStepCompoent";
export default class WizardComponent extends Component {
constructor(props) {
super();
this.state = {
currentStep: 1
};
}
next(e) {
// need to update the current step look
// const step = this.props.steps.find(x => x.name == "Step 1")
// step.changeToCompleted() ----> go form {name: "Step 1", component: <Step 1/>} to {name: "Step 1", component: <Step 1/>, isCompleted: true}
}
render() {
const props = this.props;
return (
<div>
<div className="steps">
{props.steps.map(step => {
return <WizardStepCompoent step={step} />;
})}
</div>
{props.steps.map(step => {
return step.component; // later on use current state to figure out which one to render ie if currentStep == 1 then only render the 1st component
})}
<button onClick={ (e) => this.next(e) }>Next</button>
</div>
);
}
}
export default class WizardStepCompoent extends Component {
render() {
const props = this.props;
const step = props.step;
var completed = "";
if(step.isCompleted)
{
completed = "step-item is-completed";
} else {
completed = "step-item";
}
return (
<div className={completed}>
<div className="step-marker">
<span className="icon">
<i className="fa fa-check" />
</span>
</div>
<div className="step-details">
<p className="step-title">{step.name}</p>
<p>This is the first step of the process.</p>
</div>
</div>
);
}

To solve what you are doing you need to clone the element and pass the appropriate props to it. here is an example in a codepen to see it live
class App extends React.Component {
render() {
const steps = [ {id: 'first', name: 'Step 1', component: <Step1 /> },
{id: 'second', name: 'Step 2', component: <Step2 /> }];
return (
<div>
<WizardComponent steps={steps}/>
</div>
);
}
}
class WizardComponent extends React.Component {
constructor(props) {
super();
this.state = {
currentStep: 0,
completedSteps: {
'first': {completed: false},
'second': {completed: false}
}
};
}
next(e) {
const {currentStep} = this.state
const completedSteps = {...this.state.completedSteps}
const current = this.props.steps[currentStep]
completedSteps[current.id].completed = true
this.setState({currentStep: this.state.currentStep + 1, completedSteps})
}
render() {
const props = this.props;
const {currentStep, completedSteps} = this.state
return (
<div>
<div className="steps">
{props.steps.map(step => {
return <WizardStepCompoent step={step} />;
})}
</div>
{props.steps.map((step, i) => {
return React.cloneElement(step.component, completedSteps[step.id]); // later on use current state to figure out which one to render ie if currentStep == 1 then only render the 1st component
})}
<button onClick={ (e) => this.next(e) }>Next</button>
</div>
);
}
}
the meat of what is happening is at the clone and also when you press next. I added to your steps an id as a way to cross reference each step, then you lookup the current step by its id and set a completed to true. This is a fairly basic example so please bear with me on that.

Related

React referencing const outside of class

I'm very new to React, so this might be a stupid question. But I'm making tabs. And I decided to split the content of the tabs into separate const. The issue is that now I can no longer reference anything in tabs content that I declared in the class section. I'm obviously doing something wrong. Can someone please take a look and let me know what I need to fix here?
Here's the basic outline of my code. How do I reference company inside the GeneralContent const? Do I need to declare it elsewhere? When I reference it inside my class, it works.
class Tabs extends React.Component {
constructor(props) {
super(props);
this.state = {
activeLocation: 0,
activeTabIndex: 0,
initialData: [
{
label: "General",
content: <GeneralContent />
}
]
};
this.handleTabClick = this.handleTabClick.bind(this);
}
handleTabClick(index) {
this.setState({
activeTabIndex: index
});
}
render() {
const { initialData, activeTabIndex } = this.state;
const activeItem = this.state.initialData[activeTabIndex];
const { company } = this.props;
return (
<div>
<article>
<div>
<NavBar />
<div className="container">
<Tabs
handleTabClick={this.handleTabClick}
data={this.state.initialData}
activeTabIndex={activeTabIndex}
/>
<Content content={activeItem.content} />
</div>
</div>
</article>
</div>
);
}
}
const GeneralContent = () => (
<div>
<h1>{company.name}</h1>
</div>
);
Thank you!
In the constructor, you could add the company as prop to GeneralContent
initialData: [
{
label: "General",
content: <GeneralContent company={props.company}/>
}
]
This is not a good practice due to your GeneralContent will not be aware of if the company changes. You can address this by using componentDidUpdate
componentDidUpdate(prevProps, prevState) {
if(prevProps.company.name !== this.props.company.name) {
const tabData = [...this.state.initialData];
tabData.find((tab) => tab.label === 'General');
tabData.content = <GeneralContent company={this.props.company} />;
this.setState({initialData: tabData});
}
}
This updates youGeneral tab with the new company. As you see it is a little "complicated" to maintain.
I would refactor the code to avoid doing that, something like this:
class Tabs extends React.Component {
constructor(props) {
super(props);
this.state = {
activeTabIndex: 0,
};
this.handleTabClick = this.handleTabClick.bind(this);
}
handleTabClick(index) {
this.setState({
activeTabIndex: index
});
}
render() {
const { activeTabIndex } = this.state;
const tabData = [
{
label: "General",
content: <GeneralContent company={this.props.company}/>
}
];
const tabContent = tabData[activeTabIndex];
return (
<div>
<article>
<div>
<NavBar />
<div className="container">
<Tabs
handleTabClick={this.handleTabClick}
data={tabData}
activeTabIndex={activeTabIndex}
/>
<Content content={tabContent} />
</div>
</div>
</article>
</div>
);
}
}
const GeneralContent = () => (
<div>
<h1>{company.name}</h1>
</div>
);
Basically, the tabs generation has been moved to your render method.

Cannot change parent's state from child component in React

I'm building a simple chat app, but a new comment posted from the input field in the child component is not displayed.
--Parent component--
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
comments: [],
currentUser: { displayName: "user3", uid: 3 }
};
}
addComment = comment => {
this.setState(prevState => {
comments: prevState.comments.push(comment);
});
console.log("this.state");
console.log(this.state);
};
render() {
const { comments, currentUser } = this.state;
return (
<div className="App">
{comments.map(comment => (
<div className="line__left" key={comment.createdAt}>
<figure>
<i className="fas fa-user fa-4x" />
</figure>
<div className="line__left-text">
<div className="name">{comment.createdBy.displayName}</div>
<div className="text">{comment.text}</div>
</div>
</div>
))}
<ChatInputBox addComment={this.addComment} currentUser={currentUser} />
</div>
);
}
}
--Child component--
class ChatInputBox extends React.Component {
constructor(props) {
super(props);
this.state = {
text: "",
currentUser: this.props.currentUser
};
}
handleChange = e => {
this.setState({ text: e.target.value });
};
handleClickPost = e => {
e.preventDefault();
let comment = {
createdAt: new Date().getTime(),
createdBy: this.state.currentUser,
text: this.state.text
};
this.props.addComment(comment);
this.setState({ text: "" });
};
render() {
const { text } = this.state;
return (
<div className="ChatInputBox">
ChatBox
<textarea onChange={this.handleChange} value={text} />
<button onClick={this.handleClickPost}>Post</button>
</div>
);
}
}
After I populate the text area and click the button, parent's state seems to be updated, but new comment is not shown.How can I show it?
change your code
addComment = comment => {
this.setState(prevState => {
comments: prevState.comments.push(comment);
});
console.log("this.state");
console.log(this.state);
};
to
addComment = comment => {
const { comments } = this.state;
this.setState({
comments: comments.concat(comment)
});
};
when you are using setState(), using concat instead of push since it maintains your array's immutability.

How to update the state of all the components that share the same class?

This component is rendered 3 times in my app.
class User extends React.Component {
constructor(props) {
super(props);
this.state = {
userFavorites: []
};
this.postFavorite = this.post.bind(this);
}
componentDidMount() {
this.setState(() => ({
userFavorites: [{ id: 1, title: "A" }, { id: 2, title: "B" }]
}));
}
post() {
const obj = { id: 3, title: "C" };
this.setState(
prevState => ({
userFavorites: [...prevState.userFavorites, obj]
}),
() => {
console.log("AFTER", this.state.userFavorites);
}
);
}
render() {
return (
<div className="container">
<div className="button" onClick={this.post} />
</div>
);
}
}
When I call post(), by clicking in the button, the const obj is added in the userFavorites array, merging with the last state.
However it is only added to the 'User' that was clicked and triggered the method post().
Is there any way that I can set the state to all the 3 'User component' on my app, regardless which 'User' triggers the state update?
The three User components have no knowledge of each other. The shared state should be moved higher up in your component tree.
Below is a mini example which demonstrates the idea. The state is stored in <Parent> and passed to each <Child> as a prop, along with a callback to add to the state.
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = { items: [] };
this.addItem = this.addItem.bind(this);
}
render() {
return (
<div>
<Child name="first" items={this.state.items} add={this.addItem} />
<Child name="second" items={this.state.items} add={this.addItem} />
<Child name="third" items={this.state.items} add={this.addItem} />
</div>
);
}
addItem(item) {
this.setState({ items: [...this.state.items, item] });
}
}
function Child(props) {
return (
<div>
<h3>{props.name}</h3>
{props.items.map((item, i) => (<div key={i}>{item}</div>))}
<button onClick={() => props.add(props.name)}>add</button>
</div>
);
}
ReactDOM.render(<Parent />, 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>

ReactJS, Todo list, how to remove elements from list

I'm ReactJS newbie (well, to be specific I'm programming newbie) looking for some answers, please don't exclude me for obvious stupidity :) I'm having big troubles with passing props and understanding 'this' context.
This is my mess, two simple Todo app components. TodoApp:
import React from 'react';
import uuid from 'uuid';
import style from './App.css';
import Title from '../components/Title.js';
import TodoList from '../components/TodoList.js';
class App extends React.Component {
constructor(props) {
super(props);
this.title = "Todo Application"
this.state = {
data: [{
id: 1,
text: 'clean room'
}, {
id: 2,
text: 'wash the dishes'
}, {
id: 3,
text: 'feed my cat'
}]
};
}
removeTodo(id) {
const remainder = this.state.data.filter(item => item.id !== id);
this.setState({data: remainder});
}
render() {
return (
<div className={style.TodoApp}>
<Title title="Todo Application" added={this.state.data.length} />
<TodoList data={this.state.data} remove={this.removeTodo}></TodoList>
</div>
);
}
}
export default App;
And TodoList:
import React from 'react';
const TodoList = props => (
<ul>
{props.data.map((item, index) =>
<li key={index}>
{item.text}
<button value={index} onClick={() => props.remove(index)}>x</button>
</li>
)}
</ul>
);
export default TodoList;
My question is how to correctly pass props to child component (TodoList) so that remove button would work?
To make sure the this always refers to the App context where your removeTodo method is defined, you can add the following inside your constructor (after setting the initial state):
this.removeTodo = this.removeTodo.bind(this);
Otherwise, your code isn't messy at all. It's even surprisingly concise and well thought out, even much more so for a self-proclaimed beginner. Congratulations!
As pointed out by Sag1v in the comment below, you're also not passing the correct value to your removeTodo method. You're passing the index of the item being iterated on, instead of its id.
Change your <button> invocation to the following:
<button value={index} onClick={() => props.remove(item.id)}>x</button>
Note that you could also achieve the same with the following:
<button value={index} onClick={props.remove.bind(null, item.id)}>x</button>
You got 2 main problems here:
As Jaxx mentioned you are not binding the handler removeTodo to
the class.
There are couple of ways to do it.
Bind it in the constructor:
this.removeTodo = this.removeTodo.bind(this);
Or use the ES2015(ES6) Arrow functions which will use the lexical context for this:
removeTodo = (id) => {
const remainder = this.state.data.filter(item => item.id !== id);
this.setState({data: remainder});
}
The 2nd problem is that inside TodoList onClick handler you are
not passing the correct id to the handler, you are passing the
index position.
onClick={() => props.remove(index)}
You should change that to:
onClick={() => props.remove(item.id)}
There is another problem with this approach which i'll explain next.
Here is a working example:
const Title = ({title}) => <h1>{title}</h1>
const TodoList = props => (
<ul>
{props.data.map((item, index) =>
<li key={index}>
{item.text}
<button value={index} onClick={() => props.remove(item.id)}>x</button>
</li>
)}
</ul>
);
class App extends React.Component {
constructor(props) {
super(props);
this.title = "Todo Application"
this.state = {
data: [{
id: 1,
text: 'clean room'
}, {
id: 2,
text: 'wash the dishes'
}, {
id: 3,
text: 'feed my cat'
}]
};
}
removeTodo = (id) => {
const remainder = this.state.data.filter(item => item.id !== id);
this.setState({data: remainder});
}
render() {
return (
<div>
<Title title="Todo Application" added={this.state.data.length} />
<TodoList data={this.state.data} remove={this.removeTodo}></TodoList>
</div>
);
}
}
ReactDOM.render(<App/>,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>
As i said, there is a problem with this approach, you are passing a new instance of a function on each render with this line of code:
onClick={() => props.remove(item.id)
This is considered as bad practice because this can interrupt the Reconciliation and diffing algorithm
But as we know, event Handlers should get a function reference, hence you can't just pass a function invocation like this
onClick={props.remove(item.id)}
This will pass the function's return type (if any) and not the reference for the function.
So the proper way of passing a function reference is like this:
onClick={props.remove}
But that is not good for your case as you need to pass back to the parent the current item id, but i'm afraid that the browser will only pass back the event parameter.
So what are the alternatives you ask?
Create another component and control the data you pass in and out from your component instead of relying on the goodwill of the browsers.
Here is another working example but this time without creating a new function instance on each render
const Title = ({title}) => <h1>{title}</h1>
class TodoItem extends React.Component {
handleClick = () => {
const{item, onClick} = this.props;
onClick(item.id);
}
render(){
const {item} = this.props;
return(
<li>
{item.text}
<button value={item.id} onClick={this.handleClick}>x</button>
</li>
);
}
}
const TodoList = props => (
<ul>
{props.data.map((item, index) =>
<TodoItem key={index} item={item} onClick={props.remove} />
)}
</ul>
);
class App extends React.Component {
constructor(props) {
super(props);
this.title = "Todo Application"
this.state = {
data: [{
id: 1,
text: 'clean room'
}, {
id: 2,
text: 'wash the dishes'
}, {
id: 3,
text: 'feed my cat'
}]
};
}
removeTodo = (id) => {
const remainder = this.state.data.filter(item => item.id !== id);
this.setState({data: remainder});
}
render() {
return (
<div>
<Title title="Todo Application" added={this.state.data.length} />
<TodoList data={this.state.data} remove={this.removeTodo}></TodoList>
</div>
);
}
}
ReactDOM.render(<App/>,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>

React Passing state to sibling component and up to parent class

Very very new to React and I seem to be stuck. This is a simple Todo app, I basically have 3 components, the base component, an input component and a task component. I have figured out how to edit the state within each component but I am having trouble passing state from component to component.
class App extends Component {
render() {
return (
<div id="appContainer">
<HeaderTitle />
<TaskInput />
<Task taskState={true} text="task one" />
<Task taskState={true} text="task two" />
<Task taskState={true} text="task three" />
</div>
);
}
}
class TaskInput extends React.Component {
constructor(props) {
super(props);
this.state = {}
}
update(e) {
this.setState({inputValue: e.target.value});
console.log(this.state);
}
taskCreate(e) {
this.setState({text: this.state.inputValue, completeState: false});
console.log('button clicked');
console.log(this.state);
}
render () {
return (
<div className="taskInputContainer">
<TaskInputField update={this.update.bind(this)} taskCreate={this.taskCreate.bind(this)} />
</div>
)
}
}
class Task extends Component {
constructor(props) {
super();
this.state = {
completeState: false
}
}
toggleTask (e) {
this.setState({
completeState: !this.state.completeState
});
}
delete (item) {
}
render() {
return (
<div className="taskContainer" onClick={this.toggleTask.bind(this)}>
<div className={"taskState " + this.state.completeState}></div>
<div className={"taskText " + this.state.completeState }>{this.props.text}</div>
<div className="taskDelete"><i className="fa fa-times-circle-o" aria-hidden="true"></i></div>
</div>
);
}
}
const TaskInputField = (props) =>
<div className="taskInputContainer">
<input type="text" className="taskInputField" onChange={props.update}/>
<i className="fa fa-plus-circle" aria-hidden="true" onClick={props.taskCreate}></i>
</div>;
Task.propTypes = {
text: PropTypes.string.isRequired,
completeState: PropTypes.bool
};
Task.defaultProps = {
text: 'Task',
completeState: false
};
const HeaderTitle = () => (
<h1>Davids Todo List</h1>
);
export default App;
So in the TaskInput has its own state that I can update but how do I pass that up to the parent component to update and add a Task component? Also how do I add a Task component without re-rendering the whole thing?
This issue is documented in detail in the article 'lifting the state up' in React's documentation.
TLDR, you create a handler that updates the state of the current component and pass it to children as props. In the example below (a modified version of your code), I passed down the methods that changes the state of component App, into its children components (TaskInput and Tasks).
class App extends React.Component {
constructor() {
super();
this.state = {
tasks: [],
}
}
addTask = (e, text) => {
e.preventDefault();
const newTask = {
id: new Date().getTime(),
done: false,
text
};
const newTasks = this.state.tasks.concat([newTask]);
this.setState({
tasks: newTasks
})
}
toggleTask = (id) => {
const updatedTask = this.state.tasks.filter(task => task.id === id);
updatedTask[0].done = !updatedTask[0].done;
const newTasks = this.state.tasks.map(task => {
if (task.id === id) {
return updatedTask[0];
}
return task;
});
this.setState({
tasks: newTasks
});
}
render() {
return (
<div id="appContainer">
<HeaderTitle />
<TaskInput addTask={this.addTask} />
{
this.state.tasks.length > 0 ? <Tasks tasks={this.state.tasks} toggleTask={this.toggleTask}/> : <div>no tasks yet</div>
}
</div>
);
}
}
class TaskInput extends React.Component {
constructor(props) {
super(props);
this.state = {
currentInput: ''
}
}
handleChangeText = (e) => {
this.setState({
currentInput: e.target.value,
})
}
render() {
return (<form>
<input type="text" value={this.state.currenInput} onChange={this.handleChangeText}/><input type="submit" onClick={(e) => this.props.addTask(e, this.state.currentInput)} value="Add Task"/></form>)
}
}
const Tasks = (props) => (
<div>
{
props.tasks.map(task => (
<div
style={ task.done ? { textDecoration: 'line-through'} : {} }
onClick={() => props.toggleTask(task.id)}
>{task.text}</div>
))
}
</div>
);
const HeaderTitle = () => (
<h1>Davids Todo List</h1>
);
ReactDOM.render(<App />, document.getElementById('app'))
<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="app"></div>

Resources