I am learning react and is in very early stages. I was trying to add a object to an array from another component to a different component. I was able to achieve this using props but now when I try to set it in this.state setState calls the render function again thus triggering this function again. I was able to solve this problem using a button but I don't want to do it this way. Is there some better way to do this?
getData() {
if (this.props.data.employeeData !== undefined) {
if (this.props.isNewEmployee === "true") {
this.setState({
isNewEmployee: "true",
});
setTimeout(() => {
if (this.state.isNewEmployee === "true") {
console.log(this.props.data.employeeData.firstName);
var joined = this.state.EmployeesList.concat(this.props.data.employeeData);
this.setState({
EmployeesList: joined,
isNewEmployee: "false",
})
}
else {
console.log("Don't know what's happening anymore");
}
}, 100);
}
else {
console.log("No new employee added");
}
}
else {
console.log("Something went wrong");
}
}
render() {
return (
<div>
{this.getData()}
{this.renderTable()}
{this.renderFloatingActionButton()}
</div>
)
}
If the intent of the props is to add the new data to the table I would be doing something in these lines.
componentWillReceiveProps(nextProps) {
if (this.props.data.employeeData && nextProps.isNewEmployee !== this.props.isNewEmployee) {
console.log(this.props.data.employeeData.firstName);
var joined = this.state.EmployeesList.concat(this.props.data.employeeData);
this.setState({
EmployeesList: joined,
isNewEmployee: "false",
});
} else {
console.log("No new employee added");
}
}
render() {
return (
<div>
{this.renderTable()}
{this.renderFloatingActionButton()}
</div>
)
}
Related
this is my code and I want to break it to multiple function(clean code!), for these two section
(status===edited) and (status === added) or two different function for (dataindex===ReportEffectiveDate) and (dataindex=== EffectiveDate).how can I put these if else statement in a separate function then I use this function for each status. totally I want to know which way is better : I use multiple if and else if or use multiple function for this code? thanks for your help!
function handleTableRowChange(record: LoadModel, oldValue: any, newValue: any, dataIndex: string) {
console.log(record, oldValue, newValue, dataIndex);
const status: RowStatus = tableStore.getRowStatus(record);
if (!!newValue) {
if (dataIndex === 'ReportEffectiveDate') {
if (record.EffectiveDate > record.ReportEffectiveDate) {
record.EffectiveDate = null;
tableStore.update(record);
Modal.error({
content: translate('ReportEffectiveDatecantbelessthanoldeffectivedate'),
});
console.log('error');
} else if (record.EffectiveDate == record.ReportEffectiveDate) {
record.ReportEffectiveDate = null;
tableStore.update(record);
}
}
if (dataIndex === 'EffectiveDate') {
if (status === 'added') {
const isValid: boolean = checkIsEffectiveDateValid(record);
if (!isValid) {
record.EffectiveDate = null;
tableStore.update(record);
}
} else if (status === 'edited') {
const maxEffectiveDateRecord: LoadModel = getMaxEffectiveDateRecord(record);
if (record.EffectiveDate > maxEffectiveDateRecord.EffectiveDate) {
if (newValue < maxEffectiveDateRecord.EffectiveDate) {
record.EffectiveDate = oldValue;
tableStore.update(record);
}
}
}
}
}
}
You are still going to need to add checks to see what to call. You can break things up into a function and call it. Might be simple to use a switch
function handleTableRowChange(........) {
..........
switch (dataIndex) {
case 'ReportEffectiveDate':
reportEffectiveDateFunction(record);
break;
case 'EffectiveDate':
effectiveDateFunction(record);
break;
case 'edited':
editedFunction(record);
break;
}
}
Other option is to use an object or class with the methods
const processingFunctions = {
ReportEffectiveDate: (report) => {
console.log('ReportEffectiveDate', report);
},
EffectiveDate: (report) => {
console.log('EffectiveDate', report);
},
edited: (report) => {
console.log('edited', report);
},
}
function handleTableRowChange(........) {
..........
const action = processingFunctions[dataIndex];
if (action) {
action(report);
} else {
// no command found....
}
}
It looks like your using TypeScript... Like any OOP-like language, you can actually take it one level higher and define an interface.
For readability sake, I would recommend using functions inside of the if-else-if-if... or switch over to case statements. Moving the code into functions helps with maintainability as you change the function itself, and you won't be changing the if-else part of the code, less code changes, less mistakes.
Following is the piece of code which is working fine, but I have one doubt regarding - const _detail = detail; code inside a map method. Here you can see that I am iterating over an array and modifying the object and then setting it to setState().
Code Block -
checkInvoiceData = (isUploaded, data) => {
if (isUploaded) {
const { invoiceData } = this.state;
invoiceData.map(invoiceItem => {
if (invoiceItem.number === data.savedNumber) {
invoiceItem.details.map(detail => {
const _detail = detail;
if (_detail.tagNumber === data.tagNumber) {
_detail.id = data.id;
}
return _detail;
});
}
return invoiceItem;
});
state.invoiceData = invoiceData;
}
this.setState(state);
};
Is this approach ok in React world or I should do something like -
const modifiedInvoiceData = invoiceData.map(invoiceItem => {
......
code
......
})
this.setState({invoiceData: modifiedInvoiceData});
What is the pros and cons of each and which scenario do I need to keep in mind while taking either of one approach ?
You cannot mutate state, instead you can do something like this:
checkInvoiceData = (isUploaded, data) => {
if (isUploaded) {
this.setState({
invoiceData: this.state.invoiceData.map(
(invoiceItem) => {
if (invoiceItem.number === data.savedNumber) {
invoiceItem.details.map(
(detail) =>
detail.tagNumber === data.tagNumber
? { ...detail, id: data.id } //copy detail and set id on copy
: detail //no change, return detail
);
}
return invoiceItem;
}
),
});
}
};
Perhaps try something like this:
checkInvoiceData = (isUploaded, data) => {
// Return early
if (!isUploaded) return
const { invoiceData } = this.state;
const updatedInvoices = invoiceData.map(invoiceItem => {
if (invoiceItem.number !== data.savedNumber) return invoiceItem
const details = invoiceItem.details.map(detail => {
if (detail.tagNumber !== data.tagNumber) return detail
return { ...detail, id: data.id };
});
return { ...invoiceItem, details };
});
this.setState({ invoiceData: updatedInvoices });
};
First, I would suggest returning early rather than nesting conditionals.
Second, make sure you're not mutating state directly (eg no this.state = state).
Third, pass the part of state you want to mutate, not the whole state object, to setState.
Fourth, return a new instance of the object so the object reference updates so React can detect the change of values.
I'm not saying this is the best way to do what you want, but it should point you in a better direction.
it's possible to set a timer to clear some reducer state? i have a state to show message of "success", "error", "warning"
Example:
Reducer:
const statusState = {
status: {action: '', result: ''}
}
...
CASE fetchContent:
return {
...state,
contents: [...state.contents, action.data],
status: {
...state.status,
action: action.type,
result: action.result
}
}
Component:
render(){
cost { status } = this.props
if(status.action == "something" && status.result == "success"){
alert("success");
}
}
const mapStateToProps = store => ({
status: store.initialState.status
});
export default connect(mapStateToProps)(Component);
if i don't clear status.action and status.result the alert will show all the time, it's possible to set a timer to clear it?
Create a clear action which clears status.action and status.result. Create a javscript timeout function that calls clear action after timeout. Something like below:
clearStatus = ()=>{
setTimeout(
function() {
//create a action that clears status and result
},3000
);
}
render(){
cost { status } = this.props
if(status.action == "something" && status.result == "success"){
//showMessage
clearStatus();
}
}
As you can see in the code below my ajax (i'm using fetch) is located within a handleSearch function, in App component. How can I delegate and make the ajax call in its own component? I come from angular 1, in angular there's thing like services/factories but I'm not sure how to do it in react.
const App = React.createClass({
getInitialState() {
return {
username: '',
profiles: [],
noUser : false
}
},
handleInputChange(e) {
this.setState({
username: e.target.value
})
},
handleSearch() {
if (!this.state.username) {
alert('username cannot be empty');
return false;
}
fetch('https://api.github.com/search/users?q=' + this.state.username)
.then(response => {
return response.json()
}).then(json => {
this.setState({
profiles: json.items
})
if(json.items.length === 0){
this.setState({noUsers : true});
}else{
this.setState({noUsers : false});
}
})
},
render() {
return (
<div>
<input type="text" placeholder="Github username" onChange={this.handleInputChange} value={this.state.username} />
<button onClick={this.handleSearch}>Search</button>
{this.state.profiles.length > 0 &&
<Users profiles={this.state.profiles} />
}
{this.state.noUsers &&
<p>No users found.</p>
}
</div>
)
}
});
http://codepen.io/eldyvoon/pen/ENMXQz this code need much refactoring.
Not everything in React has to be a component. It is still essentially plain old Javascript underneath. Extract the API logic out into it's own module/file, e.g.
GithubApi.js
module.exports.getUsers = function(username) {
return fetch('https://api.github.com/search/users?q=' + username)
.then(response => {
return response.json()
})
}
And then use it where ever it is required:
var GithubApi = require('/path/to/GithubApi.js');
const App = React.createClass({
..
..
handleSearch() {
if (!this.state.username) {
alert('username cannot be empty');
return false;
}
GithubApi.getUsers(this.state.username)
.then(json => {
this.setState({
profiles: json.items
})
if(json.items.length === 0){
this.setState({noUsers : true});
}else{
this.setState({noUsers : false});
}
})
},
..
..
This is just an example - how you want to design your module is up to you, depending on how specific or flexible, etc. you want it to be.
You don't need to write it in it's own React component, just write a plain old Javascript CommonJS module and either load it via require() or pass it in as a prop.
I'm a total newbie with React and I was trying to build an app with React, but after many hours of trying, I couldn't figure out why the elements that I want to delete onClick aren't getting deleted.
There are 2 similar posts on this, they say that you would need to use independent keys for every element. I tried to do that, I even created a different variable and increment it after every use. It just deletes the top element of the list.
How it works - 1) I have an array which is stored with some names channels, and I get data with that names and save that data into another array renderAll.
2) After that I filter those on how I want to render them, and then I render them with a function renderCards(). It also renders a button which if clicked, should delete the channel from the channel array and the respective data from the renderAll array
3) It also have a input field from which you can add more channels.
What doesn't work - The delete button deletes the top element instead of the element which is clicked.
I have the app running without the delete functionality
var App = React.createClass({
getInitialState() {
return {
status: 2
}
},
changeStatus(i) {
this.setState({
status: i
});
},
render() {
return (
<div>
<header><h1>Twitch Streamers</h1></header>
<Tabs status = {this.changeStatus} />
<Cards status = {this.state.status} />
</div>
);
}
});
const Cards = React.createClass({
getInitialState() {
return {
renderAll: [],
check: this.props.status,
channels: ["freecodecamp", "storbeck", "habathcx","meteos","RobotCaleb","noobs2ninjas","brunofin","comster404","cretetion","sheevergaming","TR7K","OgamingSC2","ESL_SC2"]
};
}, //AJAX REQUEST FUNCTION
getData(last) {
if(last === undefined) {
for(let i =0; i<this.state.channels.length;i++) {
let channel = this.state.channels[i];
$.getJSON("https://api.twitch.tv/kraken/streams/" + channel, (data) => {
$.getJSON("https://api.twitch.tv/kraken/channels/" + channel, (logo) => {
if(data.hasOwnProperty(status) === false) {
if(data.stream === null) {
this.setState({
renderAll: this.state.renderAll.concat([{channel: channel, url: `https://www.twitch.tv/${channel}`, status: 'offline', logo: logo.logo}])
});
} else {
this.setState({
renderAll: this.state.renderAll.concat([{channel: data.stream.channel.name, url: `https://www.twitch.tv/${channel}`, current: data.stream.channel.game + ' - ' + data.stream.channel.status, status: 'online', logo: logo.logo}])
});
}
}
});
})
.fail((jqxhr) => {
this.setState({
renderAll: this.state.renderAll.concat([{channel: channel, status: 'closed'}])
});
});
}
} else {
let channel = this.state.channels[this.state.channels.length - 1];
$.getJSON("https://api.twitch.tv/kraken/streams/" + channel, (data) => {
$.getJSON("https://api.twitch.tv/kraken/channels/" + channel, (logo) => {
if(data.hasOwnProperty(status) === false) {
if(data.stream === null) {
this.setState({
renderAll: this.state.renderAll.concat([{channel: channel, url: `https://www.twitch.tv/${channel}`, status: 'offline', logo: logo.logo}])
});
} else {
this.setState({
renderAll: this.state.renderAll.concat([{channel: data.stream.channel.name, url: `https://www.twitch.tv/${channel}`, current: data.stream.channel.game + ' - ' + data.stream.channel.status, status: 'online', logo: logo.logo}])
});
}
}
});
})
.fail((jqxhr) => {
this.setState({
renderAll: this.state.renderAll.concat([{channel: channel, status: 'closed'}])
});
});
}
},
componentWillMount() {
this.getData();
},
componentWillReceiveProps(prop) {
this.setState({
check: prop
});
}, //DELETE FUNCTION THAT DOESN'T WORK
delete(index) {
let newArr = this.state.channels.slice();
let newArrSecond = this.state.renderAll.slice();
newArr.splice(index, 1);
newArrSecond.splice(index, 1);
this.setState({
channels: newArr,
renderAll: newArrSecond
});
}, //RENDER CARDS FUNCTION
renderCards(i) {
if(i === 0 || i.status === 0) {
let cards = this.state.renderAll.map((item, i) => {
if(item.status === 'online') {
return <div className="online cards" key={i}><img src={item.logo} width="30px" height="30px" /><a target="_blank" href={item.url}><h3>{item.channel}</h3></a><button className="cross" onClick={this.delete}>✕</button><p>{item.current}</p></div>
}
});
return (
cards
)
} else if(i === 1 || i.status === 1) {
let cards = this.state.renderAll.map((item, i) => {
if(item.status === 'offline') {
return <div className="offline cards" key={i}><img src={item.logo} width="30px" height="30px"/><a target="_blank" href={item.url}><h3>{item.channel}</h3></a><button className="cross" onClick={this.delete}>✕</button><p>Channel is offline</p></div>
}
});
return (
cards
)
} else if(i === 2 || i.status === 2) {
let cards = this.state.renderAll.map((item, i) => {
if(item.status === 'offline') {
return <div className="offline cards" key={i}><img src={item.logo} width="30px" height="30px" /><a target="_blank" href={item.url}><h3>{item.channel}</h3></a><button className="cross" onClick={this.delete}>✕</button><p>Channel is offline</p></div>
} else if(item.status === 'online') {
return <div className="online cards" key={i}><img src={item.logo} width="30px" height="30px" /><a target="_blank" href={item.url}><h3>{item.channel}</h3></a><button className="cross" onClick={this.delete}>✕</button><p>{item.current}</p></div>
} else {
return <div className="closed cards" key={i}><h3>{item.channel}</h3><p>Account Closed</p></div>
}
});
return (
cards
)
}
},
newChannel(i) {
if(i.keyCode === 13) {
this.setState({channels: this.state.channels.concat([i.target.value])}, function() {
this.getData(1);
});
}
},
leave(i) {
i.target.value = '';
},
render() {
return (
<div id="cards-inside">
<input type='text' placeholder="+ Channel" onKeyDown={this.newChannel} onBlur={this.leave}/>
<ReactCSSTransitionGroup transitionName="example" transitionEnterTimeout={500} transitionLeaveTimeout={300}>
{this.renderCards(this.state.check)}
</ReactCSSTransitionGroup>
</div>
)
}
});
ReactDOM.render(<App />, document.getElementById("container-second"));
Your index is always 0, because you do not pass it when you call delete.
Therefore it always deletes top element.
Inside the JSX bit where you render your X, you should do:
onClick={this.delete.bind(this, i)}
Or try
OnClick={() => {this.delete(i)} }
to pass the index of the card clicked.
In React, you don't really want to "delete" nodes like you'd do with jQuery.
Let's say you have a list of names in your initial state:
class MyComponent extends React.Component {
state = {
names: ['Foo', 'Bar', 'Git', 'Get']
};
[...]
}
In your render() method, you are rendering them inside a ul:
render() {
const names = this.state.names.map(name => {
return <li key={name} onClick={this.remove.bind(this, name)}>{name}</li>;
});
return <ul>{names}</ul>;
}
In your remove() method you will have:
remove(name) {
this.setState({
names: this.state.names.filter(cur => cur !== name)
});
}
Now, every time you click on a name to remove it, you are removing the name from the names list, the component renders itself again, and the removed name will be removed from the DOM.
Working example:
http://codepen.io/FezVrasta/pen/MeWpzm?editors=0010