Tooltip react hook multiple functionality - javascript

I created a hook to handle a tooltip for different fields(useToolTip). This hook needs to create a piece of state with the current tip that is open in a formField that should be a string. Then return a callback if a new tip has been open which sets the current tip to be equal to the passed argument based on the fieldName.
This way a form will call thisuseToolTip hook and pass the return state and callback down to each form field. the form field will pass its fieldName property and the hook return to the tooltip component.
I also have a button (ToolTipButton) that needs to call the new tip callback function onclick with its fieldName property as a parameter.
The text box for this tooltip(ToolTipTextBox) will check to see if the current tip property (state from hook) is equal to its fieldName property if so then the tip will be render.
How can I accomplish this?
Here are all my components:
function useToolTip() {
const {
currentTip
} = useState(formField);
const showTooltip = useCallback(
(_text: string) => {
setShow(true);
setText(_text);
},
[setShow, setText]
);
const hideTooltip = () => {
setShow(false);
};
return {
currentTip
};
}
export default useToolTip;
Form:
const NewModelForm: FunctionComponent<NewModelFormProps> = (props) => {
const { chooseFolder, onSubmit, chooseFile } = props;
const history = useHistory();
return (
<Form
onSubmit={(data) => {
// console.log(data);
onSubmit(data);
}}
id="new_model_form"
>
<FormField
label="Simulation Name"
name="name"
placeholder="Enter a name for the simulation."
tip="Enter a name to save your simulation as."
required
defaultValue="Simulation 1"
/>
<OutputPathFieldset onClick={chooseFolder} defaultPath="/" />
<FormField
label="End Time (years)"
type="number"
name="end_time"
defaultValue={10}
step={1}
tip="Enter the number of years to run the simulation."
/>
<FormField
label="Time Step (years)"
type="number"
name="time_step"
defaultValue={0.5}
tip="Enter the number of years for a time step"
step={0.1}
/>
<AnalysisTypeFieldset />
<Dropdown
name="logging"
label="Logging"
choices={{
bug: 'Debug',
info: 'Info',
warn: 'Warning',
err: 'Error',
}}
tip="Select a logging type"
/>
<ReservoirTypeFieldset
chooseFile={chooseFile}
chooseFolder={chooseFolder}
/>
<FormControls
onCancel={() => history.push('/')}
formID="new_model_form"
/>
</Form>
);
};
NewModelForm.defaultProps = {};
Button:
const ToolTipButton: FunctionComponent<ToolTipButtonProps> = (props) => {
const { onClick } = props;
return (
<button type="button" className="form__tooltip" onClick={onClick}>
<BsQuestion />
</button>
);
};
ToolTipButton.defaultProps = {};
export type ToolTipButtonProps = {
onClick: () => void;
};
export default ToolTipButton;
Text box:
import React from 'react';
const ToolTipTextBox = (props) => {
const {} = props
return (
<div> Tool tip box </div>
)
}
export default ToolTipTextBox
Thank you!!!!!!!

Related

Initial value from an input element of type 'number' is Empty - React and Tsx

I am working on a react POS app with Typescript. I want to calculate the change when the I enter a the amount of money received from a buyer. When I enter the value into the input element, the first value passed to the logic that calculates the change is an empty value then the other values are added. It causes an inaccurate calculation.
const CartBill: React.FC<{}> = () => {
const [change, setChange] = useState<number>(0)
//logic to calculate change
const calculateChange: changeCalculatorType = (amountReceived) => {
if (amountReceived <= +grandTotal.toFixed(2)) {
setChange(0);
} else {
const change = amountReceived - +grandTotal.toFixed(2);
setChange(+change.toFixed(2));
}
return change;
};
return(
<div> <Received onSetChange={calculateChange} /> </div>
<div>{change}</div>
)
}
this is the component that contains the input element. It lifts the amount state up to the CartBill component
import {useState} from 'react'
import { changeCalculatorType } from '../../types/ReceivedComponentTypes';
const Received: React.FC<{ onSetChange: changeCalculatorType }> = (props) => {
const [amount, setAmount] = useState(0);
const getInputHandler = (event: React.FormEvent<HTMLInputElement>) => {
const retrieved = +(event.target as HTMLInputElement).value.trim();
setAmount(retrieved);
props.onSetChange(amount);
};
return (
<>
<input type="number" name="" id="" onChange={getInputHandler} />
</>
);
};
export default Received
I tried trim()ing the value but that doesn't seem to work. Any help is hugely appreciated
You need to set the value attribute on your input.
return (
<input type="number" name="" id="" value={amount} onChange={getInputHandler} />
);

React: How to do handleChange and handleSubmit for dynamically adding Input Textboxes?

I am displaying a list. Each item in the list is having a textbox. Textbox is showing DisplayOrder. Please find the sandbox: https://codesandbox.io/s/solitary-butterfly-4tg2w0
In Post API call, how to pass changed textbox values with corresponding description-id as a collection. StoredProcedure is taking description-id and display-sequence as parameters to save changed data in the database.
Please help on form submit how to do this? Thanks
import "./styles.css";
import React from "react";
import XMLParser from "react-xml-parser";
const data = `<?xml version="1.0"?>
<Category>
<description description-id="11" display-sequence="2">testing</description>
<description description-id="15" display-sequence="5">Guide</description>
<description description-id="20" display-sequence="7">test</description>
<description description-id="25" display-sequence="10">Guide</description>
<description description-id="30" display-sequence="12">test</description>
</Category>
</xml>`;
const REQUEST_URL = "";
const axios = {
get: () =>
new Promise((resolve) => {
setTimeout(resolve, 1000, { data });
})
};
class Sort_Descr extends React.Component {
constructor(props) {
super(props);
this.state = {
proddescriptions: [],
proddescription_id: "",
display_sequence: ""
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {}
componentDidMount() {
this.getlistofdescriptions();
}
getlistofdescriptions() {
axios
.get(REQUEST_URL, { "Content-Type": "application/xml; charset=utf-8" })
.then((response) => {
const jsonDataFromXml = new XMLParser().parseFromString(data);
const descriptions = jsonDataFromXml.getElementsByTagName(
"description"
);
console.log(descriptions);
this.setState({
proddescriptions: jsonDataFromXml.getElementsByTagName("description")
});
});
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<div>
<ul style={{ listStyle: "none" }}>
{this.state.proddescriptions.map((item, index) => {
return (
<li key={item.attributes["description-id"]}>
{item.attributes["description-id"]}
<input
type="text"
size="5"
maxlength="3"
value={item.attributes["display-sequence"]}
onChange={this.handleChange}
/>
{item.value}
</li>
);
})}
</ul>
</div>
<input type="submit" name="submit" value="Submit" id="btnsubmit" />
</form>
</div>
);
}
}
export default function App() {
return (
<div className="App">
<h4>Sort list by updating the number in the textbox</h4>
<Sort_Descr />
</div>
);
}
Instead of storing and updating JSON/XML data in state I'd suggest mapping it to a simpler object that is easier to identify and update. React state should be the minimal amount of data necessary to represent your information, store only what you need.
const proddescriptions = descriptions.map(({ attributes, value }) => ({
id: attributes["description-id"],
sequence: attributes["display-sequence"],
value
}));
this.setState({ proddescriptions });
When mapping the state.proddescriptions to the inputs, use the id as the inputs name attribute value for identification when updating.
{this.state.proddescriptions.map((item) => (
<li key={item.id}>
<label>
{item.id}
<input
type="text"
name={item.id} // <-- name attribute
size="5"
maxLength="3"
value={item.sequence}
onChange={this.handleChange}
/>
</label>
{item.value}
</li>
))}
Implement the handleChange to shallow copy the previous state and update the matching array element by id.
handleChange = (event) => {
const { name, value } = event.target;
this.setState((prevState) => ({
proddescriptions: prevState.proddescriptions.map((el) =>
el.id === name
? {
...el,
sequence: value
}
: el
)
}));
};
In the handleSubmit callback use the onSubmit event object to prevent the default form action and access the current state.proddescriptions and map it back to any format your APIs are expecting.
handleSubmit = (event) => {
event.preventDefault();
console.log(this.state.proddescriptions);
// manipulate `state.proddescriptions` into a request payload
};

React Higher order components, that modifies the input element of wrapped component in it

I have a higher order component like this.
import React from 'react';
const NewComponent = ( WrappedComponent ) => {
class UpdatedComponent extends React.Component {
render() {
// Custom Hook
// const values = useCustomHook(InitialState);
return(
<WrappedComponent />
)
}
}
return UpdatedComponent;
};
export { NewComponent };
And the wrapped component like these.
const App = () => {
return(
<Form>
<input
type = 'text'
placeholder = 'Enter your name' />
<input
type = 'email'
placeholder = 'Enter your email' />
<button
type = 'submit'>
Submit
</button>
</Form>
)
}
The thing is i want to iterate through input elements in the wrapped components and construct a compound state, which i will pass a an Argument to the custom hook in the hoc? Is there a way to achieve this functionality?
I think you should not parse jsx and create data from that but you have not really demonstrated why you need to do this and maybe have a valid use case for it.
Here is how you could parse jsx (the html created from it):
const useCustomHook = (state) => {
if (state !== null) {
console.log('custom hook, state is:', state);
}
};
const Form = ({ children }) => {
const [state, setState] = React.useState(null);
const ref = React.useRef();
useCustomHook(state);
React.useEffect(() => {
debugger;
setState(
Array.from(ref.current.querySelectorAll('input')).map(
({ type, placeholder }) => ({
type,
placeholder,
})
)
);
}, []);
return <div ref={ref}>{children}</div>;
};
const App = () => {
return (
<Form>
<input type="text" placeholder="Enter your name" />
<input type="email" placeholder="Enter your email" />
<button type="submit">Submit</button>
</Form>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

How to display the state on the same page when clicked Submit button in react

I have made a form in react which takes input from the user and stores it in the state. Now, I want to display the state values when the user clicks Submit button in an input field just below the submit button in React.
Im new to react.
You have to make an object (E.g. Credentials) and when someone clicks the button, credential takes the props of the state like this:
App.js
//import code....
import Form from './Form.js'
//class app code.....
//in the render method:
render() {
return (
<Form />
)
}
Form.js
// import code ....
state = {
firstName: '', // or what you want
lastName: '', // or what you want
email: '', // or what you want
send: false,
}
//handleChange function
const handleChange = (event) => {
const {name, value} = event.target
this.setState({
[name]: value
})
}
//handleClick function
const handleClick = () => {
this.setState({send: true})
}
In the Render method
render() {
return (
<div>
<input name='firstName' onChange={handleChange} />
<input name='lastName' onChange={handleChange} />
<input name='email' onChange={handleChange}/>
<button onClick={handleClick}>Send</button>
{send &&
<Credentials
firstName={this.state.firstName}
lastName={this.state.lastName}
email={this.state.email}
/>
}
</div>
)
}
export default Form // or your file's name
In the Credential.js
//import code...
const Credentials = ({firstName, lastName, email}) => {
return (
<h2>firstName is: {firstName}</h2>
<h4>lastName is: {lastName}</h4>
<p>email is: {email}</p>
)
}
export default Credentials
In React you can use 'useState' to initiate a number or any kind of input. Then set that number when the user clicks on a button.
import React, { useState } from "react";
function App() {
const [number, setNumber] = useState();
let typedNumber = 0;
const btnHandler = (e) => {
e.preventDefault();
setNumber(typedNumber);
};
return (
<div>
<form onSubmit={btnHandler}>
<input type="text" onChange={(e) => (typedNumber = e.target.value)} />
<input type="submit" />
</form>
<p>{number}</p>
</div>
);
}
export default App;

React Redux Redux Form - moving a function from within a <Field> produces error

Novice.
I have a class Address which I ultimately want to split into a presentational component and container. It all works as is but when I move this particular function outside the render function from initially within the actual async.select form field -
onSuburbChange = (value) => {
this.setState({ selectedSuburb: value }, () => {
input.onChange(value)
updatePostcodeValue(value ? value.postcode : null, sectionPrefix)
})
}
...I find I am getting hit with a number of errors based on the the fact that they are unreferenced.
The error I get is
address.jsx:56 Uncaught ReferenceError: input is not defined
If I comment this line out I get the same type of error on updatePostcodeValue.
Here is the entire address file. As you can see it would be good to move the presentational section in render off to another file but I need to move all the functions to the outside of the render function.
NOTE: I have commented out where the function orginal sat so anybody who has a crack at this question knows where it was and also where I intended to move it...
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import { Field, change } from 'redux-form'
import { Col, Panel, Row } from 'react-bootstrap'
import Select from 'react-select'
import { getSuburbs } from './actions'
import FormField from '../formComponents/formField'
import TextField from '../formComponents/textField'
import StaticText from '../formComponents/staticText'
import { CLIENT_FORM_NAME } from '../clients/client/client'
export class Address extends Component {
static contextTypes = {
_reduxForm: PropTypes.object.isRequired,
}
constructor(props, context) {
super(props, context)
this.state = {
selectedSuburb: null,
}
}
// Manage Select for new data request - for suburbs.
handleSuburbSearch = (query) => {
console.group('handleSuburbSearch')
console.log('query', query)
const { addressData } = this.props
console.log('addressData', addressData)
const companyStateId = addressData.companyStateId
console.log('companyStateId', companyStateId)
if (!query || query.trim().length < 2) {
console.log('no query; bailing!')
console.groupEnd()
return Promise.resolve({ options: [] })
}
const queryString = {
query: query,
companyStateId: companyStateId,
}
console.log('queryString', queryString)
return getSuburbs(queryString)
.then(data => {
console.log('Suburbs returned!', data)
console.groupEnd()
return { options: data }
})
}
//I HAVE MOVED IT TO HERE....
onSuburbChange = (value) => {
this.setState({ selectedSuburb: value }, () => {
input.onChange(value)
updatePostcodeValue(value ? value.postcode : null, sectionPrefix)
})
}
render() {
const { addressData, updatePostcodeValue } = this.props
const { value } = this.state
const sectionPrefix = this.context._reduxForm.sectionPrefix
return (
<Panel header={<h3>Client - Address Details</h3>}>
<Row>
<Field component={TextField}
name="address1"
id="address1"
type="text"
label="Address Line 1"
placeholder="Enter street 1st line..."
fieldCols={6}
labelCols={3}
controlCols={9}
/>
<Field component={TextField}
name="address2"
id="address2"
type="text"
label="Address Line 2"
placeholder="Enter street 2nd line..."
fieldCols={6}
labelCols={3}
controlCols={9}
/>
</Row>
<Row>
<Field
component={props => {
const { input, id, placeholder, type } = props
const { fieldCols, labelCols, controlCols, label, inputClass } = props
// just the props we want the inner Typeahead textbox to have
const { name, onChange } = input
const onStateChange = (state) => {
console.log('onStateChange', state)
onChange(state)
}
return (
<FormField
id={id}
label={label}
fieldCols={fieldCols}
labelCols={labelCols}
controlCols={controlCols}
inputClass={inputClass}
>
<Select
name={name}
onChange={onStateChange}
placeholder="Select state"
valueKey="id"
options={addressData.states}
labelKey="stateLabel"
optionRenderer={option => `${option.stateShortName} (${option.stateName})`}
value={input.value}
selectValue={Array.isArray(input.value) ? input.value : undefined}
/>
</FormField>
)
}}
name="state"
id="state"
label="State."
fieldCols={6}
labelCols={3}
controlCols={6}
/>
</Row>
<Row>
<Field
component={props => {
const { input, id, placeholder, type } = props
const { fieldCols, labelCols, controlCols, label, inputClass } = props
const { name, value, onChange, onBlur, onFocus } = input
const inputProps = {
name,
value,
onChange,
onBlur,
onFocus,
}
{/*onSuburbChange = (value) => {
this.setState({ selectedSuburb: value }, () => {
input.onChange(value)
updatePostcodeValue(value ? value.postcode : null, sectionPrefix)
})
}*/}
return (
<FormField
id={id}
label={label}
fieldCols={fieldCols}
labelCols={labelCols}
controlCols={controlCols}
inputClass={inputClass}
>
<Select.Async
{...inputProps}
onChange={this.onSuburbChange}
valueKey="id"
labelKey="suburbName"
loadOptions={this.handleSuburbSearch}
backspaceRemoves={true}
/>
</FormField>
)
}}
name="suburb"
id="AddressLocation"
label="Suburb."
fieldCols={6}
labelCols={3}
controlCols={9}
/>
</Row>
<Row>
<Field component={StaticText}
name="postcode"
id="postcode"
label="Postcode."
fieldCols={6}
labelCols={3}
controlCols={9}
/>
</Row>
</Panel>
)
}
}
const AddressContainer = connect(state => ({
addressData: state.addressData,
}), dispatch => ({
updatePostcodeValue: (postcode, sectionPrefix) => dispatch(change(CLIENT_FORM_NAME, `${sectionPrefix ? (sectionPrefix + '.') : ''}postcode`, postcode))
}))(Address)
export default AddressContainer
How do I structure the onSuburbChange so that it can sit outside the render function, update the onChange value and also update the Postcode etc?
well, if you look at the method, you'll see that... well, input is undefined in that scope.
onSuburbChange = (value) => { // <-- scope starts here
this.setState({ selectedSuburb: value }, () => {
input.onChange(value) // <-- input used here
updatePostcodeValue(value ? value.postcode : null, sectionPrefix)
})
}
assuming Select.Async is a "magic" blackbox Component that you don't have access to/are able to change, and the only parameter you get back from it in the callback is the new value, your best bet is a ref on the input.
<Field ref={(input) => this.input = input } ... />
and then change it to this.input instead of just input
I think you could also partially apply it (it's late any I'm not thinking straight) - it would look like
onSuburbChange = (input, value) => {
this.setState({ selectedSuburb: value }, () => {
input.onChange(value)
updatePostcodeValue(value ? value.postcode : null, sectionPrefix)
})
}
--
const mOnChange = onSuburbChange.bind(null, input) while input is in scope.
updatePostcodeValue can be referenced from props in the callback - and you've already taken care of ensuring it has the correct scope by using ES6 arrow function notation. Just destructure it out of props just like you did in render at the top of the callback.
also, unrelated, but you REALLY oughta break out those component props into another file or function...

Categories

Resources