Is there a way to use a variable as part of a value when setting state from props in ReactJS? - reactjs

I am very new to React. I am trying to make a reusable Checkbox component. These checkboxes are to send info to an API.
I have a GET command on App.JS, to set state.
Here is a simplified version of my API
{
"devices":[
{
"id":1,
"valveA": true,
"valveB": false,
"valveC": true,
},
{
"id":2,
"valveA": false,
"valveB": true,
"valveC": false,
}
]
}
I pass the props to the children like so:
render() {
const {devices} = this.state;
return (
<div >
{devices.map(device => (
<Device device={device} key={device.id} />
))}
</div>
);
I can make individual components for each checkbox and setting the checked state by setting individual state like so:
state = { checked: this.props.device.valveA }
But that means I have to make a component for each 'valve' in my API. Ideally I would like to have one Checkbox component that I can reuse for all my "valves".
I've made a semi-working component by specifying the name of the valve as a prop:
<Device device={device} key={device.id} switchFor="valveA" />
And here is my component that successfully passes the change to the API, however I need to dynamically set the last part of the setState
this.props.device.{{{I WANT THIS TO BE DYNAMIC}}}
, otherwise all switches just get the state of valveA:
state = { checked: false };
componentDidMount(){
this.setState({ checked: this.props.device.valveStatus })
}
handleCheckboxChange = event => {
const type = this.props.switchFor;
const checkedStatus = event.target.checked;
const deviceID = this.props.device.id;
const obj = {};
obj[type] = checkedStatus;
this.setState({ checked: checkedStatus });
axios
.patch(`http://localhost:3001/devices/${deviceID}`, obj)
.then(function(response) {
console.log(response);
})
.catch(function(error) {
console.log(error);
});
};
render() {
const { device } = this.props;
const theFor = this.props.switchFor + device.id;
return (
<div className="custom-control custom-switch">
<input
type="checkbox"
className="custom-control-input"
id={theFor}
checked={this.state.checked}
onChange={this.handleCheckboxChange}
/>
<label className="custom-control-label" htmlFor={theFor}>
toggle
</label>
</div>
);
}
Sorry if my code is kind of janky, I am very new to React.
I am not sure if I am moving in the right direction, somehow I have a feeling that there is an easier way to do this.

Your explanation isn't really quite clear, but I think I can KIND OF understand what you're trying to do. If I'm wrong, just ignore me. So it seems like you want to be able to change which valve/switch of the device object to be displayed by your component. If that's the case, simply set that as a state. Off the top of my head, I can think of using a an array that mirrors your "devices" array:
this.state = {
"devices":[
{
"id":1,
"valveA": true,
"valveB": false,
"valveC": true,
},
{
"id":2,
"valveA": false,
"valveB": true,
"valveC": false,
}
],
"selectedValve": ["valveA", "valveB"]
}
Then when rendering your Device components, just do:
render() {
const {devices} = this.state;
return (
<div >
{devices.map((device, ind) => (
<Device device={device} key={device.id} switchFor={this.state.selectedValve[ind]} />
))}
</div>
);
}
To change the valve of a particular device, you can have:
handleSwitchChange(deviceInd, newValve){
let copySelectedValve = [...this.state.selectedValve];
copySelectedValve[deviceInd] = newValve;
this.setState({
selectedValve: copySelectedValve;
})
}
To change the first device to valveB, you'd just do handleSwitchChange(0, "valveB");
To change the true/false of a valve of a device, you can write something like:

Related

Set values of list of Material UI Autocompletes that fetch options dynamically

I have a Material UI Autocomplete combo-box child component class that fetches results as the user types:
...
fetchIngredients(query) {
this.sendAjax('/getOptions', {
data: {
q: query
}
}).then((options) => {
this.setState({
options: options
});
});
}
...
<Autocomplete
options={this.state.options}
value={this.state.value}
onChange={(e, val) => {
this.setState({value: val});
}}
onInputChange={(event, newInputValue) => {
this.fetchIngredients(newInputValue);
}}
renderInput={(params) => {
// Hidden input so that FormData can find the value of this input.
return (<TextField {...params} label="Foo" required/>);
}}
// Required for search as you type implementations:
// https://mui.com/components/autocomplete/#search-as-you-type
filterOptions={(x) => x}
/>
...
This child component is actually rendered as one of many in a list by its parent. Now, say I want the parent component to be able to set the value of each autocomplete programmatically (e.g., to auto-populate a form). How would I go about this?
I understand I could lift the value state up to the parent component and pass it as a prop, but what about the this.state.options? In order to set a default value of the combo-box, I'd actually need to also pass a single set of options such that value is valid. This would mean moving the ajax stuff up to the parent component so that it can pass options as a prop. This is starting to get really messy as now the parent has to manage multiple sets of ajax state for a list of its Autocomplete children.
Any good ideas here? What am I missing? Thanks in advance.
If these are children components making up a form, then I would argue that hoisting the value state up to the parent component makes more sense, even if it does require work refactoring. This makes doing something with the filled-in values much easier and more organized.
Then in your parent component, you have something like this:
constructor(props) {
super(props);
this.state = {
values: [],
options: []
};
}
const fetchIngredients = (query, id) => {
this.sendAjax('/getOptions', {
data: {
q: query
}
}).then((options) => {
this.setState(prevState => {
...prevState,
[id]: options
});
});
}
const setValue = (newValue, id) => {
this.setState(prevState => {
...prevState,
[id]: newValue
};
}
render() {
return (
<>
...
{arrOfInputLabels.map((label, id) => (
<ChildComponent
id={id}
key={id}
value={this.state.values[id]}
options={this.state.options[id]}
fetchIngredients={fetchIngredients}
labelName={label}
/>
)}
...
</>

autosuggest not showing item immediately

I am looking into fixing a bug in the code. There is a form with many form fields. Project Name is one of them. There is a button next to it.So when a user clicks on the button (plus icon), a popup window shows up, user enters Project Name and Description and hits submit button to save the project.
The form has Submit, Reset and Cancel button (not shown in the code for breviety purpose).
The project name field of the form has auto suggest feature. The code snippet below shows the part of the form for Project Name field.So when a user starts typing, it shows the list of projects
and user can select from the list.
<div id="formDiv">
<Growl ref={growl}/>
<Form className="form-column-3">
<div className="form-field project-name-field">
<label className="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-animated custom-label">Project Name</label>
<AutoProjects
fieldName='projectId'
value={values.projectId}
onChange={setFieldValue}
error={errors.projects}
touched={touched.projects}
/>{touched.projects && errors.v && <Message severity="error" text={errors.projects}/>}
<Button className="add-project-btn" title="Add Project" variant="contained" color="primary"
type="button" onClick={props.addProject}><i className="pi pi-plus" /></Button>
</div>
The problem I am facing is when some one creates a new project. Basically, the autosuggest list is not showing the newly added project immediately after adding/creating a new project. In order to see the newly added project
in the auto suggest list, after creating a new project,user would have to hit cancel button of the form and then open the same form again. In this way, they can see the list when they type ahead to search for the project they recently
created.
How should I make sure that the list gets immediately updated as soon as they have added the project?
Below is how my AutoProjects component looks like that has been used above:
import React, { Component } from 'react';
import Autosuggest from 'react-autosuggest';
import axios from "axios";
import { css } from "#emotion/core";
import ClockLoader from 'react-spinners/ClockLoader'
function escapeRegexCharacters(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
// Use your imagination to render suggestions.
const renderSuggestion = suggestion => (
<div>
{suggestion.name}, {suggestion.firstName}
</div>
);
const override = css`
display: block;
margin: 0 auto;
border-color: red;
`;
export class AutoProjects extends Component {
constructor(props) {
super(props);
this.state = {
value: '',
projects: [],
suggestions: [],
loading: false
}
this.getSuggestionValue = this.getSuggestionValue.bind(this)
this.setAutoSuggestValue = this.setAutoSuggestValue.bind(this)
}
// Teach Autosuggest how to calculate suggestions for any given input value.
getSuggestions = value => {
const escapedValue = escapeRegexCharacters(value.trim());
if (escapedValue === '') {
return [];
}
const regex = new RegExp(escapedValue, 'i');
const projectData = this.state.projects;
if (projectData) {
return projectData.filter(per => regex.test(per.name));
}
else {
return [];
}
};
// When suggestion is clicked, Autosuggest needs to populate the input
// based on the clicked suggestion. Teach Autosuggest how to calculate the
// input value for every given suggestion.
getSuggestionValue = suggestion => {
this.props.onChange(this.props.fieldName, suggestion.id)//Update the parent with the new institutionId
return suggestion.name;
}
fetchRecords() {
const loggedInUser = JSON.parse(sessionStorage.getItem("loggedInUser"));
return axios
.get("api/projects/search/getProjectSetByUserId?value="+loggedInUser.userId)//Get all personnel
.then(response => {
return response.data._embedded.projects
}).catch(err => console.log(err));
}
setAutoSuggestValue(response) {
let projects = response.filter(per => this.props.value === per.id)[0]
let projectName = '';
if (projects) {
projectName = projects.name
}
this.setState({ value: projectName})
}
componentDidMount() {
this.setState({ loading: true}, () => {
this.fetchRecords().then((response) => {
this.setState({ projects: response, loading: false }, () => this.setAutoSuggestValue(response))
}).catch(error => error)
})
}
onChange = (event, { newValue }) => {
this.setState({
value: newValue
});
};
// Autosuggest will call this function every time you need to update suggestions.
// You already implemented this logic above, so just use it.
onSuggestionsFetchRequested = ({ value }) => {
this.setState({
suggestions: this.getSuggestions(value)
});
};
// Autosuggest will call this function every time you need to clear suggestions.
onSuggestionsClearRequested = () => {
this.setState({
suggestions: []
});
};
render() {
const { value, suggestions } = this.state;
// Autosuggest will pass through all these props to the input.
const inputProps = {
placeholder: value,
value,
onChange: this.onChange
};
// Finally, render it!
return (
<div>
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
getSuggestionValue={this.getSuggestionValue}
renderSuggestion={renderSuggestion}
inputProps={inputProps}
/>
<div className="sweet-loading">
<ClockLoader
css={override}
size={50}
color={"#123abc"}
loading={this.state.loading}
/>
</div>
</div>
);
}
}
The problem is you only call the fetchRecord when component AutoProjects did mount. That's why whenever you added a new project, the list didn't update. It's only updated when you close the form and open it again ( AutoProjects component mount again)
For this case I think you should lift the logic of fetchProjects to parent component and past the value to AutoProjects. Whenever you add new project you need to call the api again to get a new list.

Dropdown select component state one step behind

I have no clue why the selected dropdown value is one step behind in the URL search params string. My url is like this: http://localhost/?dropdownsel=. Below is my code:
//App.js
//update params value
function setParams({ dropdownsel }) {
const searchParams = new URLSearchParams();
searchParams.set("dropdownsel", dropdownsel);
return searchParams.toString();
}
class App extends Component {
state = {
dropdownsel: ""
};
//update url params
updateURL = () => {
const url = setParams({
dropdownsel: this.state.dropdownsel
});
//do not forget the "?" !
this.props.history.push(`?${url}`);
};
onDropdownChange = dropdownsel => {
this.setState({ dropdwonsel: dropdownsel });
this.updateURL();
};
render() {
return (
<Dropdownsel
onChange={this.onDropdownselChange}
value={this.state.dropdownsel}
/>
);
}
}
Below is dropdownsel component code:
//Dropdownsel.js
const attrData = [{ id: 1, value: AA }, { id: 2, value: BB }];
class Dropdownsel extends Component {
onDropdownChange = event => {
this.props.onChange(event.target.value);
};
render() {
return (
<div>
<select value={this.props.value} onChange={this.onDropdownChange}>
<option value="">Select</option>
{attrData.map(item => (
<option key={item.id} value={item.value}>
{" "}
{item.name}
</option>
))}
</select>
</div>
);
}
}
export default Dropdownsel;
Thanks for formatting my code. I don't know how to do it every time when I post question. I figured it out myself. I need to make a call back function for updateURL() because the setState() is not executed immediately. so my code should be revised like below:
onDropdownChange = (dropdownsel) => {
this.setState({ dropdwonsel:dropdownsel }, ()=>{this.updateURL();
});
};
The problem occurs because this.setState is asynchronous (like a Promise or setTimeout are)
So there are two workarounds for your specific case.
Workaround #1 - using a callback
Use the callback option of this.setState.
When you take a look at the declaration of setState, it accepts an "optional" callback method, which is called when the state has been updated.
setState(updater[, callback])
What it means is that, within the callback, you have an access to the updated state, which was called asynchronously behind the scene by React.
So if you call this.updateURL() within the callback, this.state.dropdownsel value will be the one you are expecting.
Instead of,
this.setState({ dropdwonsel: dropdownsel });
this.updateURL();
Call this.updateURL in the callback.
// Note: '{ dropdwonsel }' is equal to '{ dropdwonsel: dropdwonsel }'
// If your value is same as the state, you can simplify this way
this.setState({ dropdwonsel }, () => {
this.updateURL()
});
Workaround #2 - passing the new value directly
You can also pass the new value directly as an argument of this.updateURL() (which might make testing easier as it makes you method depend on a value, which you can fake).
this.setState({ dropdwonsel });
this.updateURL(dropdownsel );
Now your this.updateURL doesn't depend on the this.state.dropdownsel, thus you can can use the arg to push the history.
//update url params
updateURL = dropdownsel => {
const url = setParams({
dropdownsel
});
//do not forget the "?" !
this.props.history.push(`?${url}`);
};

React Redux - select all checkbox

I have been searching on Google all day to try and find a way to solve my issue.
I've created a "product selection page" and I'm trying to add a "select all" checkbox that will select any number of products that are displayed (this will vary depending on the customer).
It's coming along and I've got all the checkboxes working but I can't get "select all" to work. Admittedly I'm using some in-house libraries and I think that's what's giving me trouble as I'm unable to find examples that look like what I've done so far.
OK, so the code to create my checkboxGroup is here:
let productSelectionList = (
<FormGroup className="productInfo">
<Field
component={CheckboxGroup}
name="checkboxField"
vertical={true}
choices={this.createProductList()}
onChange={this.handleCheckboxClick}
helpText="Select all that apply."
label="Which accounts should use this new mailing address?"
/>
</FormGroup>
);
As you can see, my choices will be created in the createProductList method. That looks like this:
createProductList() {
const { products } = this.props;
const selectAllCheckbox = <b>Select All Accounts</b>;
let productList = [];
productList.push({ label: selectAllCheckbox, value: "selectAll" });
if (products && products.length > 0) {
products.forEach((product, idx) => {
productList.push({
label: product.productDescription,
value: product.displayArrangementId
});
});
}
return productList;
}
Also note that here I've also created the "Select All Accounts" entry and then pushed it onto the list with a value of "selectAll". The actual products are then pushed on, each having a label and a value (although only the label is displayed. The end result looks like this:
Select Products checkboxes
I've managed to isolate the "select all" checkbox with this function:
handleCheckboxClick(event) {
// var items = this.state.items.slice();
if (event.selectAll) {
this.setState({
'isSelectAllClicked': true
});
} else {
this.setState({
'isSelectAllClicked': false
});
}
}
I also created this componentDidUpdate function:
componentDidUpdate(prevProps, prevState) {
if (this.state.isSelectAllClicked !== prevState.isSelectAllClicked && this.state.isSelectAllClicked){
console.log("if this ", this.state.isSelectAllClicked);
console.log("if this ", this.props);
} else if (this.state.isSelectAllClicked !== prevState.isSelectAllClicked && !this.state.isSelectAllClicked){
console.log("else this ", this.state.isSelectAllClicked);
console.log("else this ", this.props);
}
}
So in the console, I'm able to see that when the "select all" checkbox is clicked, I do get a "True" flag, and unclicking it I get a "False". But now how can I select the remaining boxes (I will admit that I am EXTREMELY new to React/Redux and that I don't have any previous checkboxes experience).
In Chrome, I'm able to see my this.props as shown here..
this.props
You can see that this.props.productList.values.checkboxField shows the values of true for the "select all" checkbox as well as for four of the products. But that's because I manually checked off those four products for this test member that has 14 products. How can I get "check all" to select all 14 products?
Did I go about this the wrong way? (please tell me that this is still doable) :(
My guess is your single product checkboxes are bound to some data you have in state, whether local or redux. The checkbox input type has a checked prop which accepts a boolean value which will determine if the checkbox is checked or not.
The idea would be to set all items checked prop (whatever you are actually using for that value) to true upon clicking the select all checkbox. Here is example code you can try and run.
import React, { Component } from 'react';
import './App.css';
class App extends Component {
state = {
items: [
{
label: "first",
checked: false,
},
{
label: "last",
checked: false,
}
],
selectAll: false,
}
renderCheckbooks = (item) => {
return (
<div key={item.label}>
<span>{item.label}</span>
<input type="checkbox" checked={item.checked} />
</div>
);
}
selectAll = (e) => {
if (this.state.selectAll) {
this.setState({ selectAll: false }, () => {
let items = [...this.state.items];
items = items.map(item => {
return {
...item,
checked: false,
}
})
this.setState({ items })
});
} else {
this.setState({ selectAll: true }, () => {
let items = [...this.state.items];
items = items.map(item => {
return {
...item,
checked: true,
}
})
this.setState({ items })
});
}
}
render() {
return (
<div className="App">
{this.state.items.map(this.renderCheckbooks)}
<span>Select all</span>
<input onClick={this.selectAll} type="checkbox" checked={this.state.selectAll} />
</div>
);
}
}
export default App;
I have items in state. Each item has a checked prop which I pass to the checkbox getting rendered for that item. If the prop is true, the checkbox will be checked otherwise it wont be. When I click on select all, I map thru my items to make each one checked so that the checkbox gets checked.
Here is a link to a codesandbox where you can see this in action and mess with the code.
There is a package grouped-checkboxes which can solve this problem.
In your case you could map over your products like this:
import React from 'react';
import {
CheckboxGroup,
AllCheckerCheckbox,
Checkbox
} from "#createnl/grouped-checkboxes";
const App = (props) => {
const { products } = props
return (
<CheckboxGroup onChange={console.log}>
<label>
<AllCheckerCheckbox />
Select all accounts
</label>
{products.map(product => (
<label>
<Checkbox id={product.value} />
{product.label}
</label>
))}
</CheckboxGroup>
)
}
More examples see https://codesandbox.io/s/grouped-checkboxes-v5sww

Reactjs - Controlling Multiple Checkboxes

Im building a CheckAllBoxes component in Reactjs. I have a list of items
fruits = {orange, apple, grape}
A general <SelectBox /> component to display and toggle the HTML checkbox
I need to build a <Fruits /> component to list all the fruits and each of item has its own <SelectBox />
Then I need to build a <SelectAll /> component which has a <SelectBox /> and when it is checked, it will toggle all the <CheckBox /> of <Fruits />
If any fruit is unchecked again, then the <SelectAll /> should be unchecked too.
The result should look something like this:
How can I get the <SelectAll /> to control other checkboxes ?
Here is the quick example on how you could do it:
import React, { Component } from 'react';
export default class SelectBox extends Component {
constructor() {
super();
this.handleClick = this.handleClick.bind(this);
this.state = {
allChecked: false,
checkedCount: 0,
options: [
{ value: 'selectAll', text: 'Select All' },
{ value: 'orange', text: 'Orange' },
{ value: 'apple', text: 'Apple' },
{ value: 'grape', text: 'Grape' }
]
};
}
handleClick(e) {
let clickedValue = e.target.value;
if (clickedValue === 'selectAll' && this.refs.selectAll.getDOMNode().checked) {
for (let i = 1; i < this.state.options.length; i++) {
let value = this.state.options[i].value;
this.refs[value].getDOMNode().checked = true;
}
this.setState({
checkedCount: this.state.options.length - 1
});
} else if (clickedValue === 'selectAll' && !this.refs.selectAll.getDOMNode().checked) {
for (let i = 1; i < this.state.options.length; i++) {
let value = this.state.options[i].value;
this.refs[value].getDOMNode().checked = false;
}
this.setState({
checkedCount: 0
});
}
if (clickedValue !== 'selectAll' && this.refs[clickedValue].getDOMNode().checked) {
this.setState({
checkedCount: this.state.checkedCount + 1
});
} else if (clickedValue !== 'selectAll' && !this.refs[clickedValue].getDOMNode().checked) {
this.setState({
checkedCount: this.state.checkedCount - 1
});
}
}
render() {
console.log('Selected boxes: ', this.state.checkedCount);
const options = this.state.options.map(option => {
return (
<input onClick={this.handleClick} type='checkbox' name={option.value} key={option.value}
value={option.value} ref={option.value} > {option.text} </input>
);
});
return (
<div className='SelectBox'>
<form>
{options}
</form>
</div>
);
}
}
I'm sorry for the ES6 example. Will add ES5 example when I find more time, but I think you can get the idea on how to do it.
Also you definitely want to break this down into 2 components. Then you would just pass your options as props to the Child component.
I would recommend reading Communication between Components
Now in your example you have a communication between two components that don't have a parent-child relationship. In these case you could use a global event system. Flux works great with React.
In your example I would make FruitStore with the component Fruit listening to store. The FruitStore contains a list with all fruits and if they are selected or not. Fruit will saves it's content with setState().
Fruit passes to it's children their status per props. example: <CheckBox checked={this.state.fruit.checked} name={this.state.fruit.name}/>
Checkbox should fire when clicked a FruitAction.checkCheckbox(fruitName).
The FruitStore will then update the Fruit component and so on.
It take some time to get into this unidirectional architecture, but it's worth learning it. Try starting with the Flux Todo List Tutorial.

Resources