I´m trying to build a JSON configurable data table using the Facebook´s fixed-data-table. I´ve come to my first code as:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Icon } from 'semantic-ui-react';
import { Table, Column, Cell } from 'fixed-data-table';
const DateCell = ({rowIndex, data, col, ...props}) => (
<Cell {...props}>
{data.getObjectAt(rowIndex)[col].toLocaleString()}
</Cell>
);
const LinkCell = ({rowIndex, data, col, ...props}) => (
<Cell {...props}>
{data.getObjectAt(rowIndex)[col]}
</Cell>
);
const TextCell = ({rowIndex, data, col, ...props}) => (
<Cell {...props}>
{data.getObjectAt(rowIndex)[col]}
</Cell>
);
const NumericCell = ({rowIndex, data, col, ...props}) => (
<Cell {...props}>
{data.getObjectAt(rowIndex)[col]}
</Cell>
);
const BooleanCell = ({rowIndex, data, col, ...props}) => (
<Cell {...props}>
{data.getObjectAt(rowIndex)[col] ? <Icon name='checkmark' color='green' /> : <Icon name='remove' color='red' />}
</Cell>
);
class DataTable extends Component {
state = {
schema: this.props.schema,
data: this.props.data,
}
getCells = (schema, data) => {
let columns = [];
schema.columns.map((column, index) => {
let cell = (<TextCell></TextCell>);
let key = column.field + index;
if (column.type === 'string') {
cell = (<TextCell
data={this.state.data}
col={column.field}
/>);
}
if (column.type === 'integer') {
cell = (<NumericCell
data={this.state.data}
col={column.field}
/>);
}
if (column.type === 'boolean') {
cell = (<BooleanCell
data={this.state.data}
col={column.field}
/>);
}
let col = (<Column
header={column.title}
cell={cell}
width={100}
/>);
columns.push(col);
return;
});
return columns;
}
render() {
let schema = {
"columns": [
{
"title": "Name",
"field": "name",
"type": "string",
},
{
"title": "EIN",
"field": "ein",
"type": "string",
},
{
"title": "Active",
"field": "isactive",
"type": "boolean",
}
],
"edit": true,
"delete": true,
"sort": true
};
let data = [
{
name: 'Test1',
ein: '1234',
isactive: true
},
{
name: 'Test2',
ein: '123',
isactive: true
},
{
name: 'Test3',
ein: '12345',
isactive: true
},
];
let columns = this.getCells(schema, data);
return (
<Table
rowHeight={50}
schemaHeight={50}
maxHeight={100}
width={1000}
height={500}
rowsCount={data.length}
{...this.props}>
{columns}
</Table>
);
}
}
export default DataTable;
When running I´m getting the following error:
TypeError: data.getObjectAt is not a function
TextCell
D:\\WORKSPACE\test\src\components\shared\DataTable.js:42
39 |
40 | const TextCell = ({rowIndex, data, col, ...props}) => (
41 | <Cell {...props}>
**> 42 | {data.getObjectAt(rowIndex)[col]}**
43 | </Cell>
44 | );
45 |
I´ve tried different JSON structures with no luck. The data and schema are loaded accordingly.
This literally takes some time to wrap ones mind around initially.
I will describe it using references to code in 'fixed-data-table-2'.
The data list when used in the table is wrapped in an object that
consist of the data list and a data list filter array.
Your list data will only show up if you have wrapped it as a
DataListWrapper object along with a filter array where every
entry of the filter array is a boolean specifying if the corresponding
list row will be visible (true) or hidden (false).
See the class BuildObjectDataListStore.
There is a method 'getObjectAt' in there which when called retrieves list row columns based on the input variable name.
For example
var {my_id} = list.getObjectAt(44);
means that if the list row at index 44 contains a column named 'my_id'
then the value of that column will end up in your my_id variable.
This works only with 'underscore' as delimiter in column names,
so any lists made up of columns such as "my-id" must be converted to
"my_id" before use in 'fixed-data-table'. There exists a neat conversion table procedure in BuildObjectDataListStore for that.
Here is how you wrap a plain list array 'myList' into an object that
fixed-data-table' can render:
this.dataList = new BuildObjectDataListStore(myList);
Then this object is wrapped into an object along with a filter array:
this.filteredDataList = new DataListWrapper(this.filter, this.dataList);
And there you have it; DataListWrapper is a recognized list format that 'fixed-data-table' can render.
Nowadays people use 'fixed-data-table-2'
which enjoys continued support by Schrödinger, Inc.
The earlier 'fixed-data-table' is abandoned as a public component.
Evolving from the foundations of fixed-data-table, the power of fixed-data-table-2 is at least twofold;
Can cope with thousands of lines of list data without bogging down on
scrolling and other responsiveness.
The principle of cell renderers means any row column can be made to render anything
that HTML5 offers (text, image, video, etc).
So for some applications this list component can be spot on. A drawback can be the initial learning curve of the mentioned wrapping principles and column renderer methods.
Related
I'm trying to create a table component that can display its cells flexibly. Every cells on a column displays the same cell component. The table component take an array of object as initial data and an array of column properties. Table should looks like this:
interface MyTableProps {
columns: ColumnProps[];
initialData: Array<any>; // Should array of object
callbackTableDataChanged: (newData) => void; // being used to send modified table data back to MyTable's father components.
}
interface ColumnProps {
label: string;
objectPropertyName: string; // (*)
renderCell: "cell-type-1" | "cell-type-2" | "cell-type-3" | ... // (**)
}
const MyTable: React.FC<MyTableProps> = tableProps => {
// The table should have its own a copy of initial data, because of many reasons
const [tableData, setTableData] = useState(tableProps.initialData);
const handleDataChange = (thePropertyName: string, rowIndex: number, newValue: any) => {
// This function changes the table data array,
// whenever the cell component (MyCellComp1, MyCellComp2, ...) on a column make a change of value.
// For example: MyCellComp1 is a time picker, whenever a new timestamp selected,
// it will send new time value through a callback, to its father component (aka MyTable).
// Then, this func will do the changes to the corresponding "place" in the data array object of this MyTable.
}
const renderRows = (column: ColumnProps, rowIndex: number) => {
switch (column.renderCell) {
case cell-type-1: return <CellComp1 ... onDataChanged={cellData => handleDataChange(column.objectPropertyName, rowIndex, cellData)}/>
case cell-type-2: return <CellComp2 ... onDataChanged={cellData => handleDataChange(column.objectPropertyName, rowIndex, cellData)}/>
...
}
}
return (
{renderColumnLabels}
<Flatlist data={tableData} renderItem={({col, index}) => renderRows(col, index)}/>
)
}
(*): "objectPropertyName" is a string that has value which is a name of one of the properties of initial object data. Forgive my awkward grammar!
(**): I use "renderCell" to tell the table what it should render at specific column. All cells on a column have the same component type.
The table should be used like this:
const initialData = [
{id: "123", name: "Tom"}, {id: "456", name: "Jerry"}, ...
]
const columns: ColumnProps[] = [
{label: "The ID", objectPropertyName: "id", renderCell: "cell-type-1"},
{label: "The name", objectPropertyName: "name", renderCell: "cell-type-2"},
...
];
<MyTable columns={columns} initialData={initialData} ... />
// This is how table looks like:
// column 1 column 2
// labels row: The ID The name
// row 1 : <CellComp1 /> <CellComp2 />
// row 2 : <CellComp1 /> <CellComp2 />
// ...
The problem is that with above approach, with the way I declared how cells on a column should be rendered, it limits the variety of cell component that MyTable could display. I declare 3 values for ColumnProps.rendercell, columns can display only 3 kinds of cell components.
My solution is that ColumnProps will have a new props which is a function that return a component:
interface ColumnProps {
label: string;
objectPropertyName: string; // (*)
renderCell: "cell-type-1" | "cell-type-2" | "cell-type-3" | ... // (**)
renderCellComponent?: () => JSX.Element
}
The new "renderCellComponent" prop will replace default cell components with whatever it returns.
However, with is method, I cant change the data array of MyTable with its function "handleDataChange".
For example:
const initialData = [
{id: "123", name: "Tom"}, {id: "456", name: "Jerry"}, ...
]
const columns: ColumnProps[] = [
{label: "The ID", objectPropertyName: "id", renderCell: "cell-type-1", renderCellComponent: () => <NewCellComp1 ... />},
{label: "The name", objectPropertyName: "name", renderCell: "cell-type-2", renderCellComponent: () => <NewCellComp2 ... />},
...
];
<MyTable columns={columns} initialData={initialData} ... />
As you can see, NewCellComp1 and NewCellComp2 need to access the function "handleDataChange" of MyTable just like the way CellComp1 and CellComp2 did. In order to do that, I tried to use React.forwardRef and useImperativeHandle to MyTable:
const MyTable: React.FC<MyTableProps> = React.forwardRef((tableProps, ref) => {
...
useImperativeHandle(ref, () => {
refFuncHandleDataChange(objectPropertyName: string, rowIndex, newValue) {
handleDataChange(objectPropertyName, rowIndex, newValue);
}
})
return ...
})
Now, MyTable should be used like this:
const initialData = [
{id: "123", name: "Tom"}, {id: "456", name: "Jerry"}, ...
]
const columns: ColumnProps[] = [
{...other props, objectPropertyName: "id", renderCellComponent: () => <NewCellComp1 onDataChanged={data => refTable.current?.refFuncHandleDataChange("id", ..., data)} />},
{...other props, objectPropertyName: "name", renderCellComponent: () => <NewCellComp2 onDataChanged={data => refTable.current?.refFuncHandleDataChange("name", ..., data)} />},
...
];
const refTable = useRef();
<MyTable ref={refTable} columns={columns} initialData={initialData} ... />
As you can see, with this approach, I still lack the row index of the cell component that make a change of data. When cell rendering is declared inside MyTable, I can access to row index easily thanks to Flatlist, but outside MyTable does not offer that luxury, because "renderCellComponent" function of ColumnProps is a declaration for all cells on a column. This is where I'm stuck.
In conclusion, I want to create a table component that be able to display every kind of components on its cells. Also, keep its properties as "simple" as possible. You can see that I'm currently must declare only data and columns props. But my approach seems to be impossible to do that.
Can you share me an improvement for what I did or an entirely new approach for this problem. Thank you!
I'm using React Hook Form to build a basic page builder application and it's been brilliant so far, I've been using the useFieldArray hook to create lists that contain items, however, I haven't found a way to move items between lists.
I know I can use the move() function to reorder items within the same list, however, since each list has its own nested useFieldArray I can't move the item from one list component to another list component.
If anyone knows of a way around this it would be much appreciated!
Here is a very simplified example of my current setup:
export const App = () => {
const methods = useForm({
defaultValues: {
lists: [
{
list_id: 1,
items: [
{
item_id: 1,
name: 'Apple'
},
{
item_id: 2,
name: 'Orange'
}
]
},
{
list_id: 2,
items: [
{
item_id: 3,
name: 'Banana'
},
{
item_id: 4,
name: 'Lemon'
}
]
}
]
}
});
return (
<FormProvider {...methods}>
<Page/>
</FormProvider>
)
}
export const Page = () => {
const { control } = useFormContext();
const { fields } = useFieldArray({
control,
name: 'lists'
})
return (
<ul>
{fields?.map((field, index) => (
<List listIdx={index} />
))}
</ul>
)
}
export const List = ({ listIdx }) => {
const { control, watch } = useFormContext();
const { fields, move } = useFieldArray({
control,
name: `lists[${sectionIdx}].items`
})
const handleMove = (prevIdx, nextIdx) => {
// this allows me to move within lists but not between them
move(prevIdx, nextIdx);
}
return (
<li>
<p>ID: {watch(lists[${listIdx}].list_id)}</p>
<ul>
{fields?.map((field, index) => (
<Item listIdx={index} itemIdx={index} handleMove={handleMove}/>
))}
</ul>
</li>
)
}
export const Item = ({ listIdx, itemIdx, handleMove }) => {
const { control, register } = useFormContext();
return (
<li>
<p>ID: {watch(lists[${listIdx}].items[${itemIdx}].item_id)}</p>
<label
Name:
<input { ...register('lists[${listIdx}].items[${itemIdx}]) }/>
/>
<button onClick={() => handleMove(itemIdx, itemIdx - 1)}>Up</button>
<button onClick={() => handleMove(itemIdx, itemIdx + 1)}>Down</button>
</div>
)
}
Thanks in advance!
If you'd not like to alter your default values (your data structure), I think the best way to handle this is using update method returning from useFieldArray. You have the data of both inputs that are going to be moved around, knowing their list index and item index, you could easily update their current positions with each other's data.
I have several productOptionTypes with dynamic values. My problem is how do I match it with its variants. I want to get the variant id based on it.
Pls check codesandbox here CLICK HERE
{productOptionTypes.map((item, index) => (
<>
<span className="title">{item.optionName}</span>
<Select
placeholder={`${item?.optionValues?.length} ${item.optionName}s`}
options={item.optionValues.map((option) => {
return { label: option, value: option };
})}
isSearchable={false}
onChange={(value) => handleSelectVariant(value, index)}
/>
</>
))}
Best I could come up with is this:
Update the option mapping to return the option name, to be used as the foreign key into the variants array objects, i.e. "Color", "Size", etc.
options={item.optionValues.map((option) => {
return {
label: option,
value: option,
name: item.optionName
};
})}
Update the variantType state to be an object, and update the handleSelectVariant handler to store the variant types by the foreign key "name" and the selected "value"
const [variantType, setSelectedVariant] = useState({});
const handleSelectVariant = (value, index) => {
setSelectedVariant((state) => ({
...state,
[value.name]: value.value
}));
};
Use a filter and every function to reduce the option types and values to a filtered result of variants that can be easily mapped to the id properties.
const matches = variants
.filter((variant) => {
return [
["option1Value", "option1Type"],
["option2Value", "option2Type"],
["option3Value", "option3Type"],
["option4Value", "option4Type"],
["option5Value", "option5Type"],
["option6Value", "option6Type"],
["option7Value", "option7Type"],
["option8Value", "option8Type"],
["option9Value", "option9Type"],
["option10Value", "option10Type"]
].every(([key, valueKey]) =>
variantType[variant[valueKey]]
? variant[key] === variantType[variant[valueKey]]
: true
);
})
.map(({ id }) => id);
Demo
When i understand it correctly you want to safe every selected value, then compare the result to your variants and select the variantId of the item matching all selctboxes based on it?
index.js
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import Select from "react-select";
import "./styles.css";
import { productOptionTypes } from "./productOptionTypes";
import { variants } from "./variants";
function App() {
const [variantType, setSelectedVariant] = useState({});
const [result, setResult] = useState(null);
const mapIndexKey = {
0: "option1Value",
1: "option2Value",
2: "option3Value",
3: "option4Value"
};
useEffect(() => {
setResult(
variants.filter(
(el) =>
el.option1Value == variantType.option1Value &&
el.option2Value == variantType.option2Value &&
el.option3Value == variantType.option3Value &&
el.option4Value == variantType.option4Value
)
);
}, [variantType]);
useEffect(() => {
console.log(result);
}, [result]);
const handleSelectVariant = (value, index) => {
setSelectedVariant({ ...variantType, [mapIndexKey[index]]: value.value });
console.log(variantType, value, index);
};
return (
<div className="App">
<form>
{productOptionTypes.map((item, index) => (
<>
<span className="title">{item.optionName}</span>
<Select
placeholder={`${item?.optionValues?.length} ${item.optionName}s`}
options={item.optionValues.map((option) => {
return { label: option, value: option };
})}
isSearchable={false}
onChange={(value) => handleSelectVariant(value, index)}
/>
</>
))}
</form>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
variants.js
export const variants = [
{
id: "60451fd290aeb720d96d8459",
name: "Women's Bellarina Half Toe Grip Yoga Pilates Barre Socks",
wholesalePrice: "8.0",
sku: "s01524blk",
option1Type: "Color",
option1Value: "Black",
option2Type: "Size",
option2Value: "XS",
option3Type: "Toe Type",
option3Value: "Half Toe",
option4Value: "Bellarina",
retailPrice: "16.0",
__typename: "Variant"
},
{
id: "60451fd790aeb720d96d8463",
name: "Women's Bellarina Half Toe Grip Yoga Pilates Barre Socks",
wholesalePrice: "8.0",
sku: "s01525blk",
option1Type: "Color",
option1Value: "Black",
option2Type: "Size",
option2Value: "S",
option3Type: "Toe Type",
option3Value: "Half Toe",
retailPrice: "16.0",
__typename: "Variant"
}
}
Basicly what i do is saving all selected values in a map and everytime a value is changed just comparing it to all variants, if a variant is equal i select it.
For one variant i added the fourth key "Bellarina".
I hope this solution solves your problem.
When you want to verify the solution just select the first value of every selectbox and have a look at the console.
I want to be able to loop through each letter of the alphabet and then if the StoreName in my json object matches that looped letter then map it to an li tag. I want to do this to every letter in the alphabet and if there is no match just display nothing.
Hope this makes sense.
for example:
This is what I have so far.
import { data } from './data/data';
import { useState } from 'react';
export default function Home() {
const [values, setValues] = useState(data);
return (
<div>
{values.filter(store => store.storeName.startsWith('a'))
.map((item, index) => (
<li key={index}>
{item.storeName}
</li>
))}
</div>
)
}
Json object:
export const data = [
{
storeId: 1,
storeName: 'addidas',
},
{
storeId: 2,
storeName: 'axels',
},
{
storeId: 3,
storeName: 'blix',
},
{
storeId: 4,
storeName: 'benis',
},
{
storeId: 5,
storeName: 'clives',
},
];
I know i could filter and map each letter manually but there must be a way to loop through the alphabet and map?
example output:
A
Addidas
axels
B
blix
benis
c
clives
d
So I want the letter to display and then the results for each item thats been looped that starts with that looped letter.
You could make a list of alphabet and map through that
import { data } from "./data/data";
import React, { useState } from "react";
const alphabet = "abcdefghijklmnopqrstuvwxyz";
export default function Home() {
const [values, setValues] = useState(data);
return (
<div>
{alphabet.split("").map((c) => {
return (
<>
<p>{c}</p>
{values
.filter((store) => store.storeName.startsWith(c))
.map((item, index) => (
<li key={index}>{item.storeName}</li>
))}
</>
);
})}
</div>
);
}
It sounds like you want to group your datapoints by initial letter. The algorithm you describe will work, but it's not the one I'd choose.
Here's how I would do it:
Sort all stores alphabetically
Iterate through the sorted stores, keeping track of the most recent initial letter
every time the current store's initial letter is different from the most recent, insert a grouping boundary (e.g. closing the previous <li> and opening a new one)
Finally, don't implement this within the JSX. It may not be the most complex algorithm in the world, but nobody will appreciate it if this data-preparation logic is intermixed with a bunch of display literals. Instead, work with the data as pure data. Bearing that in mind, "inserting a grouping boundary" just means changing which group you insert the store into.
Here's a run at the implementation. (I haven't tested it.)
function MyComponent( props ) {
// group the stores by initial letter
// produces a list of { initialLetter: 'A', stores: [ store1, store2, store3 ] }
let prevInitialLetter = null
let groupedStores = stores
.sort(sort_storeName_alpha)
.reduce(( groups, store ) => {
let myInitialLetter = store.storeName.charAt(0)
let belongsInNewGroup = myInitialLetter !== prevInitialLetter
if(belongsInNewGroup) {
// create a new group and add this store as its first member
groups.push({ initialLetter: myInitialLetter, stores: [ store ] })
} else {
// add this store to the previous group
groups[groups.length - 1].stores.push(store)
}
return groups
}, [])
return (
<ol className="GroupedStores">
{
groupedStores.map(group => (
<li className="storeGroup" key={group.initialLetter}>
{group.initialLetter}
<ol className="stores">
{
group.stores.map(store => (
<li key={store.storeName}>
{store.storeName}
</li>
)
}
</ol>
</li>
))
}
</ol>
)
}
// this is the crudest-possible text sort; depending on your needs,
// you may need to perform a locale-aware comparison
function sort_storeName_alpha = ( a, b ) => {
if(a.storeName < b.storeName) return -1
if(b.storeName < a.storeName) return 1
return 0
}
I am working on a redux project where I want to retrieve the values stored in API server.I want to store the data from the API in my redux store and then retrieve the values and display it in my react component.The data in the API server is in the form of an object but has a unique id for each value.So,in my case, the data is a list of posts.So, each post has a unique id and has all the other details like timestamp,post-title,post-author etc.This is how the default data from the API for posts looks like:
const defaultData = {
"8xf0y6ziyjabvozdd253nd": {
id: '8xf0y6ziyjabvozdd253nd',
timestamp: 1467166872634,
title: 'Udacity is the best place to learn React',
body: 'Everyone says so after all.',
author: 'thingtwo',
category: 'react',
voteScore: 6,
deleted: false,
commentCount: 2
},
"6ni6ok3ym7mf1p33lnez": {
id: '6ni6ok3ym7mf1p33lnez',
timestamp: 1468479767190,
title: 'Learn Redux in 10 minutes!',
body: 'Just kidding. It takes more than 10 minutes to learn technology.',
author: 'thingone',
category: 'redux',
voteScore: -5,
deleted: false,
commentCount: 0
}
}
Note: The "id" which is a random number here(like "8xf0y6ziyjabvozdd253nd") becomes an integer, i.e the 1st post gets the id 1 ,the 2nd gets 2.
So, I am able to store the data from posts API in my redux "Store". And I converted the posts object into an array(as we cannot map over an object) because I want to map over this array and show the data in my React component.But,I am not able to see the result of the array in my component,maybe that's because it has an id before each object in the array.This is how I am trying to map over the arrayand I do not get any error,but I do not see the results from the object in component.My component file looks like this:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchPosts } from '../actions';
import _ from 'lodash';
class PostsIndex extends Component {
componentDidMount() {
this.props.dispatch(fetchPosts())
.then(() => {
this.setState({
loading : false
});
// console.log(this.props.posts.posts[0])
})
}
render() {
// console.log(this.props.posts.posts)
const obj = this.props.posts.posts;
let arr;
if (obj) {
arr = Object.values(obj); //Converting an Object into an array
}
console.log(arr); //returns the converted array from an object
return(
<div>
{
arr ?
<div>
{ arr.map(post =>
{
<div>
{post.title}
</div>
})
}
</div>
:
<div>
No Data
</div>
}
</div>
);
}
}
function mapStateToProps(state) {
return { posts: state.posts };
}
export default connect(mapStateToProps)(PostsIndex);
And when I console.log my state,the converted array looks like this:
When expanded looks like:
I want to retrieve almost all of the values from the above array.Can anyone please show me how to get the data from the array and map over the array to show the values in my React component?
You can keep your data as an object, and use Object.keys to get an array of keys, map over the keys, and use the keys to access nested objects. If you don't know the shape of your data source, you could use recursion.
Here is a working code sandbox example where I've taken your data source and turned it into a table to illustrate how to do so.
Here is the code:
import React from 'react';
import { render } from 'react-dom';
import Hello from './Hello';
const defaultData = {
"8xf0y6ziyjabvozdd253nd": {
id: '8xf0y6ziyjabvozdd253nd',
timestamp: 1467166872634,
title: 'Udacity is the best place to learn React',
body: 'Everyone says so after all.',
author: 'thingtwo',
category: 'react',
voteScore: 6,
deleted: false,
commentCount: 2
},
"6ni6ok3ym7mf1p33lnez": {
id: '6ni6ok3ym7mf1p33lnez',
timestamp: 1468479767190,
title: 'Learn Redux in 10 minutes!',
body: 'Just kidding. It takes more than 10 minutes to learn technology.',
author: 'thingone',
category: 'redux',
voteScore: -5,
deleted: false,
commentCount: 0
}
}
const TableHeader = ({ fields }) => (
<thead>
<tr>
{
fields.map( field => <th key={ field }>{ field }</th>)
}
</tr>
</thead>
);
const TableBody = ({ data }) => (
<tbody>
{
Object.keys(data).map(
datum =>
<tr>
{
Object.keys(data[datum]).map(
field => <td>{data[datum][field]}</td>
)
}
</tr>
)
}
</tbody>
);
const App = () => (
<table>
<TableHeader fields={ Object.keys(defaultData[Object.keys(defaultData)[0]]) } />
<TableBody data={defaultData} />
</table>
);
render(<App />, document.getElementById('root'));
You need map your data. Something like this:
<div>
<ul>
{props.incomingDataArray.map((singleArrayItem, index) => {
return (
<li key="list + index">{singleArrayItem.commentCount}</li>
<li key="list + index">{singleArrayItem.timestamp}</li>
)
})
}
</ul>
<div>
you forgot return inside your map.
render() {
const obj = this.props.posts.posts;
let arr;
if (obj) {
arr = Object.values(obj); //Converting an Object into an array
}
return (
<div>
{ arr ?
<div>
{ arr.map(post => {
return (
<div key={post.id}>
{post.title}
</div>
)
})
}
</div> :
<div>
No Data
</div>
}
</div>
);
}
or, alternatively, drop the curly braces with a oneliner:
arr.map(post => (<div key={post.id}>
{post.title}
</div>
)
)
and don't forget the key attribute.
I also suggest you abstract way your dumb components. It'll make your code more readable.