creating table with reactjs - angularjs

I am trying to create a table using reactjs. I wrote a class and using this as directive, passing config and data and trying to populate the table.
function createRows() {
let self = this;
return <tbody>{_.each(self.props.tableData, function(r) {
<tr>
{_.each(self.props.columnConfigs, function(c) {
<td>{r[c.columnName]}</td>
})}
</tr>
})}</tbody>;
}
function createHeaders() {
let self = this;
return <thead>
{_.each(self.props.columnConfigs, function(c) {
<th>c.displayName</th>
})}
</thead>
}
class TableComponent extends React.Component {
constructor(props, context) {
super(props, context);
}
render() {
return (<table>
{createHeaders.call(this)}
{createRows.call(this)}
</table>)
}
}
function TableComponentFactory() {
return TableComponent;
}
export default TableComponentFactory;
But this is not working. it throws an error:-
Invariant Violation: Objects are not valid as a React child (found: object with keys {columnName, displayName, isSortable, path, hidden, columnStyle, editable, isId}). If you meant to render a collection of children, use an array instead or wrap the object using createFragment(object) from the React add-ons. Check the render method of `TableComponent`.
which is actually the columnConfig. I am not able to understand why this happening. What mistake I am doing, also a bit of explanation will be great.

Instead of using 'each' (from which function no return value is expected), you should use 'map' and use 'return (<JSX Element>)' in its body. Each should be use to apply an action on every item in an array without returning data. But you want to return data, because you want the function to supply rows and columns.

Related

ReactJS: How to render a collection of objects

So I'm quite new on web development last couple of days. I come from c++ background and I can't wrap my head through all the principles of reactjs. I have 2 classes. The child class called JobAd should render some information that it got from props.
export default class JobAd extends Component {
constructor(props) {
super(props);
this.state ={
index: props.index,
id: props.jobId,
name: props.name,
description: props.description,
location: props.location,
adress: props.adress,
alreadyApplied: props.alreadyApplied,
open: false,
// toggleJob: props.toggleJob,
};
this.toggleJob = props.toggleJob;
}
render() {
return (
<div className={`${styles.jobAd} d-flex` + "job " + (this.state.open ? 'open': '')} key={this.state.index} onClick={() => this.toggleJob(this.state.index)}>
<div className={`${styles.jobTitle}`}>
{this.state.location} - {this.state.name}
</div>
<div className={`${styles.jobDetails}`}>
<div className={`${styles.jobDescription}`}> {this.state.description}</div>
<div className={`${styles.jobAdress}`}>{this.state.adress}</div>
<ApplyButton jobId= {this.props.id} alreadyApplied = {this.props.alreadyApplied}/>
</div>
</div>
)
}
}
The second class, queries a mongoDB db and creates jobAd objects populating them from the info gotten from db.
class JobExplorer extends React.Component
{
...
result.data.jobs.forEach(job => {
var find = job.employees.find(obj => obj === userId);
if (!(find === undefined)) {
alreadyApplied = true;
}
var toPush = new JobAd ({
index: i,
id:job._id,
description:job.description,
name:job.name,
location:job.locationName,
adress:job.locationAdress,
alreadyApplied:alreadyApplied,
open:false,
toggleJob: this.toggleJob.bind(this)
});
jobList2.push(toPush);
console.log("look");
console.log(jobList2)
});
this.setState({
jobList: jobList2
})
this.setState({
error: null,
jobs: result.data.jobs
});
...
render()
{
console.log("look2");
console.log(this.state.jobList);
return (
<div><Navigation />
{this.state.jobList}
</div>
);
}
But I am faced with the following error which I cannot find a fix for.
Error: Objects are not valid as a React child (found: object with keys {props, context, refs, updater, state, toggleJob}). If you meant to render a collection of children, use an array instead.
How should I instantiate those objects so I could render them using the "architecture" I wrote. Is there a fundamental flaw that I have in my classes?
The below snippet doesn't work because new will return an object (this) not the react component.
So, instead of
var toPush = new JobAd({
index: i,
id: job._id,
...
});
jobList2.push(toPush);
you can do this
var toPush = <JobAd
index={i}
id={job._id}
...
/>;
The above snippet works because <JobAd ... /> is converted to React.createElement(JobAd, ... ). However, you still shouldn't do it like this. since there are a lot of better ways to do this. one of them is:
save just the data in joblist and then render the data list on JobAd component
like below:-
render(){
return this.state.joblist.map((job, i) => (
<JobAd
key={job._id}
index={i}
...
/>
));
}
The key is a really important thing. Read about it: https://reactjs.org/docs/lists-and-keys.html
Things that could be improved:-
Don't copy props in the state as you are doing in JobAd class instead directly render the props.
Don't call setState twice as in JobExplorer. you could set all the keys in
setState at the same time. since that would render the component twice.
Suggestions:-
You should avoid using var as that might cause some issues here.
since, you are just a starter, try using functional component first. they are
quite easier to grasp
You seem to have a misconception about state/props in React and web development. It's very normal; I learned python and Java first and many tutorials seem to assume that people just know this already.
"State" in generally refers to variables containing/referring to values that can change without a page refresh in your application. If you know a value is not going to change, it does not need to be held in state. Storing it in a normal variable is exactly what you should do.
"Props" is just another word for arguments that are passed to React components. There's more to it in reality, but as a beginner, that's all you need to really know for now.
So in your job add, things like name, address, jobs, description shouldn't go in state because they aren't going to change as a result of user interaction or for any other reason, unless the underlying data they are loaded from changes, but then that wouldn't be handled by React but instead by the API that your app gets data from. They should just be rendered, so refer to them like this.props.address in your render method. The value for open, however, need to be in state, because that definitely can change.
As for the error, it looks like you are not calling JobAd correctly. You need to use the syntax <Job Ad/> rather than new JobAd...that won't work in React.
I would recommend doing a tutorial to get the basics down.

Migrating away from componentWillReceiveProps

The componentWillReceiveProps is becoming deprecated, however, I am unclear as to how to migrate away from it. For example, a simplified version of my current looks something like this:
import Reorder, {reorder, reorderImmutale, reorderFromTo, reorderFromToImmutable} from 'react-reorder'
class ObjectsArea extends React.Component {
constructor(props) {
super(props);
this.state = {
items: this.props.objects ? this.props.objects.items : []
};
}
componentWillReceiveProps(nextProps){
//May have to do a deep compare between nextProps.items and current items?
if (nextProps.objects){
this.setState({items: this.nextProps.objects.items})
}
}
onReorder (event, previousIndex, nextIndex, fromId, toId) {
let new_items = reorder(this.state.items, previousIndex, nextIndex)
this.setState({
items: new_items
});
//call to parent function
}
render(){
orderable_items = <Reorder reorderId="objects" onReorder={this.onReorder.bind(this)}>
{
this.state.items.map(item => (
<div key={item.id}>
{item.text}
</div>
))
}
</Reorder>
return (
<div>{orderable_items}</div>
)
}
My requirements:
Sometimes there will be no objects property (there isn't one on initial load)
When there is an objects property a sortable/draggable list is created using the react-reorder component
When items in the list are dragged to be rearranged the onReorder function is called.
The onReorder function should do two things: update the list on the screen, call a parent function passed in from props.
Currently all of this will work with componentWillReceiveProps, however, what is the proper way to migrate away from componentWillReceiveProps based on the above requirements?
While Tolsee's answer is perfectly correct it is also worth mentioning that the react docs suggest removing derived state (state that is calculated based on props) altogether. There is a great article here that is a great read in my opinion.
Your example fits the Anti-pattern: Unconditionally copying props to state example perfectly.
Without knowing your environment I cannot recommend a solution certainly, but to me it looks like you will be able to use the Fully controlled component example.
In that case, you'd need to lift your state up, simply use objects.items to render your Reorder child, and during the onReorder event simply call a function that you received as a prop.
In your problem you can do.
static getDerivedStateFromProps(nextProps, prevState){
if (nextProps.objects){){
return {items: this.nextProps.objects.items};
}
else return null;
}
Please follow this post for better understanding

React + Firebase - Deleting firebase object inside mapped components

I'm trying to pass the key of my project object through to my mapped components in order to delete them.
UPDATE: using the _.forEach(project, (proj, key) => <Project key={key} {... more props here}/>) Throws an error about objects not being valid react children. I'm guessing this is because the project i'm forEaching over needs to be turned into an array? I keep trying to format in componentWillMount() but when i try to run forEach with setState and push to a new array i keep getting duplicates
componentDidMount() {
projectRef.on('value', snap => {
this.setState({projects: snap.val()})
// somehow i need to create a new array of objects that include the key.
})
}
UPDATE: i removed the codepen example. I like code sandbox better. Much better. =)
And here's the code sandbox (If you get an error about the [DEFAULT] app already being defined just refresh the output browser and it will work. I don't know why it's doing that... oh well. I added my attempt with forEach on the code sandbox example. Hopefully someone can let me know what i'm doing wrong.
Yep, map returns an array of the values of the object, in this case an object you can then access via the props in the <Display /> component, but not the key of each element of the object.
Perhaps you could use lodash's forEach in order to loop and have access to both the key and the value of each element in your data collection. Like that you can pass the key (that will be the target for the remove method) as a specific prop and the value as the item prop in the component.
export default class extends React.Component {
constructor() {
super()
this.state = {
items: null
}
}
render() {
return() {
<div>
{_.forEach(this.state.items, (item, key) =>
<Display key={key} itemKey={key} item={item}/>
)}
</div>
}
}
}
// then the display component
removeItem() {
firebase.database().ref({this.props.itemKey}).remove();
}
render() {
return (
<div>{this.props.item.name} <button onClick={this.removeItem}>X</button>
)
}
Here's a simple live example of how forEach works:
https://jsbin.com/xolejoj/edit?js,console
Edit 08-12-2017
The problem with your code is that you're missing the fact that map is returning the key and the value of each element of the object. In this case your object has a key string and a value that is the object. Then on your JSX you're trying to pass the key as it were a part of the object (value) but is not, therefore you're getting an undefined value in the component's props.
Change your code to this:
<div>
{_.map(projects, (proj, key) => <Project
key={key}
title={proj.title}
subtitle={proj.subtitle}
desc={proj.desc}
itemKey={key} // just the key of the object
/>
)}
</div>
The thing is that the key of each object is the identifier in firebase and the value is the object with the data you need, but that object doesn't have a key property, therefore it was evaluated to null.

In Typescript, is there any way to typecheck passed-in JSX.Element's?

Consider this simple case:
Client of library:
class ClientComponent extends React.Component<any,any> {
render() {
const myNestedElement = (<LibNestedComponent/>)
return <LibComponent nested={myNestedElement}/>
}
}
Library:
class LibNestedComponent extends React.Component<any,any> {
render() { return <div>nested stuff</div> }
}
interface LibComponentProps { nested: JSX.Element }
class LibComponent extends React.Component<LibComponentProps,any> {
render() {
return <div>{this.props.nested}</div>
}
}
As the author of Lib, I'd like to be able to tell my LibComponent clients, via the LibComponentProps interface, that the passed-in nested prop must be an element of type LibNestedComponent - not just any old j.random element. But AFAICT, there's no way to do this; the Typescript doc even says:
The JSX result type. By default the result of a JSX expression is typed as any. You can customize the type by specifying the JSX.Element
interface. However, it is not possible to retrieve type information
about the element, attributes or children of the JSX from this
interface. It is a black box.
Does anyone have a workaround that achieves this kind of typechecking without too much pain?
(The example is deliberately trivial and is not meant to be a sensible use-case.)
Apart from my comment, I don't know if there is a typescript way to restrict the type of component that client component can pass. But there is a way through which you can determine the type of component that was passed as a prop.
You can check the name of the component which was passed and see which type is it.
if (this.props.nested.type.name === 'LibNestedComponent') {
console.log('A valid component is passed');
} else {
console.log('Invalid component is passed');
}
where nested is the component you passed in the example you provided.
In the below picture you can see the name of the component.
But again, this would be a run-time detection.
You can check the type of this.props.nested using the object
if (this.props.nested.type !== LibNestedComponent) {
throw new Error('Invalid prop passed. Make sure that an instance of LibNestedComponent is passed through the nested prop')
}
The problem with Hardik's answer is that, in case your code gets minified, the value of type.name will change and your code will fail. So you should go for the type property directly instead.

Passing children from XHP to ReactJS

I have an XHP component:
final class :common:message-stripe extends :ui:base {
use XHPReact;
protected function compose() {
$this->constructReactInstance( "MessageStripe", Map {} );
return <div id={$this->getID()} />;
}
}
that should look like this in my .php file:
<common:messagestripe>
This is my message :)
</common:messagestripe>
On the ReactJS side, the component looks something like this:
var MessageStripe = React.createClass({
render: function() {
return (
<div className="message"> {this.props.children} </div>
);
}
});
However, I get errors about rendering null in my ReactJS component, which means that children are not sent correctly. So, my question: how can I pass the children from XHP to ReactJS?
You're not including your children when rendering your XHP component.
final class :common:message-stripe extends :ui:base {
use XHPReact;
protected function compose() {
$this->constructReactInstance( "MessageStripe", Map {} );
return <div>{$this->getChildren()}</div>;
}
}
Additionally, (assuming you built your :ui:base element with attribute forwarding as outlined here: Building a Good UI Framework with XHP) you don't need to manually set the ID of your root element, XHPJS::element() will do that for you.

Resources