I have a component made with React Select. The options passed into the options prop on the Select depends upon state that users entered previously. Every time the component renders there are checks to see if selectOptions already includes items from the state array
<Select
styles={err === '' ? inputStyles : inputStylesErr}
className="basic-single"
classNamePrefix="select"
isClearable={true}
isSearchable={true}
isMulti={true}
placeholder={`Select or search health zones in ${province}, ${state.address.country}`}
options={selectOptions}
defaultValue={selectOptions.some((option) => option.value === state.healthZonesServed[0]) ? (
state.healthZonesServed.map((zone) => {
return { ['label']: zone, ['value']: zone }
})
) : ''}
onChange={(values) => handleAddHealthZones(values.map((value) => value.value))}
/>
const handleAddHealthZones = (value) => {
setState({
...state,
healthZonesServed: value
})
}
If a user populated their healthZonesServed array and then goes back and changes their province (the piece of state which controls the selectOptions) and then goes back to this component I need the healthZonesServed array to be reset to []
I do this in a useEffect. I can see in my console.log the healthZonesServed resets to an empty array on page load then somehow re-populates its previous values from somewhere. Would anyone have any insight as to why this is happening and a possible solution?
useEffect(() => {
if (selectOptions.some((option) => option.value === state.healthZonesServed[0])) {
return
} else {
setState({
...state,
healthZonesServed: []
})
console.log('HIT')
}
}, [])
The most probable reason, why this is not working is because you are using setState in a functional component. Try using the useState hook for the purpose of managing the state, in your case, setting the heathZoneServed array to empty array.
const [healthZoneServed,sethealthZoneServed] = useState([]);
sethealthZoneServed(value);
useEffect(() => {
if (selectOptions.some((option) => option.value === state.healthZonesServed[0])) {
return;
} else {
sethealthZonesServed([]);
console.log('HIT');
}
}, [healthZonesServed]);
Hope this was helpful.
**I'm trying to create an array with 5 values which I could use with nameArray[number].
I think that the declaration of the array is wrong but I don't know how I can fix it.
My idea is that: I have 5 buttons, when I click one of this, only one value of the 5 values in the state array change from false to true.
**
constructor(props) {
super(props);
this.state = {
activeButtons: [false, false, false, false, false]
};
}
cliccato = (e) => {
e.preventDefault();
const number = parseInt(e.target.id);
this.setState(
{
activeButtons: !this.state.activeButtons[number],
},
() => {
console.log(" "+ this.state.activeButtons[number]);
}
);
}
You're updating your state's activeButtons with a single boolean value, rather than an updated array.
You need to generate a new array and modify only the relevant element:
const newArray = [...this.state.activeButtons];
newArray[number] = !newArray[number];
this.setState({
activeButtons: newArray,
});
Declaration of the array is fine. You can make it shorter with Array(5).fill(false).
It's setting state part that needs work. In your current code, you are setting the state to the alternate of a boolean value, instead you need to set it to an array.
this.setState(prevState => ({
activeButtons: prevState.activeButtons.map((val, index) => {
if(index === number) {
return !val;
}
return val;
})
}));
Also, using the functional set state form here
It's because you're overwriting activeButtons every time with only one element value. You need to preserve the other values each time you want to update an element.
Using an Object would be a more graceful data structure in this case though.
this.setState(
{
activeButtons: {
...this.state.activeButtons
[number]: !this.state.activeButtons[number]
}
)
I am updating a sub array in a react state array of 'components' with a new key value like this.
this.setState({
components: {
...this.state.components,
[this.state.key]: {
...this.state.components[this.state.key],
[key]: v
}
}
});
this works but it changes this.state.components from an array into an object which I don't want.
I can do
var result = Object.keys(this.state.components).map(function (k) {
return { [k]: this.state.components[k] };
});
this.setState({components: result});
to fix the data but it seems messy and inefficient to set state twice. Is there a better way? I've tried various forms of using [] instead of {}, but from my understanding of the spread operator this should work.
You can use map on the components array currently in state and return the object as is if the key in the state doesn't match the component index, or change the [key] property if they do match.
this.setState(prevState => ({
components: prevState.components.map((c, index) =>
prevState.key === index ? { ...c, [key]: v } : c
)
}));
I'm going to to click in div and show a text in another div in multiple items.
I have got series of data that contains some objects in one array(json file) and it will be shown by react.The code will be done up tohandelrule = ((e,element,i) =>{....}) . There is an onClick function ({e => this.handelrule(e,element,i)}) for each item.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
OtherRooms: {},
divVisibles: {},
loadingVisible: {},
resultRule: {},
};
}
render() {
const { data } = this.state;
const renderHotel = data.map((item, i) => {
return <div class="item">
<div class="moreInfo" onClick={(e) => this.showDiv(e, item, i)}><span>show more data</span></div>
<div key={i} className={`${!this.state.loadingVisible[i] ? "unvisible" : "visible"}`}>
<div id="ballsWaveG">
</div>
</div>
<div id="box-info" key={i} className={` ${!this.state.divVisibles[i] ? "unvisible" : "visible"}`}>
<div class="table">
{this.state.OtherRooms[i]}
</div>
</div>
</div>
});
return (
<div>
{renderHotel}
</div>
);
}
showDiv = (e, element, i) => {
this.showLoading(e, element, i);
setTimeout(() => {
fetch('/json.bc', {
method: 'POST'
})
.then(response => response.text())
.then(text => {
var Maindata = JSON.parse(text.replace(/\'/g, '"'))
this.setState(prevState => ({
Details: {
...prevState.Details,
[i]: this.renderDetails(Maindata, i),
},
divVisibles: { ...prevState.divVisibles, [i]: !prevState.divVisibles[i] },
loadingVisible: { ...prevState.loadingVisible, [i]: "" }
}))
}).catch(error => console.error(error))
}, 1000);
}
renderDetails(element, i) {
var indents = [];
indents.push(<div>
<span>{this.renderRule(element, i)}</span>
<div key={i} className={`${!this.state.loadingVisible[i] ? "unvisible" : "visible"}`}>
<div id="ballsWaveG">
</div>
</div>
<div key={i}>{this.state.resultRule[i]}</div>
</div>
)
return (
indents
)
}
showLoading = (e, elem, val) => {
this.setState(prevState => ({
loadingVisible: { ...prevState.loadingVisible, [val]: !prevState.loadingVisible[val] }
}))
};
renderRule(element, i) {
return <span class="txtRul" onClick={e => this.handelruleRoom(e, element, i)}>Show Rule</span>
}
handelruleRoom = (e, element, i) => {
var mainprovider = element.id.provider
if (mainprovider == undefined) {
return ''
} else {
this.showLoading(e, element, i);
/////the loading whould not be shown //////
setTimeout(() => {
var return_rule = function () { ////This part will be done but the result will not be shown in class="resultRule" ///////
var tmp = null;
$.ajax({
'async': false,
'type': "POST",
'global': false,
'dataType': 'html',
'url': "rule.bc",
'data': { 'mainprovider': JSON.stringify(mainprovider), },
'success': (response) => {
tmp = response;
}
});
return tmp;
}();
return this.setState(prevState => ({
resultRule: { ...prevState.resultRule, [i]: return_rule }, ///In this part return_rule does not set in resultRule ////
loadingVisible: { ...prevState.loadingVisible, [i]: "" }
}))
}, 1000);
}
}
}
ReactDOM.render(<App />, document.getElementById('Result'));
Actually there is a problem with this part this.setState( prevState => ({....})
There are quite a few issues with your code. I'm going to do my best to point them out and show alternatives, not merely to be critical of you, but to help you learn!
You're using quite a few state values. Think about the problem you're trying to solve, and determine the minimum possible states you need to accomplish that. For example, some of the conditional rendering your currently handling in state, can be offloaded to the render method itself using a ternary operator to render a loading component if a state value is undefined
render() {
<div>
{
!this.state.value
? <Loading />
: this.state.value
}
</div>
}
You're using class="name" in some elements. Because in Javascript, class is a keyword, when setting class names on JSX elements, use the className="name" property instead.
You're nesting your fetch calls inside of setTimeouts. This is not a very reliable method of ensuring the fetch returns a value, as network calls should be treated as taking an arbitrary amount of time. If it takes more than 1 second to fetch those files (for whatever reason) then your whole app will break. You have a few alternatives: You can add a series of callbacks, chain together .then()s, or use the new async/await ES6 feature. I'm going to give you an example of how you could utilize async/await for much cleaner and more reliable code:
showDiv = async (event, element, index) => {
this.showLoading(event, element, index);
let response = await fetch("/json.bc", { method: "POST" });
await response.text();
let data = JSON.parse(text.replace(/\'/g, '\"'));
this.setState(prevState => ({
Details: {
...prevState.Details,
[index]: this.renderDetails(data, index)
},
divVisible: {
...prevState.divVisibles,
[index]: !prevState.divVisibles[index]
},
loadingVisible: {
...prevState.loadingVisible,
[index]: ""
}
}));
};
Since this showDiv method itself is asynchronous, when you call it in your top-level code, you will need to chain a .then() and add additional code after it to ensure the new state has taken effect going forward
render() {
// Note that you cannot use await in this top-level code
showDiv.then(() => {
// The rest of your code that relies on the state set in showDiv
}).catch(err => console.error(err));
}
You may save yourself headache of dealing with asyncronous operations by simply importing the files containing your data directly into your app. The only reason you would need to use fetch() as you are, is if you were grabbing your data from a separate backend (like if you had a separate server for your database and REST API). Otherwise, you can pull those files into the client-side bundle with everything else:
// Use whatever the relative path is to these files
import data from "./json.bc";
import rules from "./rules.bc";
The power of React lies in its component based model. I see that your using a lot of helper functions within the same class to render different aspects of your interface. It may work for now, but it looks messy, and is confusing to debug and maintain. Instead, try extracting some of that functionality into new component classes, and then importing them into your App component to render. There will be a learning curve to figure out passing props, and changing state of a parent from a child, but you will gain the benefits of using React as it was intended. Most notably, you can avoid having to store arrays of data for every single div you're rendering. By encapsulating the functionality into a component, each component can manage it's own state and properties.
In your render() method, you placed a semicolon after a div near the end of your renderHotel function. Any JavaScript you want to place within a JSX block, must be put inside curly braces { }, and semicolons are not required to terminate JSX elements. It may have been a typo, but just in case wanted to add this in.
Using var may be more familiar to you, but it's spilling the variable scope all over the place, keeping variables that are no longer needed in memory because you're allowing them a class-wide scope. Instead, use const or let, the keep the variable context contained within their lexical scope. If you need access to one of those locally scoped variables, it can be solved through composition (the way you've organized your functions).
As to the problem you originally posted about, all of the problems I listed may be contributing; typos in JSX, class instead of className, async timing problems. Although perhaps the primary issue is regarding number 3. You are returning the value of tmp and binding it to return_rule possibly before the AJAX call has time to resolve. I would recommend refactoring your handleRuleRoom function using the async/await example I provided as a guide.
Your handelruleRoom function has no index declared inside its scope
So you just need to figure out what is index inside your handelruleRoom function which is probably the last argument of it you called it - i
Try changing index to i
like this
this.showLoading(e, DetailsRoomJ, i);
// and also here
return this.setState(prevState => ({
resultRule: { ...prevState.resultRule, [i]: return_rule },
loadingVisible: { ...prevState.loadingVisible, [i]: "" }
}))
Also if you care there are many things wrong with this snippet
class should be className
are you sure you need a sync function (because you are blocking you browser until you get your response)
I am new to react and am trying to add string values in an array. I am using Material-UI objects.
My state has
this.state: {
roles: []
}
A button pushes an undefined element in roles, incrementing its length.
clickAddRole = () => {
this.setState({roles: this.state.roles.concat([undefined]) });
};
So now we have some length to the roles array.
The Textfield is generated with
this.state.roles.map((item, i)=> {
return (
<TextField id={'roles['+i+']'} label={'role '+i} key={i} onChange={this.handleChange('roles['+i+']')} />
)
})
the onchange event is handled as below
handleChange = name => event => {
console.log(name);
this.setState({[name]: event.target.value});
console.log(this.state.roles);
}
The console.log statements generate output like
roles[0]
[undefined]
I expect
roles[0]
["somedata"]
what is going wrong here? The data does not get set in the roles array.
The whole code file is
const styles = theme => ({
error: {
verticalAlign: 'middle'
},
textField: {
marginLeft: theme.spacing.unit,
marginRight: theme.spacing.unit,
width: 300
},
submit: {
margin: 'auto',
marginBottom: theme.spacing.unit * 2
}
})
class AddModule extends Component {
constructor() {
super();
this.state = {
roles:[],
open: false,
error: ''
}
}
clickSubmit = () => {
const module = {
roles: this.state.roles || undefined
}
create(module).then((data) => {
if (data.error) {
this.setState({error: data.error})
} else {
this.setState({error: '', 'open': true});
}
})
}
clickAddRole = () => {
this.setState({roles: this.state.roles.concat([undefined]) });
};
handleChange = name => event => {
console.log(name);
this.setState({[name]: event.target.value});
console.log(this.state.roles);
}
render() {
const {classes} = this.props;
return (
<div>
<Button onClick={this.clickAddRole} >Add Role</Button>
{
this.state.roles.map((item, i)=> {
return (
<TextField className={classes.textField} id={'roles['+i+']'} label={'role '+i} key={i} onChange={this.handleChange('roles['+i+']')} />
)
})
}
</div>
)
}
}
I think you're making the whole code a bit overcomplicated creating names for each input field. What I would do is change the handleRolesChange or handleChange (not really sure if you changed its name) method so that it takes the index instead of a name.
handleRolesChange = index => event => {
const { roles } = this.state;
const newRoles = roles.slice(0); // Create a shallow copy of the roles
newRoles[index] = event.target.value; // Set the new value
this.setState({ roles: newRoles });
}
Then change the render method to something like this:
this.state.roles.map((item, index) => (
<TextField
id={`roles[${index}]`}
label={`role ${index}`}
key={index}
onChange={this.handleRolesChange(index)}
/>
))
Guy I have the issue (maybe temporarily).
I an array-element is a child of the array. so changing the data in the array-element does not need setState.
So this is what I did....
handleRolesChange = name => event => {
const i = [name];
this.state.roles[i]=event.target.value;
}
I also change the Textfield onchange parameter to
onChange={this.handleRolesChange(i)}
where i is the index starting from zero in the map function.
All this works perfectly as I needed.
However, if you think that I have mutated the roles array by skipping setState, I will keep the Question unanswered and wait for the correct & legitimate answer.
Thanks a lot for your support guys.
We must try and find the solution for such basic issues. :)
Are you positive it's not being set? From React's docs:
setState() does not always immediately update the component. It may
batch or defer the update until later. This makes reading this.state
right after calling setState() a potential pitfall. Instead, use
componentDidUpdate or a setState callback (setState(updater,
callback)), either of which are guaranteed to fire after the update
has been applied. If you need to set the state based on the previous
state, read about the updater argument below.
Usually logging state in the same block you set the code in will print the previous state, since state has not actually updated at the time the console.log fires.
I would recommend using React Dev Tools to check state, instead of relying on console.log.