React API mapping issue - reactjs

I'm trying to make a simple fetch request and trying to map through the response to display on the page.
The API results correctly display when I console.log(data) but once I add the mapping function they display as undefined. I have tried both data.map and data.results.map
I couldn't find a working solution in any other thread!
componentDidMount() {
fetch("http://private-cc77e-aff.apiary-mock.com/posts")
.then(results => results.json())
.then(data => {
let posts = data.results.map(post => {
console.log(posts);
});
});
}
Any help would be appreciated!

Not sure what is the structure of your data but part of your problem is that you are trying to log posts before .map has finish populating it.
What you could do is storing the data received from the API in state (use map if you need to normalize the data) and then in render reference it using this.state.posts.
Don't forget to start with an initial value of an empty array, and you can conditionally render the posts or a loader based on the length of the array.
Here is a small example with code similar to your use case:
const Joke = ({joke}) => <div style={{border: '1px solid #ccc', padding: '15px'}}>{joke}</div>
class App extends React.Component {
// initial value (array)
state = { jokes: [] }
componentDidMount() {
// get data when component monuted
fetch("https://api.icndb.com/jokes/random/10")
.then(data => data.json())
.then(result => {
// normalize data with map
const jokes = result.value.map(obj => obj.joke);
// update the state for next render
this.setState({jokes})
})
}
render() {
const { jokes } = this.state;
return (
<div>
{
// conditionally render the array or loading (if data isn;t ready yet)
jokes.length
? jokes.map(joke => <Joke joke={joke} />)
: "Loading..."
}
</div>
);
}
}
const root = document.getElementById("root");
ReactDOM.render(<App />, root);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root" />

We are not quite sure how is your API response but you are doing the log operation somehow wrong. You are assigning the posts to a variable, but you re trying to use it inside the .map method.
I assume that your API response is something like that:
{
results: [
{ id: 1, name: "foo" },
{ id: 2, name: "bar" },
{ id: 3, name: "baz" },
],
};
This is data and since you are assigning data.results as posts this is why I assume a response like that. I'm providing the options you can try. Here, I am mimicking the API request with a function, so do not bother with this part, please.
Just log the whole results array.
const data = {
results: [
{ id: 1, name: "foo" },
{ id: 2, name: "bar" },
{ id: 3, name: "baz" },
],
};
const fakeRequest = () =>
new Promise(resolve => setTimeout(() => resolve(data), 1000));
class App extends React.Component {
componentDidMount() {
fakeRequest()
.then( data => {
console.log( data.results );
})
}
render() {
return <div>Look the console. We are logging the whole array.</div>;
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
You can map the array and log each item one by one
Since we just want to log the items, we don't need to use map here, forEach is enough.
const data = {
results: [
{ id: 1, name: "foo" },
{ id: 2, name: "bar" },
{ id: 3, name: "baz" },
],
};
const fakeRequest = () =>
new Promise(resolve => setTimeout(() => resolve(data), 1000));
class App extends React.Component {
componentDidMount() {
fakeRequest()
.then( data => {
data.results.forEach( post => console.log( post) )
// or as a shorthand
// data.results.forEach(console.log)
})
}
render() {
return <div>Look the console. We are logging each post one by one.</div>;
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Set your state with the results and map it in your render method
This is most of the time you want to do. Just logging is not enough as we know.
const data = {
results: [
{ id: 1, name: "foo" },
{ id: 2, name: "bar" },
{ id: 3, name: "baz" },
],
};
const fakeRequest = () =>
new Promise(resolve => setTimeout(() => resolve(data), 1000));
class App extends React.Component {
state = {
posts: [],
}
componentDidMount() {
fakeRequest()
.then( data => {
this.setState( { posts: data.results})
})
}
render() {
if ( !this.state.posts.length ) {
return <p>No posts yet</p>
}
return (
<div>
{
this.state.posts.map( post =>
<div key={post.id}>
<p>{post.id}</p>
<p>{post.name}</p>
</div>
)
}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Related

getStaticProps did't return data

I am trying to fetch data from Storyblok API, but in getStaticProps I get the data from the storybook but it can't return data to the page.
pages/docs/[slug].js
import React from 'react'
import Storyblok from "../../lib/storyblok"
export default function DocsArticle( props ) {
console.log("PROPS: ", props)
return (
<>
<div className="page">
{props.story.name}
</div>
</>
);
}
export async function getStaticProps({ params, preview = false }) {
let slug = params.slug ? params.slug : "home";
let sbParams = {
version: "draft", // or 'published' / ' draft
};
if (preview) {
sbParams.version = "draft";
sbParams.cv = Date.now();
}
// load the stories insides the pages folder
let { data } = await Storyblok.get(`cdn/stories/${slug}`, sbParams);
console.log("STORY DATA:", data);
return {
props: {
story: data ? data.story : null,
preview,
},
revalidate: 10, // revalidate every hour
};
}
export async function getStaticPaths() {
let { data } = await Storyblok.get('cdn/links/', {
starts_with: 'article'
})
let paths = [];
Object.keys(data.links).forEach((linkKey) => {
// don't create routes for folders and the index page
if (data.links[linkKey].is_folder || data.links[linkKey].slug === "home") {
return;
}
// get array for slug because of catch all
const slug = data.links[linkKey].slug;
// remove the pages part from the slug
let newSlug = slug.replace('docs', '')
let splittedSlug = newSlug.split("/");
paths.push({ params: { slug: splittedSlug.toString() } });
});
return {
paths: paths,
fallback: false,
};
}
pages/_app.js
...
export default withRedux(initStore)(
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
return {
pageProps: Component.getInitialProps
? await Component.getInitialProps(ctx)
: {},
}
}
render() {
const { Component, pageProps, store } = this.props
return (
<>
<DefaultSeo
title="My page"
description="My test page"
openGraph={{
type: 'website',
locale: 'en_IE',
}}
/>
<Head>
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
</Head>
<Provider store={store}>
<ThemeProvider theme={theme}>
<GetCurrentUser />
<Component {...pageProps} />
</ThemeProvider>
</Provider>
</>
);
}
}
)
If I display the data using console.log(), I get the data, but it doesn't return it.
STORY DATA: {
story: {
name: 'article1',
created_at: '2021-08-13T00:36:04.648Z',
published_at: '2021-08-15T16:24:54.810Z',
id: 66059334,
uuid: '900a311a-2ad4-461c-9304-e2f36fd25b07',
content: {
_uid: 'd21214b5-1e80-4c6a-aa74-259f082a8242',
content: [Array],
component: 'page',
_editable: '<!--#storyblok#{"name": "page", "space": "122749", "uid": "d21214b5-1e80-4c6a-aa74-259f082a8242", "id": "66059334"}-->'
},
slug: 'article1',
full_slug: 'article1',
sort_by_date: null,
position: 10,
tag_list: [],
is_startpage: false,
parent_id: 0,
meta_data: null,
group_id: 'bdf123cc-0044-4d02-b4b3-28034ee457d0',
first_published_at: '2021-08-14T00:02:05.000Z',
release_id: null,
lang: 'default',
path: 'docs',
alternates: [],
default_full_slug: null,
translated_slugs: null
},
cv: 1629044699,
rels: [],
links: []
}
PROPS: {}
TypeError: Cannot read property 'title' of undefined
I'd appreciate it if you could let me know what I did wrong.

How can I make a chart through JSONfile from highchats? please help me

I'm using highchat to make a chart, but it's not working well.
mycode
const [newData, setNewData] = useState();
const [datas, setDatas] = useState({
x: null,
y: null,
});
useEffect(() => {
for (let key in data) {
return setNewData(data[key]);
}
}, []);
useEffect(() => {
newData?.map((u) => {
delete u.time;
for (let key in u) {
setDatas({
x: null,
y: u[key],
});
}
});
}, []);
Jsonfile
{"dataset":[
{
"time": "2020.3.29 21:45",
"EC_slab1": 5.614382,
"EC_slab2": 5.084232,
"EC_drain_PC": 6.48888298,
"WC_slab1": 67.823,
"WC_slab2": 56.684,
"CO2air": 610.0000001,
"HumDef": 1.819999991,
"Rhair": 87.5000001,
"Tair": 17.2,
"EnScr": 0,
"BlackScr": 94.9999995,
"PipeGrow": 37.49999999,
"PipeLow": 0,
"Iglob": 0,
"RadSum": 1766,
"Tout": 3.7
},
I'm going to put the data on the x-axis and the dimension of each item on the y-axis.
How can I proceed with the work? Please give me some advice.
Here you can see how you can create a chart with dynamic data:
class App extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
fetch("https://api.myjson.com/bins/q4us4")
.then(response => response.json())
.then(data => {
options.series[0].data = data;
this.setState({ data: data });
});
}
render() {
return (
<div>
{this.state &&
this.state.data && (
<Chart options={options} highcharts={Highcharts} ref={"chart"} />
)}
</div>
);
}
}
render(<App />, document.getElementById("root"));
Demo: https://codesandbox.io/s/mz35zwxjoj

Create a multidimensional list from Array Objects

I am trying to create a multidimensional list from object full of arrays from a rest request in Javascript. The issue is my ability iterate over an array of objects. Can someone give me an example on how to turn this data structure into a JSX component?
I am trying to create a list that is wrapped in a div and looks like:
<div>
<lo>
<li>
<ul>
<li>Row Cell</li>
<li>Row Cell</li>
</ul>
</li>
<li>
<ul>
<li>Row Cell</li>
<li>Row Cell</li>
</ul>
</li>
</lo>
</div>
The data structure looks like this,
The function that is set in the React Component is the following,
createBodyDisplay(){
var ar = this.state.data.request.body;
var returnString = '';
for (var key in ar) {
console.log(ar);
if (ar.hasOwnProperty(key)) {
if(ar instanceof Array){
console.log('This is a test to see if there is an array');
} else if (ar instanceof Object){
for (var key1 in ar) {
if (ar.hasOwnProperty(key1)) {
console.log(ar[key1]);
}
}
console.log(ar);
} else {
console.log('Not sure what this is');
}
// returnString= returnString+'<div>';
/// var x = numbers.map(Math.sqrt)
// console.log(ar[key]);
// returnString= returnString+'</div>';
}
}
// console.log(returnString);
return returnString;
}
See sandbox here for live example: https://codesandbox.io/s/confident-heyrovsky-s0zg4
Assuming your data-structure looks something like:
const newData = {
dogs: [
{ type: "row-cell", value: "Golden" },
{ type: "row-cell", value: "Husky" }
],
cats: [
{ type: "row-cell", value: "Feline" },
{ type: "row-cell", value: "Hairless" }
]
};
We can use Object.entries() to cleanly create an array of arrays, for each key-value pair. Then use .map() to create our outer-ordered-list items. And within each group, we will use another .map() to create the unordered-list-items.
Working code:
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends React.Component {
state = {
data: {}
};
componentDidMount() {
const newData = {
dogs: [
{ type: "row-cell", value: "Golden" },
{ type: "row-cell", value: "Husky" }
],
cats: [
{ type: "row-cell", value: "Feline" },
{ type: "row-cell", value: "Hairless" }
]
};
this.setState({
data: newData
});
}
createNestedLists = () => {
const { data } = this.state;
const lists = Object.entries(data).map(([type, arr]) => {
return (
<li>
<ul>
{arr.map(item => {
return (
<li>
{item.type} - {item.value}
</li>
);
})}
</ul>
</li>
);
});
return <ol>{lists}</ol>;
};
render() {
return <div>{this.createNestedLists()}</div>;
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Update property of certain object inside array in state

I am trying to update my code: https://repl.it/#colegonzales1/HeftyJoyfulAbilities
I am trying to make my handleToggle function work properly. It is blank now for the sake of tinkering, but I have spent the last 2-3 hours trying to make it work to my own knowledge but I cannot figure out how to access a specific item in state. I know how to overwrite it all, but that is not what I want to do. Say I have:
this.state = {
todos: [
{
title: 'Wash car',
id: 1,
done: false
},
{
title: 'Go shopping',
id: 2,
done: false
}
],
inputValue: ''
}
How can I ONLY change the value of done on the 'Go shopping' todo, to true?
Use an array.map to toggle the done flag only on the element which matches the clicked id as follows. The other properties of the todo are copied with an object spread:
handleToggle (e) {
const id = parseInt(e.target.id,10)
this.setState((prevState) => ({
todos: prevState.todos.map(t => t.id === id ? {...t, done: !t.done} : t)
}))
}
You can find the index of the object with the given id with findIndex and create a new array with a copy of this object in it with its done flag toggled.
Example
class App extends React.Component {
state = {
todos: [
{
title: "Wash car",
id: 1,
done: false
},
{
title: "Go shopping",
id: 2,
done: false
}
],
inputValue: ""
};
handleToggle = id => {
this.setState(prevState => {
const todos = [...prevState.todos];
const todoIndex = todos.findIndex(todo => todo.id === id);
todos[todoIndex] = { ...todos[todoIndex], done: !todos[todoIndex].done };
return { todos };
});
};
render() {
return (
<div>
<div>{JSON.stringify(this.state)}</div>
<button onClick={() => this.handleToggle(2)}>
Toggle todo with id 2
</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<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>
<div id="root"></div>

How to generate snapshot after all life cycle methods have called in React Jest

snapshot file has created before componentDidMount() is being called. In my situation, I fetch data from server inside the componentDidMount(). Based on the results, I draw the table. But in my test case, it doesn't show those received mock results.
Test file
import React from 'react';
import renderer from 'react-test-renderer';
import { fakeRequestLibrary } from '../../../__mocks__/fakeRequestLibrary';
import ReportAsTableView from '../../../components/reports/common/ReportAsTableView';
const FAKE_RESPONSE = {
dataSets: [
{
metadata: {
columns: [
{
name: "username",
label: "username"
},
{
name: "date_created",
label: "date_created"
}
]
},
rows: [
{
date_created: "2010-04-26T13:25:00.000+0530",
username: "daemon"
},
{
date_created: "2017-06-08T21:37:18.000+0530",
username: "clerk"
},
{
date_created: "2017-07-08T21:37:18.000+0530",
username: "nurse"
},
{
date_created: "2017-07-08T21:37:19.000+0530",
username: "doctor"
},
{
date_created: "2017-07-08T21:37:18.000+0530",
username: "sysadmin"
}
]
}
]
};
describe('<ReportAsTableView /> ', () => {
it('renders correctly with success data received from server', () => {
const params = {
"startDate": "2017-05-05",
"endDate": "2017-10-05"
};
var rendered = renderer.create(
<ReportAsTableView reportUUID="e451ae04-4881-11e7-a919-92ebcb67fe33"
reportParameters={params}
fetchData={fakeRequestLibrary('openmrs-fake-server.org', {}, true, FAKE_RESPONSE)} />
);
expect(rendered.toJSON()).toMatchSnapshot();
});
});
Targeted component class
import React, { Component } from 'react';
import { ApiHelper } from '../../../helpers/apiHelper';
import * as ReportConstants from '../../../helpers/ReportConstants';
import ReactDataGrid from 'react-data-grid';
import DataNotFound from './DataNotFound';
import moment from 'moment';
import './ReportAsTableView.css';
class ReportAsTableView extends Component {
constructor(props) {
super();
this.state = {
report: {
definition: {
name: ''
}
},
reportColumnNames: Array(),
reportRowData: Array()
};
this.resolveResponse = this.resolveResponse.bind(this);
this.rowGetter = this.rowGetter.bind(this);
this.init = this.init.bind(this);
}
componentDidMount() {
this.init(this.props.reportParameters);
}
componentWillReceiveProps(nextProps) {
this.init(nextProps.reportParameters);
}
init(params) {
if(this.props.fetchData != null){
//Test Path
this.props.fetchData
.then((response) => {
console.log('>>>>>'+JSON.stringify(response.body));
this.resolveResponse(response.body);
});
}else{
new ApiHelper().post(ReportConstants.REPORT_REQUEST + this.props.reportUUID, params)
.then((response) => {
this.resolveResponse(response);
});
}
}
resolveResponse(data) {
this.setState({ report: data });
this.setState({ reportColumnNames: data.dataSets[0].metadata.columns });
this.setState({ reportRowData: data.dataSets[0].rows });
}
// ... there are some other methods as well
render() {
return (
<div style={{ border: '1px solid black' }}>
{this.getColumns().length > 0 ? (
<ReactDataGrid
columns={this.getColumns()}
rowGetter={this.rowGetter}
rowsCount={this.state.reportRowData.length} />
) : (
<DataNotFound componentName="Report Table"/>
)}
</div>
);
}
}
export default ReportAsTableView;
Snapshot file
// Jest Snapshot v1,
exports[`<ReportAsTableView /> renders correctly with success data received from server 1`] = `
<div
style={
Object {
"border": "1px solid black",
}
}
>
<div
className="NotFoundWrapper"
>
<div
className="attentionSign"
>
<img
src="./warning.png"
width="300"
/>
</div>
<div>
No Data found
<span>
for
Report Table
</span>
</div>
</div>
</div>
`;
Update:
fakeRequestLibrary
import Response from 'http-response-object';
export const fakeRequestLibrary = (requestUrl, requestOptions, shouldPass = true, responseData = null) => {
return new Promise((resolve, reject) => {
if (shouldPass) {
resolve(new Response(200, {}, responseData || { message: `You called ${requestUrl}` }, requestUrl));
} else {
reject(new Response(404, {}, responseData || { message: `The page at ${requestUrl} was not found` }, requestUrl));
}
});
};
Instead of passing an http end point what you can do for fix your problem is changing your init method and passing the data if no data are passed fetch them. Like this
init(params) {
if(this.props.fetchData != null){
this.resolveResponse(this.props.fetchData);
}else{
new ApiHelper().post(ReportConstants.REPORT_REQUEST + this.props.reportUUID, params)
.then((response) => {
this.resolveResponse(response);
});
}
}
Then in your test you will have
var rendered = renderer.create(
<ReportAsTableView reportUUID="e451ae04-4881-11e7-a919-92ebcb67fe33"
reportParameters={params}
fetchData={FAKE_RESPONSE} />
);
expect(rendered.toJSON()).toMatchSnapshot();
This solution works for my own project. It might also work for this question as well, but I haven't tested it. Add an await wait(); statement to wait for the async function in componentDidMount to complete.
const wait = async () => 'foo'; // a dummy function to simulate waiting
describe('<ReportAsTableView /> ', async () => {
it('renders correctly with success data received from server', async () => {
const params = {
startDate: '2017-05-05',
endDate: '2017-10-05',
};
var rendered = renderer.create(
<ReportAsTableView
reportUUID="e451ae04-4881-11e7-a919-92ebcb67fe33"
reportParameters={params}
fetchData={fakeRequestLibrary(
'openmrs-fake-server.org',
{},
true,
FAKE_RESPONSE,
)}
/>,
);
await wait(); // wait for <ReportAsTableView> to finish async data fetch in componentDidMount()
expect(rendered.toJSON()).toMatchSnapshot(); // shall render the component AFTER componentDidMount() is called
});
});

Resources