added item to state but can't render - reactjs

I managed to add Item to the list but can't render it.
My App.js:
class App extends Component {
constructor(props) {
super(props);
this.state = {
recipes: [{
...sample data...
}]
}
}
createRecipe(recipe) {
this.state.recipes.push({
recipe
});
this.setState({ recipes: this.state.recipes });
}
render() {
...
export default App;
and my RecipeAdd:
export default class RecipeAdd extends Component {
constructor(props) {
super(props);
this.state = {
url: '',
title: '',
description: '',
error: ''
};
}
...event handlers...
onSubmit = (e) => {
e.preventDefault();
if (!this.state.url || !this.state.title || !this.state.description) {
this.setState(() => ({ error: 'Please provide url, title and description.'}));
} else {
this.setState(() => ({ error: ''}));
this.props.createRecipe({
url: this.state.url,
title: this.state.title,
description: this.state.description
});
}
}
render () {
return (
<div>
...form...
</div>
)
}
}
In React dev tools I see the recipe is added with 'recipe'. How should I change my createRecipe action to add new recipe properly? :

It looks like you're wrapping the recipe in an extra object. You can see in your screenshot that index 3 has an extra recipe property.
It should be more like this -- just .push the recipe object directly:
createRecipe(recipe) {
this.state.recipes.push(recipe);
this.setState({ recipes: this.state.recipes });
}
Even better would be to not mutate the state object directly, by using concat instead:
createRecipe(recipe) {
this.setState({
recipes: this.state.recipes.concat([recipe])
});
}

Related

Why is setState(this.state) not triggering a rerender?

I am trying to get my component to rerender after deleting an entry from the table.
I have tried binding my functions and using this.forceUpdate() as well but nothing seems to work. Any help will be much appreciated!
This is my component
class Directory extends Component {
constructor() {
super();
this.state = {
tenantData: [],
searchText: "",
searchedColumn: "",
tenantInfo: {},
visible: false,
userId: null,
rerender: "",
};
// this.delTenant = this.delTenant.bind(this);
this.deleteAudit = deleteAudit.bind(this);
// this.deleteTenant = this.deleteTenant.bind(this);
// this.onDeleteClick = this.onDeleteClick.bind(this);
}
This is my delete function within my component
onDeleteClick = () => {
this.setState({
...this.state,
visible: false,
});
var tenantList = this.state.tenantData;
for (var i = 0; i < tenantList.length; i++) {
if (tenantList[i].userId == this.state.userId) {
console.log(this.state.userId);
delTenant({ _id: tenantList[i]._id });
deleteTenant({ _id: this.state.userId });
this.deleteAudit({ tenantID: tenantList[i]._id }).then(() => {
this.setState(this.state); // this should rerender the component but it does not
console.log("force update");
});
break;
}
}
And this is my AntD component
<Modal
title="Modal"
visible={this.state.visible}
onOk={this.onDeleteClick}
onCancel={this.hideModal}
okText="Confirm"
cancelText="Cancel"
>
<p>Are you sure you want to delete this Tenant?</p>
</Modal>
EDIT:
Soultion found - My component did rerender, but my tenantData state was unchanged as I forgot to get the data from the database after deleting the entry.
this.deleteAudit({ tenantID: tenantList[i]._id }).then(() => {
// this.setState(this.state); // this should rerender the component but it does not
this.getTenantFunction(); // this gets the new data from database and sets the state of tenantData to the updated one
});
this.setState(this.state); // this should rerender the component but it does not
React doesn't re-render your component if your state or props doesn't change.
I don't know why forceUpdate doesn't work, look at this example
import React from "react";
export default class App extends React.Component {
constructor() {
super();
this.state = {
tenantData: [],
searchText: "",
searchedColumn: "",
tenantInfo: {},
visible: false,
userId: null,
rerender: ""
};
}
onDeleteClick = () => {
console.log("click");
};
onDeleteClickForced = () => {
console.log("clickForced");
this.forceUpdate();
};
render = () => {
console.log("render");
return (
<>
<button onClick={() => this.onDeleteClick()}>Not forced</button>
<button onClick={() => this.onDeleteClickForced()}>Forced</button>
</>
);
};
}

How to use react-data-table-component to display array stored in the constructor this:state?

I am new to the React, And I want to using react-data-table-component to display my fetch data from Api in a sorted table. but the issue I do not know the correct method to use the react-data-table-component.and the instruction of react-data-table-component do not include such example.
Following is my code:
I was trying to put offence or this.state.offence direct into data, but show nothing, anyone please give me some advises about the correct way to use this or some other way create sorted table to show this data.and there is link to the react-data-table-component a link:
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import { off } from 'rsvp';
import DataTable from 'react-data-table-component';
const columns = [
{
name: 'Offences',
selector: 'Offences',
sortable: true,
},
{
name: 'Year',
selector: 'year',
sortable: true,
right: true,
},
];
class SignInForm extends Component {
constructor() {
super();
this.state = {
email: '',
password: '',
offence:[],
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleClick =this.handleClick.bind(this);
this.handleLogin =this.handleLogin.bind(this);
}
handleClick(){
const url ="https://xxxxxxxxxx.sh/offences";
fetch(url)
.then(response => {
console.log(response.clone().json())
console.log(response.headers.get('Content-Type'))
if (response.ok) {
return response.clone().json();
} else {
throw new Error('Something went wrong ...');
}
})
.then((res) =>{
console.log(res)
this.setState({
offence: [res]
});
}) // get just the list of articles
console.log(this.state.offence);
}
render() {
return (
<button className="FormField__offence" onClick{this.handleClick}>offence</button>
</div>
<div>
<DataTable
title="Offences"
columns={columns}
data={this.state.offence}
/>
</div>
</form>
</div>
);
}
}
export default SignInForm;
I was expecting one column decent table show
const columns = [
{
name: 'Offences',
selector: 'Offences',
sortable: true,
}
];
class SignInForm extends React.Component {
constructor() {
super();
this.state = {
email: '',
password: '',
offence: [],
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
const url = "https://xxxxxxxx.sh/offences";
fetch(url)
.then(response => {
if (response.ok) {
return response.clone().json();
} else {
throw new Error('Something went wrong ...');
}
})
.then((res) => {
this.setState({
offence: res.offences.map((item,id) => ({id, Offences: item}))
});
}) // get just the list of articles
}
render() {
console.log(this.state.offence)
return (
<div className="App">
<button
className="FormField__offence"
onClick={this.handleClick}>offence</button>
<DataTable
title="Offences"
columns={columns}
data={this.state.offence}
/>
</div>
);
}
}
Live Link
Replace this,
this.setState({
offence: [res]
});
with this,
this.setState({
offence: res
});
In your case this is res from API call
{offences: Array(88)}
offences: (88) ["Advertising Prostitution", ... "Weapons Act Offences", "Weapons Act Offences - Other"]
__proto__: Object
So you can get offences like this,
this.setState({
offence: res.offences
});

Discarding changes when using Formik with React

I am using Formik form with React. Whenever the user submits (handleSubmit), I put an option whether or not to discard the change or keep the change.
In my render,
<Formik
initialValues={this.state.experiment}
onSubmit={this.handleSubmit}
component={formikProps => (
<ExperimentForm {...formikProps} submitText="Save Changes" />
)}
/>
handleSubmit()
handleSubmit(formdata: any, actions: any) {
const data = processFormData(formdata);
let changes = this.detectChanges(this.state.experiment, data);
this.setState({ tempFormData: data });
// changed field exists
if (changes.length !== 0) {
this.setState({
isDialogOpen: true,
changedFields: changes,
});
} else {
actions.setSubmitting(false);
this.setState({
message: 'Nothing Changed',
});
}
}
keepChanges() and discardChanges()
keepChanges () {
const data = this.state.tempFormData
makeMutation(UpdateExperimentQuery, {
update: {
id: this.props.match.params.id,
data,
},
})
.then(responseData => {
console.log(responseData)
this.setState({ isDialogOpen: false });
this.props.history.push('/i/experiments');
})
.catch(err => {
this.setState({
message: 'Error Updating Experiment',
});
console.log(err);
});
}
discardChanges () {
this.setState({ isDialogOpen: false });
this.componentWillMount();
}
The keepChanges() successfully updates the data with the given field, but discardChanges just closes the dialog but does not reset the data to original value even though I try to call componentWillMount() which fetches and renders the original unchanged data in the DB.
How can I reset the fields when I choose to discard the changes?
Edit
discardChanges () {
this.formik.current.resetForm();
this.setState({ isDialogOpen: false });
this.componentWillMount();
}
//I get an error when I do React.createRef();
class EditExperiment extends Component<EditExperimentProps, EditState> {
constructor(props: EditExperimentProps) {
super(props);
this.formik = React.createRef();
this.state = {
experiment: null,
message: null,
changedFields: [],
isDialogOpen: false,
tempFormData: []
};
this.handleSubmit = this.handleSubmit.bind(this);
this.clearMessage = this.clearMessage.bind(this);
this.detectChanges = this.detectChanges.bind(this);
this.keepChanges = this.keepChanges.bind(this);
this.discardChanges = this.discardChanges.bind(this);
}
EDIT2
type EditExperimentProps = {
history: RouterHistory,
match: Match,
experiments: ExperimentsState,
refetch: () => void,
};
type EditState = {
experiment: ?Experiment,
message: ?string,
};
class EditExperiment extends Component<EditExperimentProps, EditState> {
constructor(props: EditExperimentProps) {
super(props);
this.formik = React.createRef();
this.state = {
experiment: null,
message: null,
changedFields: [],
isDialogOpen: false,
tempFormData: []
};
this.handleSubmit = this.handleSubmit.bind(this);
this.clearMessage = this.clearMessage.bind(this);
this.detectChanges = this.detectChanges.bind(this);
this.keepChanges = this.keepChanges.bind(this);
this.discardChanges = this.discardChanges.bind(this);
}
To reset the Formik you need to call resetForm - see an example here.
handleSubmit(formdata: any, actions: any) {
...
// changed field exists
if (changes.length !== 0) {
...
} else {
actions.setSubmitting(false);
actions.resetForm();
}
}
EDIT:
There is another way to get "actions" and call them wherever in component by using react refs:
constructor(props) {
super(props);
this.formik = React.createRef();
}
//somewhere in render
<Formik
ref={this.formik}
initialValues={this.state.experiment}
onSubmit={this.handleSubmit}
component={formikProps => (
<ExperimentForm {...formikProps} submitText="Save Changes" />
)}
/>
// now somewhere else in the same component ...
componentDidUpdate(prevProps) {
if(somethingHappend) {
if(this.formik.current) {
this.formik.current.resetForm();
}
}
}
You need to include the initial state when you want to use resetForm. Example:
this.formik.current.resetForm(this.initialState.experiment);
This means you need to save the initialState too:
constructor(props) {
super(props);
this.initialState = this.state;
}

react child component not updating after parent state changed

I am trying to make a react messaging app where the channel page is composed a channel bar Channel.js (Parent Component) with a general and random channel and a message-list ChannelMessage.js (Child Component).
Currently, I can click on the channel bar and it changes the url and this.props.channelName, but the child component displays the same text, regardless of Link clicked. I believe it is because ComponentDidMount does not get called in the child component. How would I go about updating/rerendering the child component to get ComponentDidMount to reload?
Channel.js
export default class Channel extends React.Component {
constructor(props) {
super(props);
this.state = {
channelName: 'general'
};
this.handleSignOut = this.handleSignOut.bind(this);
}
...
render() {
return (
<div className="container">
<div className="row">
<div className="channel-list col-lg-2">
<h2>Channels</h2>
<ul className="list-group">
<li><Link to="/channel/general"
onClick={() => this.setState({ channelName: 'general' })}>General</Link></li>
<li><Link to="/channel/random"
onClick={() => this.setState({ channelName: 'random' })}>Random</Link></li>
</ul>
<div className="footer-segment">
...
</div>
</div>
<ChannelMessages channelName={this.state.channelName} />
</div>
</div>
);
}
ChannelMessages.js
export default class ChannelMessages extends React.Component {
constructor(props) {
super(props);
this.state = {
channelName: this.props.channelName,
message: '',
messages: [],
textValue: ''
}
...
}
componentWillReceiveProps(nextProps) {
this.setState({channelName: nextProps.channelName})
}
componentDidMount() {
this.messageRef = firebase.database().ref('messages/' + this.state.channelName);
this.messageRef.limitToLast(500).on('value', (snapshot) => {
let messages = snapshot.val();
if (messages !== null) {
let newMessages = [];
for (let message in messages) {
newMessages.push({
author: {
displayName: messages[message].author.displayName,
photoURL: messages[message].author.photoURL,
uid: messages[message].author.uid
},
body: messages[message].body,
createdAt: messages[message].createdAt,
id: message
});
}
this.setState({ messages: newMessages, textValue: newMessages.body });
}
console.log(this.state.textValue)
});
}
componentWillUnmount() {
this.messageRef.off('value');
}
handleSubmitMessage(event) {
event.preventDefault();
let user = firebase.auth().currentUser;
this.messageRef.push().set({
author: {
displayName: user.displayName,
photoURL: user.photoURL,
uid: user.uid
},
body: this.state.message,
createdAt: firebase.database.ServerValue.TIMESTAMP
});
this.setState({ message: '' });
}
...
render() {
return (...);
}
}
why are you not using the channelName directly from the props? you are using the componentDidMount event. It runs only once. If you want to run the firebase code whenever a new channel name is passed; extract the fetching logic function and run it in componentDidMount and componentDidUpate(check here if it's different from the previous one)
ComponentDidMount runs only once - more here
ComponentDidUpdate doesn't run on initial trigger - more here
export default class ChannelMessages extends React.Component {
constructor(props) {
super(props);
this.state = {
message: '',
messages: [],
textValue: ''
}
...
}
componentDidMount() {
const {channelName} = this.props;
fetchFromDb(channelName);
}
componentDidUpdate(prevProps) {
if (prevProps.channelName !== this.props.channelName) {
fetchFromDb(this.props.channelName);
}
}
componentWillUnmount() {
this.messageRef.off('value');
}
handleSubmitMessage(event) {
event.preventDefault();
let user = firebase.auth().currentUser;
this.messageRef.push().set({
author: {
displayName: user.displayName,
photoURL: user.photoURL,
uid: user.uid
},
body: this.state.message,
createdAt: firebase.database.ServerValue.TIMESTAMP
});
this.setState({ message: '' });
}
...
render() {
return (...);
}
}

React setState of array of objects

I have an array of 10 objects (Lets call them "Blogs") which contain title, description and image-URL properties. I need to wrap each of the properties in HTML tags and export them all so they all load on a webpage together.
With my current code, I am only getting 1 of the objects in the current state loading on the page. How do I get all the objects in the same state?
class NewBlogs extends React.Component {
constructor(props) {
this.state = {
title: [],
description: [],
image: [],
loading: true
};
}
componentDidMount() {
axios.get('/new-blogs').then(data => {
const blogs = data.data;
var component = this;
for(var i in blogs) {
component.setState({
title: blogs[i].title,
description: blogs[i].description,
image: blogs[i].image,
loading: false
});
}
})
.catch(function(error) {
console.log(error);
});
}
render() {
return (
<div>
<h2>New Blogs:</h2>
<h3>{this.state.title}</h3>
<em>{this.state.description}</em>
<img src={this.state.image}></img>
</div>
);
}
}
export default NewBlogs
I haven't run/test this but try something like this
The API call appears to return a list of objects. If so just set state once the xhr completes and set loading false once.
In the react render() is where you could iterate over your list. The easiest way to do that is with '.map()'. You then simply return react elements for each object in your list.
Also let's rename 'component' to 'list'
class NewBlogs extends React.Component {
constructor(props) {
this.state = {
list: [],
loading: true
};
}
componentDidMount() {
axios.get('/new-blogs').then(data => {
// const blogs = data.data;
// var component = this;
this.setState({list: data.data, loading: false })
// for(var i in blogs) {
// this.setState({
// title: blogs[i].title,
// description: blogs[i].description,
// image: blogs[i].image,
// loading: false
// });
// }
})
.catch(function(error) {
console.log(error);
});
}
render() {
return (
<div>
{this.state.list.map(e => (
<h2>New Blogs:</h2>
<h3>{e.title}</h3>
<em>{e.description}</em>
<img src={e.image}></img>
))}
</div>
);
}
}
export default NewBlogs

Resources