How to push checkbox values in a array? - javascript

I am building a Treeview application in react and I have some problem that I am not solving.
When I click Apply button I want to push in a array all selected values, but when I click Cancel button I want to remove all the last selected values.
My code is as below, please can anyone modify the code and help the for the solution?
export default class TreeView extends Component {
constructor(props) {
super(props);
this.state = {
checked: [],
expanded: [],
keyword:"",
pushOn:[],
};
}
onSearchInputChange = (event, data, searchedNodes) => {
this.setState(prevState => {
if (prevState.keyword.trim() && !data.value.trim()) {
return {
expanded: [],
keyword: data.value
};
}
return {
expanded: this.getAllValuesFromNodes(searchedNodes, true),
keyword: data.value
};
});
};
getHighlightText = (text, keyword) => {
const startIndex = text.indexOf(keyword);
return startIndex !== -1 ? (
<span>
{text.substring(0, startIndex)}
<span style={{ color: "#2cb664" }}>
{text.substring(startIndex, startIndex + keyword.length)}
</span>
{text.substring(startIndex + keyword.length)}
</span>
) : (
<span>{text}</span>
);
};
keywordFilter = (nodes, keyword) => {
let newNodes = [];
for (let n of nodes) {
if(n.children) {
const nextNodes = this.keywordFilter(n.children, keyword);
if (nextNodes.length > 0) {
n.children = nextNodes;
} else if (n.label.toLowerCase().includes(keyword.toLowerCase())) {
n.children = nextNodes.length > 0 ? nextNodes : [];
}
if (
nextNodes.length > 0 ||
n.label.toLowerCase().includes(keyword.toLowerCase())
) {
n.label = this.getHighlightText(n.label, keyword);
newNodes.push(n);
}
}else {
if(n.label.toLowerCase().includes(keyword.toLowerCase())) {
n.label = this.getHighlightText(n.label, keyword);
newNodes.push(n);
}
}
}
return newNodes;
};
getAllValuesFromNodes = (nodes, firstLevel) => {
if (firstLevel) {
const values = [];
for (let n of nodes) {
values.push(n.value);
if(n.children) {
values.push(...this.getAllValuesFromNodes(n.children, false));
}
}
return values;
} else {
const values = [];
for (let n of nodes) {
values.push(n.value);
if(n.children) {
values.push(...this.getAllValuesFromNodes(n.children, false));
}
}
return values;
}
};
shouldComponentUpdate(nextProps, nextState) {
if(this.state.keyword !== nextState.keyword) {
return true;
}
if ( !lodash.isEqual(this.state.checked, nextState.checked)) {
return true;
}
if ( !lodash.isEqual(this.state.expanded, nextState.expanded)) {
return true;
}
return true;
}
render () {
let searchedNodes = this.state.keyword.trim()
? this.keywordFilter(lodash.cloneDeep(nodesData), this.state.keyword)
: nodesData;
return (
<div style={{marginLeft:"30px", marginTop:"30px", width:"30%"}}>
<div className="search">
<Input style={{marginBottom:"10px", width:"100%"}}
icon="fas fa-search"
placeholder="Search Categories"
iconPosition="left"
onChange={(event, data) => {
this.onSearchInputChange(event, data, searchedNodes);
}}
className="Change"
/>
</div>
<hr></hr>
<div className="checkbox-tree">
<CheckboxTree
nodes={searchedNodes}
checked={this.state.checked}
expanded={this.state.expanded}
onCheck={checked => this.setState({ checked })}
onExpand={expanded => this.setState({ expanded })}
expandOnClick
onClick = { () => { console.log("clicked"); }}
showNodeIcon={false}
icons={{
expandClose: <i class="fas fa-chevron-right fa-xs"></i>,
expandOpen: <i class="fas fa-chevron-down fa-xs"></i>,
}}
nameAsArray={true}
/>
</div>
<div>
<form class="butt">
<button type="button" onClick={this.cancel}>Cancel</button>
<button onClick={this.pushOnArray}>Apply</button>
</form>
</div>
</div>
)
}
}
Thanks in advance!

So I don't know what your project setup is but the easiest way to go with what I know is:
export default class TreeViewParent extends Component {
constructor(props) {
super(props);
this.state = {
checked: [],
};
onSave = (newCheckedValues) => {
this.setState({checked: newCheckedValues})
// perform action based on selected values here
}
render() {
return <TreeView checked={this.state.checked} save={this.onSave} />
}
}
// I am omitting parts of this component that are not related to the problem
export default class TreeView extends Component {
constructor(props) {
super(props);
this.state = {
checked: this.props.checked,
expanded: [],
keyword: "",
pushOn:[],
};
}
cancel = () => {
this.props.save(this.props.checked)
this.setState({checked: this.props.checked})
}
save = () => {this.props.save(this.state.checked)}
render () {
let searchedNodes = this.state.keyword.trim()
? this.keywordFilter(lodash.cloneDeep(nodesData), this.state.keyword)
: nodesData;
return (
<div style={{marginLeft:"30px", marginTop:"30px", width:"30%"}}>
<div className="search">
<Input style={{marginBottom:"10px", width:"100%"}}
icon="fas fa-search"
placeholder="Search Categories"
iconPosition="left"
onChange={(event, data) => {
this.onSearchInputChange(event, data, searchedNodes);
}}
className="Change"
/>
</div>
<hr></hr>
<div className="checkbox-tree">
<CheckboxTree
nodes={searchedNodes}
checked={this.state.checked}
expanded={this.state.expanded}
onCheck={checked => this.setState({ checked })}
onExpand={expanded => this.setState({ expanded })}
expandOnClick
onClick = { () => { console.log("clicked"); }}
showNodeIcon={false}
icons={{
expandClose: <i class="fas fa-chevron-right fa-xs"></i>,
expandOpen: <i class="fas fa-chevron-down fa-xs"></i>,
}}
nameAsArray={true}
/>
</div>
<div>
<form class="butt">
<button type="button" onClick={this.cancel}>Cancel</button>
<button onClick={this.pushOnArray}>Apply</button>
</form>
</div>
</div>
)
}
Additionally I see you use some "advanced" stuff like cloneDeep while you do not understand basics of React. I recommend you to read the react docs, cuz answer I provided is the entry level concept of react.

Related

Set input to a controller input using button onclick method in React JS

I find myself struggling to change the form input value from the button onClick handler. My problem is escalated by the fact that I have the line items on a form and things become even more complex that way. I tried to bring in a ref to set value,,, the value is displayed but at the form state after submission it remains empty which means the value is not being set. Below are all my code files. May someone kindly help me on where I may be missing the point.
class VehicleTemplate extends React.Component {
constructor(props){
super(props);
this.ws = new WebSocket('ws://localhost:8000/ws/weightData/');
this.socketRef = null;
this.state = {
data: 0,
name: '',
model: '',
plate_number: '',
type: 'truck',
wagons: [{ index: uuid(), label: '', type: 'once', weight: '' }],
total: 0,
};
this.onSubmit = this.onSubmit.bind(this);
this.handleChange= this.handleChange.bind(this);
this.handleChangeTable = this.handleChangeTable.bind(this);
this.addNewRow = this.addNewRow.bind(this);
this.deleteRow = this.deleteRow.bind(this);
this.handleLineItemChange = this.handleLineItemChange.bind(this);
}
componentDidMount() {
this.ws.onopen = () => {
// on connecting, do nothing but log it to the console
console.log('connected')
}
this.ws.onmessage = evt => {
// listen to data sent from the websocket server
// const message = JSON.parse(evt.data)
const {data} = this.state;
this.setState({data: evt.data})
// this.setState({this.state.lines.weight: data})
}
this.ws.onclose = () => {
console.log('disconnected')
// automatically try to reconnect on connection loss
}
};
onSubmit = (e) => {
e.preventDefault();
const {
name,
model,
plate_number,
type,
wagons,
} = this.state;
const vehicle = {
name,
model,
plate_number,
type,
wagons,
};
this.props.addVehicle(vehicle, this.props.token);
console.log(vehicle);
this.setState({
name: '',
model: '',
plate_number: '',
wagons: [],
});
};
handleLineItemChange = (elementIndex) => (event) => {
let wagons = this.state.wagons.map((item, i) => {
if (elementIndex !== i) return item
return {...item, [event.target.name]: event.target.value}
})
this.setState({wagons})
}
handleChange = name => event => {
this.setState({
[name]: event.target.value,
});
};
handleChangeTable = (name, id) => event => {
this.updateItem(id, { [name]: event.target.value });
};
addNewRow = (event) => {
this.setState({
wagons: this.state.wagons.concat(
[{index: uuid(), label: '', type: 'once', weight: '' }]
)
})
}
deleteRow = (index) => (event) => {
this.setState({
wagons: this.state.wagons.filter((item, i) => {
return index !== i
})
})
}
handleReorderLineItems = (newLineItems) => {
this.setState({
wagons: newLineItems,
})
}
clickOnDelete(record) {
this.setState({
wagons: this.state.lines.filter(r => r !== record)
});
}
render() {
const { classes } = this.props;
const {
data,
name,
model,
plate_number,
wagons,
} = this.state;
return (
<InformationTechnologyLayout>
<PapperBlock title="ADD VEHICLE" icon="ios-document-outline" desc="VEHICLE">
<Form>
<div style={{ clear: 'both' }} />
<h1>WEIGHT: {data}KG</h1>
<Grid container>
<Grid item xs={6}>
<Controls.Input
name="name"
label="Name"
value={name}
onChange={this.handleChange('name')}
/>
<Controls.Input
name="model"
label="MODEL"
value={model}
onChange={this.handleChange('model')}
/>
</Grid>
<Grid item xs={6}>
<Controls.Input
name="plate_number"
label="PLATE NUMBER"
value={plate_number}
onChange={this.handleChange('plate_number')}
/>
</Grid>
</Grid>
<Extensions
items={wagons}
addHandler={this.addNewRow}
changeHandler={this.handleLineItemChange}
focusHandler={this.handleFocusSelect}
deleteHandler={this.deleteRow}
reorderHandler={this.handleReorderLineItems}
data ={data}
/>
<div className='valueTable'>
<div className='row'>
<div className='label'>Subtotal</div>
<div className='value'>{this.calcLineItemsTotal()}KG</div>
</div>
<div className='row'>
<div className='label'>Total Due</div>
<div className='value'>{this.calcGrandTotal()}KG</div>
</div>
</div>
<Button variant="contained" onClick={this.onSubmit}>SUBMIT</Button>
</Form>
</PapperBlock>
</InformationTechnologyLayout>
);
}
}
On top is the main form component which has got Vehicle exetnsions and I am getting my weight from a websocket connection streaming scale outputs.
class Extensions extends Component {
handleDragEnd = (result) => {
if (!result.destination) return
// helper function to reorder result (src: react-beautiful-dnd docs)
const reorder = (list, startIndex, endIndex) => {
const result = Array.from(list)
const [removed] = result.splice(startIndex, 1)
result.splice(endIndex, 0, removed)
return result
}
// perform reorder
const lineItems = reorder(
this.props.items,
result.source.index,
result.destination.index
)
// call parent handler with new state representation
this.props.reorderHandler(lineItems)
}
render = () => {
const {
items,
addHandler,
changeHandler,
focusHandler,
deleteHandler,
reorderHandler,
products,
data,
readOnly,
} = this.props
return (
<form>
<div className='lineItems'>
<div className='gridTable'>
<DragDropContext onDragEnd={this.handleDragEnd}>
<Droppable droppableId="droppable">
{(provided, snapshot) => (
<div
ref={provided.innerRef}
className= 'listDraggingOver'
>
{this.props.items.map((item, i) => (
<Draggable key={item.index} draggableId={item.index} index={i}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={provided.draggableProps.style}
className='listItemDragging'
>
<Extension
addHandler={addHandler}
changeHandler={changeHandler}
focusHandler={focusHandler}
deleteHandler={deleteHandler}
reorderHandler={reorderHandler}
data={data}
style={{color: 'red'}}
key={i + item.index}
index={i}
name={item.index}
label={item.label}
weight={item.weight}
/>
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
</div>
<div className='addItem'>
<button type="button" onClick={addHandler}><AddIcon size="1.25em" className='addIcon' /> Add Item</button>
</div>
</div>
</form>
)
}
}
This are the extensions and the last file is where I would like to capture weight from
class LineItem extends Component {
constructor(props){
super(props);
this.ref = React.createRef();
this.state = {
buttonState: false,
};
}
requestWeight = () => {
const { weight } = this.props;
const {buttonState} =this.state;
this.ref.current.value = this.props.data;
this.setState({
buttonState: true
})
};
render = () => {
const { index, label, weight } = this.props;
const {buttonState} = this.state;
return (
<div className='lineItem'>
<div>{index + 1}</div>
<Controls.Input
name="label"
label="LABEL"
value={label}
onChange={this.props.changeHandler(index)}
/>
<input
className='currency'
type='number'
readOnly={true}
ref={this.ref}
name='weight'
onChange={this.props.changeHandler(index)}
/>
<div>
<button type="button"
className='requestItems'
onClick={this.requestWeight}
disabled={buttonState}
>REQUEST WEIGHT</button>
</div>
<div>
<button type="button"
className='deleteItem'
onClick={this.props.deleteHandler(index)}
><DeleteIcon size="1.25em" /></button>
</div>
</div>
)
}
}
export default LineItem
If I add the value and put weight on the input then the ref stops working. I assume I might be missing something between my handleChange function and the requestWeight function but I really dont know what exaclty

React Plus Django: Can't update the django backend while creating a new Todo item

I created a Django + React Todo App which is working as expected on the frontend but the functionality of updating the backend when a new item/todo is not know to me well. Also, i was able to manage the delete functionality but not the rest (got stuck with handleItem function). Need help to make the add items functionality work :(
Frontend Code below, Backend code is here "https://bitbucket.org/Yash-Marmat/backend/src/master/"
import React, { Component } from "react";
import axios from "axios";
import "bootstrap/dist/css/bootstrap.min.css";
import "font-awesome/css/font-awesome.min.css";
//import "#fortawesome/react-fontawesome";
import "./App.css";
class App extends Component {
constructor() {
super();
this.state = {
newID: 11,
edit: false,
cloneID: 0,
cloneTitle: "",
todoData: [], // will come from django backend
};
this.handleUserInput = this.handleUserInput.bind(this);
this.handleAddItem = this.handleAddItem.bind(this);
this.handleEdits = this.handleEdits.bind(this);
this.renderEdits = this.renderEdits.bind(this);
this.handleUpdates = this.handleUpdates.bind(this);
this.onRemove = this.onRemove.bind(this);
this.handleStrike = this.handleStrike.bind(this);
this.refreshList = this.refreshList.bind(this);
this.getTodos = this.getTodos.bind(this);
this.increment = this.increment.bind(this);
}
// increment id
increment() {
this.setState((prevState) => ({
newID: prevState.newID + 1,
}));
}
// Todo Creation Function (part 1)
handleUserInput(event) {
this.setState({
userInput: event.target.value,
});
}
// PROBLEM BELOW
// Todo Creation Function (part 2)
handleAddItem(id) {
const someID = this.state.newID;
//console.log(someID)
this.setState((prevState) => ({
todoData: [
...prevState.todoData,
{ id: someID, task: this.state.userInput },
],
userInput: "", // im telling react to empty my userInput (the input box)
}));
}
// Todo edit funciton (part 1)
// function 1 (runs the very first time (if edit gets clicked))
handleEdits(theId, theTitle) {
this.setState({
edit: true,
cloneID: theId,
cloneTitle: theTitle,
});
}
// Todo edit function (part 2)
// function 2 (runs automatically after function 1)
// (will run only when the edit condition is true (when we click on edit button))
renderEdits() {
if (this.state.edit) {
return (
<div>
<form onSubmit={this.handleUpdates}>
<input
autoFocus={true}
placeholder="Update Todos"
type="text"
name="data"
defaultValue={this.state.cloneTitle} // from the cloneTitle
className="form-control"
/>
<button
type="submit"
className="btn btn-sm btn-success ml-2 updateButton"
>
Update
</button>
</form>
</div>
);
}
}
// Todo edit Function (part 3)
// function 3 (will run when function 2 runs)
handleUpdates(event) {
event.preventDefault();
this.setState({
todoData: this.state.todoData.map((item) => {
if (item.id === this.state.cloneID) {
item["task"] = event.target.data.value;
return item;
} else {
return item;
}
}),
});
this.setState({
edit: false,
});
}
// Todo delete function
// onRemove(myId) {
// this.setState((prevState) => {
// const updatedList = prevState.todoData.filter((each) => each.id !== myId);
// return {
// todoData: updatedList
// };
// });
// }
onRemove(myId) {
axios
.delete(`http://localhost:8000/api/todos/${myId}`)
.then((res) => this.refreshList());
}
refreshList = () => {
axios
.get("http://localhost:8000/api/todos/")
.then((res) => this.setState({ todoData: res.data }))
.catch((err) => console.log(err));
};
handleStrike(theId, theTask) {
const todoData = this.state.todoData.map((item) => {
if (item.id === theId) {
item["id"] = theId;
item["task"] = theTask;
item["completed"] = !item.completed;
return item;
} else {
return item;
}
});
this.setState({
todoData: todoData,
});
}
// Lifecycle Method
componentDidMount() {
this.getTodos(); // managing the django apis
}
// working with componentDidMount Method
getTodos() {
axios
.get("http://127.0.0.1:8000/api/todos")
.then((res) => {
this.setState({ todoData: res.data });
})
.catch((err) => {
console.log(err);
});
}
render() {
console.log(this.state.todoData)
return (
<div className="card mb-3 sizing mx-auto">
{this.renderEdits()}
{this.state.todoData.map((item) => (
<div className="card-body border text-grey" key={item.id}>
<span className={"crossed-line" + (item.completed ? "" : "active")}>
{item.task}
</span>
{/* edit button below */}
<span
onClick={() => this.handleEdits(item.id, item.task)}
className="shift2 mr-1"
>
<i className="fas fa-edit"></i>
</span>
{/* delete button */}
<span onClick={() => this.onRemove(item.id)} className="mr-2">
<i className="shift fas fa-trash ml-20"></i>
</span>
<span
className="shift3"
onClick={() => this.handleStrike(item.id, item.task)}
>
{item.completed ? (
<i className="fas fa-undo-alt"></i>
) : (
<i className="fas fa-check-circle"></i>
)}
</span>
</div>
))}
<br />
<span>
<input
autoFocus={true}
placeholder="Add Todos"
value={this.state.userInput || ""}
onChange={this.handleUserInput}
className="form-control"
/>
<span
style={{ color: "purple" }}
className="addButton"
onClick={() => {
this.handleAddItem();
this.increment();
}}
disabled={!this.state.userInput}
>
<i className="fas fa-plus-square shift ml-20"></i>
</span>
</span>
</div>
);
}
}
export default App;

show loading bar when parent component pass data to and render child component. React.JS

I am loading a child component on parent component in React.js. With a click on the button, data will be pass to child component through props, and child component will map through that data and render on screen. I am getting data from localstorage and processing its data structure to child component.
But, the issue is when I click on the button and data is being passed and rendered, the button is shown and after the child component is rendered that shows up. I need the loading spinner when I click on the button and it disappears and shows the actual component.
I have tried methods like loading: false in the state but to no avail.
Thanks for your help
import ShowPatientAppointments from './ShowPatientAppointments.component';
class PatientAppointmnent extends Component {
state = {
doctorSlots: null,
timingSlot: null,
bookDay: null,
bookTime: null,
hasTiming: false,
}
getSlots = () => {
let slot = [];
let time = [];
for (let i=0; i< localStorage.length; i++) {
let key = localStorage.key(i);
let value = JSON.parse(localStorage[key]);
slot.push(key);
time.push(value);
this.setState({doctorSlots: slot, timingSlot: time});
}
console.log(this.state.doctorSlots, this.state.timingSlot);
}
render() {
const { doctorSlots, timingSlot, hasTiming } = this.state;
return(
<div>
<button onClick={this.getSlots} className='button'>Show me dates</button>
{doctorSlots === null ? <p></p> : <PatientSelectDay props={doctorSlots} timing={timingSlot} getTimings={this.getTiming} />}
</div>
)
}
}
export default PatientAppointmnent;
class PatientSelectDay extends Component {
state = {
options: [...this.props.props].map(obj => {
return {value: `${obj}`, label: `${obj}`}
}),
timingOptions: [...this.props.timing],
open_id: [],
part_id: '',
doctorDay: 'day',
doctorTiming: 'timing',
}
changeSingleHandler = e => {
this.setState({ part_id: e ? e.value : '' });
};
changeHandler = e => {
let add = this.state.open_id;
add.push(e.map(x => x.value));
this.setState({ open_id: e ? add : [] });
};
saveState = (option) => {
...save selected options
}
render() {
const {options, timingOptions} = this.state;
return (
<div>
<div className='carousel'>
{options.map((option, index) => {
const timing = timingOptions[index].map(obj => {
return {value: `${obj}`, label: `${obj}`}});
return(
<div key={index}>
<Select
name="open_id"
value={option}
onChange={this.changeSingleHandler}
options={option}
className='select'
/>
<Select
name="open_id"
value={this.state.open_id}
onChange={this.changeHandler}
options={timing}
className='select'
/>
<button onClick={() => this.saveState(option)} className='button-left'>Select Days</button>
</div>
)
})}
</div>
</div>
)
}
}
export default PatientSelectDay;
You need to update your code adding a loading state variable.
class PatientAppointmnent extends Component {
state = {
doctorSlots: null,
timingSlot: null,
bookDay: null,
bookTime: null,
hasTiming: false,
loading: false
}
getSlots = () => {
let slot = [];
let time = [];
this.setState({
loading: true
})
for (let i=0; i< localStorage.length; i++) {
let key = localStorage.key(i);
let value = JSON.parse(localStorage[key]);
slot.push(key);
time.push(value);
this.setState({doctorSlots: slot, timingSlot: time, loading: false});
}
console.log(this.state.doctorSlots, this.state.timingSlot);
}
renderButton = () => {
const { doctorSlots, timingSlot, loading } = this.state;
if(doctorSlots === null && timingSlot === null) {
return <div>
{loading ? <p>Loading...</p> : <button onClick={this.getSlots} className='button'>Show me dates</button>}
</div>
}
return null;
}
render() {
const { doctorSlots, timingSlot, hasTiming, loading } = this.state;
return(
<div>
{this.renderButton()}
{doctorSlots === null ? <p></p> : <PatientSelectDay props={doctorSlots} timing={timingSlot} getTimings={this.getTiming} />}
</div>
)
}
}
export default PatientAppointmnent;
class PatientSelectDay extends Component {
state = {
options: [...this.props.props].map(obj => {
return {value: `${obj}`, label: `${obj}`}
}),
timingOptions: [...this.props.timing],
open_id: [],
part_id: '',
doctorDay: 'day',
doctorTiming: 'timing',
}
changeSingleHandler = e => {
this.setState({ part_id: e ? e.value : '' });
};
changeHandler = e => {
let add = this.state.open_id;
add.push(e.map(x => x.value));
this.setState({ open_id: e ? add : [] });
};
saveState = (option) => {
...save selected options
}
render() {
const {options, timingOptions} = this.state;
return (
<div>
<div className='carousel'>
{options.map((option, index) => {
const timing = timingOptions[index].map(obj => {
return {value: `${obj}`, label: `${obj}`}});
return(
<div key={index}>
<Select
name="open_id"
value={option}
onChange={this.changeSingleHandler}
options={option}
className='select'
/>
<Select
name="open_id"
value={this.state.open_id}
onChange={this.changeHandler}
options={timing}
className='select'
/>
<button onClick={() => this.saveState(option)} className='button-left'>Select Days</button>
</div>
)
})}
</div>
</div>
)
}
}
export default PatientSelectDay;

React: search form with multiple inputs is submitting separate search each time additional input is used

My search form is dynmically created using a ajax call for the inputs. Each input can then be used alone or in combination with other inputs to narrow down the search results. The problem I am having is that the submit method is running a new search each time an additional input is added to the form. For example: User just searches with one input. Submit method runs once. User searches with two inputs. Search runs once for the single input and then another time for the two inputs. And so on...
Here is my parent file..
class SearchPage extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
labels: [],
contracts: [],
formValues:[],
pdfs:[],
titles:[],
alertShow: false,
show: false,
};
this.onClick = this.handleContract.bind(this);
this.handleShow = this.handleShow.bind(this);
this.handleClose = this.handleClose.bind(this);
this.handleShowAlert = this.handleShowAlert.bind(this);
this.handleCloseAlert = this.handleCloseAlert.bind(this)
}
initialState = {
formValues: {},
}
state = this.initialState
componentDidMount(){
this.loadLabels();
}
componentWillUnmount(){
}
toggleHidden () {
this.setState({
isHidden: !this.state.isHidden
})
}
handleFormReset = () => {
this.setState(() => this.initialState)
this.setState({contracts:[]})
}
handleClose() {
this.setState({ show: false });
}
handleShow() {
this.setState({ show: true });
}
handleCloseAlert() {
this.setState({ alertShow: false });
}
handleShowAlert() {
this.setState({ alertShow: true });
}
loadLabels = () => {
API.getLabels()
.then(res => {
const labels = res.data;
this.setState({ labels })
})
.catch(err => console.log(err));
};
handleInputChange = (key, value) => {
const newFormValues = Object.assign({}, this.state.formValues, {[key]: value});
this.setState({ formValues: newFormValues })
};
handleContract = (id) => {
API.openRow(id)
.then(res => {
const pdfs = res.data;
this.setState({pdfs});
this.props.history.push({
state: { labels:this.state.labels,
pdfs:this.state.pdfs,
titles:this.state.titles }
})
})
.catch(err => console.log(err));
API.titles(id)
.then(res => {
const titles = res.data;
this.setState({titles});
})
this.setState({ show: true });
}
handleFormSubmit = event => {
event.preventDefault();
const formData = this.state.formValues
let query = '';
let keys = Object.keys(formData);
keys.map(k => {
if (query !== "")
query += `&`;
query += `filter=`
query += `${k}|${formData[k]}`
return this.loadContracts(query);
})
};
noResults() {
this.setState({alertShow:true})
}
loadContracts = (query) => {
API.search(query)
.then(res => {
const contracts = res.data;
if (contracts.length > 0 ){
this.setState({ contracts });
}
else {
this.noResults();
this.setState({contracts:[]});
};
})
.catch(err => console.log(err));
};
render() {
return (
<div className="container" style={{ marginTop: "80px" }}>
<div className="jumbotron">
<div className="container">
<h1>Contract Document Search</h1>
</div>
<br/>
<Container>
<SearchForm
labels={this.state.labels}
handleFormSubmit={this.handleFormSubmit}
handleInputChange={this.handleInputChange}
handleReset={this.handleReset}
handleFormReset={this.handleFormReset}
/>
<div className='modal'>
<Modal show={this.state.alertShow}
onHide={this.handleCloseAlert}
{...this.props}
size="sm"
aria-labelledby="contained-modal-title-vcenter"
centered>
<Modal.Header closeButton>
<Modal.Body>No results found</Modal.Body>
</Modal.Header>
</Modal>
</div>
</Container>
</div>
<div className="container">
<div className="jumbotron-fluid">
</div>
<SearchResults
labels={this.state.labels}
contracts={this.state.contracts}
pdfs={this.state.pdfs}
handleContract={this.onClick}
handleTitles={this.onClick}
/>
<br/>
<br/>
</div>
);
}
}
export default SearchPage;
And My search form component..
export default class SearchForm extends Component {
constructor(...args) {
super(...args);
this.state = {
};
}
render() {
return (
<form className="form-inline col-md-12" onReset={this.props.handleFormReset}>
{this.props.labels.map(label => (
<div className="card border-0 mx-auto" style={styles} key={label.Id}>
<ul className="list-inline ">
<span>
<li>
<Labels htmlFor={label.DisplayName} >{label.DisplayName}:</Labels>
</li>
<li >
<Input
key={label.Id}
onChange={(event) => {
this.props.handleInputChange(label.DataField, event.target.value)}}
value={this.props.newFormValues}
maxLength="999"
style={{height:34}}
name="value"
type="search"
className={"form-control mb-2 mr-sm-2"}
id={label.DataField}
/>
</li>
</span>
</ul>
</div>
))}
<div className=" col-sm-12">
<Button
style={{ float: "left", marginBottom: 10 }}
className="btn btn-success"
type="submit"
onClick={this.props.handleFormSubmit}
>
Search
</Button>
<Help />
<Button
style={{ float: "left", marginBottom: 10 }}
className="btn btn-secondary"
type="reset"
onClick={this.props.handleFormReset}
>
Reset
</Button>
</div>
</form>
);
}
}
The problem was that I had my return statement inside the input mapping.
handleFormSubmit = event => {
event.preventDefault();
const formData = this.state.formValues
let query = '';
let keys = Object.keys(formData);
keys.map(k => {
if (query !== "")
query += `&`;
query += `filter=`
query += `${k}|${formData[k]}`
**return this.loadContracts(query);**
})
};
Solved by moving the return statement outside the mapping.
handleFormSubmit = event => {
event.preventDefault();
const formData = this.state.formValues
let query = '';
let keys = Object.keys(formData);
keys.map(k => {
if (query !== "")
query += `&`;
query += `filter=`
query += `${k}|${formData[k]}`
})
**return this.loadContracts(query);**
};

is there a simple way to create a nested dropdown in ReactJS without crazy amounts of state?

My code works but I feel like there's a way to do this without declaring a ton of state.
When the nav is clicked, it opens all SectionHeaders, and when one of those SectionHeaders is clicked, it opens the SubSections (only one SubSection allowed to be opened at once)
isFilterOpen
Open but subs closed
One sub open (only one at a time, they toggle)
Right now, my code looks like this:
class MobileFilter extends React.Component {
constructor(props) {
super(props);
this.state = {
isFilterOpen: false,
isSectionOpen: {
Business: false,
Resource: false,
Need: false,
Implementation: false,
Type: false,
Foundations: false,
Advantage: false,
Advanced: false,
Catalyst: false,
Team: false,
},
};
this.filterBar = React.createRef();
}
handleFilterClick = () => {
const {
isFilterOpen
} = this.state;
this.setState({
isFilterOpen: !isFilterOpen,
});
};
handleSectionClick = title => {
let selectedSection = title;
if (title.split(' ').length > 1) {
selectedSection = title.split(' ')[0]; // eslint-disable-line
}
this.setState(prevState => {
const newState = {};
Object.keys(prevState.isSectionOpen).forEach(key => {
newState[key] = false;
});
newState[selectedSection] = !prevState.isSectionOpen[selectedSection];
return {
...prevState,
isSectionOpen: {
...newState,
},
};
});
};
render() {
const { isFilterOpen } = this.state;
const {
need = '',
implementation = '',
type = '',
customerStoriesURL = '',
vertical,
} = this.props;
const filterClasses = isFilterOpen
? 'showMobileSections'
: 'hideMobileSections';
const wrapperClass = isFilterOpen
? 'mobileFilterWrapperActive'
: 'mobileFilterWrapper';
const filterData = this.getData(vertical);
if (vertical === 'services') {
return (
<div className="filterBarMobile" ref={this.filterBar}>
<div className="mobileFilterWrapperContainer">
<div className={wrapperClass}>
<button
type="button"
onClick={this.handleFilterClick}
className="filterHead"
>
Navigate Hub
</button>
<div className={filterClasses}>
{this.renderSections('Foundations', filterData.Foundations)}
</div>
<div className={filterClasses}>
{this.renderSections('Advantage', filterData.Advantage)}
</div>
<div className={filterClasses}>
{this.renderSections('Advanced', filterData.Advanced)}
</div>
<div className={filterClasses}>
{this.renderSections('Catalyst', filterData.Catalyst)}
</div>
<div className={filterClasses}>
{this.renderSections(
'Team Edition',
filterData['Team Edition'],
)}
</div>
</div>
</div>
</div>
);
}
return (
<div className="filterBarMobile" ref={this.filterBar}>
<div className="mobileFilterWrapperContainer">
<div className={wrapperClass}>
<button
type="button"
onClick={this.handleFilterClick}
className="filterHead"
>
Navigate Hub
</button>
<div className={filterClasses}>
{this.renderSections(need, filterData.need)}
</div>
{implementation ? (
<div className={filterClasses}>
{this.renderSections(implementation, filterData.implementation)}
</div>
) : null}
<div className={filterClasses}>
{this.renderSections(type, filterData.type)}
</div>
<div className={filterClasses}>
<div className="sectionTab">
<Link className="sectionLabel" to={customerStoriesURL}>
Customer Stories
</Link>
</div>
</div>
</div>
</div>
</div>
);
}
}
export default MobileFilter;
As you can see, there's way too much state going on -- there as to be a way to make this more founded on the data / props that are coming in and not in a way that requires me listing out all of the SubSections as a nested state.
Any ideas would help. Thanks!
i think i've found the solution. i needed to start from scratch. here's what i have:
import React, { Component } from 'react';
import { Link } from 'gatsby';
import Search from '../Search';
import { businessData } from './filterData';
import './newFilter.less';
class NewFilter extends Component {
constructor(props) {
super(props);
this.state = {
isOpen: false,
openSubSection: '',
};
}
handleClick = () => {
const { isOpen } = this.state;
if (!isOpen) {
this.setState({
openSubSection: '',
});
}
this.setState({
isOpen: !isOpen,
});
};
handleSubClick = (e, title) => {
const { openSubSection } = this.state;
if (openSubSection === title) {
this.setState({
openSubSection: '',
});
} else {
this.setState({
openSubSection: title,
});
}
};
// renderLinks = sublevels => sublevels.map(({ title }) => <div>{title}</div>);
renderLinks = sublevels =>
sublevels.map(({ url_slug, title }) => {
if (!url_slug) {
return (
<div className="sectionLabelSub" key={title}>
{title}
</div>
);
}
return (
<Link
className="mobileSubLinks"
key={url_slug}
to={`/${url_slug}/`}
style={{ display: 'block' }}
>
{title}
</Link>
);
});
renderSection = section => {
const { isOpen, openSubSection } = this.state;
const { title, sublevels } = section;
let sectionClass = 'hideMobileSections';
let sectionOpen = 'sectionTabClosed';
let subSectionClass = 'hideMobileContent';
let arrowClass = 'arrow arrow--active';
if (isOpen) {
sectionClass = 'showMobileSections';
}
if (openSubSection === title) {
subSectionClass = 'showMobileContent';
sectionOpen = 'sectionTabOpen';
arrowClass = 'arrow';
}
// const sectionClass = isOpen ? 'section__open' : 'section__closed';
return (
<div className={sectionClass}>
<button
onClick={e => this.handleSubClick(e, title)}
type="button"
key={title}
className={sectionOpen}
>
<button type="button" className="sectionLabel">
{title}
</button>
<div className={arrowClass} />
</button>
<div className={subSectionClass} role="button" tabIndex="0">
{this.renderLinks(sublevels)}
</div>
</div>
);
};
renderSections = sections =>
sections.map(section => this.renderSection(section));
render() {
const { isOpen } = this.state;
const { navTitle, sections } = businessData;
let wrapperClass = 'mobileFilterWrapper';
if (isOpen) {
wrapperClass = 'mobileFilterWrapperActive';
}
return (
<div className="filterBarMobile" ref={this.filterBar}>
<Search vertical='business' />
<div className="mobileFilterWrapperContainer">
<div className={wrapperClass}>
<button
onClick={() => this.handleClick()}
type="button"
className="filterHead"
>
{navTitle}
</button>
{this.renderSections(sections)}
</div>
</div>
</div>
);
}
}
export default NewFilter;
basically i let the data inform the components, pass in the title to the button and the click event, and then the class looks to see if the title from the data matches the title (string) attached to the state

Categories