I would like to the list of options to go back to empty on getting new a vesselname. Using the below code, updating optionList to an empty array inside componentDidUpdate does not trigger a re-render. So on selecting a new vessel, the previous keydate remains selected, although if I click the dropdown, I can see it is populated with new values from the api call.
KeydateDropdown.js
import React from 'react';
import Select from 'react-select';
import axios from 'axios';
class KeydateDropdown extends React.Component {
constructor(props) {
super(props);
this.state = {
optionList: []
};
};
componentDidUpdate(prevProps) {
let vesselname = this.props.vesselname;
if (prevProps.vesselname !== vesselname) {
let keydateList = [];
this.setState({
optionList: keydateList
});
axios.get('list-keydates', {
params: {
vesselname: vesselname
}
})
.then((response) => {
let data = response.data['intervention'];
data.forEach((element) => {
keydateList.push({ value: element, label: element });
});
this.setState({ optionList: keydateList });
})
}
}
render() {
return (
<Select
isDisabled={this.props.isDisabled}
onChange={this.props.handleKeydateChange}
options={this.state.optionList}
className={styles.dropdown}
/>
);
}
}
export default KeydateDropdown;
use
componentWillUpdate(nextProps)
or
componentDidUpdate(prevProps, prevState)
Related
I'm unable to update the state using the axios response in a class component. It works just fine with function based components. Could somebody please point out what is causing this issue. I'm new to react and tried most of the possible answers out there.
code :
import React, { Component } from "react";
import "./Table.css";
import { Button } from "./Button";
import { Table } from "react-bootstrap";
import axios from "axios";
import Example from "./Progress";
class Tables extends Component {
constructor(props) {
super(props);
this.state = {
submitted: false,
task_id: "",
};
this.handlesubmit = this.handlesubmit.bind(this);
}
handlesubmit(event) {
var self = this;
event.preventDefault();
axios({
method: "post",
url: "http://127.0.0.1:8000/api/test/",
data: {
project_name: "test",
},
}).then(function (response) {
console.log(response.data.task_id); //prints taskid (12345678)
self.setState({
task_id: response.data.task_id,
});
console.log(self.task_id); //prints undefined
});
this.setState({ submitted: true }); //works fine, state is set to true
console.log(this.task_id); //prints undefined
}
render() {
let modal;
let task_id = this.state.task_id;
let submitted = this.state.submitted;
if (submitted === true) {
modal = <Example pro="test" task={task_id} />;
}
return (
<div className="table-div">
<Button
buttonStyle="btn--secondary"
buttonSize="btn--small--opt"
onClick={this.handlesubmit}
>
test
</Button>
{modal}
</div>
);
}
}
export default Tables;
You should update your state inside then when the axios call succeeds:
handleSubmit(event) {
event.preventDefault();
axios({...})
.then((response) => {
this.setState({
task_id: response.data.task_id,
submitted: true
})
})
.catch((error) => { /* handle errors appropriately */ })
}
Then, inside your render method, make sure that both task_id and submitted have appropriate values before rendering the modal:
render() {
const { task_id, submitted } = this.state
const modal = submitted && task_id !== ""
? <Example pro="test" task={task_id} />
: null // don't render anything
return (
<div className="table-id">
<Button ...>test</Button>
{modal}
</div>
)
}
/keywordsActions
import { UPDATE_KEYWORDS } from "./actionTypes";
import queryString from "query-string";
const keywordsArrayFromUrl = () => {
const query = queryString.parse(window.location.search);
if (query.keywords) {
const removeDuplicate = new Set(query.keywords.split(" "));
return Array.from(removeDuplicate);
}
return [];
};
export function updateKeywords() {
return async dispatch => {
dispatch({
type: UPDATE_KEYWORDS,
payload: await keywordsArrayFromUrl()
});
};
}
/keywordReducer
import { UPDATE_KEYWORDS } from "../actions/actionTypes";
export default function(state = [], action) {
switch (action.type) {
case UPDATE_KEYWORDS:
return action.payload;
default:
return state;
}
}
/SearchBar -- React Component
import React, { Component } from "react";
import { withRouter } from "react-router-dom";
//Redux
import { connect } from "react-redux";
import { updateKeywords } from "../store/actions/KeywordsAction";
class Searchbar extends Component {
constructor(props) {
super(props);
this.state = {
keywords : this.props.keywords
keywordsString: this.props.keywords.join(" ")
};
}
componentDidMount() {
this.props.updateKeywords();
console.log(this.props)
setTimeout(() => console.log(this.props), 10);
}
_handleChange = e => {
this.setState({ keywordsString: e.target.value });
};
_handleSearch = value => {
this.setState({ keywordsString: value });
this.props.history.push(`/search?keywords=${value}`);
};
render() {
return (
<Search
className="Searchbar"
placeholder="Cauta prin iBac..."
value={this.state.keywordsString}
onChange={this._handleChange}
onSearch={this._handleSearch}
/>
);
}
}
const mapStateToProps = state => {
return {
keywords: state.keywords
};
};
export default connect(
mapStateToProps,
{ updateKeywords }
)(withRouter(Searchbar));
I want to save the keywords from the Url to the store and then pass it to the Search bar state.
But i dont understand this :
componentDidMount() {
this.props.updateKeywords();
console.log(this.props); // this.props.keywords is empty
setTimeout(() => console.log(this.props), 10); // After 10 ms this.props.keywords is no empty
}
After 10 ms the props of Searchbar gets updated but the component doesn't render again.
Sorry for my question, I am really new to React / Redux. Please let me know what I am doing wrong. Thank you all!
Update :
componentDidMount() {
this.props.updateKeywords();
setTimeout(() => {
this.setState({
keywordsString: this.props.keywords.join(" ")
});
}, 0);
}
This code is also working... but this other is not working
componentDidMount() {
this.props.updateKeywords();
this.setState({
keywordsString: this.props.keywords.join(" ")
});
}
The reason is that componentDidMount is only called once on mount. What you're looking for is either componentShouldUpdate or componentDidUpdate or the render function, all of which are called when your component receives the updated state from redux. You can read here for more information on what these functions do.
https://reactjs.org/docs/react-component.html#updating
I'm working on my first API with React. I am able to console log my current state after its loaded and the state for that array is set. However, running my component with a prop "FragrancesArray" which is set after loading the data from this.state.fragrances returns in not a function.
Using axios async and await.
No idea why? Can someone help?
Thanks.
My code:
// Core React
import React, { Component } from 'react';
// Axios
import axios from 'axios';
// Constants
import { FRAGRANCES_URL, BLOGS_URL, MAKE_UP_URL } from 'constants/import';
// Components
import Fragrances from 'components/Fragrances/Fragrances';
class App extends Component {
state = {
fragrances: [],
blogs: [],
makeup: []
}
getCoffee() {
return new Promise(resolve => {
setTimeout(() => resolve('☕'), 0); // it takes 1 seconds to make coffee
});
}
async showData() {
try {
// Coffee first
const coffee = await this.getCoffee();
console.log(coffee); // ☕
// Axios API's
const fragranceData = axios(FRAGRANCES_URL);
const blogData = axios(BLOGS_URL);
const makeupData = axios(MAKE_UP_URL);
// await all three promises to come back and destructure the result into their own variables
await Promise.all([fragranceData, blogData, makeupData])
.then((data) => {
this.setState({
fragrances: data[0],
blogs: data[1],
makeup: data[2]
});
const { blogs } = this.state;
console.log(blogs);
})
} catch (e) {
console.error(e); // 💩
}
}
componentDidMount() {
this.showData();
}
render() {
return (
<Fragrances FragranceArray={this.state.fragrances} AppURL={FRAGRANCES_URL} />
)
}
}
export default App;
In react, before you can set/use state, you need to declare it with getInitialState() but with ES6 class model you initialize state in a constructor.
class App extends Component {
constructor(props) {
super(props)
//- Initialize default state values
this.state = {
fragrances: [],
blogs: [],
makeup: []
}
}
//The rest of code stays the same.
render() {
return (
<Fragrances FragranceArray={this.state.fragrances} AppURL={FRAGRANCES_URL} />
)
}
}
More about React state
I am using nested route. Parent component shows category list and the child component shows modal. On performing delete action in modal i.e. child, I am redirecting to the parent route using history.push. The component is getting rendered but without re-rendering the component i.e. the record which was deleted still appears in the view, but when I refresh the page the record does not appear. Any suggestions on this?
Here is my code -
CategoryList.js
import React, { Component } from 'react';
import { Route, Link } from 'react-router-dom';
import CustomisedModal from './../../elements/CustomisedModal';
class CategoryList extends Component {
state = {
categories: [],
}
componentDidMount() {
fetch('http://localhost:8080/xxxx')
.then(results => {
return results.json();
}).then(data => {
this.setState({ categories: data.categories });
})
.catch(error => {
this.setState({ error: true });
});
}
deleteCategoryHandler = (id) => {
**//navigate to modal**
this.props.history.replace('/category/delete/' + id);
}
render() {
if (!this.state.error) {
categories = this.state.categories.map(category => {
return (
xxxxx
)
})
}
return (
<Container>
xxxx
<Route path="/category/delete/:id" exact component={DeleteCategory} /> **<!--Nested route-->**
</Container>
);
}
}
export default CategoryList;
CustomisedModal.js
import React from 'react'
import { Button, Header, Modal, Icon } from 'semantic-ui-react';
class CustomisedModal extends React.Component {
constructor(props) {
super(props);
this.state = {
showModal: this.props.props
}
}
onClose = () => {
this.props.props.history.go('/category');
}
deleteCategory = () => {
fetch('http://localhost:8080/xxxx/' + this.props.props.match.params.id , {
method: 'delete'
})
.then(results => {
return results.json();
}).then(data => {
**//Redirect to parent route**
this.props.props.history.go('/category');
})
.catch(error => {
this.setState({ error: true });
});
}
render() {
return (
<Modal> xxxx
</Modal>
)
}
}
export default CustomisedModal;
the problem here is your parent component has the fetch call in componentDidMount. use componentWillReceiveProps with some condition to reload the data after delete action. As the current parent state hold the old data.
Hope this will help
Fixed the issue by updating state on receiving response so that component gets re-rendered
CustomisedModal.js
import React from 'react'
import { Button, Header, Modal, Icon } from 'semantic-ui-react';
class CustomisedModal extends React.Component {
constructor(props) {
super(props);
this.state = {
categories: this.props.props.categories **added categories to state**
}
}
onClose = () => {
this.props.props.history.go('/category');
}
removeByAttr = function (arr, attr, value) {
var i = arr.length;
while (i--) {
if (arr[i]
&& arr[i].hasOwnProperty(attr)
&& (arguments.length > 2 && arr[i][attr] === value)) {
arr.splice(i, 1);
}
}
return arr;
}
deleteCategory = () => {
fetch('http://localhost:8080/xxxx/' + this.props.props.match.params.id , {
method: 'delete'
})
.then(results => {
return results.json();
}).then(data => {
**//Redirect to parent route**
let newArray = this.removeByAttr(this.state.categories, 'id', data.id);
this.setState({ categories: newArray }); **//updated setState**
this.props.props.history.go('/category');
})
.catch(error => {
this.setState({ error: true });
});
}
render() {
return (
<Modal> xxxx
</Modal>
)
}
}
export default withRouter(CustomisedModal);**// added withRouter**
I'm trying to test rendering a list component from an JSON object called with fetch, however I suspect the render is called before the JSON is returned as I get an undefined error. Note the debug alert() gets the value, but the render has already error on undefined. How do I handle this? Is there a way to call the render function after the data has been fetched? OR could how would I get the data to render with '?' and then update when the data arrives? Note my if (!object) doesn;t work when called as an inline wrapper - why not - or more impoartanly how would I achive this? Sorry for the begginer questions - I suspect I meant to be using a data framework to bring data for react to display but I wanted to start learning with less frameworks and get the basics. Many thanks! - code is:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class ItemLister extends React.Component {
constructor(props ) {
super(props);
this.state = { items: [] };
}
componentDidMount() {
let url = "http://localhost:8888";
let iterator = fetch(url, {method: 'GET'});
iterator
.then(response => {
//console.log('sss', response)
return response.json();
})
.then(post => {
//console.log(post)
this.state.items = post;
alert(this.state.items.users[3].firstname);
});
}
output(object){
if (!object){
return '?';
}else{
return object;
}
}
render() {
return(
<div>
<div>Items:</div>
<div>{this.output(this.state.items.users[3].firstname)}</div>
</div>
);
}
}
export default ItemLister;
your render function could be something like this:
render() {
const username = (this.state.items === []) ? placeholder : this.output(this.state.items.users[3].firstname;
return(
<div>
<div>Items:</div>
<div>{username}</div>
</div>
);
}
basically, you render a placeholder component until your data arrives, since your data fetch is async and in componentDidMount, so your render will happen initially before you have data.
although you probably want to rethink how this component is constructed in general, why are you accessing specifically users[3]?
Try this code it will help you
import React, { Component } from "react";
import logo from "./logo.svg";
import "./App.css";
class ItemLister extends Component {
constructor(props) {
super(props);
this.state = { items: [], firstname: "" };
}
componentDidMount() {
let url = "http://localhost:8888";
let iterator = fetch(url, { method: "GET" });
iterator
.then(response => {
//console.log('sss', response)
return response.json();
})
.then(post => {
//console.log(post)
this.setState({
items: post,
firstname: this.state.items.users[3].firstname
});
alert(this.state.items.users[3].firstname);
});
}
output(object) {
if (!object) {
return "?";
} else {
return object;
}
}
render() {
return (
<div>
<div>Items:</div>
<div>{this.state.firstname}</div>
</div>
);
}
}
export default ItemLister;