Right way to update data in ReactJS - reactjs

what is the right way in ReactJS to update and display the correct value.
Example: I show data on the screen from a database. When I change the values in the database I want to see the new values directly on the screen.
I have two files:
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
containers: []
};
}
componentDidMount() {
var self = this;
axios.get('http://reactlaravel.dev/container/count').then(function (response) {
self.setState({
containers: response.data
});
})
}
render() {
const containers = this.state.containers.map( (container, i) => <StockCount key={i} {...container} /> );
return (
<div>
{containers}
</div>
)
}
const Container = ({ name, total, current }) => (
<div>
<span>{name}</span>
<span>{total}</span>
<span>{current}</span>
</div>
);
ReactDOM.render(
<Parent />,
document.getElementById('app')
);
And the second file:
export class StockCount extends React.Component {
render() {
const currentBar = this.props.current;
const totalBar = this.props.total;
const progressBar = currentBar / totalBar * 100;
return (
<div className="container">
<h1>{this.props.current} - {this.props.total} - {this.props.name}</h1>
<div className="progress">
<div className={progressBarType} role="progressbar" aria-valuenow={progressBar} aria-valuemin="0" aria-valuemax="100" style={style}>
<span className="sr-only">{progressBar}% Complete (success)</span>
</div>
</div>
</div>
);
}

You have to implement server push (will require server changes) in order to notify client app about database changes. Here is easiest way to go without server modifications short pulling:
componentDidMount() {
this.lookupInterval = setInterval(() => {
axios
.get('http://reactlaravel.dev/container/count')
.then(function(response) {
this.setState({
containers: response.data,
})
})
}, 500)
}
componentWillUnMount() {
clearInterval(this.lookupInterval)
}

Use a life cycle method for updating? like componentWillReceiveProps() https://reactjs.org/docs/react-component.html

Related

I cannot get image to render after a network query

Using Parse, I am querying the database and getting an imageURL back. React is not updating the dom.
componentWillMount and just regular curly brackets.
export const profileImage = async objectId => {
const query = new Parse.Query(Parse.User);
query.equalTo("objectId", objectId);
const image = await query.find();
console.log(typeof image[0].attributes.image);
console.log(image[0].attributes.image);
return image[0].attributes.image; // return image;
}; // Query for a specific user and get image!
I imported it currently and it does the console logs so the function is executing but never rendering.
export default class MyDashboard extends Component {
constructor(props) {
super(props);
this.state = {
profilePic: "",
};
}
componentDidMount() {
this.setState({ profilePic: profileImage(window.localStorage.objectId) });
}
render() {
return (
<div>
<Sidebar />
<div className={style.Componentcontainer} />
<div className={style.main}>
</div>
<div className={style.profile}>
<div> {this.state.profilePic} </div>
}
I eventually plan to put the string into an image tag, I just got to get this rendering first.
Your function is asynchronous, so setState will not wait and will render undefined.
To fix this, you should return a promise, and consume it with a .then() and set the state there instead. You should also use window.localStorage.getItem(), rather than trying to access a property immediately.
export const profileImage = objectId => {
const query = new Parse.Query(Parse.User);
query.equalTo("objectId", objectId);
return query.find();
};
export default class MyDashboard extends Component {
constructor(props) {
super(props);
this.state = {
profilePic: ""
};
}
componentDidMount() {
profileImage(window.localStorage.getItem(objectId)).then(image => {
this.setState({ profilePic: image[0].attributes.image });
});
}
render() {
return (
<div>
<Sidebar />
<div className={style.Componentcontainer} />
<div className={style.main} />
<div className={style.profile}>
<img src={this.state.profilePic} />
</div>
</div>
);
}
}

Setting State Array and Appending Value on Update

I'm still learning about state and lifecycle with ReactJS and have run into a scenario where I have a form that on submit should save the form value and then append the returned JSON object to the end of an array which would re-render the component storing the original array.
With my current setup, I have the components setup and form submit with returned JSON object, but the state contains an empty array rather than the object spread {...comment} and it doesn't look like the setState is updating component, but that could be due to the empty array mentioned before. Can anyone point me in the right direction?
Comment:
import React from 'react';
import fetch from 'node-fetch';
//record Comment - Comment Form Handle POST
class CommentForm extends React.Component {
constructor(props){
super(props);
this.state = {
value: '',
comments: []
};
this.onChange = this.onChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
postComment(comment, recordId, csrfToken) {
var body = { comment: comment };
var route = 'http://localhost:3000/record/' + recordId + '/comment';
fetch(route,
{
method: 'POST',
body: JSON.stringify(body),
headers: {
'X-CSRF-Token': csrfToken,
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})
.then(res => {
return res.json();
})
.then(data => {
console.log(data);
let commentsArr = this.state.comments;
this.setState({comments: commentsArr.concat(data)});
})
.catch(err => {
console.log(err);
});
}
onChange(e){
this.setState({
value: e.target.value
});
}
handleSubmit(e){
e.preventDefault();
this.postComment(this.state.value, this.props.recordId, this.props.csrf);
}
render(){
return (
<div className="record-comment__form">
<div className="row">
<form action={"/record/" + this.props.recordId + "/comment"} method="post" onSubmit={this.handleSubmit}>
<input type="hidden" name="_csrf" value={this.props.csrf}/>
<textarea name="comment" className="record-comment__form-text-area" onChange={e => this.setState({ value: e.target.value })} value={this.state.value}></textarea>
<button type="submit" className="record-comment__form-button" disabled={!this.state.value}>Comment</button>
</form>
</div>
</div>
)
}
}
//record Comment - Comment
const Comment = props => {
return (
<div className="row">
<div className="col-md-12">
<h5>{props.user_id}</h5>
<h4>{props.comment}</h4>
<h3>{props.synotate_user.fullNameSlug}</h3>
</div>
</div>
)
}
//record Comment - Container
export default class Comments extends React.Component {
render() {
return (
<div className="record-comment-container">
<CommentForm recordId={this.props.recordId} csrf={this.props.csrf}/>
{ this.props.record_comments.map((comment, i) =>
<Comment {...comment} key={this.props.recordCommentId}/>
)}
</div>
);
}
}
Record (Parent component)(Where Comment is being set):
//GET /api/test and set to state
class RecordFeedContainer extends React.Component{
constructor(props, context) {
super(props, context);
this.state = this.context.data || window.__INITIAL_STATE__ || {records: []};
}
fetchList() {
fetch('http://localhost:3000/api/test')
.then(res => {
return res.json();
})
.then(data => {
console.log(data);
this.setState({ records: data.record, user: data.user, csrf: data.csrfToken });
})
.catch(err => {
console.log(err);
});
}
componentDidMount() {
this.fetchList();
}
render() {
return (
<div className="container">
<h2>Comments List</h2>
<RecordFeed {...this.state} />
</div>
)
}
};
//Loop through JSON and create Record and Comment Container Component
const RecordFeed = props => {
return (
<div>
{
props.records.map((record, index) => {
return (
<div className="row">
<div className="col-md-6 col-md-offset-3 record-card">
<RecordCard {...record} key={record.recordIdHash} user={props.user} />
<Comments {...record} key={index} recordId={record.recordIdHash} csrf={props.csrf}/>
</div>
</div>
);
})
}
</div>
)
}
Your problem is that when rendering <Comments>, the this.props.record_comments is not the comments you've updated in the state of the <CommentForm> component. Each component has it's own internal state.
You need to pass the state along to your <Comments> component. You will need to move your state up to the top level or use a state management system like Redux which will allow you to access a shared state which could contain your comments array.
From the top level component you could manage the state there, like so:
this.state = {
comments: [],
// other shared state
};
You can pass along an update comments function, named for example updateCommentsFunc() to <CommentForm> like so:
<CommentForm updateComments={this.updateCommentsFunc} recordId={this.props.recordId} csrf={this.props.csrf}/>
Which will allow you to pass the updated comments back up to the parent component via something like:
const updateCommentsFunc = (newComments) => {
this.setState({comments: [...this.state.comments, newComments]});
}
Your postComment() function doesn't appear to be properly bound to your enveloping <CommentForm/> component's this. As a result; calling this.setState() from within the function isn't really doing anything.
Try binding it within your constructor method.
constructor(props) {
// ...
this.postComment = this.postComment.bind(this)
}
Or by declaring it using an arrow function.
postComment = (comment, recordId, csrfToken) => {
// ...
}
See this article for more info on React binding patterns.

Trying to manipulate a div with reactjs on async data

I try to animate a div with reactjs using async data via redux and it's not clear to me when can I have a reference to the virtual dom on state loaded.
In my case I have a div with id header where I would like to push down the container when data was populated.
If I try in componentDidMount than I get Cannot read property 'style' of undefined because componentDidMount still having a reference to an on load container
class HomePage extends React.Component {
constructor(props) {
super(props);
this.state = {
sliderLength: null
}
}
componentDidMount() {
this.props.actions.getSlides()
if(this.header) {
setTimeout(function() {
this.header.style.bottom = -(this.header.clientHeight - 40) + 'px';
}, 2000);
}
//header.style.bottom = -pushBottom+'px';
}
componentWillReceiveProps(nextProps) {
let {loaded} = nextProps
if(loaded === true ) {
this.animateHeader()
}
}
animateHeader() {
}
componentWillMount() {
const {slides} = this.props;
this.setState({
sliderLength: slides.length,
slides: slides
});
}
render() {
const {slides, post, loaded} = this.props;
if(loaded ===true ) {
let sliderTeaser = _.map(slides, function (slide) {
if(slide.status === 'publish') {
return <Link key={slide.id} to={'portfolio/' + slide.slug}><img key={slide.id} className="Img__Teaser" src={slide.featured_image_url.full} /></Link>
}
});
let about = _.map(post, function (data) {
return data.content.rendered;
})
return (
<div className="homePage">
<Slider columns={1} autoplay={true} post={post} slides={slides} />
<div id="header" ref={ (header) => this.header = header}>
<div className="title">Title</div>
<div className="text-content">
<div dangerouslySetInnerHTML={createMarkup(about)}/>
</div>
<div className="sliderTeaser">
{sliderTeaser}
</div>
<div className="columns">
<div className="column"></div>
<div className="column"></div>
<div className="column"></div>
</div>
</div>
<div id="bgHover"></div>
</div>
);
} else {
return <div>...Loading</div>
}
}
}
function mapStateToProps(state) {
return {
slides: state.slides,
post: state.post,
loaded: state.loaded
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(slidesActions, dispatch)
};
}
function createMarkup(markup) {
return {__html: markup};
}
export default connect(mapStateToProps, mapDispatchToProps)(HomePage);
How do I deal in this case with states?
Between I found a solution but not sure if is the right workaround
componentDidUpdate() {
if(this.header) {
setTimeout(function() {
this.header.style.bottom = -(this.header.clientHeight - 35) + 'px';
}, 2000);
}
}
In general, try to avoid using ref as much as possible. This is particularly difficult if you're new to React but with some training, you'll find yourself not needing it.
The problem with modifying the styles like you're doing is that when the component will render again your changes will be overwritten.
I would create a new state property, say state.isHeaderOpen. In your render method you will render the header differently depending on the value of this header e.g.:
render () {
const {isHeaderOpen} = this.state
return (
<header style={{bottom: isHeaderOpen ? 0 : 'calc(100% - 40px)'}}>
)
}
Here I'm using calc with percentage values to get the full height of the header.
Next, in your componentDidMount simply update the state:
componentDidMount () {
setTimeout(() => this.setState({isHeaderOpen: false}), 2000);
}
In this way, the component will render again but with the updated style.
Another way is to check if the data has been loaded instead of creating a new state value. For example, say you're loading a list of users, in render you would write const isHeaderOpen = this.state.users != null.
If you are trying to animate a div why are you trying to access it by this.header just use the javaScript's plain old document.getElementById('header') and then you can play around with the div.

I can not access the right data of property sent from parent to child component

I am facing an issue with react and I am totally stuck. I have 3 components: channel as a parent and header and story as a children:
class Channel extends React.Component {
constructor(props) {
super();
}
componentDidMount() {
this.props.getChannels();
}
render() {
return (
<div>
<div className="col-xs-12 col-md-8 col-lg-8>
<div className="row">
<Header activeChannelList={this.props.channels.channelsArr}/>
</div>
<div className="row">
{
this.props.channels.channelsArr.map((item, i) => <StoryBoard
newsChanel={item}
key={"storyBoard" + i}
></StoryBoard>)
}
</div>
</div>
<div className="col-xs-12 col-md-2 col-lg-2 color2">.col-sm-4</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
channels: state.channelReducer
};
};
const mapDispatchToProps = (dispatch) => {
return {
getChannels: () => {
dispatch(getChannels());
}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Channel);
As you can see I have a ajax call with this.props.getChannels(); and I put it in componentDidMount to make sure that it is called before rendering then after I pass the channels to the Header ans story which are children components.
Now my problem is when I try to access it in Header via console.log(this.props.activeChannelList); I get 0 thought I should have 5 channels. More intrestingly when I try to access the props I send in Stroryboard I can easily access them without any problem. The following is my code for Header:
export class Header extends React.Component {
constructor(props) {
super();
}
componentDidMount() {
console.log("dddddddddddddddddddddddddddddddddddddddddd");
console.log(this.props.activeChannelList);// I get 0 though I should get 5
}
render() {
return (
<div className="col-xs-12 header tjHeaderDummy">
<div className="col-xs-1"></div>
</div>
);
}
}
And my storyboard is :
class StoryBoard extends React.Component {
constructor(props) {
super();
}
componentDidMount() {
if(this.props.isFreshLoad ){
do sth
}
}
render() {
return (
<div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
stories: state.storyBoardReducer
};
};
const mapDispatchToProps = (dispatch) => {
return {
//some funcs
}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(StoryBoard);
Can anyone help?
U r printing the value in componentDidMount method in Header component, this lifecycle method get called only once, if ur api response come after the rendering of Header, it will never print 5, put the console in render method, so that at any time when u get the response it will populate the value.
From Docs:
componentDidMount: is invoked immediately after a component is mounted
first time. This is where AJAX requests and DOM or state updates
should occur.
Try this Header Comp, it will print the proper value:
export class Header extends React.Component {
constructor(props) {
super();
}
componentDidMount() {
}
render() {
return (
<div className="col-xs-12">
{this.props.activeChannelList}
</div>
);
}
}

Passing a function in props to a component

I'm new to react and trying to pass a global function to components to avoid repeating it in each of them. That doesn't work, I get an undefined error when I try to call it in the components.
Here is my code :
import React from 'react';
//components
import League from './League';
class App extends React.Component {
state = {
leagues: {},
};
componentDidMount() {
this.getLeagues();
}
get(url) {
var myHeaders = new Headers();
myHeaders.append("Accept", "application/json");
myHeaders.append("X-Mashape-Key", "mysecretkeyblablabla");
var myInit =
{
headers: myHeaders
};
return fetch(url,myInit)
.then(function(response) {
if(response.ok) {
return response.json().then(function(json) {
return json.data;
});
}
});
};
getLeagues() {
this.get('https://sportsop-soccer-sports-open-data-v1.p.mashape.com/v1/leagues').then((data) => {
this.setState({leagues: data.leagues});
});
}
render() {
const leagues = Object
.keys(this.state.leagues)
.map(key => <League get={this.get} key={key} details={this.state.leagues[key]} />
);
return(
<div className="App">
<div className="App-header">
<h1>Welcome to Foot Stats app (made in ReactJS)</h1>
</div>
<p className="App-intro">
Here is the place where I should put the countries.
</p>
<ul>
{leagues}
</ul>
</div>
);
};
}
export default App;
and my League component
import React from 'react';
import Season from './Season';
class League extends React.Component {
state = {
seasons: {},
};
constructor(props) {
super(props);
}
componentDidMount() {
//this.getSeasonsAvailable(this.props.details.league_slug);
}
getSeasonsAvailable(league) {
const url = 'https://sportsop-soccer-sports-open-data-v1.p.mashape.com/v1/leagues/{league_slug}/seasons'.replace('{league_slug}',league);
const seasons = [];
console.log(this.props);
this.props.get(url).then((data) => {
data.seasons.map(function(object, i) {
seasons[data.seasons[i].identifier] = data.seasons[i];
});
this.setState({seasons: seasons});
});
};
render() {
const seasons = Object
.keys(this.state.seasons)
.map(key => <Season key={key} league_slug={this.props.details.league_slug} details={this.state.seasons[key]} />
);
return (
<li>
<span onClick={this.getSeasonsAvailable.bind(this.props.details.league_slug)}>{this.props.details.nation} : {this.props.details.name}</span>
<ul>
{seasons}
</ul>
</li>
);
}
static propTypes = {
get: React.PropTypes.func.isRequired
};
}
export default League;
When I click on the season component, I get this error :
Cannot read property 'get' of undefined
And my console.log(this.props) returns me undefined.
Thanks !
You just need to change
<span onClick={this.getSeasonsAvailable.bind(this.props.details.league_slug)}>
to
<span onClick={this.getSeasonsAvailable.bind(this, this.props.details.league_slug)}>
Apart from this, if you want to use ES6 way to do this. You can use arrow functions
<span onClick={() => this.getSeasonsAvailable(this.props.details.league_slug)}>
or you can bind the function getSeasonsAvailable in the constructor using
constructor() {
super();
this.getSeasonsAvailable = this.getSeasonsAvailable.bind(this);
}
You can read in more detail about it here and here.
Because your onClick: .bind(this.props.details.league_slug)
what is this.props.details.league_slug actually?
bind will change the reference of this in getSeasonsAvailable (this will ref to this.props.details.league_slug, I don't know what it is), of course you will get undefined when you call this.props
Try just .bind(this), so the this in getSeasonsAvailable can ref to the component itself.

Resources