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.
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 trying to change the class of an element using the onClick event handler. When the div is clicked the class changes causing some css to change in turn. When I add another div, it assumes the same state as the first div.
class Content extends React.Component {
constructor(props) {
super(props);
this.startGambling = this.startGambling.bind(this);
this.toggleClass = this.toggleClass.bind(this);
this.state = {
prize: '',
tries: 0,
isFlipped: false
};
}
toggleClass() {
this.setState({ isFlipped: true });
};
render() {
return (
<div className="card-box" style={divStyle}>
<div class="flip-card-inner" className={this.state.isFlipped ? 'flipped' : null} onClick={this.toggleClass}></div>
</div>
);
}
}
State is not html-element-scoped. It is the state of the current Component.
Implement the FlipCard component and handle its flipped state within that component.
class FlipCard extends React.Component {
state = {
isFlipped: false
}
toggle = () => {
this.setState(prevState => ({
isFlipped: !prevState.isFlipped
}))
}
render(){
const { isFlipped } = this.state;
return (
<div
onClick={this.toggle}
className={isFlipped ? 'flipped' : ''}>
Card
</div>
)
}
}
const CardBox = () => (
<article>
<FlipCard />
<FlipCard />
</article>
);
ReactDOM.render(<CardBox />, document.getElementById('root'))
div {
cursor: pointer;
}
div.flipped {
background: red;
}
<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>
This is a different approach than #gazdagergo's answer but logic is the same. You need to track toggled state somewhere. If you don't need this toggled state anywhere else, you can keep it in its own components as #gazdagergo showed. But, for example, if you need to know how many items are toggled (just an example) you can keep the state in the parent component to share this info with another component.
const cards = [
{ id: 1, name: "foo" },
{ id: 2, name: "bar" },
{ id: 3, name: "baz" }
];
const Card = ({ card, toggleClass, isFlipped }) => {
const handleClick = () => toggleClass(card.id);
return (
<div onClick={handleClick} className={isFlipped[card.id] ? "flipped" : ""}>
{card.name}
</div>
);
};
class Content extends React.Component {
constructor(props) {
super(props);
this.toggleClass = this.toggleClass.bind(this);
this.state = {
prize: "",
tries: 0,
isFlipped: {}
};
}
toggleClass(id) {
this.setState(state => ({
isFlipped: { ...state.isFlipped, [id]: !state.isFlipped[id] }
}));
}
render() {
return (
<div>
{cards.map(card => (
<Card
key={card.id}
card={card}
toggleClass={this.toggleClass}
isFlipped={this.state.isFlipped}
/>
))}
</div>
);
}
}
ReactDOM.render(<Content />, document.getElementById("root"));
.flipped {
background: red;
}
<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" />
In the example, I assumed that card items have a unique id but you can do that with indexes also. Your isFlipped state is not a boolean anymore, it is an object and keeps the flipped ids.
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 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.
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>