React - set state on unmounted component - javascript

I tried a lot of ways to make it works but it didn't. Probably I don't understand the React idea or I missed something. My child via callback try to modify the global/parent state and then I got the well known error.
index.js:1452 Warning: Can't call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
I try to use variable _isMounted so before setState I did:
if (this.isMounted()) {
this.setState({...});
}
Unfortunately it doesn't work.
The problem is when the function this.handleChangeAlgorithms is invoked from the child.
My code
class ConfigurationForm extends Component {
constructor(props) {
super(props);
this.state = {
choosenAlgorithm: null,
htmlType: null,
customHtml: null,
isOpenModal: false,
generatedHTMlConfig: {
headers: 1,
paragraphs: 1,
buttons: 1,
links: 1,
inputs: 1,
images: 1,
},
};
}
handleChangeAlgorithms = event => {
let algorithmName = event.target.value;
let newChoosenAlgorithm = this.props.algorithms.filter(
e => e.value === algorithmName,
)[0];
// this.setState({choosenAlgorithm: newChoosenAlgorithm});
this.props.callbackConfigurationForm(algorithmName);
};
handleChangeHtmlType(event) {
let newHtmlType = event.target.value;
console.log(newHtmlType);
if (newHtmlType === "default") {
this.setState({ isOpenModal: true });
} else {
this.setState({ htmlType: newHtmlType });
}
}
handleUserFile(event) {
let file = event.target.files[0];
this.setState({ customHtml: file });
this.props.callbackConfigurationFormPreview(file);
}
handleSubmit(event) {
event.preventDefault();
let htmlContent = null;
let htmltypeKey = Object.keys(HtmlType).find(
key => HtmlType[key] === HtmlType.default,
);
console.log(htmltypeKey);
// if (this.state.htmlType === 'default'){
// htmlContent = this.loadTemplate();
htmlContent = generateHTML(this.state.generatedHTMlConfig);
console.log(htmlContent);
// } else {
// htmlContent=this.state.customHtml;
// }
let config = {
algorithm: this.state.choosenAlgorithm,
html: htmlContent,
};
console.log(config);
this.props.callbackConfigurationForm(config);
}
loadTemplate() {
let loadedTemplate = null;
let file = "html-templates/example.html";
let request = new XMLHttpRequest();
request.open("GET", file, false);
request.send(null);
if (request.status === 200) {
loadedTemplate = request.responseText;
}
return loadedTemplate;
}
renderHtmlTypesList = () => {
let list = [];
for (const htmlKey of Object.keys(HtmlType)) {
list.push(
<option key={htmlKey} value={htmlKey}>
{HtmlType[htmlKey]}
</option>,
);
}
return list;
};
generateHTMLFromPopup() {
this.setState({ isOpenModal: false });
}
changeHeaders(event, type) {
console.log(type);
console.log(event.target.value);
let prevConfig = { ...this.state.generatedHTMlConfig };
switch (type) {
case "Headers":
prevConfig.headers = event.target.value;
this.setState({ generatedHTMlConfig: prevConfig });
break;
case "Paragraphs":
prevConfig.paragraphs = event.target.value;
this.setState({ generatedHTMlConfig: prevConfig });
break;
case "Buttons":
prevConfig.buttons = event.target.value;
this.setState({ generatedHTMlConfig: prevConfig });
break;
case "Links":
prevConfig.links = event.target.value;
this.setState({ generatedHTMlConfig: prevConfig });
break;
case "Inputs":
prevConfig.inputs = event.target.value;
this.setState({ generatedHTMlConfig: prevConfig });
break;
case "Images":
prevConfig.images = event.target.value;
this.setState({ generatedHTMlConfig: prevConfig });
break;
}
}
render() {
return (
<Row>
{/* todo 12.11.2018 extract to another component! */}
<Modal
open={this.state.isOpenModal}
header="Generate Html"
actions={
<div>
<Button
modal="close"
waves="light"
className="red lighten-2"
>
Cancel
</Button>
<Button
modal="close"
waves="light"
className="blue"
onClick={this.generateHTMLFromPopup.bind(this)}
>
<Icon left>build</Icon>Generate
</Button>
</div>
}
>
<p>Choose HTML elements for generated HTML.</p>
<Input
type="number"
label="Headers"
value={this.state.generatedHTMlConfig.headers}
onChange={e => this.changeHeaders(e, "Headers")}
/>
<Input
type="number"
label="Paragraphs"
value={this.state.generatedHTMlConfig.paragraphs}
onChange={e => this.changeHeaders(e, "Paragraphs")}
/>
<Input
type="number"
label="Buttons"
value={this.state.generatedHTMlConfig.buttons}
onChange={e => this.changeHeaders(e, "Buttons")}
/>
<Input
type="number"
label="Links"
value={this.state.generatedHTMlConfig.links}
onChange={e => this.changeHeaders(e, "Links")}
/>
<Input
type="number"
label="Inputs"
value={this.state.generatedHTMlConfig.inputs}
onChange={e => this.changeHeaders(e, "Inputs")}
/>
<Input
type="number"
label="Images"
value={this.state.generatedHTMlConfig.images}
onChange={e => this.changeHeaders(e, "Images")}
/>
</Modal>
<h2>Algorithm</h2>
<Row>
<Input
s={12}
type="select"
label="Select algorithm"
defaultValue=""
onChange={this.handleChangeAlgorithms}
>
<option value="" disabled>
Choose an algorithm
</option>
{this.props.algorithms.map(item => (
<option key={item.value} value={item.value}>
{item.name}
</option>
))}
</Input>
</Row>
{this.state.choosenAlgorithm ? (
<Collapsible popout>
<CollapsibleItem header="Details" icon="notes">
<ol>
{this.state.choosenAlgorithm.details.steps.map(
(step, index) => (
<li key={index}>{step}</li>
),
)}
</ol>
</CollapsibleItem>
</Collapsible>
) : null}
<h2>HTML to obfuscate</h2>
<Row>
<Input
s={12}
type="select"
label="HTML type"
defaultValue=""
onChange={this.handleChangeHtmlType}
>
<option value="" disabled>
Choose HTML
</option>
{this.renderHtmlTypesList()}
</Input>
</Row>
{this.state.htmlType === "custom" ? (
<Row>
<Input
type="file"
label="File"
onChange={this.handleUserFile}
/>
</Row>
) : null}
<div className="center-align">
<Button type={"button"} onClick={this.handleSubmit}>
Process<Icon left>autorenew</Icon>
</Button>
</div>
</Row>
);
}
}
class App extends Component {
_isMounted = false;
constructor(props) {
super(props);
this.state = {
isMounted: false,
//export to enum
algorithms: [
{
name: "Html to Javascript",
value: "1",
details: {
steps: [
"Split HTML file line by line.",
"Replace white characters.",
"Create function which add lines to document using document.write function.",
],
},
},
{
name: "Html to Unicode characters",
value: "2",
details: {
steps: [
"Create js function encoding characters to Unicode characters.",
"Create decoding function.",
"Add output from decoding function to HTML.",
],
},
},
{
name: "Html to escape characters",
value: "3",
details: {
steps: [
"Change endcoding using escape javascript function.",
"Decode using unescape javascript function.",
"Add element to HTML.",
],
},
},
{
name:
"Using own encoding and decoding function. [NOT IMPLEMENTED YET]",
value: "4",
details: {
steps: [
"Encode HTML using own function.",
"Save encoded content into js variable.",
"Decode using own decoding function.",
"Add element to HTML document.",
],
},
},
{
name: "Combine above methods [NOT IMPLEMENTED YET]",
value: "5",
details: {
steps: ["To be done..."],
},
},
],
previewHtml: null,
obfuscationConfig: null,
activeTab: 1,
};
}
componentDidMount() {
this._isMounted = true;
}
componentWillUnmount() {
this._isMounted = false;
}
processDataFromConfigurationForm = config => {
console.info(config);
if (this._isMounted) {
this.setState({
test: Date.now(),
// obfuscationConfig: config,
// doObfuscation: true,
// activeTab:3,
// previewHtml: config.html
});
}
};
render() {
return (
<div>
<header className="App">
<h1>HTML obfuscator</h1>
</header>
<Tabs>
<Tab
title="Configuration"
active={this.state.activeTab === 1}
>
<ConfigurationForm
algorithms={this.state.algorithms}
config={this.state.obfuscationConfig}
callbackConfigurationForm={
this.processDataFromConfigurationForm
}
/>
</Tab>
<Tab
title="HTML Preview"
active={this.state.activeTab === 2}
disabled={!this.state.previewHtml}
>
<HTMLPreview previewHtml={this.state.previewHtml} />
</Tab>
<Tab
title="Result"
active={this.state.activeTab === 3}
disabled={!this.state.obfuscationConfig}
>
{this.state.obfuscationConfig ? (
<ObfuscationOutput
config={this.state.obfuscationConfig}
/>
) : null}
</Tab>
</Tabs>
</div>
);
}
}
export default App;
The error

Try to add a constructor to your parent class, indeed, you can initialize the component state before the first modification:
class App extends Component {
constructor(props) {
super(props);
this.state = {
test1: Date.now()
};
}
// here you can update the state as you want, for example
foobar(event) {
this.setState({test1: event.target.value });
}
}
Hope it can help you...

Setting the state inside of the lifecycle method componentDidMount() should do the trick.
Oh I didn't see that someone already suggested that, glad you got it working

Related

TypeError: Cannot read property 'value' of undefined - React-typeahead

I'm using react-bootstrap-typeahead component to predict user input into text box, but I am having trouble setting the state so user can click on a selection in the dropdown menu.here
So far code I have is here. The function handleAddtask adds task to tasklist when typed in, but I cannot get it to assign to typeahead dropdown. (I am still learning react so any resources on this would also be appreciated).
import React, { Component } from "react";
import PropTypes from "prop-types";
import { Typeahead } from "react-bootstrap-typeahead";
class AddWatchlistForm extends Component {
constructor(props) {
super(props);
this.state = {
taskName: ""
};
this.handleAddTask = this.handleAddTask.bind(this);
}
static propTypes = {
newTask: PropTypes.func.isRequired
};
render() {
return (
<div className="row">
<div className="col-md-6 col-md-offset-3">
<div style={{ margin: "20px" }}>
<div className="row">
<div className="col-md-6">
<Typeahead
placeholder=""
onChange={e => this.updateTaskName(e)}
onClick={(e => this.updateTask(e), this.handleAddTask)}
value={this.state.taskName}
onKeyPress={e => this.checkEnterKey(e)}
labelKey={option =>
`${option.ticker} ${option.security_type}`
}
options={[
{
"": 0,
ticker: "A",
security_type: "Stock"
},
{
"": 1,
ticker: "AA",
security_type: "Stock"
},
{
"": 2,
ticker: "AAA",
security_type: "Stock"
},
{
"": 3,
ticker: "AAAU",
security_type: "Stock"
},
{
"": 4,
ticker: "AACG",
security_type: "Stock"
}
]}
/>
</div>
<div className="col-md-4">
<button
type="button"
className="btn btn-primary"
onClick={this.handleAddTask}
>
{" "}
Add New...{" "}
</button>
</div>
</div>
</div>
</div>
</div>
);
}
checkEnterKey(e) {
var keyCode = e.which || e.keyCode;
if (keyCode == 13) {
if (this.state.taskName.trim() !== "") {
this.props.newTask(this.state.taskName);
}
}
}
updateTaskName(e) {
this.setState({ taskName: e.target.value });
}
updateTask(e) {
this.setState({ taskName: e.target.options.ticker });
console.log(this.state);
}
handleAddTask(e) {
let name = e.target.value;
if (this.state.taskName.trim() !== "")
this.props.newTask(this.state.taskName);
}
}
export default AddWatchlistForm;
Suggestion, a better way of doing things using Arrow Functions preserving the context of this:
Remove the following...
constructor(props) {
super(props);
this.state = {
taskName: ""
};
this.handleAddTask = this.handleAddTask.bind(this);
}
handleAddTask(e) {
let name = e.target.value;
if (this.state.taskName.trim() !== "")
this.props.newTask(this.state.taskName);
}
And add this instead:
state = {
taskName: ""
};
handleAddTask = e => {
let name = e.target.value;
if (this.state.taskName.trim() !== "")
this.props.newTask(this.state.taskName);
}
And the reason why you get the TypeError due to undefined is the same reason. I just tried by replacing all the normal functions to arrow functions and they all worked fine now. Else you should be binding to this for every single class property function, which is a tedious double process and performance intensive.
Also, in the handleAddTask function, please update:
handleAddTask = e => {
let name = e.target.value;
if (this.state.taskName.trim() !== "")
this.props.newTask(name);
};
Solution
The problem is with the e that gets passed to. Change your updateTaskName function to:
updateTaskName = e => {
this.setState({ taskName: e.length > 0 ? e[0].security_type : "" });
};
This is because, e is:
e = {
"": 2,
ticker: "AAA",
security_type: "Stock"
};
Working Demo: https://codesandbox.io/s/confident-moore-wsq5p

How to make search bar render changes after "search" button is clicked?

im currently trying to figure out how to solve this problem:
As you can see, the search bar value is initialized to "TSLA", however, instead of replacing the TSLA data widget with an AAPL data widget, below the TSLA widget it shows a new widget for "A", "AA", "AAP" and finally "AAPL", rendering a new widget as I type each letter of the ticker into the search bar.
Here is the code for the search bar, with the state variable "value" being what I pass to my widgets:
class OneStockData extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 'TSLA',
clicked: false
}
this.handleChange = this.handleChange.bind(this)
}
toggleBtnHandler = () => {
return this.setState({
clicked:!this.state.clicked
})
}
handleChange (event) {
this.setState({value: event.target.value })
}
render() {
const styles = ['button'];
let text = 'Search';
return (
<div>
<Container fluid>
<Row>
<Card>
<CardTitle className="text-uppercase text-muted h6 mb-0">Enter Stock Ticker: </CardTitle>
<input type="text" value={this.state.value} onChange={this.handleChange} />
<div>
<button className="button" onClick={this.toggleBtnHandler}>{text}</button>
</div>
</Card>
<Card>
<SSIWidget value = {this.state.value}/>
</Card>
</Row >
And here is the code in the SSIWidget:
class SSIWidget extends React.Component {
constructor(props){
super(props);
}
AddWidget = () => {
const script = document.createElement('script');
script.src ="https://s3.tradingview.com/external-embedding/embed-widget-symbol-info.js";
script.async = true;
script.innerHTML = JSON.stringify(
{
"symbol": this.props.value,
"width": 1000,
"locale": "en",
"colorTheme": "light",
"isTransparent": false
}
)
document.getElementById("myContainer6").appendChild(script);
}
componentDidMount() {
this.AddWidget();
}
componentDidUpdate(prevProps) {
if(prevProps.value !== this.props.value) {
this.AddWidget();
}
}
render() {
return(
<div id="myContainer6">
<div className="tradingview-widget-container">
<div className="tradingview-widget-container__widget">
</div>
</div>
</div>
);
}
}
There are 2 problems going on:
It renders an new "SSIWidget" as each letter of the ticker is typed, and stacks them below each other. This is a big no-no, I want the widget where TSLA data is being shown to be the ONLY SSIWidget, and the data replaced by each new ticker after I click "Search"
As I clear the search bar, I would want the widget to show the value of "TSLA" as it was initialized to be.
I have tried different search bars and a few resourced on "onClick", but haven't yet found a solution to my issue. Can someone here point me in the right direction?
Maintain another state say searchedValue and update it upon search button click.
Refactored code
class OneStockData extends React.Component {
constructor(props) {
super(props);
this.state = {
value: "TSLA",
searchedValue: "TSLA", //<------- here
clicked: false,
};
this.handleChange = this.handleChange.bind(this);
}
toggleBtnHandler = () => {
return this.setState({
clicked: !this.state.clicked,
searchedValue: this.state.value === "" ? "TSLA" : this.state.value, //<------- here
});
};
handleChange(event) {
this.setState({ value: event.target.value });
}
render() {
const styles = ["button"];
let text = "Search";
return (
<div>
<Container fluid>
<Row>
<Card>
<CardTitle className="text-uppercase text-muted h6 mb-0">
Enter Stock Ticker:{" "}
</CardTitle>
<input
type="text"
value={this.state.value}
onChange={this.handleChange}
/>
<div>
<button className="button" onClick={this.toggleBtnHandler}>
{text}
</button>
</div>
</Card>
<Card>
<SSIWidget value={this.state.searchedValue} /> //<------- here
</Card>
</Row>
</Container>
</div>
);
}
}
Edit: Follow up qn based on comment.
You are appending the child. Before appending you need to clear children.
AddWidget = () => {
const script = document.createElement('script');
script.src ="https://s3.tradingview.com/external-embedding/embed-widget-symbol-info.js";
script.async = true;
script.innerHTML = JSON.stringify(
{
"symbol": this.props.value,
"width": 1000,
"locale": "en",
"colorTheme": "light",
"isTransparent": false
}
)
document.getElementById("myContainer6").innerHTML = ''; //<----here
document.getElementById("myContainer6").appendChild(script);
}

React, a modal data only renders properly on second opening

I have a react modal, which has a group of checkboxes. Those checkboxes "checked" value comes from an API call. But the problem is, that they only get updated on the second click.
I have tried to set a condition to wait for the fetch of the data before loading the group of checkboxes. So mi app looks like this
In my parent component where I load my data
I have set a permUpdated state, which equals to false, but once the data is loaded, i set it to true
getPermissionsValue() {
API.get('/user/' + this.state.rowId + '/permission')
.then(response => {
this.setState({
permissionValue: response.data,
permUpdated: true
}, () => {
// console.log(this.state.permissionValue)
});
})
}
That state is passed as a prop to the child component which is the modal
<EditUserModal permUpdated={this.state.permUpdated} ....>
And in the childs render, I used to have this, and it worked properly but with doesnt get updated
<div className="checkboxesIn">
{permissionsPrint}
</div>
{permissionsPrint} are the checkboxes that I want to render. So ive set it like:
<div className="checkboxesIn">
{this.props.permUpdated ? {permissionsPrint} : null}
</div>
But that way my app crashes.
×
Error: Objects are not valid as a React child (found: object with keys {permissionsPrint}). If you meant to render a collection of children, use an array instead.
This is how permissionPrint looks
(5) [{…}, {…}, {…}, {…}, {…}]
0: {$$typeof: Symbol(react.element), type: "div", key: null, ref: null, props: {…}, …}
1: {$$typeof: Symbol(react.element), type: "div", key: null, ref: null, props: {…}, …}
2: {$$typeof: Symbol(react.element), type: "div", key: null, ref: null, props: {…}, …}
3: {$$typeof: Symbol(react.element), type: "div", key: null, ref: null, props: {…}, …}
4: {$$typeof: Symbol(react.element), type: "div", key: null, ref: null, props: {…}, …}
length: 5
__proto__: Array(0)
This is how i create it
var permissionsPrint = [];
var valuesData = []
this.props.permissionValue.map(e => valuesData = Object.entries(e))
console.log(valuesData)
for (let i = 1; i < valuesData.length; i++) {
//console.log(valuesData[i][0]) //name of the permission
//console.log(valuesData[i][1]) //true false
permissionsPrint.push(<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id={valuesData[i][0]} value={valuesData[i][1]} defaultChecked={valuesData[i][1]} onChange={this.props.handleChangePermissions} />
<label class="form-check-label" for={"inlineCheckbox" + i} style={{ textTransform: "capitalize" }}>{valuesData[i][0]}</label>
</div>)
EDIT: Whole code of the modal
class EditUserModal extends React.Component {
constructor(props) {
super(props);
this.state = {
userId: ""
};
console.log("PROOPPSS");
console.log(props);
console.log(this.props.permUpdated);
}
componentDidUpdate() {}
componentDidMount() {
this.setState({
email: this.props.email
});
}
render() {
var permissionsPrint = [];
var valuesData = [];
this.props.permissionValue.map(e => (valuesData = Object.entries(e)));
console.log(valuesData);
for (let i = 1; i < valuesData.length; i++) {
//console.log(valuesData[i][0]) //name of the permission
//console.log(valuesData[i][1]) //true false
permissionsPrint.push(
<div class="form-check form-check-inline">
<input
class="form-check-input"
type="checkbox"
id={valuesData[i][0]}
value={valuesData[i][1]}
defaultChecked={valuesData[i][1]}
onChange={this.props.handleChangePermissions}
/>
<label
class="form-check-label"
for={"inlineCheckbox" + i}
style={{ textTransform: "capitalize" }}
>
{valuesData[i][0]}
</label>
</div>
);
}
console.log("this is THE PROP OF PERMUPDATED");
console.log(this.props.permUpdated);
if (!this.props.show) {
return null;
}
return (
<div className="modalBg">
<div className="flex-container">
<div id="open-modal" className="modal-window">
<form onSubmit={this.props.handleSubmit}>
<div>
{/* <p>{this.props.userId}</p> */}
<FontAwesomeIcon
className="closeIcon"
onClick={this.props.close}
icon={faTimesCircle}
/>
<br></br>
{this.props.userDataUpdated ? (
<Alert
className="alertEditUser"
variant="success"
dismissible
onClose={this.props.handleDismiss}
>
User Data Updated
</Alert>
) : null}
{this.props.passwordMatchFailure ? (
<Alert
className="alertEditUser"
variant="danger"
dismissible
onClose={this.props.handleDismiss}
>
Passwords do not match
</Alert>
) : null}
{this.props.emailCantBeBlank ? (
<Alert
className="alertEditUser"
variant="danger"
dismissible
onClose={this.props.handleDismiss}
>
Email Cant Be Blank
</Alert>
) : null}
<div class="form-group emailgroup">
<label for="exampleFormControlInput1">
Change Email Address
</label>
<input
type="email"
class="form-control"
placeholder="name#example.com"
value={this.props.email}
onChange={this.props.handleChangeMail}
/>
</div>
{/* <div class="form-group emailgroup">
<label for="exampleFormControlInput1">Old Password</label>
<input type="password" class="form-control" />
</div> */}
<div class="form-group emailgroup">
<label for="exampleFormControlInput1">New Password</label>
<input
type="password"
class="form-control"
placeholder="Input new password"
onChange={this.props.handleChangePass}
/>
<input
type="password"
class="form-control"
placeholder="Confirm new password"
onChange={this.props.handleChangePass2}
style={{ marginTop: "5px" }}
/>
</div>
<div class="form-group emailgroup">
<label for="exampleFormControlInput1">User Permissions</label>
<br></br>
<div className="checkboxes">
<div className="checkboxesIn">
{/* {permissionsPrint} */}
{this.props.permUpdated ? permissionsPrint : null}
</div>
</div>
</div>
<div class="text-center">
<button class="btn btn-primary " type="submit">
Update
</button>
</div>
</div>
</form>
</div>
</div>
</div>
);
}
}
export default EditUserModal;
Whole code of the admin
import React, { Component } from "react";
import API from "../services/axiosObject.js";
import "./css/Admin.css";
import Reactable from "reactable";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faEdit } from "#fortawesome/free-regular-svg-icons";
import EditUserModal from "./EditUserModal";
export default class Admin extends Component {
constructor(props) {
super(props);
this.state = {
retracted: false,
userList: [],
showModal: false,
rowId: "",
selectedMail: "",
password: "",
password2: "",
userDataUpdated: false,
passwordMatchFailure: false,
emailCantBeBlank: false,
permissionValue: [],
permUpdated: false
};
this.getUserList = this.getUserList.bind(this);
this.showModal = this.showModal.bind(this);
this.closeModal = this.closeModal.bind(this);
this.handleChangeMail = this.handleChangeMail.bind(this);
this.handleChangePass = this.handleChangePass.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleDismiss = this.handleDismiss.bind(this);
this.handleChangePermissions = this.handleChangePermissions.bind(this);
this.getPermissionsValue = this.getPermissionsValue.bind(this);
}
componentDidUpdate() {}
componentDidMount() {
this.getUserList();
}
handleChangeMail = evt => {
this.setState({
selectedMail: evt.target.value
});
};
handleChangePass = evt => {
this.setState({
password: evt.target.value
});
};
handleChangePass2 = evt => {
this.setState({
password2: evt.target.value
});
};
handleChangePermissions = evt => {
console.log(this.state.permissionValue);
console.log("id: " + evt.target.id);
var idu = evt.target.id;
var checked = evt.target.checked;
console.log("checked: " + evt.target.checked);
var data = this.state.permissionValue[0];
console.log("data1");
console.log(data);
data[idu] = checked;
console.log("data2");
console.log(data);
this.setState({
permissionValue: [data]
});
};
getUserList() {
API.get("/userlist").then(response => {
this.setState(
{
userList: response.data
},
() => {
console.log(this.state.userList);
}
);
});
}
handleSubmit(event) {
event.preventDefault();
//console.log("updating")
//console.log("email: " + this.state.selectedMail)
var email = this.state.selectedMail;
var password = this.state.password;
var password2 = this.state.password2;
var permissionValue = this.state.permissionValue;
console.log("....");
console.log("password: " + this.state.password);
console.log("password2: " + this.state.password2);
console.log("....");
console.log("userId: " + this.state.rowId);
console.log("email: " + email);
if (password2 != password || email == "") {
console.log("P2: " + password2);
console.log("P1: " + password);
if (password2 != password) {
console.log("CONTRASEÑAS DISTINTAS");
this.setState({
passwordMatchFailure: true
});
} else {
this.setState({
emailCantBeBlank: true
});
}
} else {
console.log("ENTRA EN EL ELSE");
if (password == undefined || password2 == undefined) {
password = "";
password2 = "";
}
API.post("/user/update/" + this.state.rowId, {
email,
password,
permissionValue
}).then(response => {
console.log(permissionValue);
if (response.data == "user data updated") {
this.setState(
{
userDataUpdated: true
},
() => {
console.log(this.state.userDataUpdated);
}
);
}
});
}
}
handleDismiss() {
console.log("HANDLING DISMISSSSSSSSSSSSSSSSS");
this.setState({
userDataUpdated: false,
passwordMatchFailure: false,
emailCantBeBlank: false
});
}
showModal(rowId, rowEmail) {
this.setState(
{
showModal: true,
rowId: rowId,
selectedMail: rowEmail
},
() => {
this.getPermissionsValue();
}
);
}
closeModal() {
console.log("CLOOOOOOSSSSINNGGGGGGGGGGGGG");
this.setState(
{
showModal: false
},
() => {
// console.log("clicked closeModal")
// console.log(this.state.showModal)
}
);
}
getPermissionsValue() {
API.get("/user/" + this.state.rowId + "/permission").then(response => {
this.setState(
{
permissionValue: response.data,
permUpdated: true
},
() => {}
);
});
}
render() {
var users = this.state.userList;
const Table = Reactable.Table,
Td = Reactable.Td,
Tr = Reactable.Tr;
if (users.length === 0) {
return <p>loading</p>;
}
return (
<div class="maincontainer">
<div className="content-landing">
<button
class="btn btn-primary "
style={{ float: "right", marginRight: "20px" }}
onClick={() => this.props.history.push("/register")}
>
New user
</button>
<Table
className="table"
filterable={["Email"]}
itemsPerPage={8}
currentPage={0}
sortable={true}
>
{users.map(row => {
return (
<Tr className={row.className}>
<Td column="Email">{row.email}</Td>
<Td column="Edit">
<FontAwesomeIcon
className="editIcon"
onClick={() => this.showModal(row.id, row.email)}
icon={faEdit}
/>
</Td>
</Tr>
);
})}
</Table>
<EditUserModal
permUpdated={this.state.permUpdated}
permissionValue={this.state.permissionValue}
emailCantBeBlank={this.state.emailCantBeBlank}
userDataUpdated={this.state.userDataUpdated}
handleChangePermissions={this.handleChangePermissions}
passwordMatchFailure={this.state.passwordMatchFailure}
handleDismiss={this.handleDismiss}
show={this.state.showModal}
close={this.closeModal}
userId={this.state.rowId}
email={this.state.selectedMail}
handleChangePass={this.handleChangePass}
handleChangePass2={this.handleChangePass2}
handleChangeMail={this.handleChangeMail}
handleSubmit={this.handleSubmit}
/>
</div>
</div>
);
}
}
Here {this.props.permUpdated ? {permissionsPrint} : null} I don't think you need curly braces around permissionsPrint it should be :
{this.props.permUpdated ? permissionsPrint : null}
{permissionsPrint} this is the same as {permissionsPrint: permissionsPrint} which is an Object with same key and value names, where value is a variable of the same name as of key name.
Se issue was that when the modal was closed, permUpdated state was kept as true, so I had to set it to false on close
closeModal() {
console.log("CLOOOOOOSSSSINNGGGGGGGGGGGGG")
this.setState({
showModal: false,
permUpdated: false
}, () => {
// console.log("clicked closeModal")
// console.log(this.state.showModal)
});
}

Fetch data from API when form's search button clicked and show data on another page in React JS

I am developing a React JS web application where I have a form with four select fields (Make, Model, Min price and Max price) and a Search button. The data for search results will be fetched from API according to the selection of options. I want to show that data on another page in a card (page route path: /search) when user clicked on search button. I am using react router. The API url/end point is https://mysterious-journey-51969.herokuapp.com/api/search-vehicle/?q=mercedes&m=sprinter&pf=0&pt=100000 where "q" field matches Vehicle Make, "m" field matches Model, "pf" field matches Min Price, "pt" field matches Max Price. How I can do that?
Here is my Form component code:
import React, { Component } from 'react';
import { Form, FormGroup, Input } from 'reactstrap';
import { veh_data } from '../shared/vehicle_make_and_models';
const defaultValues = [
{ value: 0, text: 0, key: 1 },
{ value: 500, text: 500, key: 2 },
{ value: 1000, text: 1000, key: 3 },
{ value: 1500, text: 1500, key: 4 },
{ value: 2000, text: 2000, key: 5 },
{ value: 2000, text: 2000, key: 6 }
];
const MIN_TITLE = { selected: true, disabled: true, text: 'Min Price' };
const MAX_TITLE = { selected: true, disabled: true, text: 'Max Price' };
class ImgAndForm extends Component {
constructor(props) {
super(props);
this.handleSearch = this.handleSearch.bind(this);
this.keyToOption = this.keyToOption.bind(this);
this.renderOptions = this.renderOptions.bind(this);
this.handleModelChange = this.handleModelChange.bind(this);
this.state = {
minData: [MIN_TITLE, ...defaultValues],
maxData: [MAX_TITLE, ...defaultValues],
minValue: null,
maxValue: null,
modelSelected: null
};
}
renderOptions(data) {
return data.map(datum => {
// this allows us to indicate whether we are selecting or disabling
const selected = datum.selected || false;
const disabled = datum.disabled || false;
return (
<option key={datum.key} value={datum.value} selected={selected} disabled={disabled}>
{datum.text}
</option>
);
});
}
handleModelChange(event) {
console.log(event.target.value);
this.setState({ modelSelected: event.target.value });
}
handleSearch(event) {
alert("Search button clicked");
}
keyToOption(key) {
return key.split("-")
.map(word => word.slice(0, 1).toUpperCase() + word.slice(1))
.join(" ");
}
handleMinSelect = event => {
const value = event.target.value;
const newMaxValues = [];
defaultValues.forEach(datum => {
if (datum.value >= Number.parseInt(value, 10)) {
newMaxValues.push(datum);
}
});
this.setState({
maxData: [MAX_TITLE, ...newMaxValues],
minValue: value
});
};
handleMaxSelect = event => {
const value = event.target.value;
this.setState({ maxValue: value });
};
render() {
const vehicles = veh_data.reduce((acc, veh, i) => {
let make = Object.keys(veh)[0],
vehModels = veh[make];
return {
makes: [
...acc.makes,
<option key={make + i} value={make}>{this.keyToOption(make)}</option>
],
models: {
...acc.models,
[make]: vehModels.map((model, i) => {
return (
<option key={make + model + i} value={model}>
{this.keyToOption(model)}
</option>
);
})
}
};
}, { makes: [], models: [] });
const selectedModels =
this.state.modelSelected && this.state.modelSelected.length ? (
vehicles.models[this.state.modelSelected]
) : (
<option value="">Model (select make first)</option>
);
return (
<div>
<header className="headerbg d-flex">
<div className="container my-auto">
<div className="row">
<div className="offset-1 col-10 offset-lg-0 col-lg-4">
<div id="search-form-div" className="container">
<div className="row">
<div className="col-12 my-4">
<h3>Search</h3>
<Form onSubmit={this.handleSearch}>
<FormGroup>
<Input
onChange={e => this.handleModelChange(e)}
type="select"
name="q"
id="q"
>
<option value="">Make</option>
{vehicles.makes}
</Input>
</FormGroup>
<FormGroup>
<Input type="select" name="m" id="m">
{selectedModels}
</Input>
</FormGroup>
<FormGroup>
<Input type="select"
name="pf"
id="pf"
value={this.state.minValue}
onChange={this.handleMinSelect}>
{this.renderOptions(this.state.minData)}
</Input>
</FormGroup>
<FormGroup>
<Input
type="select"
name="pt"
id="pt"
value={this.state.maxValue}
onChange={this.handleMaxSelect}>
{this.renderOptions(this.state.maxData)}
</Input>
</FormGroup>
<FormGroup>
<Input type="submit" name="search" id="search" className="btn btn-primary" value="Search" />
</FormGroup>
</Form>
</div>
</div>
</div>
</div>
</div>
</div>
</header>
</div>
);
}
}
export default ImgAndForm;
Here is my Search result component code:
import React, { Component } from 'react';
import Smallheader from './SmallHeader';
import { Card, CardImg, CardTitle, CardSubtitle } from 'reactstrap';
class SearchResult extends Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
return (
<div>
<Smallheader />
<div className="my-5">
<div className="container text-center" id="contactContainer">
<div className="row">
<div className="col-lg-12 mx-auto">
<h2 className="text-center">Search Results</h2>
<hr className="my-4 thick-hr" />
</div>
</div>
<div className="row">
<div className="col-6 col-lg-3 mt-4">
<Card>
<a href="#">
<CardImg src="" className="img-fluid" />
<CardTitle>Title Here</CardTitle>
<CardSubtitle>Price Here</CardSubtitle>
</a>
</Card>
</div>
</div>
</div>
</div>
</div>
);
}
}
export default SearchResult;
Here is a working solution...
https://codesandbox.io/s/lrv2w3qxlq?moduleview=1
I've imported your SearchResults component and put it directly below your ImgAndForm, but you can move it anywhere in that render function.
For this specific situation you would need a way to render this on a new 'page' you would need a way to manage shared application state, like Redux or at least a container component as #MikeZinn mentioned, but to do that properly would require as significant amount of work to implement the routing and re-architect your entire program. (If you want I can show you a small hack to produce the same result without that for now, but I'd advise looking into a more permanent solution.)
Since the SearchResults component can be 'stateless' I removed the constructor function, but I left it as a class for now because this component will likely need state eventually.
I added the axios library to fetch the data from the API, but any other XHR module already used in your program will do.
NOTE: Since the specific API endpoints that your form is currently able to query are unavailable, I've hard coded the 'mercedes' example you provided, but the program will log both 'realQuery' and 'dummyQuery' so you see that it is producing the correct query structure for whenever you fix that.
Form Component
import React, { Component } from "react";
import { Form, FormGroup, Input } from "reactstrap";
// import { veh_data } from '../shared/vehicle_make_and_models';
import SearchResult from "./result";
import axios from "axios";
const veh_data = [
{ "alfa-romeo": ["145", "90", "Alfa 6", "Alfasud"] },
{ "aston-martin": ["15", "2-Litre", "AM Vantage", "Atom", "Cygnet", "DB2"] },
{ audi: ["100", "200", "A1", "A2", "A3", "A4", "A5", "A6", "A7"] }
];
const defaultValues = [
{ value: 0, text: 0, key: 1 },
{ value: 500, text: 500, key: 2 },
{ value: 1000, text: 1000, key: 3 },
{ value: 1500, text: 1500, key: 4 },
{ value: 2000, text: 2000, key: 5 },
{ value: 2000, text: 2000, key: 6 }
];
const MIN_TITLE = { selected: true, disabled: true, text: "Min Price" };
const MAX_TITLE = { selected: true, disabled: true, text: "Max Price" };
class ImgAndForm extends Component {
constructor(props) {
super(props);
this.handleSearch = this.handleSearch.bind(this);
this.keyToOption = this.keyToOption.bind(this);
this.renderOptions = this.renderOptions.bind(this);
this.handleModelChange = this.handleModelChange.bind(this);
this.state = {
minData: [MIN_TITLE, ...defaultValues],
maxData: [MAX_TITLE, ...defaultValues],
minValue: "",
maxValue: "",
modelSelected: "",
makeSelected: "",
searchResults: ""
};
}
renderOptions(data) {
return data.map(datum => {
// this allows us to indicate whether we are selecting or disabling
const selected = datum.selected || false;
const disabled = datum.disabled || false;
return (
<option
key={datum.key}
value={datum.value}
selected={selected}
disabled={disabled}
>
{datum.text}
</option>
);
});
}
handleModelChange(event) {
console.log(event.target.value);
this.setState({ modelSelected: event.target.value });
}
handleMakeChange(event) {
console.log(event.target.value);
this.setState({ makeSelected: event.target.value });
}
async handleSearch(event) {
event.preventDefault();
alert("Search button clicked");
let { makeSelected, modelSelected, minValue, maxValue } = this.state;
let realQuery =
"https://mysterious-journey-51969.herokuapp.com/api/search-vehicle/?" +
`q=${makeSelected.split("-").join("")}` +
`&m=${modelSelected.split("-").join("")}` +
`&pf=${minValue}` +
`&pt=${maxValue}`;
let dummyQuery =
"https://mysterious-journey-51969.herokuapp.com/api/search-vehicle/?q=mercedes&m=sprinter&pf=0&pt=100000";
console.log("realQuery (was not run)", realQuery);
console.log("dummyQuery (was run)", dummyQuery);
let res = await axios.get(dummyQuery).catch(err => console.log(err));
console.log("res", res.data);
if (res && res.data) {
this.setState(prevState => {
return {
...prevState,
searchResults: res.data
};
});
}
}
keyToOption(key) {
return key
.split("-")
.map(word => word.slice(0, 1).toUpperCase() + word.slice(1))
.join(" ");
}
handleMinSelect = event => {
const value = event.target.value;
const newMaxValues = [];
defaultValues.forEach(datum => {
if (datum.value >= Number.parseInt(value, 10)) {
newMaxValues.push(datum);
}
});
this.setState({
maxData: [MAX_TITLE, ...newMaxValues],
minValue: value
});
};
handleMaxSelect = event => {
const value = event.target.value;
this.setState({ maxValue: value });
};
render() {
const vehicles = veh_data.reduce(
(acc, veh, i) => {
let make = Object.keys(veh)[0],
vehModels = veh[make];
return {
makes: [
...acc.makes,
<option key={make + i} value={make}>
{this.keyToOption(make)}
</option>
],
models: {
...acc.models,
[make]: vehModels.map((model, i) => {
return (
<option key={make + model + i} value={model}>
{this.keyToOption(model)}
</option>
);
})
}
};
},
{ makes: [], models: [] }
);
const selectedModels =
this.state.makeSelected && this.state.makeSelected.length ? (
vehicles.models[this.state.makeSelected]
) : (
<option value="">Model (select make first)</option>
);
return (
<div>
<header className="headerbg d-flex">
<div className="container my-auto">
<div className="row">
<div className="offset-1 col-10 offset-lg-0 col-lg-4">
<div id="search-form-div" className="container">
<div className="row">
<div className="col-12 my-4">
<h3>Search</h3>
<Form onSubmit={this.handleSearch}>
<FormGroup key={1}>
<Input
onChange={e => this.handleMakeChange(e)}
type="select"
name="q"
id="q"
>
<option value="">Make</option>
{vehicles.makes}
</Input>
</FormGroup>
<FormGroup key={2}>
<Input
onChange={e => this.handleModelChange(e)}
type="select"
name="m"
id="m"
>
{selectedModels}
</Input>
</FormGroup>
<FormGroup key={3}>
<Input
type="select"
name="pf"
id="pf"
value={this.state.minValue}
onChange={this.handleMinSelect}
>
{this.renderOptions(this.state.minData)}
</Input>
</FormGroup>
<FormGroup key={4}>
<Input
type="select"
name="pt"
id="pt"
value={this.state.maxValue}
onChange={this.handleMaxSelect}
>
{this.renderOptions(this.state.maxData)}
</Input>
</FormGroup>
<FormGroup key={5}>
<Input
type="submit"
name="search"
id="search"
className="btn btn-primary"
value="Search"
/>
</FormGroup>
</Form>
<SearchResult results={this.state.searchResults} />
</div>
</div>
</div>
</div>
</div>
</div>
</header>
</div>
);
}
}
export default ImgAndForm;
Results Component
import React, { Component } from "react";
// import Smallheader from './SmallHeader';
import { Card, CardImg, CardTitle, CardSubtitle } from "reactstrap";
class SearchResult extends Component {
renderResults() {
let { results } = this.props;
console.log("results", results);
if (results && results.length) {
return results.map(({ price, text, title, remote_image }, i) => {
return (
<Card key={"card-" + i}>
<a href="#">
<CardImg src={remote_image} className="img-fluid" />
<CardTitle>{title}</CardTitle>
<CardSubtitle>{price}</CardSubtitle>
</a>
</Card>
);
});
}
}
render() {
return (
<div>
{/* <Smallheader /> */}
<div className="my-5">
<div className="container text-center" id="contactContainer">
<div className="row">
<div className="col-lg-12 mx-auto">
<h2 className="text-center">Search Results</h2>
<hr className="my-4 thick-hr" />
</div>
</div>
<div className="row">
<div className="col-6 col-lg-3 mt-4">{this.renderResults()}</div>
</div>
</div>
</div>
</div>
);
}
}
export default SearchResult;
This is exactly the type of problem Redux Solves without using Redux you will need to store the state on a shared parent component. For example,
class Search extends Component {
state = {
searchResult: null,
};
handleSearch = searchResult => {
this.setState({
searchResult,
});
}
render(){
const { searchResult, } = this.state;
if(searchResult === null){
return (
<ImgAndForm handleSearch={this.handleSearch} />
);
}
return (
<SearchResult searchResult={searchResult} />
);
}
}

React.js Increment and Decrement counter not mapping correctly

Let me preface that I'm in the process of learning React and I'm still pretty green at this.
I'm going to give the necessary parts of the code:
I have built a counter with increment and decrement buttons that are utilized by ways of state, they work just fine until I introduce and array and map over it. Then things start to break down. I know my code is wrong, I know there's something amiss however I'm completely ignorant as to what to even look for.
In my counting.js I have:
const players = [
{
name: "Jon Smith",
score: 10,
id: 1,
},
{
name: "Jon Doe",
score: 40,
id: 2,
},
{
name: "John Ham",
score: 30,
id: 3,
},
];
Which I have mapped in here:
class Counting extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
nameof: 'Full Name',
}
this.incrementCount = this.incrementCount.bind(this)
this.decrementCount = this.decrementCount.bind(this)
}
incrementCount(e) {
this.setState({
count: (this.state.count + 1),
})
}
decrementCount(e) {
this.setState({
count: (this.state.count - 1),
})
}
render() {
const listPlayers = players.map((players) =>
<Counter
key={players.id}
incrementCount={this.incrementCount}
decrementCount={this.decrementCount}
nameof={players.name}
count={players.score}
/>
);
return (
<div className="wrap">
<Titles header="Counter" subhead="A setState() project" subtitle="this will change" />
<h3>This doesn't work correctly</h3>
<ul>{listPlayers}</ul>
<ScoreList>
<h3> works</h3>
<li>
<Counter
incrementCount={this.incrementCount}
decrementCount={this.decrementCount}
nameof={this.state.nameof}
count={this.state.count}
/>
</li>
<li>
<Counter
incrementCount={this.incrementCount}
decrementCount={this.decrementCount}
nameof={this.state.nameof}
count={this.state.count}
/>
</li>
</ScoreList>
</div>
)
}
}
I have imported my Counter.jswhich is comprised of:
class Counter extends Component {
render() {
const { count } = this.props
const { decrementCount } = this.props
const { incrementCount } = this.props
const { nameof } = this.props
return (
<div>
<CountCell>
<Row style={{alignItems: 'center'}}>
<Col>
<CountButton
onClick={incrementCount}>
<Icon
name="icon" className="fa fa-plus score-icon"
/>
</CountButton>
</Col>
<Col >
<ScoreName>{nameof}</ScoreName>
</Col>
<Col >
<Score>{count}</Score>
</Col>
<Col>
<CountButton
onClick={decrementCount}>
<Icon
name="icon" className="fa fa-minus score-icon"
/>
</CountButton>
</Col>
</Row>
</CountCell>
</div>
)
}
}
So the increment and decrement buttons are only working globally and only for my static <li>, not my ones generated from the array. If I'm making any sense at all, how do I individually map my inc/dec buttons to each <li> and not globally?
Thank you!
You need to keep the state also be an array of objects, each storing data for a corresponding user
class Counting extends React.Component {
constructor(props) {
super(props);
this.state = {
countInfo: []
}
this.incrementCount = this.incrementCount.bind(this)
this.decrementCount = this.decrementCount.bind(this)
}
incrementCount(index) {
var countInfo = [...this.state.countInfo];
if(countInfo[index]) {
countInfo[index].count = countInfo[index].count + 1
countInfo[index].nameOf = players[index].name
}
else {
countInfo[index] = {count: 1, nameOf: players[index].name}
}
this.setState({
countInfo
})
}
decrementCount(index) {
var countInfo = [...this.state.countInfo];
if(countInfo[index]) {
countInfo[index].count = countInfo[index].count - 1
countInfo[index].nameOf = players[index].name
}
else {
countInfo[index] = {count: -1, nameOf: players[index].name}
}
this.setState({
countInfo
})
}
render() {
const listPlayers = players.map((players, index) =>
<Counter
key={players.id}
incrementCount={() => this.incrementCount(index)}
decrementCount={() => this.decrementCount(index)}
nameof={players.name}
count={players.score}
/>
);
return (
<div className="wrap">
<Titles header="Counter" subhead="A setState() project" subtitle="this will change" />
<h3>This doesn't work correctly</h3>
<ul>{listPlayers}</ul>

Categories