I'm working on a React component that accepts a list of data items, a function that constructs a component from the data items, and formats the items into a table. The code looks something like this:
export default class DataGrid extends React.Component{
constructor(props) {
super(props);
this.generateChild = item => this.props.template(item);
}
render () {
return tabulate (
this.props.data.map(this.generateChild),
this.props.cols
);
}
}
export function tabulate (components, cols)
{
return <table><tbody>{
collateRows (components, cols).map(cols =>
<tr>{cols.map(v =>
<td key={keyOf(v)}>{v}</td>)
}</tr>)
}</tbody></table>;
}
(where collateRows is a function that allocates the data values into a 2D array of rows and columns)
What I'm wondering is, how can I write the keyOf function referred to there: it needs to produce an identifier that is unique to each item, but remains consistent across calls for the same item. It also needs to do this with as little knowledge about the data item itself as possible, as this is intended to be a completely generic component that can be used for any reasonable data type.
What I do know is that the template property is a function that returns a React component, and that that component has a property key that contains an appropriate value. But I don't seem to be able to access that property (I get undefined and a warning that says I should provide another prop with the same value, but as I want to be able to use this with any component type, I don't see how this would work...), so what can I do instead?
Related
https://www.primefaces.org/primereact/showcase/#/datatable
https://www.primefaces.org/primereact/showcase/#/multiselect
I am using the PrimeReact library to create and customize a Data Table.
My table is dynamic and will build itself based on the data given to it. I am assigning different filters to each column depending on the column's data type, but because there are a variable number of columns I must create the filters dynamically.
To accomplish this I am factoring out the filter logic into a separate class which contain their state and logic.
My issue is that the MultiSelect component I am using as a filter interface does not update its value when it's value's state is updated. After updating the state the value remains null. As the MultiSelect component does not have a reference to the previously selected values I can only choose one value at a time.
I think I am missing some understanding regarding class components, as I usually use functional components. I used a class component in this case so that I could access filterElement from the instantiated DropDownFilter class through DropDownFilter.filterElement() and use as a prop in the Column component.
import React from 'react'
import { MultiSelect } from 'primereact/multiselect';
class DropDownFilter extends React.Component {
constructor(props) {
super(props);
this.multiSelRef = React.createRef();
this.state = {
selectedOptions: [],
}
// Added following two lines trying to fix issue but they did not alter behaviour
this.onOptionsChange = this.onOptionsChange.bind(this)
this.filterElement = this.filterElement.bind(this)
}
onOptionsChange = (e) => {
this.props.dt.current.filter(e.value, this.props.field, 'in');
this.setState({selectedOptions : e.value})
}
filterElement = () => {
return (
<React.Fragment>
<MultiSelect
ref={this.multiSelRef}
value={this.state.selectedOptions} //*** value is null even when selectedOptions has been updated after user chooses option.
// Confirmed by viewing value through multiSelRef
options={this.props.options}
onChange={this.onOptionsChange}
optionLabel="option"
optionValue="option"
className="p-column-filter"
/>
</React.Fragment>
)
}
}
export default DropDownFilter;
I learned the state was not working in this case because I was instantiating the DropDownFilter using the new keyword in when using it. This means it wasnt being mounted to the DOM and could not use state.
I am still having issues regarding implementing the custom columns with filters, but I have posted a new question to handle that scope with my new findings.
I'm working on a ReactJS app and i'm a new comer.
I have a Component like this
class Type extends React.Component {
constructor(props) {
super(props);
this.state = {
name : props.type.name,
description : props.type.description,
price : props.type.price,
imageList : props.type.images,
mode : 'view',
// i'm cloning the whole object
clone : props.type
};
}
handleDeleteImage(event) {
const imageId = event.target.getAttribute('data-imageId');
// get the current imageList of this Component
var imageList = this.state.imageList;
// checking the length of 2 image list before removing the
// targeted image
console.log(imageList.length) // displays 3
console.log(this.state.clone.images.length) // displays 3
// remove the targeted imageId
imageList.splice(imageId, 1);
// checking the length of 2 image list after removing the
// targeted image
console.log(imageList.length) // displays 2
console.log(this.state.clone.images.length) // displays 2
}
}
So what i'm doing here is i want to clone the object so when the user changes there mind and doesn't want to make changes anymore, they can hit the cancel button and everything is back to the state they were before (i have a function to handle this as well. I set the fields -name, description, price- to the values of the clone)
But as you can see, i didn't touched the image list in the clone at all still it got changed anyway.
Am i doing anything wrong here?
Thank you for any help.
Hey guys! So I realized that the concept I used in this service is not so efficient.
Like #Michael McQuade said, I should control the data in one flow only which is changing the data in the parent Component, not the child ones. I also reviewed the ReactJS Documentation and I can see why.
But with that being said. Let's say I'm working on a Component which has lots of Child-Component, does that mean I have to callback all the way up to the Parent Component to make changes in the Child one? And does that mean i must have multiple handlers in the Parent one that will be passed down to the Child that needs them?
I hope my question doesn't border you guys. Thanks!
You're using state and props together in a way I wouldn't recommend.
Instead of trying to make a copy of the props and storing it as state, make a stateless function and pass down a function which handles the deletion.
Here is an example:
class Child extends React.PureComponent {
render () {
return (<button onClick={this.props.handleBye}>{this.props.text}</button>)
}
}
class Parent extends React.PureComponent {
state = {
text: "Hello"
}
handler = () => {
this.setState({text: "bye"})
}
render() {
return (<Child text={this.state.text} handleBye={this.handler} />)
}ˆ
}
ReactDOM.render(<Parent />, document.body)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
This line will not clone an object, rather it will create a reference
clone : props.type
To clone you can use various techniques (depending on your need) one simple one would be
clone: Object.assign({}, props.type)
beware that this will only create a shallow copy of the object.
To create a deep copy you can use
clone: JSON.parse(JSON.stringify(props.type))
this is an easy technique but it is slow and will not copy dates correctly.
If you need fast and reliable deep clone you better search for something else that suits your needs (maybe a library like lodash).
this.state.clone is just a reference to the props.type object. So when you use splice() you change the contents of the array and therefore "mutate" props.type.
If you really want to clone the object do it like that:
this.state = {
clone: {...props.type} // create a new object and spread the props.type object properties
}
You can read more about the spread operator here
Suppose I have a React JS component, and for the sake of ease of use, I want that component to have access to an array that maps indexes to text.
For example, let's say that in scenario A I pass the component the ID 1, whereas in scenario B I pass the component the ID 3. I want the component to 'know' what to do with those IDs, so I want the component itself to have an array like the following:
[
0 => 'Text for scenario 0',
1 => 'Text for scenario 1',
2 => 'Text for scenario 2',
]
Now there are two ways that I personally know of that would allow this component to have access to that array. I could define it as part of the state in the constructor:
constructor() {
this.state = {
// put the array here
}
}
Or, I could make it a default prop:
Component.defaultProps = {
// put the array here
}
Which would then allow the component to access the array from either this.state or this.props.
However, I don't like either of these options, because both allow the array to be changed. If I put it in state, then the array can be changed from (almost) anywhere within the component, and if I put it in props, then the component could be passed an alternative array as a prop.
So: is there a way, in React JS, to define a static, unchangeable property on a component?
You shouldn't put things in the React state that aren't going to be reactive or manipulated in some way and setting it as a default prop is not a great solution either as that implies that sometimes the component will receive a prop of that name.
One solution could be to set up a function which simply returns this list:
getIdList() {
return [];
}
However, I think the better solution would be to just define it as a const above the React component. Seeing as this data will never be changed it doesn't really need to live within the component itself.
So, that would like something like the following:
const idArray = [];
class SomeReactComponent extends React.Component {
Since every module has it's own scope you can just put the array locally in the module of your component (above the component). If you don't export it it will be private
const texts = [
'Text for scenario 0',
'Text for scenario 1',
'Text for scenario 2',
]
export default class MyComponent extends Component {
...you can access texts as a const here
}
You can save variables in more than just state and props, neither of which you want want to use here, since this array is neither the state of the component or a property being passed to it.
You can, as others have said, store the array elsewhere in the file and simply reference it within your component.
You can store the array as a property of your component, and just reference it as this.myArray where needed:
constructor(props) {
super(props);
this.myArray = [1, 2, 3];
}
Or, if the array is not going to be changing from one instance of the component to the next, you can store it as a static property of the component and reference it as MyComponent.myArray where needed:
static myArray = [1, 2, 3];
The solution to just place it elsewhere in the file is a good one, especially if it is a const variable or if you may ever want to export and use it elsewhere in your project.
When I am asking this question, lots of doubts are coming into my mind. well, first I will give my problem description.
I have component X. and it contains checkboxes and a search box.
while something typed (call it search_query) in search box,
X needed to update the checkboxes which matches the search_query. [note that I got all the values of checkboxes by some api call. and it is done when component created. ]
First doubts I came to my mind is that
store (search_query) and (values of checkboxes) in component state
if the values are more searching takes more time.
is it possible to change the values of props inside the component
or is there any other way to do it ??
Since no code is shared. Assuming you are using plain React ( no Redux, middlewares are used ).
Answering your question:
[1] Is it possible to change the values of props inside the component?
Changing props values inside the component is a bad approach.
"All React components must act like pure functions with respect to their props."
[ref: https://facebook.github.io/react/docs/components-and-props.html#props-are-read-only]
Also, the view doesn't get the update if props values changed within the component.
[2] or is there any other way to do it.
yes ( without mutation inside the component )
using "state" property to hold values & setState to update values.
[3] How to store the information in react component?
Let's rename component X as FilterData,
searchbox ( SearchBox ) & checkboxes (SelectionBox) are two individual components.
// Define state to FilterData
class FilterData extends React.Component {
constructor() {
super()
this.state = {
term: '',
searchResults: []
}
}
....
}
// pass the state values to child components as props
class FilterData extends React.Component {
....
render() {
return (
<div>
<SearchBox term={this.state.term} />
<SelectionBox options={this.state.searchResults} />
</div>
)
}
}
In React App,
data flows top down (unidirectional) and there should be a single source of truth.
SearchBox & SelectionBox are two individual (sibling) components,
SearchBox's state has terms ( which has the search string )
When the user enters input SearchBox will update its state and possible to detect change event and fire ajax and get the response.
How SelectionBox can detect search that had happened, how it can get data.
This is why the state is moved to common ancestor FilterData.
[Ref: https://facebook.github.io/react/docs/lifting-state-up.html]
[Ref: https://facebook.github.io/react/docs/state-and-lifecycle.html#the-data-flows-down]
Code Sample -----------------------------------------------------
Selected values are not saved:
https://codepen.io/sudhnk/pen/NgWgqe?editors=1010
Selected values are saved:
https://codepen.io/sudhnk/pen/JJjyZw?editors=1010
I want to use Chart.js on my website. As you can see title, I'm using React.js. To use Chart.js, I need the canvas and context like this:
let context = document.getElementById('canvas').getContext('2d');
let chart = new Chart(context, ...);
so I design the component like this:
export function updateChart() {
let context = this.refs.chart.getContext('2d');
let chart = new Chart(context ,... );
...
}
export default class GraphChart extends React.Component {
constructor() {
super();
updateChart = updateChart.bind(this);
}
componentDidMount() {
updateChart();
}
render() {
return <canvas ref="chart" className="chart"></canvas>;
}
}
as you can see, I exported two things, update chart function and GraphChart class. Both will using in parent component like this:
import { updateChart } from './GraphChart';
import GraphChart from './GraphChart';
class Graph extends React.Component {
...
someKindOfAction() {
// update chart from here!
updateChart();
}
render() {
return (
<div>
<SomeOtherComponents />
<GraphChart />
</div>
);
}
}
then Parent class using exported updateChart function to update chart directly. It was working, but only first time. After unmount and mount the GraphChart component, it's refs are just empty.
Why refs is empty? And If I did wrong way, how can I get canvas context for initialize Chart.js?
Object refs is undefined, because this is not what you think it is. Try logging it.
The function you’re exporting is not bound to this of your component. Or perhaps it is, but to the last created instance of your component. You can never be sure that’s the mounted instance. And even if you are, you can not use multiple instances at the same time. So, I would dismiss this approach entirely.
Other than that, providing the function to alter some component’s state is exactly the opposite of what’s React is trying to accomplish. The very basic idea is that the component should know to render itself given some properties.
The problem you are trying to solve lies in the nature of Canvas API, which is procedural. Your goal is to bridge the gap between declarative (React) and procedural (Canvas) code.
There are some libraries which do exactly that. Have you tried react-chartjs? https://github.com/reactjs/react-chartjs
Anyways, if you’re wondering how the hell should you implement it the “React way”, the key is to declare properties your component handles (not necessarily, but preferably), and then to use component lifecycle methods (e.g. componentWillReceiveProps and others) to detect when properties change and act accordingly (perform changes to the canvas).
Hope this helps! Good luck!