componentWillReceiveProps in child doesn't receive new props when setState() in parent - javascript

I have a parent component in React called "App" that renders a "Calories" child component with a HighCharts implementation.
What I expect is according to the React lifecycle, parent renders child component and then will call componentDidMount(). I then use a fetch to get data async and once done it setState's the parent with a user object. Then it would re-render the child component with user={this.state.user} and it will be available in the child component. But when i log this.props in the child's componentWillReceiveProps the user object doesn't exist. So this line in child component logs "undefined":
componentWillReceiveProps: function(){
const series = this.props.series;
console.log("component Will Receive Props")
console.log(this.props);
}
Here is my full code:
const App = React.createClass({
//parent component to render all elements and hold state
getInitialState(){
return{
user: {},
series: [{
name: 'Jane',
data: [1, 0, 4]
}, {
name: 'John',
data: [5, 7, 3]
}]
};
},
componentDidMount: function(){
const fb_id = location.pathname.replace("/users/","");
fetch("https://someurl.com/usersdata/" + fb_id)
.then(rsp => rsp.json())
.then(json => {
if(json.error && json.error.message){
throw new Error(json.error.message);
}
this.setState({user:json}, ()=>{
console.log("state updated");
console.log(this.state);
});
});
},
render: function(){
return (
<div className="container">
<div clasNames="row">
<div className="col-xs-12">
{/*Send this.state.user data from fetch to child component*/}
<Calories series={this.state.series} user={this.state.user}/>
</div>
</div>
<div className="row">
<div className="col-xs-7">
<div className="bottom-left" id="weight-line-chart">
<Weight/>
</div>
</div>
<div className="col-xs-5">
<div className="bottom-right" id="avg-calories-pie-chart">
<AverageCal/>
</div>
</div>
</div>
</div>
);
}
});
//Calories Line chart
const Calories = React.createClass({
componentDidMount: function(){
const series = this.props.series;
console.log("component Did Mount");
console.log(this.props);
$(function () {
const myChart = Highcharts.chart('calories-line-chart', {
chart: {
type: 'line'
},
title: {
text: 'Your Calories Over Time'
},
xAxis: {
categories: ['Apples', 'Bananas', 'Oranges']
},
yAxis: {
title: {
text: 'Fruit eaten'
}
},
series: series
});
});
},
componentWillReceiveProps: function(){
const series = this.props.series;
console.log("component Will Receive Props")
console.log(this.props);
$(function () {
const myChart = Highcharts.chart('calories-line-chart', {
chart: {
type: 'line'
},
title: {
text: 'Your Calories Over Time'
},
xAxis: {
categories: ['Apples', 'Bananas', 'Oranges']
},
yAxis: {
title: {
text: 'Fruit eaten'
}
},
series: series
});
});
},
render:function(){
return(
<div>
<h3>Calories Intake</h3>
<div className="top" id="calories-line-chart">
</div>
</div>
);
}
});
Anybody can help me what I am doing wrong?

componentWillReceiveProps get called when props values of child (inside parent component) will get updated, you need to receive the new values as a parameter in this lifecycle method, like this:
componentWillReceiveProps: function(newProps){ //here
console.log("component Will Receive Props", newProps); //it will log the new values
...
}
this.props inside componentWillReceiveProps will have the previous values and it will get updated after this lifecycle method. If you do console.log(this.props) inside render, you will see the updated values.
Why we need to receive the new values as parameter?
I think reason is (not sure), this method get called whenever we do setState in parent component, irrespective of whether that is related to child component or not, so we need to put some logic before doing any task in child (new props and old props are same or not), because of that this.props will have the old values inside this method.
Check the DOC for more details on componentWillReceiveProps.

Related

How to make simple messaging using socket.io and react useEffect()

I want to make simple messaging using socket.io and react useEffect() for a setup but I have no idea on how to properly manage/update state from inside of useEffect.
This is my code:
function App() {
const [messages, setMessages] = useState([{ type: "system", text: "Please stay nice!" }]);
useEffect(() => {
socket.current.on("messageSent", (data) => {
setMessages([...messages, { type: "you", text: data.message }]);
});
socket.current.on("receiveMessage", (data) => {
setMessages([...messages, { type: "partner", text: data.message }]);
});
}, []);
return (
<>
<span className="container">
<Chat messages={messages} />
</span>
</>
);
}
The point is I don't want to re-run useEffect(), I want it to run once at the beginning to set-up socket callbacks.
Right now when I try to access messages from inside of my useEffect I got only the starting value which is [{ type: "system", text: "Please stay nice!" }] also setMessages doesn't refresh props of my parent component and child component has only access to this starting value [{ type: "system", text: "Please stay nice!" }].

How can I change data value from one component to another component in Vue Js?

I am new in Vue Js. So, I am facing a problem to changes data value from another component.
I have a component A:
<template>
<div id="app">
<p v-on:click="test ()">Something</p>
</div>
</template>
import B from '../components/B.vue';
export default {
components: {
B
},
methods: {
test: function() {
B.data().myData = 124
B.data().isActive = true
console.log(B.data().myData);
console.log(B.data().isActive);
}
}
}
Component B:
export default {
data() {
return {
myData: 123,
isActive: false
}
}
}
It still component B data.
But it cannot be affected component B data. I want to data changes of component B from component A. How can I do that?
Please explain me in details. I have seen vue js props attribute but I don't understand.
You're looking for Vuex.
It's the centralized store for all the data in your applications.
Take a look at their documentation, it should be pretty straightforward.
You can pass down props to the component B. These props can be updated by the parent component. You can think of B as a stupid component that just renders what the parent tells it to rendern. Example:
// Component A
<template>
<div id="app">
<p v-on:click="test ()">Something</p>
<b data="myData" isActive="myIsActive"></b>
</div>
</template>
<script>
import B from '../components/B.vue';
export default {
components: {
B
},
data() {
return {
myData: 0,
myIsActive: false,
};
},
methods: {
test: function() {
this.myData = 123
this.myIsActive = true
}
}
}
</script>
// Component B
<template>
<div>{{ data }}{{ isActive }}</div>
</template>
<script>
export default {
props: {
data: Number,
isActive: Boolean
};
</script>
There are few ways...
if your components have a parent child relationship you can pass data values from parent into child.
If your want to communicate back to parent component when child component has changed something, you can use vuejs event emitter(custom event) to emit a event when data value change and that event can be listened in another component and do what you want.
If your components doesn't have a relationship, then you have to use use something else than above things. You can use two things.one is event bus, other one is state management library.for vue there is a official state management library called VueX.it is very easy to use.if you want to use something else than vuex, you can use it such as redux, mobx etc.
This documentation has everything what you want to know. I don't want to put any code, because of doc is very clear.
VueX is the most preferable way to do this! Very easy to use..
https://v2.vuejs.org/v2/guide/components.html
//component A
Vue.component('my-button', {
props: ['title'],
template: `<button v-on:click="$emit('add-value')">{{title}}</button>`
});
Vue.component('my-viewer', {
props: ['counter'],
template: `<button>{{counter}}</button>`
});
new Vue({
el: '#app',
data: {
counter: 0,
},
methods: {
doSomething: function() {
this.counter++;
}
}
})
Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
});
//parent
new Vue({
el: '#blog-post-demo',
data: {
posts: [{
id: 1,
title: 'My journey with Vue'
},
{
id: 2,
title: 'Blogging with Vue'
},
{
id: 3,
title: 'Why Vue is so fun'
}
]
}
});
Vue.component('blog-post2', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<button v-on:click="$emit('enlarge-text')">
Enlarge text
</button>
<div v-html="post.content"></div>
</div>`
})
new Vue({
el: '#blog-posts-events-demo',
data: {
posts: [{
id: 1,
title: 'My journey with Vue'
},
{
id: 2,
title: 'Blogging with Vue'
},
{
id: 3,
title: 'Why Vue is so fun'
}
],
postFontSize: 1
},
methods: {
onEnlargeText: function() {
this.postFontSize++;
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<p>Two components adding & viewing value</p>
<div id="app">
<my-button :title="'Add Value'" v-on:add-value="doSomething"></my-button>
<my-viewer :counter="counter"></my-viewer>
</div>
<br>
<br>
<p>Passing Data to Child Components with Props (Parent to Child)</p>
<div id="blog-post-demo">
<blog-post v-for="post in posts" v-bind:key="post.id" v-bind:title="post.title"></blog-post>
</div>
<p>Listening to Child Components Events (Child to Parent)</p>
<div id="blog-posts-events-demo">
<div :style="{ fontSize: postFontSize + 'em' }">
<blog-post2 v-for="post in posts" v-bind:key="post.id" v-bind:post="post" v-on:enlarge-text="onEnlargeText"></blog-post2>
</div>
</div>
First, you need a parent so two component can communicate. when my-button component is clicked triggers an event add-value that calls doSomething() function, then updates the value & show it to my-viewer component.
HTML
<!--PARENT-->
<div id="app">
<!--CHILD COMPONENTS-->
<my-button :title="'Add Value'" v-on:add-value="doSomething"></my-button>
<my-viewer :counter="counter"></my-viewer>
</div>
VUE.JS
//component A
Vue.component('my-button',{
props:['title'],
template:`<button v-on:click="$emit('add-value')">{{title}}</button>`
});
//Component B
Vue.component('my-viewer',{
props:['counter'],
template:`<button>{{counter}}</button>`
});
//Parent
new Vue({
el: '#app',
data:{
counter:0,
},
methods:{
doSomething:function(){
this.counter++;
}
}
})
This is base on Vue Components Guide
Passing Data to Child Components with Props (Parent to Child)
VUE.JS
//component (child)
//Vue component must come first, else it won't work
Vue.component('blog-post', {
/*Props are custom attributes you can register on a component. When a
value is passed to a prop attribute, it becomes a property on that
component instance*/
props: ['title'],
template: '<h3>{{ title }}</h3>'
});
//parent
new Vue({
el: '#blog-post-demo',
data: {
posts: [
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
]
}
});
HTML:
v-for will loop on posts and pass data to blog-post component
<div id="blog-post-demo">
<blog-post v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"></blog-post>
</div>
Listening to Child Components Events (Child to Parent)
HTML
You must first register the event by v-on:enlarge-text="onEnlargeText" to use $emit and make sure that it's always set to lower case or it won't work properly. example enlargeText and Enlargetext will always be converted to enlargetext, thus use enlarge-text instead, because its easy to read & valid, for a brief explanation about $emit you can read it here
<div id="blog-posts-events-demo">
<div :style="{ fontSize: postFontSize + 'em' }">
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
v-on:enlarge-text="onEnlargeText"></blog-post>
</div>
</div>
VUE.JS
When user clicks the button the v-on:click="$emit('enlarge-text')" will trigger then calling the function onEnlargeText() in the parent
//component (child)
Vue.component('blog-post', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<button v-on:click="$emit('enlarge-text')">
Enlarge text
</button>
<div v-html="post.content"></div>
</div>`
})
//parent
new Vue({
el: '#blog-posts-events-demo',
data: {
posts: [
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
],
postFontSize: 1
},
methods:{
onEnlargeText:function(){
this.postFontSize++;
}
}
})
Actually props suck sometimes you got some old external library in jquyer and need just damn pass value. in 99% of time use props that do job but.
A) spend tons of hours debuging changing tones of code to pass variables
B) one line solution
Create main variable in data letmeknow as object {}
this.$root.letmeknow
then somewhere in code from component
this.$root.letmeknow = this;
and then boom i got component console.log( this.$root.letmeknow ) and see now can change some values

Props not being assigned to data() attribute in Vue

I am creating a Vue component, which should refresh restaurants depending on user dynamically selected filters.
Therefor I have to update the filteredRestaurants in the data() function of my Vue component.
However, at first, when the Vue component is rendered, it takes the restaurant information from the "restaurants" prop.
I have tried to insert the "restaurants" into the filteredRestaurants data attribute to set it as a default value. Unfortunatelly then the stores wouldnt show at tall, as if the "restaurants" prop is inserted after the filteredRestaurants is assigned its value.
My question is, how can i get the "restaurants" prop into filteredRestaurants so that I can later on, re-render the Vue component when the user changes the filters.
<template lang="html">
<div class="test">
<Filters></Filters>
<div>
<ul class="o-list c-stores">
<Result v-bind:total="restaurants.length" v-bind:open="isOpen" v-on:toggle="toggleRestaurantList"></Result>
<li v-for="(restaurant, index) in restaurants" class="c-stores__location" :class="{'first': isFirst(index), 'last': isLast(index, restaurants)}">
<Location :index="index" :store="restaurant" :link="() => setCurrentRestaurant(restaurant)"></Location>
</li>
</ul>
</div>
</div>
</template>
<script>
import eventHub from './../../event-hubs/storefinder'
import Location from './Location'
import Filters from './Filters'
import Result from './Result'
export default {
props: ["restaurants", "isOpen", "currentSearch"],
data() {
return {
attributes : [],
// Here I am assigning the prop
filteredRestaurants : this.restaurants
}
},
head: {
title: function () {
return {
inner: this.$t('storefinder.overview')
}
},
meta: function functionName() {
return [{
name: 'og:title',
content: this.$t('storefinder.overview') + ' - ' + this.$t('storefinder.name'),
id: "og-title"
},
{
name: 'description',
content: this.$t('storefinder.description'),
id: "meta-description"
},
{
name: 'og:description',
content: this.$t('storefinder.description'),
id: "og-description"
},
]
}
},
components: {
Location,
Filters,
Result
},
methods: {
toggleRestaurantList() {
eventHub.$emit('showRestaurantList');
},
setCurrentRestaurant(restaurant) {
this.trackRestaurantSelect(restaurant.publicNameSlug);
this.$router.push({
name: "store",
params: {
restaurant: restaurant.publicNameSlug
}
});
},
trackRestaurantSelect(restaurantName) {
dataLayer.push({
'event': 'GAEvent',
'eventCategory': 'restaurants',
'eventAction': 'clickResult',
'eventLabel': restaurantName,
'eventValue': undefined,
'searchTerm': this.currentSearch && this.currentSearch.toLowerCase(),
'amountSearchResults': 1
});
},
created() {
eventHub.$on('addFilterTheRestaurants', (attribute) => this.attributes.push(attribute));
eventHub.$on('removeFilterTheRestaurants', (attribute) => this.attributes = this.attributes.filter(item => item !== attribute));
},
isLast: function (idx, list) {
return idx === list.length - 1;
},
isFirst: function (idx) {
return idx === 0;
},
}
}
</script>
The only way this worked, was when I had the filteredRestaurants as a function which returned "restaurants", and I called it inside the Vue template:
filteredRestaurants(){
return this.restaurants
}
Any help appreciated.
If I understand your question correctly, you are looking for a computed property:
computed: {
filteredRestaurants() {
return this.restaurants;
}
}
This will update whenever the value of this.restaurants changes.

ReactSelect: pass in extra data to be used by custom render

I am using React-Select.
Currently I am fetching data from elasticsearch and setting it to state:
var new_titles = []
body.hits.hits.forEach(function(obj){ // looping through elasticsearch
new_titles.push({value: obj._source.title_id, label: obj._source.title_name})
})
this.setState({titleResults: new_titles})
Later I am using this.state.titleResults and passing it into my React-Select component:
<Select autofocus optionComponent={DropdownOptions} options={this.state.titleResults} simpleValue clearable={this.state.clearable} name="selected-state" value={this.state.selectedTitleValue} onChange={this.handleTitleChosen} searchable={this.state.searchable} />
This works fine. But now I would like to pass in extra meta data pertaining to this title when users search my React-Select componentOptions. Something like this:
I am only passing in {value: obj._source.title_id, label: obj._source.title_name}, but I would like to pass in more information to be used by my DropdownOption component:
const DropdownOptions = React.createClass({
propTypes: {
children: React.PropTypes.node,
className: React.PropTypes.string,
isDisabled: React.PropTypes.bool,
isFocused: React.PropTypes.bool,
isSelected: React.PropTypes.bool,
onFocus: React.PropTypes.func,
onSelect: React.PropTypes.func,
option: React.PropTypes.object.isRequired,
},
handleMouseDown (event) {
event.preventDefault();
event.stopPropagation();
this.props.onSelect(this.props.option, event);
},
handleMouseEnter (event) {
this.props.onFocus(this.props.option, event);
},
handleMouseMove (event) {
if (this.props.isFocused) return;
this.props.onFocus(this.props.option, event);
},
render () {
return (
<div className={this.props.className}
onMouseDown={this.handleMouseDown}
onMouseEnter={this.handleMouseEnter}
onMouseMove={this.handleMouseMove}
title={this.props.option.title}>
<span>Testing Text</span>
{this.props.children}
</div>
);
}
});
How would you pass in more information into this component?
Well if I am looking at the code correctly it looks like you can pass in an optionRenderer prop along with your optionComponent.
https://github.com/JedWatson/react-select/blob/master/src/Select.js#L874
It takes your option as an argument so hypothetically you can pass additional fields in your option object and render via the optionRenderer function. Maybe something like this...
// ...
optionRenderer(option) {
return (
<div>
{option.value} - {option.label} - {option.someOtherValue}
</div>
)
}
// ...
render() {
let selectProps = {
optionComponent: DropdownOptions,
optionRenderer: this.optionRenderer,
options: this.state.titleResults,
simpleValue: true,
clearable: this.state.clearable,
name: 'selected-state',
value: this.state.selectedTitleValue,
onChange: this.handleTitleChosen,
searchable: this.state.searchable,
autofocus: true
}
return <Select {...selectProps} />
}

React: How to know which component calls the function

I am doing my first project using React and there is one thing I can't figure out. So I have many different Type components which are being set as the main component's TypesPage state. And when the onChange event happens on Type component I want to know which type it is in a TypesPage state or what index it is in a types array, so I can reupdate my data state.
Inside handleChange function I used jQuery's grep function comparing clicked Type title value with all the types array, but I am sure that is not the right way to do it and it would be an overkill with huge arrays.
Why I want to know which
handleChange:function(element, event){
var typeIndex;
$.grep(types, function(e, index){
if(element.title === e.title){
typeIndex = index
}
});
types[typeIndex] //Now I know that this is the Type that was changed
}
Fiddle
var types = [
{
type_id: 1,
type_name: "Logo"
},
{
type_id: 2,
type_name: "Ad"
},
{
type_id: 3,
type_name: "Catalog"
},
];
var Type = React.createClass({
render: function() {
return(
<li>
<input type="text" value={this.props.title}
onChange={this.props.handleChange.bind(null, this.props)} />
</li>
);
}
});
var TypesContainer = React.createClass({
render: function() {
var that = this;
return(
<ul>
{this.props.data.map(function(entry){
return(
<Type
key={entry.type_id}
title={entry.type_name}
handleChange={that.props.handleChange}
/>
);
})}
</ul>
);
}
});
var TypesPage = React.createClass({
getInitialState: function(){
return({data: types})
},
handleChange: function(element, event){
},
render: function() {
return(
<TypesContainer
data={this.state.data}
handleChange={this.handleChange}
/>
);
}
});
ReactDOM.render(
<TypesPage />,
document.getElementById('container')
);
I prefer ES6. The problem is, you have to bind your handleChange event with correct context of this and pass your arguments which you are expect to get inside your handle. See example below
class Example extends React.Component {
constructor(){
super();
this.state = {
data: [{id: 1, type: 'Hello'},{id: 2, type: 'World'},{id: 3, type: 'it"s me'}],
focusOn: null
};
}
change(index,e){
const oldData = this.state.data;
oldData[index].type = e.target.value;
this.setState({data:oldData, focusOn: index})
}
render(){
const list = this.state.data.map((item,index) =>
// this is the way how to get focused element
<input key={item.id} value={item.type} onChange={this.change.bind(this, index)}/>
);
return <div>
{list}
<p>Focused Element with index: {this.state.focusOn}</p>
</div>
}
}
React.render(<Example />, document.getElementById('container'));
fiddle
Thanks

Categories

Resources