I have looked at other questions that seemingly had a similar issue, but none of the accepted answers have solved my issue. I am attempting to fetch new names and load them into child component when redux is updated with new IDs.
When I use only redux and no state (as I would prefer), the new IDs do not get passed along to the child component and the names do not load at all
Alternatively, I have tried using state for the names in the child component (as you can see in the commented text below). However ... Oddly enough, every time the IDs are changed, the component loads the names based on the previous IDs rather than the current IDs.
Redux
const initialState = {
objectOfIds: {"someID":"someID", "aDifferentID":"aDifferentID"}, // I know this format may seem redundant and odd, but I have to keep it this way
arrayOfNames: ["John Doe", "Jenny Smith"]
}
Parent Compoenent
// React
import React from 'react';
import firebase from 'firebase';
// Components
import ListOfNames from './ListOfNames';
// Redux
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {set} from './../actions/index.js';
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.changeIDs = this.changeIDs.bind(this);
}
changeIDs() {
this.props.set("objectOfIds",{"aNewID":"aNewID","someOtherID":"someOtherID","anotherID":"anotherID"});
}
render (
return (
<div>
<h2>Parent Component</h2>
<button onClick={this.changeIDs}>Change Data</button>
<ListOfNames objectOfIds={this.props.reduxData.objectOfIds}/>
</div>
)
)
}
function mapStateToProps(state) {
return {
reduxData: state.reduxData
};
}
function matchDispatchToProps(dispatch) {
return bindActionCreators({
set: set
}, dispatch)
}
export default connect(mapStateToProps, matchDispatchToProps)(ParentComponent);
Child Compoenent
// React
import React from 'react';
import firebase from 'firebase';
// Redux
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {set} from './../actions/index.js';
// Firebase Database
var databaseRef;
class ListOfNames extends React.Component {
constructor(props) {
super(props);
this.state= {
arrayOfNames: []
}
this.fetchNamesForIds = this.fetchNamesForIds.bind(this);
this.add = this.add.bind(this);
}
componentDidMount() {
console.log("componentDidMount triggering...");
firebase.auth().onAuthStateChanged(function (user) {
if (!user) {
console.log("no user authenticated");
}
databaseRef = firebase.database().ref('/people/' + user.uid);
this.fetchNamesForIds(this.props.reduxData.objectOfIds);
})
}
// I have tried making the fetch in componentWillReceiveProps so that the function would run anytime the IDs were updated in redux, but "this.props.objectOfIds" and "this.props.reduxData.objectOfIds"
componentWillReceiveProps() {
console.log("componentWillReceiveProps triggering...");
console.log("this.props.objectOfIds");
console.log(this.props.objectOfIds);
console.log("this.props.reduxData.objectOfIds");
console.log(this.props.reduxData.objectOfIds);
this.fetchNamesForIds(this.props.reduxData.objectOfIds);
// Note: I have also tried: this.fetchNamesForIds(this.props.objectOfIds); so that the data is passed in from the parent
}
// fetched the names for the associated IDs
fetchNamesForIds(personIds) {
if (personIds === [] || personIds === undefined || personIds === null) {
ALTERNATIVE TO LINE ABOVE
I would prefer to store the data in redux so that it is accessible to other components, but doing this did allow the data to load, but it loads with a lag (i.e. when I change the IDs, it loads the names associated to the previous IDs)
// this.setState({
// arrayOfNames: []
// });
this.props.set("arrayOfNames", []);
return
}
var arrayOfNames = [];
// loop through person and set each value into the arrayOfNames array
Object.keys(IDs).map(function(person, index) {
console.log("person = " + person);
console.log("index = " + index);
// get names associated with the ids obtained
var name = ''
databaseRef.child('people').child(person).limitToFirst(1).on("value", function(snapshot) {
var firstName = snapshot.child('firstName').val()
var lastName = snapshot.child('firstName').val()
name = firstName + " " + lastName
console.log("name = " + name);
arrayOfNames.push(name);
console.log("arrayOfNames = " + arrayOfNames);
this.props.set("arrayOfNames", arrayOfNames);
ALTERNATIVE TO LINE ABOVE
I would prefer to store the data in redux so that it is accessible to other components, but doing this did allow the data to load, but it loads with a lag (i.e. when I change the IDs, it loads the names associated to the previous IDs)
// this.setState({
// arrayOfNames: arrayOfNames
// });
}.bind(this));
}.bind(this));
}
render() {
return(
(this.props.user.arrayOfNames === [] || this.props.user.arrayOfNames === undefined || this.props.user.arrayOfNames === null || this.props.user.arrayOfNames.length < 1)
? <span>no people selected</span>
: <div>
<h5>List of People</h5>
{this.props.user.arrayOfNames.map((name, index) => {
return (
<h5>{name}</h5>
)
})}
</div>
)
}
}
ListOfNames.propsTypes = {
objectOfIds: React.PropTypes.Object
};
function mapStateToProps(state) {
return {
reduxData: state.reduxData
};
}
function matchDispatchToProps(dispatch) {
return bindActionCreators({
set: set
}, dispatch)
}
export default connect(mapStateToProps, matchDispatchToProps)(ListOfNames);
Similar Questions:
https://github.com/gaearon/redux-thunk/issues/80
React Native Child Component Not Updated when redux state changes
update child component when parent component changes
Does anyone understand how I can get my component to load the data based on the current IDs in redux?
Probably because the object keys are changed but not the object reference.
A hacky solution would be to call this.forceUpdate() to update the component after the change:)
I had a similar issue where I was loading a child component multiple times on one page and despite passing in what I thought was a unique ID it would only reference the first ID. I know this isn't exactly the situation you have but this will allow you to have a unique object key AND a unique object reference which will hopefully fix your issue.
This is the package I used for this: https://www.npmjs.com/package/react-html-id. When you import the package you need to have curly brackets.
import { enableUniqueIds } from 'react-html-id'
The rest is explained on npm.
Tip: you don't need to bind your functions if you use the new javascript syntax.
this.add = this.add.bind(this); will be solved by writting the add method like:
add = () => {
};
The issue is the Child Component componentWillReceiveProps. You are not using the new props that are propagated to this component. componentWillReceiveProps is called with nextProps, which contains the updated props.
Use this in your child component
componentWillReceiveProps(nextProps) {
console.log("componentWillReceiveProps triggering...");
console.log("nextProps.objectOfIds ", nextProps.objectOfIds);
console.log("nextProps.reduxData.objectOfIds ", nextProps.reduxData.objectOfIds);
this.fetchNamesForIds(nextProps.reduxData.objectOfIds);
}
Related
I'm using React and Redux to build a website and I have the code below for AppInfo component. I have result variable in the render method which is initialized before return statement. At first the result is an empty string but once the state changes it is set to an object which contains various app attributes. I want to display the result variable inside a div. The result always stays an empty string although I do see that the redux state is updated. In addition if call directly this.props.appId inside the div the updated state is indeed displayed. Why the result variable doesn't work?
This is my code:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { connect } from 'react-redux';
class AppInfo extends Component {
render() {
const { appInfo } = this.props;
const { appId } = appInfo;
let result = '';
if (appId) {
result = JSON.stringify(appInfo);
}
return (
<div>
{result}
{`JSON.stringify(appInfo) = ${JSON.stringify(appInfo)}`}
{`this.props = ${JSON.stringify(this.props.appInfo)}`}
</div>
);
}
}
AppInfo.propTypes = {
appInfo: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
appInfo: state.app
});
export default connect(mapStateToProps, null)(AppInfo);
Could you try to use result = JSON.stringify(this.props.appInfo);?
I think the issue might be related to the first 2 const’s you create.
I'm currently learning react and redux and stumbled into a problem i can't really get my head around. Trying to implement the same functionality
as in this article: https://medium.com/#yaoxiao1222/implementing-search-filter-a-list-on-redux-react-bb5de8d0a3ad but even though the data request from the rest api i'm working with is successfull i can't assign the local state in my component to my redux-state, in order to be able to filter my results. Heres my component:
import React from 'react'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import * as fetchActions from '../../actions/fetchActions'
import Stafflist from './Stafflist'
class AboutPage extends React.Component {
constructor(props) {
super(props)
this.state = {
search: '',
currentlyDisplayed: this.props.store.posts
}
this.updateSearch = this.updateSearch.bind(this)
}
updateSearch(event) {
let newlyDisplayed = this.state.currentlyDisplayed.filter(
(post) => {
return (
post.name.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1
|| post.role.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1
)}
)
console.log(newlyDisplayed)
this.setState({
search: event.target.value.substr(0, 20),
currentlyDisplayed: newlyDisplayed
})
}
render() {
return (
<div className="about-page">
<h1>About</h1>
<input type="text"
value={this.state.search}
onChange={this.updateSearch}
/>
//component for rendering my list of posts.
<Stafflist posts={this.state.currentlyDisplayed} />
</div>
)
}
}
// this is here i assign my api data to this.props.store.posts
function mapStateToProps(state, ownProps) {
return {
store: state
}
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(fetchActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(AboutPage)
Comparing how i assign my stores state to my local component with how it works in the article, it seems to be done in the same way. Mine:
this.state = {
search: '',
currentlyDisplayed: this.props.store.posts
}
article:
this.state = {
searchTerm: '',
currentlyDisplayed: this.props.people
}
within react devtools i can see my data in as it should be in the store, but it won't work to assign it to my local state within the component in order to perform the filtering, and i don't really know how to debug this. My state in the local component just says
State
currentlyDisplayed: Array[0]
Empty array
also if i change
<Stafflist posts={this.state.currentlyDisplayed} />
to
<Stafflist posts={this.props.store.posts} />
the list renders as it should :)
Reducer:
import * as types from '../actions/actionTypes'
import initialState from './initialState'
export default function postReducer(state = initialState.posts, action) {
switch(action.type) {
case types.FETCH_POSTS_SUCCESS:
return action.posts.data.map(post => {
return {
id: post.id,
name: post.acf.name,
role: post.acf.role
}
})
default:
return state
}
}
Any ideas?
The problem with your code is that you do not handle how to get newly received props to your state. This means that when you receive the data from your api call only the props are updated while component state is not.
If you look carefully at the referenced article, in the onInputChange method they recalculate the state from the props whereas your updateState method only filters from the local state.
Setting the state in the constructor only ensures that the props are copied on the initial mount of the component. At that point in time the store only contains the initial state (initialState.posts in your reducer code). This is the price of keeping both component state and store; you must think of how to keep the two in sync after the initial load.
One solution is to update the state in componentWillReceiveProps:
componentWillReceiveProps(nextProps){
const nextFiltered = nextProps.store.posts.filter(your filtering code here);
this.setState({currentlyDisplayed: nextFiltered});
}
This will update your state whenever the component receives new props. Note react has phased out componentWillReceiveProps and you should use getDerivedStateToProps as of react 16.3. Refer here for more details.
I'm using Meteor, React, react-meteor-data, and React Router. I am linking to a page and adding an /:id, which I am then trying to use to query the database and build out the component with the returned data.
The problem is the initial query returns an empty array. I can then see in my render method and componentWillReceiveProps that it returns the data I expect a moment later. The problem is that my component does not re-render. I am using withTracker because I want the component to update and re-render every time the database changes in the targeted Collection.
Here is the React code on the client-side:
export default withTracker((props) => {
const handle = Meteor.subscribe('game', props.match.params.id);
return {
listLoading: !handle.ready(),
game: ActiveGames.find({ _id: props.match.params.id}).fetch(),
};
})(Game);
And here is the publication in 'imports/api/activeGames.js':
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
import { check } from 'meteor/check';
export const ActiveGames = new Mongo.Collection('activeGames');
if (Meteor.isServer) {
Meteor.publish('game', function (id) {
check(id, String);
return ActiveGames.find({ _id: id });
});
Meteor.publish('activeGames', function activeGamesPublication() {
return ActiveGames.find();
});
}
Here is a screenshot of the output I'm getting, with console logs to track the pertinent life cycle methods.
I believe it's a simple matter of doing something with the listLoading prop.
For instance, your Game component could be:
class Game extends Component {
constructor(props){
super(props);
}
renderGames = () => {
return this.props.games.map(g => {
return (
<li>{g.title}</li>
)
})
}
render() {
return (
<ul>
{this.props.listLoading ? <span>Loading...</span> : this.renderGames()}
</ul>
);
}
}
export default withTracker((props) => {
const handle = Meteor.subscribe('game', props.match.params.id);
return {
listLoading: !handle.ready(),
game: ActiveGames.find({ _id: props.match.params.id}).fetch(),
};
})(Game);
This is how I handle the time between subscribing and receiving data.
Hope it helps, though I'm sure you found a solution by now ;)
I have a main component with a child chart component. On connecting to a websocket, the main component updates the state of the child chart component. This however does not redraw. When I click on the chart however, the labels appear, and when I click again, the values appear with the labels.
Main.js:
import IO from 'socket.io-client';
import React from "react";
import { Switch, Route } from 'react-router-dom';
import { Chart } from "./Chart";
let ftse100Tickers = require('./ftse_100_tickers.json');
let randomInt = Math.floor(Math.random() * ftse100Tickers.tickers.length);
/**
* Main application component which contains
*/
export class Main extends React.Component {
componentWillMount() {
this.socket = IO(location.protocol + "//" + document.domain + ":" + location.port);
this.socket.on("connect", (() => this.connect()));
this.socket.on("disconnect", (() => this.disconnect()));
this.socket.on("initial data", ((data) => this.createInitialChart(data)))
}
connect(){
this.setState({status: 'connected'});
this.socket.emit("get initial data", this.state.ticker);
}
disconnect(){
this.setState({status: 'disconnected'})
}
createInitialChart(data){
let tempErrorChart= this.state.errorChart;
for (let row of data){
tempErrorChart.labels.push(row.time_stamp);
tempErrorChart.datasets[0].data.push(row.error);
}
this.setState({errorChart: tempErrorChart});
}
constructor(props){
super(props);
this.state = {
errorChart: {
labels: [],
datasets: [
{
label: 'Error',
data: [],
},
]
},
status: 'disconnected',
ticker : ftse100Tickers.tickers[randomInt],
twitter : ftse100Tickers.twitter[randomInt]
}
}
render() {
return (
<div className="row">
<div className="row">
<div className="col-lg-6">
<div className="card border-0">
<div className="card-body">
<Chart chart={this.state.errorChart}/>
</div>
</div>
</div>
</div>
</div>
)
}
}
The chart component is as so:
Chart.js
import { Line } from "react-chartjs-2"
import React from "react";
/*
* General charting component used for rendering charts
*/
export class Chart extends React.Component {
render() {
return (
<Line data={this.props.chart} options={{}}/>
)
}
}
I see one problem and that is you are not changing object references in this.state.errorChart upon errorChart update before you call setState. Even though you push to its properties new items, the object and even the inner array references do not change, and if the Line component does some props checking on whether it should rerender itself or not, it figures by receiving still the same references that nothing has changed and there is no need to rerender.
Now this was just my assumption, but either way it is a good practice to always create new objects while creating new state once those objects are about to be modified. This allows for fast object (state) references comparisons in shouldComponentUpdate methods or while using PureComponentwhich in turn makes it easier and more performant to determine whether to rerender the component or not. On the other hand, if you would use the same references still, you would have to implement deep comparison of the old and the new state, which is definitely more expensive and very fragile in the long run.
Example on how to correctly update the state follows:
createInitialChart(data) {
const errorChart = this.state.errorChart
const newErrorChart = {
...errorChart
}
newErrorChart.labels = [...errorChart.labels, data.map(row => row.time_stamp)]
newErrorChart.datasets[0].data = [
...errorChart.datasets[0].data,
data.map(row => row.error)
]
this.setState({ errorChart: newErrorChart })
}
Edit:
By looking at the component's shouldComponentUpdate implementation - ChartComponent, It can be clearly seen, that there are multiple options on how to make the Line rerender, eg. by giving redraw={true} prop to the Line component. The procedure above is generally still the safest way to ensure rerender though.
You might need componentWillReceiveProps(nextProps, nextState).
You can compare the old state here with the new state and update the state accordingly.
Please set the initialState like so:
constructor(props){
super(props);
this.state = {errorChart: {...}}; //your initial values here.
}
then,
componentWillReceiveProps(nextProps, nextState){
if(this.state.errorChart !== nextState.errorChart){
let tempErrorChart = {...this.state.errorChart};
for (let row of data){
tempErrorChart.labels.push(row.time_stamp);
tempErrorChart.datasets[0].data.push(row.error);
}
this.setState({errorChart: tempErrorChart});
}
}
How do I filter data in a component based on a parent prop?
My data to compare to contains something like:
{
"id": "5a7847508f9337cf77712128",
"index": 0,
"projectName": "Ovolo",
"location": "Stoddard Place, Riverton, New York, 3571"
},
{
"id": "5a7847503101a8ef7f7d3c30",
"index": 1,
"projectName": "Isonus",
"location": "Elliott Walk, Magnolia, Minnesota, 4488"
}...
My component looks something like this:
import React, {Component} from 'react';
import data from "../data/projects.json";
const ProjectTitle = data.filter((projects, props) => {
if (projects.id === props.project_id )
var title = projects.projectName;
return title;
});
class ProjectName extends Component {
constructor() {
super();
this.state = {
projectTitle: ProjectTitle
}
}
componentDidMount() {
this.setState({
projectTitle: ProjectTitle
});
}
render() {
return (
<h4 className="projectName">
{ this.state.projectTitle }
</h4>
);
}
}
export default ProjectName;
Basically, I want to match props.project_id from the parent component with the first project from my data that matches the id, so I can display the projectName using the component. Currently, the one that I tried does not return the name of the project. I am new to this so I need help and guidance, maybe an example or a reference may help.
Basically you wants to watch for different project_id sent from the parent and depending on which want to filter the data, as far as i understood.
One thing you can do is use componentWillReceiveProps lifecycle to watch for new props. and can also componentDidMount lifecycle to set the projecttitle.
you need to change the projectTitle function also
const ProjectTitle = (projectId) => {
const project = data.filter((projects) => {
if (projects.id === projectId ) {
return true;
}
return false
});
if(project) {
return projects.projectName;
}
return project
}
componentWillReceiveProps(nextProps) {
const {project_id} = nextProps
const newProjectTitle = ProjectTitle(project_id)
this.setState({
projectTitle: newProjectTitle,
})
}
componentDidMount() {
const {project_id} = this.props
const newProjectTitle = ProjectTitle(project_id)
this.setState({
projectTitle: newProjectTitle,
})
}
By adding componentWIllReceiveprops and componentDidMount you can
dynamically change the project name whenever new project_id is passed
as a props. you can also use the constructor function to initialize the state like this
constructor(props) {
super(props)
this.state = {
projectTitle: ProjectTitle(props.project_id),
}
}
Refer this lifecycle methods in react for more reference
Not sure why it isn't working exactly but that ProjectTitle that you create outside the class doesn't ring well with me. I think that you're assigning it to the state and that's not working as expected. You should do the filter (or find) operation in the componentWillMount function of the React lifecycle . After you find the project you do the setState and it should work.
class ProjectName extends Component {
constructor() {
super();
this.state = {
projectTitle: ''
}
}
componentWillMount() {
const project = data.find(project => project.id === this.props.project_id);
if (project) {
this.setState({
projectTitle: project.projectName
});
}
}
render() {
return (
<h4 className="projectName">
{this.state.projectTitle}
</h4>
);
}
}
export default ProjectName;
Assuming props.project_id is a ProjectName's prop the easiest solution is to move the filter to the render function. Also, if you want to find one record use find not filter.
render() {
const project = data.find(( {id} ) => id === this.props.project_id)
return (
<h4 className="projectName">
{ project && project.title }
</h4>
);
}
You could do as other suggested and set the state during the component's creation, but then you'd have to do the same in componentWillReceiveProps, this creates a lot of unnecessary complexity.
If performance is an issue (it generally isn't) and you want to find the project only once per id, use a memoized function and keep the logic in the render function. This is a lot simpler and has the same effect.