I have a component
class App extends React.Component {
constructor(props) {
super(props);
this.state = {stories: []};
}
componentDidMount() {
$.get(Api.getList(), (result) => {
const data = result;
if (data) {
this.setState((prevState) => ({
stories: data.stories
}));
}
});
}
render() {
if (this.state.stories.length == 0) {
return (
<Tpl>
<Loading/>
</Tpl>
);
} else {
return (
<Tpl>
<Stories stories={this.state.stories}/>
</Tpl>
);
}
}
}
and everytime when I switch to this component,
it will run constructor first.
so the state will be empty.
what I want is if stories had items, it don't need to get data again.
is it any method to keep that state?
I think the best way to achieve this would be to use react-redux whereby you have a centralized store which maintains state to all the components and thus saving it from refreshing everytime the component loads
Here is a good article to help you get started with redux.
The other way to do what you want is to have you states saved in localStorage whereby on every component load you load data from localStorage and set to state initially and when updating a state also update the value there. Below is a sample method that you can follow for this approach.
constructor(props) {
super(props);
var stories = JSON.parse(localStorage.getItem( 'stories' )) || null
this.state = {stories: stories};
}
componentDidMount() {
$.get(Api.getList(), (result) => {
const data = result;
if (data) {
JSON.stringify(localStorage.setItem( 'stories', data.stories));
this.setState((prevState) => ({
stories: data.stories
}));
}
});
}
Related
I have a react class based component where I have defined a state as follows:
class MyReactClass extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedDataPoints: new Set()
};
}
// This method is called dynamically when there is new addition of data
storeData = (metricName, dataPoint) => {
if (this.state.selectedDataPoints.has(dataPoint)) {
this.state.selectedDataPoints.delete(dataPoint);
} else {
this.state.selectedDataPoints.add(dataPoint);
}
};
render () {
return (
<p>{this.state.selectedDataPoints}</p>
);
}
}
Note that initially, the state is an empty set, nothing is displayed.
But when the state gets populated eventually, I am facing trouble in spinning up the variable again. It is always taking as the original state which is an empty set.
If you want the component to re-render, you have to call this.setState () - function.
You can use componentshouldUpdate method to let your state reflect and should set the state using this.state({}) method.
Use this code to set state for a set:
export default class Checklist extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedDataPoints: new Set()
}
this.addItem = this.addItem.bind(this);
this.removeItem = this.removeItem.bind(this);
}
addItem(item) {
this.setState(({ selectedDataPoints }) => ({
selectedDataPoints: new Set(selectedDataPoints).add(item)
}));
}
removeItem(item) {
this.setState(({ selectedDataPoints }) => {
const newSelectedDataPoints = new Set(selectedDataPoints);
newSelectedDataPoints.delete(item);
return {
selectedDataPoints: newSelectedDataPoints
};
});
}
getItemCheckedStatus(item) {
return this.state.checkedItems.has(item);
}
// This method is called dynamically when there is new addition of data
storeData = (metricName, dataPoint) => {
if (this.state.selectedDataPoints.has(dataPoint)) {
this.state.selectedDataPoints.removeItem(dataPoint);
} else {
this.state.selectedDataPoints.addItem(dataPoint);
}
};
render () {
return (
<p>{this.state.selectedDataPoints}</p>
);
}
}
Im trying to make an api request from redux then take that data and put it in my react state (arrdata). The api call works but i cant seem to get the state on my app.js to update based on the redux api call. Am i missing something?
App.js
class App extends Component {
constructor(props) {
super(props);
this.state = {
arrdata: []
};
}
componentDidMount() {
this.props.loadData();
console.log(this.props.data);
}
render() {
const {arrdata} = this.state
return ( ......)}}
const mapStateToProps = state => {
return {
data: state.data
};
};
export default connect(mapStateToProps, dataAction)(App);
Action
export function loadData() {
return dispatch => {
return axios.get("https://api.coincap.io/v2/assets").then(response => {
dispatch(getData(response.data.data.slice(0, 10)));
});
};
}
export function getData(data) {
return {
type: "GET_DATA",
data: data
};
}
Reducer
let initialState = {
data: []
};
const mainReducer = (state = initialState, action) => {
if (action.type === "GET_DATA") {
return {
...state,
data: action.data
};
} else {
return {
...state
};
}
};
export default mainReducer;
I think you are misleading store with state. Your arrdata is empty since it's stored inside state, but your data comes from props.
Anyways, arrdata in state remains empty, since you are not setting the state anywhere. To do that, you would have to use e.g. getDerivedStateFromProps lifecycle hook, however I wouldn't recommend that.
render() {
const { data } = this.props;
console.log(this.props.data);
return (
// do something with your data
);
}
It should log your data properly.
Note: You don't need state, actually. It's a better approach to manipulate over props, instead of saving data from props into state (in most cases).
I am trying to make my component reactive on updates. I am using componentDidUpdate() to check if the components prop state has changed, then if it has it is has I need the getPosts() function to be called and the postCount to update if that prop is changed.
export default class JsonFeed extends React.Component<IJsonFeedProps, IJsonFeedState> {
// Props & state needed for the component
constructor(props) {
super(props);
this.state = {
description: this.props.description,
posts: [],
isLoading: true,
jsonUrl: this.props.jsonUrl,
postCount: this.props.postCount,
errors: null,
error: null
};
}
// This function runs when a prop choice has been updated
componentDidUpdate() {
// Typical usage (don't forget to compare props):
if (this.state !== this.state) {
this.getPosts();
// something else ????
}
}
// This function runs when component is first renderd
public componentDidMount() {
this.getPosts();
}
// Grabs the posts from the json url
public getPosts() {
axios
.get("https://cors-anywhere.herokuapp.com/" + this.props.jsonUrl)
.then(response =>
response.data.map(post => ({
id: `${post.Id}`,
name: `${post.Name}`,
summary: `${post.Summary}`,
url: `${post.AbsoluteUrl}`
}))
)
.then(posts => {
this.setState({
posts,
isLoading: false
});
})
// We can still use the `.catch()` method since axios is promise-based
.catch(error => this.setState({ error, isLoading: false }));
}
You can change componentDidUpdate to:
componentDidUpdate() {
if (this.state.loading) {
this.getPosts();
}
}
This won't be an infinite loop as the getPosts() function sets state loading to false;
Now every time you need an update you just need to set your state loading to true.
If what you want to do is load everytime the jsonUrl updates then you need something like:
componentDidUpdate(prevProps) {
if (prevProps.jsonUrl!== this.props.jsonUrl) {
this.getPosts();
}
}
Also I don't get why you expose your components state by making componentDidMount public.
Modify your getPosts to receive the jsonUrl argument and add the following function to your class:
static getDerivedStateFromProps(props, state) {
if(props.jsonUrl!==state.jsonUrl){
//pass jsonUrl to getPosts
this.getPosts(props.jsonUrl);
return {
...state,
jsonUrl:props.jsonUrl
}
}
return null;
}
You can get rid of the componentDidUpdate function.
You can also remove the getPosts from didmount if you don't set state jsonUrl in the constructor.
// This function runs when a prop choice has been updated
componentDidUpdate(prevProps,prevState) {
// Typical usage (don't forget to compare props):
if (prevState.jsonUrl !== this.state.jsonUrl) {
this.getPosts();
// something else ????
}
}
this way you have to match with the updated state
Try doing this
componentDidUpdate(prevState){
if(prevState.loading!==this.state.loading){
//do Something
this.getPosts();
}}
I'm trying to create variables and a function inside a state like this
state = {
modalVisible: false,
photo:""
getDataSourceState
}
which i have done, how can i call the function outside the state and set a new state.
This what i have done but i keep getting errors
getDataSourceState() {
return {
dataSource: this.ds.cloneWithRows(this.images),
};
}
this.setState(this.getDataSourceState());
see what prompted me to ask the question, because i was finding it difficult to access modalVisible in the state since there is a this.state = this.getDataSource()
constructor(props) {
super(props);
this.state = {
modalVisible: false,
photo:"",
sourceState: getDataSourceState()
}
this.ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.lastPhotoFetched = undefined;
this.images = [];
this.fetchPhotos();
this.getDataSourceState = this.getDataSourceState.bind(this)
}
componentDidMount(){
this.getDataSourceState();
}
getDataSourceState() {
return {
dataSource: this.ds.cloneWithRows(this.images),
};
}
getPhotosFromCameraRollData(data) {
return data.edges.map((asset) => {
return asset.node.image;
});
}
}
You can't the way you have attempted but technically yes, you can have a function that returns the desired state you want initialised in your constructor. I wouldn't suggest doing it though.
You will quickly run into issues where your components aren't updating state correctly.
What you are looking for is a function that returns a value as opposed to sets state. You would do something like this:
constructor(){
super()
this.state = {
modalVisible: false,
photo:""
sourceState: this.getDataSourceState()
}
this.getDataSourceState = this.getDataSourceState.bind(this)
}
getDataSourceState(){
return this.ds.cloneWithRows(this.images)
}
As I mentioned, it is not a good idea to do it this way. You are better off initialising the state values as a default value and then setting the state in your componentDidMount like so:
constructor(){
super()
this.state = {
modalVisible: false,
photo:""
sourceState: null
}
this.getDataSourceState = this.getDataSourceState.bind(this)
}
componentDidMount(){
this.getDataSourceState()
}
getDataSourceState(){
const data = this.ds.cloneWithRows(this.images)
this.setState({soureState: data})
}
This way you have a reusable function which you can call in componentDidUpdate() if need be for when you move navigate between the same component with different data and want the state to update.
Yes you can.
class App extends Component {
func1 = () => {
this.setState({flag:!this.state.flag})
}
state = {
flag:true,
doclick:this.func1
}
}
I have a component, which has to download a JSON file and then iterate over it and display each element from the JSON on the screen.
I'm kinda new with React, used to be ng dev. In Angular, I used to do it with lifecycle hooks, e.g. ngOnInit/ngAfterViewInit (get some JSON file and then lunch the iteration func). How can I achieve it in React? Is it possible to reach it with lifecycle hooks, like ComponentWillMount or ComponentDidMount.
My code (it's surely wrong):
export default class ExampleClass extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
}
}
componentWillMount(){
getData();
}
render() {
return (
<ul>
{this.state.data.map((v, i) => <li key={i}>{v}</li>)}
</ul>
)
};
}
const getData = () => {
axios.get(//someURL//)
.then(function (response) {
this.setState({data: response.data});
})
.catch(function (error) {
console.log(error);
})
};
How to force React to get the JSON before rendering the component?
Thank you so much.
Making an AJAX request in ComponentWillMount works. https://facebook.github.io/react/docs/react-component.html#componentwillmount
You could also just work that logic into your constructor depending on your exact needs.
https://facebook.github.io/react/docs/react-component.html#constructor
export default class ExampleClass extends React.Component {
constructor(props) {
super(props)
this.state = {
data: [],
}
axios.get(/*someURL*/)
.then(function (response) {
this.setState({data: response.data});
})
.catch(function (error) {
console.log(error);
})
}
}
You can do a simple if statement in your render function.
render () {
if (Boolean(this.state.data.length)) {
return <ul>{this.state.data.map((v, i) => <li key={i}>{v}</li>)}</ul>
}
return null
}
You can also use a higher order component to do the same thing.
const renderIfData = WrappedComponent => class RenderIfData extends Component {
state = {
data: []
}
componentWillMount() {
fetchData()
}
render() {
if (Boolean(this.state.data.length)) {
return <WrappedComponent {...this.state} />
}
return null
}
}
Then you can wrap the presentational layer with the HOC.
renderIfData(ExampleClass)
Not sure what version of React you are using but you may need to use <noscript> instead of null.
This is essentially preventing your component from rendering until it has all the data.