I have a diary object with 2 meals
function Magicdiary() {
const [diary, setDiary] = React.useState<MagicDiaryDay[]>([
{ mealName: "Breakfast", ingredient: null },
{ mealName: "Lunch", ingredient: null },
]);
return (
<div>
<p>meal 1: {diary[0].ingredient?.productName}</p>
<Button onClick={() => console.log(diary[0].ingredient?.productName)}>
log diary
</Button>
{diary.map((meal, index) => {
return (
<MealComponentForMagicDiary
diary={diary}
setDiary={setDiary}
index={index}
/>
);
})}
</div>
);
}
I have selection of ingredients that I call from my backend, and everytime I select current ingredient, I set it to the diary:
// MealComponentForMagicDiary
useEffect(() => {
if (hit) {
const diaryCopy = diary;
diaryCopy[index].ingredient = {
productName: hit.productName,
nutrients: {
"energy-kcal_serving": hit.calories,
protein_serving: hit.protein,
carbohydrates_serving: hit.carbs,
fat_serving: hit.fats,
},
};
setDiary(diaryCopy);
}
}, [hit, selectedHit]);
As you can see meal 1 is empty, but when I log it on the console I can see the correct productName what is the cause of this bug?
You are updating the state in the wrong way, you are mutating the original array that is overwriting the exiting array, Instead, you need to do it in an immutable way that is providing a new Instance of diary whenever you want to update it, you can do in the following way
useEffect(() => {
if (hit) {
const diaryCopy = diary.map((d, ind) => {
if (ind === index) {
// The Diary ingredient you want to update
d.ingredient = {
productName: hit.productName,
nutrients: {
"energy-kcal_serving": hit.calories,
protein_serving: hit.protein,
carbohydrates_serving: hit.carbs,
fat_serving: hit.fats,
}
};
}
return d;
} );
setDiary(diaryCopy);
}
}, [hit, selectedHit]);
From my limited react experience, when funny things like this happen I have a few go-to methods to try. One of them is to call an empty function when passing a function down as a prop. I.e. instead of:
<MealComponentForMagicDiary
diary={diary}
setDiary={setDiary}
index={index}
/>
try:
<MealComponentForMagicDiary
diary={diary}
setDiary={() => setDiary}
index={index}
/>
I'd love to know why this works sometimes, if anybody (does anybody?) understands react properly.
My fingers are crossed that it works for you!
It's hard to say exactly with this given information but I'm inclined to say this:
You're not getting the information you want because when the component renders, it's not there. Take for example this:
<p>meal 1: {diary[0].ingredient?.productName}</p>
You check if ingredient exists but are you sure diary[0] exists? Since you're setting this data elsewhere in a useEffect, I suspect that it's not available at render - even though you can console it.
I suggest using the React Developer Tools to look at your component tree and see what that state looks like when it's rendered.
That's all I can guess without more code.
I'm trying to find a neater way to handle this pattern I keep coming across with react when handling changes for form fields.
For each element of my form object that I handle a change in value for I find myself replicating this pattern quite a bit with the setter function of useState(). I've tried a couple of things like creating shallow copies of the formState and mutating that but the only way I can really get things to work is with the bellow pattern which feels a little repetitive.
const handleTitle = evt => {
props.setFormState({
title: evt.target.value,
bio: props.formState.bio,
formExpertise: props.formState.formExpertise,
formExpertiseYears: props.formState.formExpertiseYears
});
};
If you want to include this.props.formState you can spread the object into the new state. Further, you can use the input’s name as the state key so you don’t have to rewrite this for every input:
props.setFormState({
...this.props.formState, // copy props.formState in
[evt.target.name]: evt.target.value // use input name as state key
});
Suggestion:
You might consider moving the state merging up into the parent component:
// parent component
const [formState, setFormState] = React.useState({});
const onFieldChange = (field, value) => {
setFormState({
...formState,
[field]: value
});
}
return (
<MyFormComponent
formState={formState}
onFieldChange={onFieldChange}
/>
);
Each input can then invoke onFieldChange with the field name and value without concerning itself with the rest of the state:
function MyFormComponent ({onFieldChange}) {
const handler = ({target: {name, value}}) => onFieldChange(name, value);
return (
<div>
<input name="title" value={formState.title} onChange={handler} />
<input name="bio" value={formState.bio} onChange={handler} />
<input name="expertise" value={formState.expertise} onChange={handler} />
</div>
);
}
I'm in the early process of building a commissioning checklist application for our company. Since the checklist is fairly large (and many of them) I wanted to create a function that maps through an object and after rendering the values written would update the appropriate states with a useState Hook.
The page is rendering without any issues. The problem only appears once the input is changed. Instead of updating the correct state in the object. It seems the logic is adding an additional section in my object and creating another input element.
import React, { useState } from 'react'
const ProjectInfo = () => {
const _renderObject= () => {
return Object.keys(answers).map((obj, i) => {
return(
<div key={obj}>
<label>{answers[obj].question}</label>
<input type="text" onChange={(e, obj) => setAnswer(
{
...answers,
obj:{
value: e.target.value
}})} />
</div>
)
})
}
const [answers, setAnswer] = useState({
11:{
question:"Project Name",
value:""
},
12:{
question:"Project Number",
value:""
}
})
return(
<div>
<section>
{_renderObject()}
</section>
<p>{`Project Number is: ${answers[11].value}`}</p>
<p>{`Project Name is: ${answers[12].value}`}</p>
</div>
)
}
export default ProjectInfo
I was expecting for the state to just update as normal. But what I'm suspecting is in my renderObject method my obj variable for my .map function is not being used inside my setAnswer function and causes another field to be created with a key name of "obj".
If this is the issue is it possible to have the setAnswer function in my renderObject Method to use the "obj" value of the map function and not the actual value of the word obj as key?
If not what would be the best way to approach this? I was thinking of adding a submit button at the bottom of the screen and updating all states with an onClick event listener. But now I'm think I'll have the same issue since the scope of the obj variable isn't resolved.
Any help would be greatly appreciated. I've only been doing this for a couple of months, any advice and feedback would also be appreciated!
You seem to be not using the dynamic key correctly while updating state. Also you need to update the value within the key and not override it. Also obj shouldn't be the second argument to onChange instead it must be received from the enclosing scope
const _renderObject= () => {
return Object.keys(answers).map((obj, i) => {
return(
<div key={obj}>
<label>{answers[obj].question}</label>
<input type="text" onChange={(e) => setAnswer(
{
...answers,
[obj]:{
...answers[obj],
value: e.target.value
}})} />
</div>
)
})
onChange={(e, obj) => setAnswer(
{
...answers,
obj:{
value: e.target.value
}})}
Here you spreading answers and add another object with the target value. that is the issue. Hope you understand the point.
TRY THIS
onChange={
(e, obj) => {
const updatedAnswer = answer.map(ans => ans.question === obj.question ? {...ans,value: e.target.value }:ans)
setAnswer(
{
...updatedAnswer
}
)
}
}
BW your object should contain propper ID for the key.
Its because you are not updating the keys correctly and you need to pass obj in input onchange callback as it make another reference, not the mapped array(obj). So in your case that obj is undefined. Here is working code :
const _renderObject = () => {
return Object.keys(answers).map((obj, i) => {
return (
<div key={obj}>
<label>{answers[obj].question}</label>
<input
type="text"
onChange={e =>
setAnswer({
...answers,
[obj]: { //take obj
...answers[obj],//keep specific object question
value: e.target.value//change only specfic object value
}
})
}
/>
</div>
);
});
};
Here is working url: https://codesandbox.io/s/hardcore-pike-s2hfx
I want to upstream the key of the component on change. I.e., I write
onChange= {(e) => this.props.onChange(e, key)}
However, in my case this.props.onChange also points in its turn to its props.onChange function. I've tried to define all the hierarchically "upper" assignments as onChange={this.props.onChange} as well as onChange={(e,key) => this.props.onChange(e,key)}, but nothing helps the fact that key is undefined when it comes to the actual function body.
Here's the full code of that what has to do with the peoblem:
handleValueChange = (e, key) => {
console.log('Event was on ' + key);
let currElemData = this.state.currentElementsData.slice();
currElemData[key] = {value: e.target.value};
this.setState({currentElementsData: currElemData});
};
addFilter = () => {
let activeFiltersNow = this.state.activeFilterElements.slice();
activeFiltersNow.push(<Filter key={this.state.filterCounter} onSelect={(e, key) => this.handleReferenceSelection(e, key)}
onChange={(e,key) =>this.handleValueChange(e, key)}/>);
let currElemData = this.state.currentElementsData.slice();
currElemData.push(InitialHelper.getInitialFilterData());
this.setState({activeFilterElements: activeFiltersNow, currentElementsData: currElemData, filterCounter: this.state.filterCounter++});
};
...
class Filter extends Component {
render() {
return <div className="filterContainer">
{this.props.isFirst? '':<FilterPrefix />}
<FilterReference onSelect={(e,key) => this.props.onSelect(e,key)} key={this.props.key}/>
<FilterAssignment />
<FilterValue onChange={(e,key) => this.props.onChange(e,key)} key={this.props.key}/>
<DeleteFilterButton />
</div>
}
}
...
class FilterValue extends Component{
render() {
return <input onChange={(e) => this.props.onChange(e,this.props.key)} key={this.props.key}/>
}
loadSuggestions(reference) {
}
}
I'm not quite sure what you are trying to do here, but I've noticed that you are falling for a pretty common ReactJs pitfall which is that:
Keys serve as a hint to React but they don't get passed to your
components. If you need the same value in your component, pass it
explicitly as a prop with a different name
meaning that this.props.key will always be undefined. So, I'm afraid that all the instances in where you are using this.props.key are not going to behave as you expect...
Is there a way to only add attributes to a React component if a certain condition is met?
I'm supposed to add required and readOnly attributes to form elements based on an Ajax call after render, but I can't see how to solve this since readOnly="false" is not the same as omitting the attribute completely.
The example below should explain what I want, but it doesn't work.
(Parse Error: Unexpected identifier)
function MyInput({isRequired}) {
return <input classname="foo" {isRequired ? "required" : ""} />
}
Apparently, for certain attributes, React is intelligent enough to omit the attribute if the value you pass to it is not truthy. For example:
const InputComponent = function() {
const required = true;
const disabled = false;
return (
<input type="text" disabled={disabled} required={required} />
);
}
will result in:
<input type="text" required>
Update: if anyone is curious as to how/why this happens, you can find details in ReactDOM's source code, specifically at lines 30 and 167 of the DOMProperty.js file.
juandemarco's answer is usually correct, but here is another option.
Build an object how you like:
var inputProps = {
value: 'foo',
onChange: this.handleChange
};
if (condition) {
inputProps.disabled = true;
}
Render with spread, optionally passing other props also.
<input
value="this is overridden by inputProps"
{...inputProps}
onChange={overridesInputProps}
/>
Here is an example of using Bootstrap's Button via React-Bootstrap (version 0.32.4):
var condition = true;
return (
<Button {...(condition ? {bsStyle: 'success'} : {})} />
);
Depending on the condition, either {bsStyle: 'success'} or {} will be returned. The spread operator will then spread the properties of the returned object to Button component. In the falsy case, since no properties exist on the returned object, nothing will be passed to the component.
An alternative way based on Andy Polhill's comment:
var condition = true;
return (
<Button bsStyle={condition ? 'success' : undefined} />
);
The only small difference is that in the second example the inner component <Button/>'s props object will have a key bsStyle with a value of undefined.
Here is an alternative.
var condition = true;
var props = {
value: 'foo',
...(condition && { disabled: true })
};
var component = <div {...props} />;
Or its inline version
var condition = true;
var component = (
<div value="foo" {...(condition && { disabled: true })} />
);
Here's a way I do it.
With a conditional:
<Label
{...{
text: label,
type,
...(tooltip && { tooltip }),
isRequired: required
}}
/>
I still prefer using the regular way of passing props down, because it is more readable (in my opinion) in the case of not have any conditionals.
Without a conditional:
<Label text={label} type={type} tooltip={tooltip} isRequired={required} />
Let’s say we want to add a custom property (using aria-* or data-*) if a condition is true:
{...this.props.isTrue && {'aria-name' : 'something here'}}
Let’s say we want to add a style property if a condition is true:
{...this.props.isTrue && {style : {color: 'red'}}}
You can use the same shortcut, which is used to add/remove (parts of) components ({isVisible && <SomeComponent />}).
class MyComponent extends React.Component {
render() {
return (
<div someAttribute={someCondition && someValue} />
);
}
}
If you use ECMAScript 6, you can simply write like this.
// First, create a wrap object.
const wrap = {
[variableName]: true
}
// Then, use it
<SomeComponent {...{wrap}} />
Using undefined works for most properties:
const name = "someName";
return (
<input name={name ? name : undefined} />
);
This should work, since your state will change after the Ajax call, and the parent component will re-render.
render : function () {
var item;
if (this.state.isRequired) {
item = <MyOwnInput attribute={'whatever'} />
} else {
item = <MyOwnInput />
}
return (
<div>
{item}
</div>
);
}
For some boolean attributes listed by React [1]:
<input disabled={disabled} />
// renders either `<input>` or `<input disabled>`
For other attributes:
<div aria-selected= {selected ? "" : undefined} />
// renders either `<div aria-selected></div>` or `<div></div>`
[1] The list of boolean attributes: https://github.com/facebook/react/blob/3f9480f0f5ceb5a32a3751066f0b8e9eae5f1b10/packages/react-dom/src/shared/DOMProperty.js#L318-L345
For example using property styles for custom container
const DriverSelector = props => {
const Container = props.container;
const otherProps = {
...( props.containerStyles && { style: props.containerStyles } )
};
return (
<Container {...otherProps} >
In React you can conditionally render Components, but also their attributes, like props, className, id, and more.
In React it's very good practice to use the ternary operator which can help you conditionally render Components.
An example also shows how to conditionally render Component and its style attribute.
Here is a simple example:
class App extends React.Component {
state = {
isTrue: true
};
render() {
return (
<div>
{this.state.isTrue ? (
<button style={{ color: this.state.isTrue ? "red" : "blue" }}>
I am rendered if TRUE
</button>
) : (
<button>I am rendered if FALSE</button>
)}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
From my point of view the best way to manage multiple conditional props is the props object approach from #brigand. But it can be improved in order to avoid adding one if block for each conditional prop.
The ifVal helper
rename it as you like (iv, condVal, cv, _, ...)
You can define a helper function to return a value, or another, if a condition is met:
// components-helpers.js
export const ifVal = (cond, trueValue=true, falseValue=null) => {
return cond ? trueValue : falseValue
}
If cond is true (or truthy), the trueValue is returned - or true.
If cond is false (or falsy), the falseValue is returned - or null.
These defaults (true and null) are, usually the right values to allow a prop to be passed or not to a React component. You can think to this function as an "improved React ternary operator". Please improve it if you need more control over the returned values.
Let's use it with many props.
Build the (complex) props object
// your-code.js
import { ifVal } from './components-helpers.js'
// BE SURE to replace all true/false with a real condition in you code
// this is just an example
const inputProps = {
value: 'foo',
enabled: ifVal(true), // true
noProp: ifVal(false), // null - ignored by React
aProp: ifVal(true, 'my value'), // 'my value'
bProp: ifVal(false, 'the true text', 'the false text') // 'my false value',
onAction: ifVal(isGuest, handleGuest, handleUser) // it depends on isGuest value
};
<MyComponent {...inputProps} />
This approach is something similar to the popular way to conditionally manage classes using the classnames utility, but adapted to props.
Why you should use this approach
You'll have a clean and readable syntax, even with many conditional props: every new prop just add a line of code inside the object declaration.
In this way you replace the syntax noise of repeated operators (..., &&, ? :, ...), that can be very annoying when you have many props, with a plain function call.
Our top priority, as developers, is to write the most obvious code that solve a problem.
Too many times we solve problems for our ego, adding complexity where it's not required.
Our code should be straightforward, for us today, for us tomorrow and for our mates.
just because we can do something doesn't mean we should
I hope this late reply will help.
<input checked={true} type="checkbox" />
In react functional component you can try something like this to omit unnecessary tag property.
<div className="something" ref={someCondition ? dummyRef : null} />
This works for me if I need to omit tags like ref, class, etc. But I don't know if that's work for every tag property
<Button {...(isWeb3Enabled ? {} : { isExternal: true })}>
Metamask
</Button>
Given a local variable isRequired You can do the following in your render method (if using a class) or return statement (if using a function component):
<MyComponent required={isRequired ? 'true' : undefined} />
In this case, the attribute will not be added if isRequired is undefined, false, or null (which is different from adding the attribute but setting it to 'false'.) Also note that I am using strings instead of booleans in order to avoid a warning message from react (Boolean value received on non-boolean attribute).
Considering the post JSX In Depth, you can solve your problem this way:
if (isRequired) {
return (
<MyOwnInput name="test" required='required' />
);
}
return (
<MyOwnInput name="test" />
);
I think this may be useful for those who would like attribute's value to be a function:
import { RNCamera } from 'react-native-camera';
[...]
export default class MyView extends React.Component {
_myFunction = (myObject) => {
console.log(myObject.type); //
}
render() {
var scannerProps = Platform.OS === 'ios' ?
{
onBarCodeRead : this._myFunction
}
:
{
// here you can add attribute(s) for other platforms
}
return (
// it is just a part of code for MyView's layout
<RNCamera
ref={ref => { this.camera = ref; }}
style={{ flex: 1, justifyContent: 'flex-end', alignItems: 'center', }}
type={RNCamera.Constants.Type.back}
flashMode={RNCamera.Constants.FlashMode.on}
{...scannerProps}
/>
);
}
}
in an easy way
const InputText= ({required = false , disabled = false, ...props}) =>
(<input type="text" disabled={disabled} required={required} {...props} />);
and use it just like this
<InputText required disabled/>
In addition, you can make other value to Boolean
const MyComponent = function() {
const Required = "yes";
return (
<input
required={Required === "yes"}
type="text"
key={qs.Name}
name="DefaultValue"
label={qs.QuestionTitle}
onChange={(event) => handleInputChange(index, event)}
placeholder={qs.QuestionTitle}
/>
);
}
If it is for a limited number of properties this will do
function MyInput({isRequired}) {
if (isRequired) {
return <input classname="foo" isRequired={isRequired} />
}
return <input classname="foo" />
}
If you have a large number of properties, it will be difficult to write if else conditions for every property and return accordingly. For that, you can push those properties in an object and use the spread operator in the returned element.
function MyInput({ prop1, prop2, ...propN }) {
const props = {};
if (prop1) props.prop1 = prop1;
.
.
.
if (propN) props.propN = propN;
return <input classname="foo" {...props} />
}
You must set as undefined the value for when you do not need the attribute
Example:
<a data-tooltip={sidebarCollapsed?'Show details':undefined}></a>
In React, we pass values to component from parent to child as Props. If the value is false, it will not pass it as props. Also in some situation we can use ternary (conditional operator) also.