React.js - Creating Simple Accordion example - javascript

I'm relevantly new to React and I am having trouble on how to tackle this logic:
Essentially I am creating a table using flexbox, and I also want it so that when you click on one of the rows, it expands and reveals another row (for example, it will give a small description what it is about).
So far what I have is just the table.
class Application extends React.Component {
render() {
const renderDataRows = (
[
<div key={0} className='row'>
<div className='cell description'> Mortage Bill
</div>
<div className='cell amount'>$0,000,000</div>
<div className='cell amount'>$2.50</div>
<div className='cell amount'v>000%</div>
</div>,
<div key={1} className='row'>
<div className='cell description'> Electric Bill
</div>
<div className='cell amount'>$0,000,000</div>
<div className='cell amount'>$2.50</div>
<div className='cell amount'v>000%</div>
</div>,
]
)
const containerTable = (
<div className='table-container'>
{renderDataRows}
</div>
)
return (
<div>
{containerTable}
</div>
)
}
}
More specifically, what would be the best way to structure the hidden rows? Create as a child of the cells, or siblings?
I am assuming I will need state to keep in track what is current open, etc?
I've attached Codepen link to mess around

This can be done in the following way:
Let all the cells be in a single parent div and let the cell description be another sibling div (although using would be better). Put a class on the sibling div such as hidden. Not add a click handler on the cells div. Whenever this div is clicked, update the state with that div's id/key. Now use this to set the hidden class to the other divs. Compare this.state.key with the current id/key and show or hide accordingly. I am not giving the specific code.
Note: Instead of storing the divs in the renderDataRows, just put the data in it and map over it to create all the divs. That way you can easily manipulate the hidden class and any other variation in a single place without having to update it separately for each row of data.

Related

react.js image onClick not working for some reason, while same function works for another div

i am making Faq questions in react and my question div displays text when clicked on whole body as shown below:
Here is my react code. div with class "question-body" works with onclick but when I click the Plus img it has no action,any mistakes?
export default function Question({data}){
let imagid = 'img'+data.id;
function toggle(){
document.getElementById(data.id).classList.toggle('question-p') ;
document.getElementById(imagid).classList.toggle('rotateimg');
}
return(
<div className="column width100 question">
<div onClick={() => toggle()} className="question-body">
<div className="flex-between">
<label className="faq-question">{data.question}</label>
<img onClick={() => toggle()} id={imagid} className='togglequestion' src={togglequestion}></img>
</div>
<p id={data.id} className='none' dangerouslySetInnerHTML={{ __html: data.answer }}></p>
</div>
</div>
)
}
even though the answer is pretty simple here, it would require you to redo most of your code.
In order to be working with React.js effectively, you need to learn the basics (component state management,... ) - https://reactjs.org/docs/getting-started.html
I highly encourage you not to use dangerouslySetInnerHTML as it can expose your code to easy exploits.
With React.js, you don’t really want to use Document Object Model APIs (via toggle function)

onClick Removing data from components

So basically I have two components. In Component 1 there is an img. In component 2 there is a button. I need when i press the button img disappears and also button disappears.
const Icons=()=> {
return (
<div className="_icons">
<div className="icons__Top">
<img src="./icons/-48.png" alt="Twitter"/>
<figcaption>Whatever</figcaption>
</div>
</div>
const Button= () => {
return (
<div className="button">
<Button variant="outlined" className="button__rightpage" >REMOVE</Button>
<caption className="text" ></caption>
</div>
)
}
There can be more than one solution,
1)I think you need to have one parent component for both components and make one state in parent component and then pass it in icon component and give it to img and default make it to false and then from button component change that state to true.
2)without parent component -> You can also use context api to change value directly from button component and hide img.

How to make editable cards in React?

I can't figure out how to make card text editable and reflect that changes in the card state.
So, I have an input field where I type in text and click on add button which makes a call for add function,
addFunction = () => {
this.cardId ++;
this.setState({
t_c: [
...this.state.t_c,
{
cardId: this.cardId,
terms: this.state.terms
}
]
});
}
So terms are the text and its provided with cardId as id.
So to display the data inside state t_c I am mapping each object inside the state and displaying them,
{this.state.t_c.map(terms =>
<section>
<section>
<input defaultValue={terms.terms} />
</section>
<img ..../>
</section>
)}
The problem here if I try to change the added terms the changes are not displayed in the t_c state.
At first, you should approach your problem like it's a Form.
Form components are whether controlled or uncontrolled. For your scenario, if you want to save every change you have done to state, you should implement your input as controlled.
<section>
<input
value={terms.terms}
onChange={(e) => setTerms(/* your state controller code */)}
/>
</section>
Also to see the changes, you are missing key prop to populated sections. Change mapping function to this and you'll see the changes.
{this.state.t_c.map((terms, t_index)=>
<section key={"terms" + t_index}>
<section>
<input defaultValue={terms.terms} />
</section>
<img ..../>
</section>
)}
See more detail here.

ReactJS Collapsable Element with "Progess/Completion bar"

I am struggling to make a relatively simple component in React. The goal is to get something close to the following image:
In the image there are two components of the same type (lets call them a Panel) in which content can be collapsed and expanded. Ideally a Panel would have a Header Component, a Progress Bar Component, and Content(i.e radio buttons, text, etc). When collapsed, I would like the "progress bar" to turn green. I've started with the following code snippet to create a progress bar, though it is very rough (some of the code in there will be placed in other components and is there for testing).. I am new to react so any insight or direction would be appreciated!
import React, { Component } from 'react';
import './styles.css';
export default class IconProgressBar extends React.Component {
constructor(props) {
super(props)
this.state = {
}
}
render() {
return (
<div className="row">
<div className="progress-bar">
<span className="progress-line"></span>
<span className="progress-icon"></span>
</div>
<div className="step-container">
<div className="step-header">
<span className="col s6"><h3>Header 1</h3></span>
<span className="col s6 text-right step-summary">
<button className="secondary right edit-button">Edit</button>
<span className="current-value right"> . </span>
</span>
</div>
<div className="step-body">
</div>
</div>
</div>
);
}
}
Edit: In any answers I'm not super concerned about styling (that can be worked out later) i.e a Circle with a line under it in a collapsible & stackable element would suffice :)
I have implemented a majority of your functionality, except the binding and some of the styling. You will just have to finish up the styling and add your icons and callbacks to record user actions.
The idea is simple in this case, you have a list of panels which contain a header and a bunch of options to go with each. First, you can simply render the elements and then within your new ProgressBar component, maintain the state of the bar i.e. click to toggle and changing the styles of the bar on the left. Let me know if you have any further questions.
For the sake of simplicity, I have kept everything in a single component, but you might want to change that in case you want to isolate and reuse sub components.
https://react-2nhcbe.stackblitz.io

Learning React.js

I am trying to learn React. I already have a good grasp of javascript. I am trying to learn by creating a small app that is basically a task manager. In my case it's for grocery related items. I have a fiddle created here. Could you please take a look at how I composed the react code and let me know if this is the best approach to building with components/classes? You can see that I have nested components. I am not sure if there is a better way of doing this.
Finally, I wan't a new "add-item-row" created every time a user clicks on the big blue Add button. Right now one is showing be default but I don't want any showing by default. I want one created (add-item-row, div) only when a user clicks on the Add button.
Here is the fiddle.
https://jsfiddle.net/j0mpsbh9/4/
<div id="app" class="container">
<script type="text/babel">
var AddItemWrapper = React.createClass({
render: function() {
return (
<div>
<div className="row">
<AppTitle />
<AddItemForm />
</div>
<AddItemRow />
</div>
);
}
});
var AppTitle = React.createClass({
render: function() {
return (
<div>
<h1>Grocery List</h1>
</div>
);
}
});
var AddItemForm =React.createClass({
render: function() {
return (
<div>
<div className="col-sm-6 col-lg-6">
<div className="form-group">
<label htmlFor="enter-grocery-item" className="sr-only">Enter Grocery Item</label>
<input type="text" className="form-control" id="enter-grocery-item" placeholder="Enter Grocery Item" />
</div>
</div>
<div className="col-sm-6 col-lg-6">
<button type="button" id="add" className="btn btn-block btn-info">Add <span className="glyphicons circle_plus"></span></button>
</div>
</div>
);
}
});
var AddItemRow =React.createClass({
render: function() {
return (
<div className="add-item-row">
<div className="row">
<div className="col-sm-12 grocery-items">
<div className="col-sm-6">
<div className="form-group">
<label htmlFor="grocery-item" className="sr-only">Grocery Item</label>
<input type="text" className="form-control" id="grocery-item" placeholder="" />
</div>
</div>
<div className="col-sm-6 center">
<button type="button" className="btn btn-blockx btn-warning"><span className="glyphicons pencil"></span></button>
<button type="button" className="btn btn-blockx btn-lgx btn-danger"><span className="glyphicons remove"></span></button>
<button type="button" className="btn btn-blockx btn-lgx btn-success"><span className="glyphicons thumbs_up"></span></button>
</div>
</div>
</div>
</div>
);
}
});
ReactDOM.render(
<AddItemWrapper />,
document.getElementById('app')
);
</script>
</div>
You have a good start here! Your component hierarchy is organized in a sensible way. However you are missing any kind of interactivity or internal state.
The main way you make React components interactive is by using state plus event callbacks which modify said state. "State" is pretty self explanatory - it describes values inherent to how the components looks and behaves, but which change over time. Every time a React component's state is altered (with this.setState()) that component will re-render (literally by re-running the render() function) to reflect the changes.
First let's edit your AddItemWrapper class to keep track of some internal state when it is first mounted. We know that you want to have multiple rows of data, so let's give it an empty array to store future information about rows:
getInitialState(){
return {rows: []};
},
Now instead of rendering a single AddItemRow directly, we'll render a dynamic set of rows that is based on the current component state. Array.map() is perfect for this and a common use case in React:
{this.state.rows.map(function(ea, i){
return <AddItemRow initialItemName={ea} key={ea + "-" + i} />
})}
Basically what that does is take every entry in the array AddItemWrapper.state.rows array and renders it as an AddItemRow component. We give it two properties, initialItemName and key. "initialItemName" will just tell the child component what its name was when it was first added, and "key" is a unique string that allows React to differentiate components from their siblings.
Now we've set up AddItemWrapper to properly render rows based on its internal state. Next we have to modify AddItemForm so that it will react to user input and trigger new rows being added.
In AddItemForm, first we need to add a "ref" to the input text box. This is so that React can identify and read data from this HTML element after it is rendered:
<input ref={function(el){this.inputElement = el;}.bind(this)} ... />
Then give the button element a callback that will trigger when it's clicked:
<button onClick={this.handleClick} ... />
Finally write the callback handler itself:
handleClick(){
this.props.onAdd(this.inputElement.value);
this.inputElement.value = "";
}
Notice how this callback is calling this.props.onAdd()? That means we need to pass in a callback function from the parent (AddItemWrapper) to this component to use. This is how we communicate between parents and children in React: pass a function from a parent to a child which will be triggered from within the child, but will effect the parent.
In AddItemWrapper we make sure AddItemForm has access to the callback function:
AddItemForm onAdd={this.onAdd} />
And then we write the callback function itself:
onAdd(newItem){
var newRows = this.state.rows.slice();
newRows.push(newItem);
this.setState({rows: newRows});
}
Notice how we're copying the old array held in state (using Array.slice()), push a new item into the new array, and then update state with the new array? Never mutate state directly; ALWAYS copy it, modify the copy, and then update state with the new copy.
Almost done. We've created a way for AddItemWrapper to render its rows, and a way for AddItemForm to create new rows. Now we edit AddItemRow to render in a way that maintains its own internal state too.
First make sure it initializes its own state when it's mounted. We'll have it keep track of a string value, which initially is the same as what the user entered into the text box when they pressed "Add", but because it's kept in AddItemRow.state it can be modified later by the user:
getInitialState() {
return {itemName: this.props.initialItemName}
}
Now that the row name is kept in the component state, we can render it in the HTML like this:
<input value={this.state.itemName} ... />
Here's what it looks like when you put it all together!
There are obviously more features that you would want to add, such as letting the user edit, move, or delete a row entry. I'll leave those exercises up to you. I highly recommend you read through all of the official documentation as well as do a few tutorials to get your head in the game. It's obvious that you have a bit of experience under your belt given what you had so far, but getting the hang of how state, render(), and callbacks work takes some practice. Good luck!

Categories

Resources