I want multiple material-ui sliders in one react component sharing a common event handler. However, to make this work, I would need to identify the originating slider. From the API documentation I can't see how that is achieved. I've tried applying id and name attributes to the <Slider>-component, yet I'm not seeing these in the synthesized event in the event handler.
handleChange = (event, value) => {
console.log(event); // 'Id' and 'name' attributes in 'target' are empty
this.setState({ value });
};
render() {
const { classes } = this.props;
const { value } = this.state;
return (
<div className={classes.root}>
<Typography id="label">Slider label</Typography>
<Slider
classes={{ container: classes.slider }}
value={value}
aria-labelledby="label"
onChange={this.handleChange}
/>
</div>
);
}
This is fetched from the official demo project:
https://codesandbox.io/s/4j9l9xn1o4
Any help would be greatly appreciated!
You can format your state like so:
state = {
slider1: 50, //slider1 is the name of the first slider
slider2: 50, //slider2 is the name of the second slider
}
After that, you have 2 ways to set the state when the value of the slider is changed:
(Update: This method doesn't work! However I will leave it here for future reference) By using HTML attribute id, then access it by using event.target.id. The whole handleChange method would look like this:
handleChange = (e, value) => {
this.setState({
[e.target.id]: value
});
}
By passing then name of the slider straight to the handleChange method, and it would be like this:
handleChange = name => (e, value) => {
this.setState({
[name]: value
});
}
Overall, your component should be:
class SimpleSlider extends Component {
state = {
slider1: 50,
slider2: 50
};
handleChange = name => (e, value) => {
this.setState({
[name]: value // --> Important bit here: This is how you set the value of sliders
});
};
render() {
const { classes } = this.props;
const { slider1, slider2 } = this.state;
return (
<div className={classes.root}>
<Typography id="label">Slider label</Typography>
<Slider
classes={{ container: classes.slider }}
value={slider1}
aria-labelledby="label"
onChange={this.handleChange("slider1")}
/>
<Slider
classes={{ container: classes.slider }}
value={slider2}
aria-labelledby="label"
onChange={this.handleChange("slider2")}
/>
</div>
);
}
}
See it in action: https://codesandbox.io/s/4qz8o01qp4
Edit: After running the code I found that the #1 doesn't work because the id attribute is not being passed down to the event target
Related
My app gets initialized with a json payload of 'original values' for attributes in a task form. There is a shared state between various components through a context manager submissionState that also gets imported to this form component. This shared state is a copy of the original values json but includes any edits made to attributes in the payload. I would like to include individual 'reset' buttons for each input of the form which would update the shared state to the original value in the json payload. The original values get passed into the parent form component as props and the edited value state gets called from within the parent component as well.
const FormInput = ({ fieldName, fieldValue, onChange, originalValue }) => {
const handleReset = e => {
//????
}
return (
<div>
<label htmlFor={fieldName}>
{fieldName}
</label>
<br />
<input type="text"
name={fieldName}
value={fieldValue}
onChange={onChange}/>
<button id="reset" onClick={handleReset}>↻</button>
<br />
<div>{originalValue}</div>
</div>
);
};
const TaskForm = (payload: TaskPayload) => {
const { submissionState, setSubmission } = useTask();
function handleChange(evt) {
const value = evt.target.value;
setSubmission({
...submissionState,
[evt.target.name]: value,
});
}
const taskFields = ["name", "address", "address_extended", "postcode", "locality", "region", "website"];
return (
<div>
<form>
{taskFields.map((field) => {
<FormInput
key={field}
fieldName={field}
fieldValue={submissionState[field]}
onChange={handleChange}
originalValue={payload[field]}
/>
})
}
</form>
</div>
);
};
export default TaskForm;
What I would like to do is include logic in the reset button function so that any edits which were made in a form input (from state) get reverted to the original value (stateless), which comes from the payload props: payload[field].
The form input is controlled through a global shared state submissionState, so the reset button logic can either modify the shared state itself with something like:
const handleReset = (submissionState,setSubmissionState) => {
setSubmission({
...submissionState,
fieldName: originalValue,
});
but I would need to pass the submissionState and setSubmission down through to the child component. It would be better if I can somehow update the value attribute in the input, which in-turn should potentially update the shared state? And the logic can just be something like this (assuming I can somehow access the input's value state in the reset button)
const handleReset = (?) => {
/*psuedo code:
setInputValueState(originalValue)
*/
}
I would highly recommend using react-hook-form if it's an option. I've implemented it across several projects and it has never let me down. If it's just not possible to use a library, then keep in mind that React is usually unidirectional. Don't try to work around it, since it works that way by design for most cases you can encounter. Otherwise…
const TaskForm = (payload: TaskPayload) => {
const { submissionState, setSubmission } = useTask();
const upsertSubmission = (upsert) =>
setSubmission({
...submissionState,
...upsert,
});
const handleChange = ({ target }) => {
upsertSubmission({
[target.name]: target.value,
});
};
const reset =
(originalValue) =>
({ target }) => {
upsertSubmission({
[target.name]: originalValue,
});
};
/* Also something like this. RHF will handle most of this for you!
* const reset = (originalValue, fieldName) =>
* upsertSubmission({[fieldName]: originalValue})
*/
const taskFields = [];
return (
<div>
<form>
{taskFields.map((field) => (
<FormInput
key={field}
fieldName={field}
onChange={handleChange}
reset={reset(originalValue)}
value={submissionState[field]}
/>
))}
</form>
</div>
);
};
I'm trying to change a variable value given to a component through props inside of a function in this component.
I cannot get the value of the observable to change (even when passing the variable through parameter) so I wanted to know if I could retrieve the observable address to modify it directly this way.
#observer
class Exemple extends React.Component {
#observable Nom
#action onChangeNom = newValue => {
this.Nom = newValue //I want to have something similar to this function inside the component but it would take the value to update and the newValue as parameters
}
render() {
return (
<FormComponent //this is where I call my component
ContainerStyle={{
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
}}
ObjArray={[
{
InputDivStyle: { width: '49%', marginRight: '2%' },
FieldType: 'textInput',
FieldStyle: { borderColor: this.missingLastName ? 'red' : COLOR_NOT_BLACK },
OnChange: this.onChangeNom,
Placeholders: 'Ex. Durand',
Value: this.Nom, //this is the value I want to change inside the component
InputTitle: 'Nom',
},
]}
/>
)
}
}
#observer
class FormComponent extends React.Component<props> {
render() {
const { ObjArray, ContainerStyle } = this.props
return (
<div style={ContainerStyle}>
{ObjArray.map((e, index: number) => {
const { Disabled, FieldStyle, InputDivStyle, FieldType, Placeholders, OnChange, Value, InputTitle } = e
return (
<>
<div style={InputDivStyle ?? {}} key={index + 'Input'}>
{InputTitle && (
<>
<Label medium>{InputTitle}</Label>
</>
)}
<GenericInput
disabled={Disabled ?? false}
placeholder={Placeholders ?? ''}
style={FieldStyle ?? {}}
type={FieldType ?? ''}
value={Value ?? ''}
onChange={evt => {
OnChange(evt.target.value)
}}
/>
</div>
</>
)
})}
</div>
)
}
}
export default FormComponent
My concern is that I want to be able to change the value inside of the component without having to create a function for each value (if there was multiple objects in ObjArray for example. (I have already tried by passing the value as parameters inside of the component but it doesn't update it outside that's why I want to be able to change the value directly at the memory address where it is stored (like you can do in C with pointers)).
I want to be able to change the value directly at the memory address where it is stored
It is not possible in Javascript, you can't "pass-by-reference" (in original meaning) and so you can't change values like that.
The easiest way would be to have function like that:
#action onChange = (key, newValue) => {
this[key] = newValue
}
You pass it into component with a key and then invoke like that:
onChange={evt => {
// Or you can use InputTitle instead of key
OnChange(key, evt.target.value)
}}
That way you don't need to create lots of functions for every value
I am trying to update the title and body objects within my array with the value provided from the input fields that mapped to the array length. The body object updates perfectly but the title updates with only the current input value. For instance, if I were to try and type "the".
The title object would change as follows "title: "t"" => "title: "h""
=> "title: "e".
Desired output would be "title: "t"" => "title: "th"" => "title:
"the".
This works with the body so I am confused why it is not with the title maybe I have missed something.
export const NewsArticlesPage = () => {
const [newsArticlesJson, setNewsArticlesJson] = useContext(
NewsArticlesContext
)
const [numberOfSteps, setNumberOfSteps] = useState(0)
const stepsMap = Array.apply(
null,
Array(numberOfSteps).fill({ title: '', body: '' })
)
let stepsArray = { ...stepsMap } as StepsMapProps
const updateTileArray = (index: number, titleEventData: string) => {
const stepsArrayData = { ...stepsArray }
stepsArray = {
...stepsArrayData,
[index]: { ...stepsArrayData[index], title: titleEventData },
}
console.log(stepsArray[index])
console.log(stepsArray[index]?.title)
}
const updateRichTextArray = (index: number, richTextEventData: string) => {
const stepsArrayData = { ...stepsArray }
stepsArray = {
...stepsArrayData,
[index]: { ...stepsArrayData[index], body: richTextEventData },
}
console.log(stepsArray[index])
console.log(stepsArray[index]?.body)
}
return (
<NewsArticlesWrapper>
<TextField
type="number"
label="Number of steps"
value={numberOfSteps}
InputProps={{ inputProps: { min: 0 } }}
onChange={(e) => setNumberOfSteps(Number(e.target.value))}
/>
{stepsMap.map((n, index) => (
<>
<Typography key={'heading' + index}>Step: {index + 1}</Typography>
<TextField
key={'title' + index}
type="text"
label="Title"
value={stepsArray[index]?.title}
onChange={(titleEventData) =>
updateTileArray(index, titleEventData.target.value)
}
/>
<ReactQuill
key={'quill' + index}
theme="snow"
value={stepsArray[index]?.body}
modules={modules}
onChange={(richTextEventData) =>
updateRichTextArray(index, richTextEventData)
}
/>
</>
))}
<Button
variant="contained"
colour="primary"
size="medium"
onClick={updateNewsArticleJson}
>
Submit Article
</Button>
</NewsArticlesWrapper>
)
}
If any extra information is needed to help me please ask.
Code Sandbox set up to replicate issue: https://codesandbox.io/embed/kind-buck-shg2t?fontsize=14&hidenavigation=1&theme=dark
<TextField /> and <ReactQuill /> input components are defined in a ‘controlled component’ way. The object they’re setting / getting values to is stepsArray which you have created as a normal object
let stepsArray = { ...stepsMap } as StepsMapProps
The onChange handler on both these components are changing values on this object
stepsArray = {
...stepsArrayData,
[index]: { ...stepsArrayData[index], title: titleEventData },
}
but, there isn't a re-render, because React re-renders when state changes. stepsArray is a normal js object, and although the value is changing, it is not causing a re-render. This is the reason, you cannot see the value you type on Title <TextField />component.
To solve this, make the object where you’re setting/getting values to, with useState, and use the set(State) function provided by the useState hook accordingly to update values inside the object. This will cause a re-render whenever input's value changes, because now you will be changing the object created through useState's setter function and your UI will be in sync with the state.
I want to handle disable attribute of input elements by ID in my React app
It's possible with states but number of elements is not fixed, maybe 10 input or 20 or more ...
I've decided to set ID for each input and access to them with ID, for example :
document.getElementById('title-12') ....
So, is it a suitable trick or best practice to handle this issue ?
Performance and clean code is very important for me :-)
Thanks
Oops... my bad. I digged into your discussion and here's a new solution.
That's still correct that React approach is advised so we should use reusable components for inputs (we can have any number of inputs right now). All the input's data are stored in parent component's store as a collection. We map through collection and send properties to each component (in the simplest version - id, isDisabled and disableInput() function).
class Titles extends Component {
constructor(props) {
super(props);
this.state = {
titles: [
{
id: 0,
disabled: true
},
{
id: 1,
disabled: false
}
]
};
}
addNewInput = () => {
const prevList = this.state.titles;
const newItem = {
id: prevList.length,
disabled: false
};
this.setState({ titles: [...prevList, newItem] });
};
disableInput = id => {
const titles = this.state.titles;
titles[id].disabled = !titles[id].disabled;
this.setState({ titles });
};
render() {
return (
<div>
<h1>Titles list</h1>
<form style={{ display: "flex", flexDirection: "column" }}>
{this.state.titles.map(title => (
<Title
key={title.id}
id={title.id}
isDisabled={title.disabled}
disableInput={id => this.disableInput(id)}
/>
))}
</form>
<button onClick={() => this.addNewInput()}>Dodaj nowy</button>
</div>
);
}
}
in Title component we just render the props in to <input> and a button with onClick function that sends id of this component to its parent, where disable attribute's value is being reversed.
const Title = ({ id, isDisabled, disableInput }) => {
return (
<div>
<input
id={id}
type="text"
placeholder={id}
disabled={isDisabled}
/>
<button type="button" onClick={() => disableInput(id)}>
disable input
</button>
</div>
);
};
Working example can be found here.
Please let me know if it works for you.
I need to show tooltips for react-select container (not for a separate options) using react-tooltip library.
I made my own SelectContainer component based on the original one and added there data-tip and data-for HTML attributes. Tooltip shows up but when I change selected value it disappears and is not displayed any more.
Here is my code:
const getSelectContainer = options => props => {
return (
<components.SelectContainer
{...props}
innerProps={{
...props.innerProps, ...{
'data-tip': options.tooltipText,
'data-for': options.tooltipId,
}
}}
/>
)
}
const CustomSelect = (props) => {
const tooltipId='tooltip_id'
const tooltipText='tooltip'
const selectedOption = colourOptions.filter(option => option.value === props.value)
return (
<div>
<ReactTooltip effect="solid" html={true} place="bottom" id={tooltipId} />
<Select
defaultValue={colourOptions[4]}
value={selectedOption}
options={colourOptions}
classNamePrefix="react-select"
onChange={item => props.onChange(item.value)}
className="my-select"
components={{
SelectContainer: getSelectContainer({
tooltipText:tooltipText,
tooltipId:tooltipId
})
}}
/>
</div>
)
}
class Page extends Component {
constructor (props) {
super(props)
this.state = {
selectValue: 'red'
}
}
render () {
const onChange = (value)=> {
this.setState({selectValue: value})
//alert(value)
}
return (
<CustomSelect
value={this.state.selectValue}
onChange={onChange}>
</CustomSelect>
)
}
}
See full example here:
If I wrap Select with another <div> and assign tooltip HTML attributes to it everything works correctly but I don't want to add one more DOM element just for that.
What can I do to show tooltips after changing selection?
Try rebuilding the react-tooltip when the state changes.
useEffect(() => {
ReactTooltip.rebuild();
}, [state]);