I have made a parent component and child component (Piechart.js). I passed props from parent component to Piechart component like this ->
<Pie batdata={this.props.BattingRecord}/>
Above code is in parent component. Now I want to extract data from props inside my Piechart component (which is child component).
When I try console.log(this.props) inside my Piechart component constructor function it shows JSON data in console. See screenshot below:
Now I want to get the how parameter inside dismissal see the screenshot above. I think I am not accessing props properly. So what I tried in Piechart component is as follows:
Piechart.jsx:
import React, { Component } from 'react';
const wickets = this.props.batdata.reduce( (a,{dismissal}) =>{ <--Getting error at this line
if(!a[dismissal.how]){
a[dismissal.how]=1;
}else{
a[dismissal.how]=a[dismissal.how] + 1;
}
return a;
}, {});
console.log(wickets);
const dismissals = Object.values(wickets);
const labels = Object.keys(wickets);
//console.log(dismissals);
//console.log(labels);
class Pie extends Component {
constructor(props){
super(props);
console.log(this.props)
}
componentDidMount(){
const piechart = new Chartist.Pie('.ct-pie-chart', data, options, responsiveOptions);
}
render(){
return(
<div>
<div className="col-md-3 col-xs-6 ct-pie-chart">
{this.piechart}
</div>
</div>
)}
}
export default Pie ;
For above code I am getting type error: TypeError: Cannot read property 'batdata' of undefined
The problem is that const wickets is assigned outside of a class which extends Component. You need to move this code inside a member function of class Pie. this.props will only be defined inside of a class which extends Component.
class Pie extends Component {
...
componentDidMount(){
const wickets = this.props.batdata.reduce( (a,{dismissal}) =>{
if(!a[dismissal.how]){
a[dismissal.how]=1;
}else{
a[dismissal.how]=a[dismissal.how] + 1;
}
return a;
}, {});
// Now you can use the value of "wickets" however you wish in this function
this.piechart = new Chartist.Pie('.ct-pie-chart', data, options, responsiveOptions);
}
Even after fixing this problem you are likely to encounter other problems with the pie chart library you are using. If it is intended for use within a React app, you are not rendering it in the correct way.
The problem lies in your Piechart component. The error is quite self-explaining, your this.props is undefined. Without Piechart component's code, the exact problem cannot be told, however, there are couple of common mistakes that can lead to this error:
you are trying to access the props inside an event listener which is not properly bound or
you are trying to access this.props inside a stateless functional component or
inside the component's constructor before super(props) is called.
Edited: You are accessing this.props our side a proper context, you might want to get some idea how this work in JS first: http://javascriptissexy.com/understand-javascripts-this-with-clarity-and-master-it/ .
To correct your component, you can move the reduce function inside your component class, you can put it inside componentDidMount method.
Related
I have a Dashboard component, inside which I want to display multiple Charts (these charts can be line charts, bubble charts, you name it). For this example, I want to display a LineChart. You can see LineChart being passed in as the chartType value.
import React from 'react';
import Chart from './Chart';
import LineChart from '../LineChart';
const Dashboard = function () {
return (
<div>
<Chart chartType={LineChart} />
</div>
);
};
Chart acts like a generic wrapper, all it does is handle rendering, it shouldn't matter what chartType it is given as a parameter. If a LineChart obj is passed into it, it should render the LineChart component, if a ScatterChart obj then the ScatterChart component.
class Chart extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
};
this.ref = React.createRef();
this.myChartObj = new LineChart(props); // I DONT WANT THIS AS IT IS HARDCODING THE LINE CHART TYPE
// this.myChartObj = new props.chartType(props); // DOESNT WORK
}
componentDidMount() {
this.myChartObj.create(this.ref.current);
}
componentDidUpdate() {
this.myChartObj.update(this.ref.current);
}
render() {
return (
<div ref={this.ref} role="graphics-datachart" />
);
}
}
So I'm wondering how to instantiate a new LineChart object inside Chart's constructor by using the ChartType param. As you can see in the code, it's currently hardcoded with this.chartObj = new LineChart(props); Ideally, the solution would be something like this: this.chartObj = new props.chartType(props); because there's no hardcoding but it doesn't work and throws A constructor name should not start with a lowercase letter error.
Could anyone assist? Surely it should be OK to pass a class as a parameter and later use that parameter value to instantiate a new object of that class..? Any help would be greatly appreciated, thank you!
Edit: I'm aware that the error is being generated by my linter, but the question still stands as to how I instantiate an obj from a parameter. I'm getting a similar error in unit testing, which means that simply silencing my linter won't fit anything: TypeError: props.chartType is not a constructor
Unit test results*
note: I'm using "chartType" in the stack question but its equivalent to d3Chart in screenshot
My question is just same as the title.
Let's say I wrote the following code.
class TODOList extends Component {
render() {
const {todos, onClick} = this.props;
return (
<ul>
{todos.map(todo =>
<Todo
key={todo.id}
onClick={onClick}
{...todo}
/>
)}
</ul>
);
}
}
const mapStateToProps = (state) => {
return {
todos: state.todos
}
}
const mapDispatchToProps = (dispatch) => {
return {
onClick(data){
dispatch(complete(data))
}
}
}
export default connect(mapStateToProps,mapDispatchToProps)(TODOList);
Now, after the last line, this code will export the TODOList component with the state as props. It's not that it contains state, but just received state and will have them as 'props', just like the method name 'mapStateToProps' explains.
In the medium post(https://medium.com/#dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0) written by Dan Abramov, container component handles data as state, and presentational property do as props. Isn't it a presentational component that deals with data as props? I'm stuck with the idea that the right container should be one like below.
class CommentList extends React.Component {
this.state = { comments: [] };
componentDidMount() {
fetchSomeComments(comments =>
this.setState({ comments: comments }));
}
render() {
return (
<ul>
{this.state.comments.map(c => (
<li>{c.body}—{c.author}</li>
))}
</ul>
);
}
}
I'm not sure why react-redux named the API 'mapStateToProps', when I tried to make 'stateful'(not handling data by property) container component
First of all these guidelines are not part of the bible
you should write code that is easy to reason about for YOU and your TEAM.
I think you are missing something, A redux Container is different than a react Container.
I mean, connect will create the container for you, it doesn't mean the wraped component is a Container.
Basically you can export both versions from the same file, the Container (connected version) and the presentation version (the none connected one).
Another thing that usually throw people off, is the name of the function and argument of mapStateToProps.
I prefer the name mapStoreToProps as in
map the redux store to the component's props.
the name state can be confusing when we are in the context of react.
Edit
As a followup to your comment:
I totally didn't know these two are actually different. Could you please tell me about more details
They are different in the way that connect is creating a "Container" for you.
connect is a High Order Component that creates the Container Component for us with all the subscription logic + functions to pass portions of the store and action-creators to its children as props (mapStateToProps & mapDispatchToProps).
A "normal" Container is usually refers to a component that you write by hand, its often doesn't deal with how things should look but instead deal with certain logic of the app.
As for the other comments like
The connect HoC of react-redux just injects the properties you can request into your component. It returns a new component that is wrapped around your component so that it can update your component whenever the state you're interested in the redux store is modified
As i mentioned above, this is partially true. It's not just injecting the properties into our component, its subscribing to the store, grabbing it from the Provider (via context) and its doing all these with optimizations in mind, so we won't have to do it by ourselves.
I'm not sure how mapStateToProps can confuse someone. We are talking about a state management library
I've seen some devs that misunderstood this because react has a state and redux has a store (at least that's how it was called in most of the tutorials and documentations).
this can be confusing to some people that are new to either react or redux.
Edit 2
It was a bit confusing due to the sentence 'it doesn't mean the wraped component is a Container.' Why is the wrapped component not a container? Isn't a component created by connect also a container?
I mean that the wrapped component that you wrote doesn't have to be a Container.
You can connect a "Presentation" component:
const Link = ({ active, children, onClick }) => {
if (active) {
return <span>{children}</span>
}
return (
<a
href=""
onClick={e => {
e.preventDefault()
onClick()
}}
>
{children}
</a>
)
}
// ...
export default connect(mapState, mapDispatch)(Link)
mapStateToProps will be called when store data changes. It will pass the returned object as new props for the component. This will not affect the component's state. If you'd like to set a new state after the component got its new props you need to use another lifecycle method: static getDerivedStateFromProps (in earlier versions of react componentWillRecieveProps). The object returned by static getDerivedStateFromProps will be your new state.
https://reactjs.org/docs/state-and-lifecycle.html#adding-lifecycle-methods-to-a-class
connect() will connect your component to the redux store. Withouth the connect function (of course) your mapStateToProps will not work.
I'm not sure why react-redux named the API 'mapStateToProps'
We are talking about the store's state :)
The high level purpose is to seamlessly integrate Redux's state management into the React application. Redux revolves around the store where all the state exists. There is no way to directly modify the store except through reducers whom receive actions from action creators and for that to happen we need for an action to be dispatched from the action creator.
The connect() function directly connects our components to the Redux store by taking the state in the Redux store and mapping it into a prop.
This is power of Redux and its why we use it.
Lets say you are building a component called LaundryList and you want it to render a laundry list. After you have wired up the Provider in your "parent" component, I put it in quotes because technically Provider is a component so it becomes the parent.
You can then import the connect() function from react-redux, pass it mapStateToProps in order to get that laundry list from the Redux store into your LaundryList component.
Now that you have your list of linens inside of the LaundryList component you can start to focus on building a list of elements out of them like so:
class LaundryList extends Component {
render() {
console.log(this.props.linens);
return <div>LaundryList</div>;
}
}
That contains the list of linens object and for every list of linens inside of there we are going to return some jsx that is going to represent that linen on my list.
Back inside my laundry list component I will add a helper method inside the laundry list component called render list like so:
class LaundryList extends Component {
renderList() {
}
render() {
return <div>LaundryList</div>;
}
}
So this purpose of this helper method is to take the list of linens, map over them and return a big blob of jsx like so:
class LaundryList extends Component {
renderList() {
return this.props.linens.map((linen) => {
return (
);
});
}
render() {
return <div>LaundryList</div>;
}
}
Ok so this question is a bit tricky. I have been thinking about whether this is even correct concept wise, considering React is supposed to be a one-way flow of data, from parent to children, and not viceversa. But I would like to post the question anyway so I get different opinions and even possibly a way to get this to work.
In my app, I have a pretty large component that accepts forms as its children, and does some nifty React magic to pass its methods to the children so when the children elements are changed, they trigger the parent components methods that store the data in state and handles the form submissions. It works very nicely, however it is not so good at catching "defaultValues".
In a nutshell, I'm trying to trigger my parent method on the chilren's componentidMount() method, and it works, however, if there's more than one child trying to do this, the method gets called twice but it only uses the second child's dataset.
I have created a simplified version of my issue in the following code:
import React from 'react'
export class Parent extends React.Component {
constructor(props){
super(props)
this.state = {
data : {name:'james'}
}
this.updateData = this.updateData.bind(this)
}
updateData(key,data){
console.log('updating data')
this.setState({
data : {...this.state.data,[key]:data}
})
}
render(){
console.log(this.state)
return (
<div>
<Child1 updateData={this.updateData}/>
<Child2 updateData={this.updateData}/>
</div>
)
}
}
class Child1 extends React.Component {
componentDidMount(){
this.props.updateData('child1','myData')
}
render(){
return (
<div>
I am Child 1
</div>
)
}
}
class Child2 extends React.Component {
componentDidMount(){
this.props.updateData('child2','myData2')
}
render(){
return (
<div>
I am Child 2
</div>
)
}
}
This code will render 'updating data' twice on the console, but it will only update the state with the data sent in child2. Again, I can see how this may not be the best approach considering that im setting the state of a parent from its children, but it would be a good solution for setting default values on a parent component that gets reused a lot with different children.
Im all ears stack overflow
I think the problem is that setState does both updates at the same time (batches them) meaning the same initial state is used when merging both partial states. You need to use updater function as shown by kind user:
this.setState((prevState) => ({ data: { ...prevState.data, [key]: data } }));
I created a topbar menu thats needs access to a react component. Inside the topbar file I do not render the other components, but would like to access a function inside one of the components I use.
The project structure looks like this:
Header Component
TopBar Component
LayoutWrapper Component <-- Here I render other components
CustomerDetails Component <-- Here sits the functon I want to call.
This is the TopBar File:
class AdminTopbar extends Component {
renderTopMenu() {
...
if (currentPage.projects || currentPage.customers || currentPage.activities) {
return(
<nav>
...
<li>
// Function needs to be called here
{menuPageType == null ? null : <button onClick={updateActivityCardDetails.bind(this)}>Archiveren</button>}
</li>
</nav>
);
}`enter code here`
}
render() {
return (
<div className="topbar clear">
...
</div>
);
}
}
export default withRouter(AdminTopbar);
ActivityCardDetails file where the function sits:
class ActivityCardDetails extends Component {
constructor(props){
super(props);
// set page title
document.title = 'Strippenkaarten overzicht';
}
updateActivityCardDetails() {
}
}
I found some posts about refs to the parent but I don't have a nested structure between those files.
As a recap: On the TopBar components, which is a separate components without any relations, I would like to call the updateActivityCard method that sits in the ActvityCardDetails components.
React has recently(ish added the context API ) https://reactjs.org/docs/context.html
You'd probably be best off breaking the function out of the component because it's used by different components who aren't strictly hierarchically related. Then just use context to access it from each component.
You can try to wrap your whole structure with a wrapper component and follow the instruction given on each line below:
Wrapper Component <-- Here place a state and a custom function that can set a callbackFunction to its state e.g. myFunc = (callback) => this.setState({updateFunction: callback})
Header Component
TopBar Component <-- Pass the Wrapper Component's state here through props, like state={this.state}
LayoutWrapper Component <-- Pass the Wrapper Component's custom function here through props
CustomerDetails Component <-- Pass the Wrapper Component's custom function here through its parent's props, and call it like 'this.props.myFunc(this.yourInnerUpdateFunction)' on DidMount
Once you're done with this, you should be able to call the updateFunction() from your TopBar component through 'this.props.state.updateFunction()'
P.S. It's not an ideal approach, but it can get the job done if your app isn't too heavy. Hope this helps.
The docs for React state that component functions can be accessed by a parent component via refs. See: https://facebook.github.io/react/tips/expose-component-functions.html
I am attempting to use this in my application but run into an "undefined is not a function" error when the child function is called. I'm wondering if this has anything to do with using the ES6 format for React classes because I don't see any other differences between my code and the docs.
I have a Dialog component that looks like the following pseudocode. The Dialog has a "Save" button that calls save(), which needs to call the save() function in the child Content component. The Content component collects information from child form fields and performs the save.
class MyDialog extends React.Component {
save() {
this.refs.content.save(); <-- save() is undefined
}
render() {
return (
<Dialog action={this.save.bind(this)}>
<Content ref="content"/>
</Dialog>);
}
}
class Content extends React.Component {
save() {
// Get values from child fields
// and save the content
}
}
I could instead pass a prop (saveOnNextUpdate) down to Content and then execute save whenever it is true, but I would rather figure out how to get the method detailed in the React doc above to work.
Any ideas on how to get the doc approach to work or access the child component function in a different way?
Redux connect accepts an option parametre as the forth parameter. In this option parameter you can set the flag withRef to true. Then you can access functions to refs by using getWrappedInstance(). Like this:
class MyDialog extends React.Component {
save() {
this.refs.content.getWrappedInstance().save();
}
render() {
return (
<Dialog action={this.save.bind(this)}>
<Content ref="content"/>
</Dialog>);
}
}
class Content extends React.Component {
save() { ... }
}
function mapStateToProps(state) { ... }
module.exports = connect(mapStateToProps, null, null, { withRef: true })(Content);
Read more about it here: https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options
Worth reading this article about use of refs and consider if there's better approaches: https://facebook.github.io/react/docs/refs-and-the-dom.html#dont-overuse-refs
An alternative way to do this would be to use some other prop name (other than ref). I've found that this also works well if you're using a library like styled-components or emotion For example in a connected MyComponent:
<MyComponent
...
innerRef={(node) => { this.myRef = node; }}
/>
As it turns out, m90 was right -- this was a different issue entirely. I'm posting the solution in case someone runs into the same problem in the future.
My application is built with Redux, and the problem stems from using the react-redux connect function to connect a component to the store/global state. For some reason, exporting a component and connecting it to the store makes it impossible to access the functions inside of it. In order to get around this, I had to remove all use of global state from Content so that I could export it as a "dumb" component.
To be more clear, Content.js looked like this:
var connect = require('react-redux').connect;
class Content extends React.Component {
save() {
// Get values from child fields
// and save the content
// Use of this.props.stateObject
}
}
function mapStateToProps(state) {
const {
stateObject
} = state;
return {
stateObject
};
}
module.exports = connect(mapStateToProps)(Content);
Removing the use of global state (and therefore the use of connect and mapStateToProps allowed me to export the component using:
module.exports = Content;
Accessing this.refs.content.save() magically worked after doing this.