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>
Related
I'm making a comment system with React Quill as my editor and Firebase Firestore. Each comment post gets stored in firestore. Each stored comment has a reply button, and when clicked, the editor should be populated with the comment content I want to reply to. Basically I need to populate my editor with the content stored in firestore database. Here's a screenshot as to watch I want to achieve:
Comment reply
Here's some code from the comment editor component
class NewComment extends Component {
constructor(props) {
super(props);
this.state = {
comment: {
commentID: "",
content: "",
createDate: new Date(),
featureImage: "",
isPublish: "True",
createUserID: "",
},
};
}
...
onChangeCommentContent = (value) => {
this.setState({
comment: {
...this.state.comment,
content: value,
},
});
};
...
render() {
return (
<Container>
<Row>
<Col xl={9} lg={8} md={8} sn={12}>
<h2 className={classes.SectionTitle}>Comment</h2>
<FormGroup>
<ReactQuill
ref={(el) => (this.quill = el)}
value={this.state.comment.content}
onChange={(e) => this.onChangeCommentContent(e)}
theme="snow"
modules={this.modules}
formats={this.formats}
placeholder={"Enter your comment"}
/>
</FormGroup>
</Col>...
The reply button is in a different component where I render the stored comments. Tell me if you need the full code from the components.
Here is a simple example on how to pass on information between two components via the parent component using function components:
// Index.js
const MyComponent = () => {
const [replyValue, setReplyValue] = useState("");
const onClick = (value) => {
setReplyValue(value);
};
return (
<>
<Comment value="This is a reply" onClick={onClick} />
<Comment value="This is another reply" onClick={onClick} />
<CreateReply quoteValue={replyValue} />
</>
);
};
// Comment.js
export const Comment = ({ value, onClick }) => {
return (
<div className="comment" onClick={() => onClick(value)}>
{value}
</div>
);
};
// CreateReply.js
export const CreateReply = ({ quoteValue = "" }) => {
const [value, setValue] = useState("");
useEffect(() => {
setValue(quoteValue);
}, [quoteValue]);
const onValueUpdated = (newValue) => {
if (newValue !== value) {
setValue(newValue);
}
};
return (
<>
<ReactQuill value={value} onChange={onValueUpdated} />
</>
);
};
Here is the same example using class components:
// Index.js
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
this.state = {
replyValue: ""
};
}
onClick = (value) => {
this.setState({
replyValue: value
});
};
render() {
return (
<>
<Comment value="This is a reply" onClick={this.onClick} />
<Comment value="This is another reply" onClick={this.onClick} />
<CreateReply quoteValue={this.state.replyValue} />
</>
);
}
}
// Comment.js
export class Comment extends React.Component {
render() {
return (
<div
className="comment"
onClick={() => this.props.onClick(this.props.value)}
>
{this.props.value}
</div>
);
}
}
// CreateReply.js
export class CreateReply extends React.Component {
constructor(props) {
super(props);
this.onValueUpdated = this.onValueUpdated.bind(this);
this.state = {
value: props.quoteValue
};
}
componentDidUpdate(prevProps) {
if (this.props.quoteValue !== prevProps.quoteValue) {
this.setState({
value: this.props.quoteValue
});
}
}
onValueUpdated = (newValue) => {
if (newValue !== this.state.value) {
this.setState({
value: newValue
});
}
};
render() {
return (
<>
<ReactQuill value={this.state.value} onChange={this.onValueUpdated} />
</>
);
}
}
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.
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>
I have a parent container which has a couple of child components. When the user clicks onClick={props.toggleReviewForm}, the function
toggleReviewForm () {
this.setState(prevState => ({
reviewFormActive: !prevState.reviewFormActive,
displayNameModalActive: !prevState.displayNameModalActive
}))
}
toggles the reviewForm state to visible. It's visibility is set with reviewFormActive={reviewFormActive} in the child component and the parent has `this.state = {reviewFormActive: false} set in the constructor. I am passing
displayNameModalActive={displayNameModalActive}
into the child component for the modal, but getting the error
Uncaught TypeError: Cannot read property 'displayNameModalActive' of undefined at DisplayNameModal.render
Parent Container
class ReviewsContainer extends React.Component {
constructor (props) {
super(props)
this.state = {
reviewFormActive: false,
displayNameModalActive: false
}
this.config = this.props.config
this.toggleReviewForm = this.toggleReviewForm.bind(this)
}
toggleReviewForm () {
this.setState(prevState => ({
reviewFormActive: !prevState.reviewFormActive,
displayNameModalActive: !prevState.displayNameModalActive
}))
}
render () {
const {
reviewFormActive,
displayNameModalActive
} = this.state
return (
<div className='reviews-container'>
<ReviewForm
config={this.config}
reviewFormActive={reviewFormActive}
toggleReviewForm={this.toggleReviewForm}
/>
{this.state.displayName &&
<div className='modal-container'>
<DisplayNameModal
bgImgUrl={this.props.imageUrl('displaynamebg.png', 'w_1800')}
config={this.config}
displayNameModalActive={displayNameModalActive}
displayName={this.state.displayName}
email={this.state.email} />
</div>
}
</div>
)
}
}
export default ReviewsContainer
Child Component (modal)
class DisplayNameModal extends React.Component {
constructor (props){
super(props)
this.state = {
displayName: this.props.displayName,
email: this.props.email.split('#')[0]
}
}
render (props) {
const {contentStrings} = this.props.config
return (
<div>
//Should only allow the modal to show if the username is the same as the email or there is no username available
{ props.displayNameModalActive && this.state.displayName === this.state.email || !this.state.displayName &&
<div className='display-name-container' style={{ backgroundImage: `url(${this.props.bgImgUrl})` }}>
<div className='display-name-content'>
<h2 className='heading'>{contentStrings.displayNameModal.heading}</h2>
<p>{contentStrings.displayNameModal.subHeading}</p>
<input type="text"
defaultValue={this.state.displayName}
placeholder={this.state.displayName}
minLength="3"
maxLength="15"/>
<button
onClick={this.updateDisplayName}
className='btn btn--primary btn--md'>
<span>{contentStrings.displayNameModal.button}</span>
</button>
<p className='cancel'>{contentStrings.displayNameModal.cancel}</p>
</div>
</div>
}
</div>
)
}
}
export default DisplayNameModal
Whyt this:
props.displayNameModalActive
and not this:
this.props.displayNameModalActive
?
Correct me if I'm wrong but render doesn't get props as argument.
due to certain factors(SalesForce) I am looking of only using reactjs and not something like redux.
I going to have something like this
<script type="text/babel">
class Main extends React.Component {
constructor() {
super();
this.state = {
items : [
{
year: 2016,
},
{
year: '',
},
]
};
}
componentDidMount() {
}
handleChange(event, index){
let items = this.state.items;
let item = items[index];
item[event.target.name] = event.target.value;
items[index] = item;
this.setState({ items: items});
}
render() {
return (
<div className="main">
{
this.state.items.map((item, i) => {
return <Item item={item} index={i} handleChange={(event,index) => this.handleChange(event,index)} />
})
}
</div>
)
}
}
class Item extends React.Component {
constructor() {
super();
}
componentDidMount() {
}
handleChange(event){
this.props.handleChange(event, this.props.index);
}
render() {
return (
<div className="item">
<Sub item={this.props.item} index={this.props.index} handleChange={(event) => this.handleChange(event)} />
</div>
)
}
}
class Sub extends React.Component {
constructor() {
super();
}
componentDidMount() {
}
handleChange(event) {
this.props.handleChange(event, this.props.index);
}
render() {
return (
<div className="container">
<div>
<label>Year </label>
<input type="text" name="year" value={asset.year} className={year} onChange={(event) => this.handleChange(event)}/>
</div>
</div>
)
}
}
ReactDOM.render(<Main />, document.getElementById("Container"));
</script>
I am wondering is there away so that when someone types something in "year" in sub component I don't have to have handleChange that goes up to the Item handleChange and finally to the Main handleChange.