Cannot add a new article in a simple blog built with react - node.js

I have built a simple blog with React. I am having an issue, whenever I try and add a new article. I get the below error.
TypeError: Cannot read property 'upvotes' of null
The article page code is below:
const ArticlePage = ({ match }) => {
const name = match.params.name;
const article = ArticleContent.find(article => article.name === name);
const [articleInfo, setArticleInfo] = useState({ upvotes: -1, comments: [] });
useEffect(() => {
const fetchData = async () => {
const result = await fetch(`/api/articles/${name}`);
const body = await result.json();
setArticleInfo(body);
}
fetchData();
}, [name]);
if (!article) return <notFoundPage />
const otherArticles = ArticleContent.filter(article => article.name !== name);
return (
<>
<h1>{article.title}</h1>
<UpvotesSection articleName={name} upvotes={articleInfo.upvotes} setArticleInfo={setArticleInfo} />
{article.content.map((paragraph, key) => (
<p key={key}>{paragraph}</p>
))}
<CommentsList comments={articleInfo.comments} />
<AddCommentForm articleName={name} setArticleInfo={setArticleInfo} />
<h3>Other Articles:</h3>
<ArticlesList articles={otherArticles} />
</>
);
}
That is where the error tells me to look. I have tried to comment the upvotes section out, it just changes the error to comments. I comment them both out and the article renders but it has no comments or upvotes.

You get the error because your articleInfo is null.
That's why it doesn't find upvotes or comments in your return method.
Your initial state contains an object { upvotes: -1, comments: [] }. So that's not the problem. The only place where this can go wrong is where you call setArticleInfo.
Have you tried logging your body variable?
My guess is that your body variable is null, and by calling setArticleInfo(body) you set your articleInfo to be null hence why it throws an error.

Related

React - fill datagrid with json fetch from node backend - TypeError null is not an object

I try to fill a React DataGrid with data from a JSON provided by node backend.
The backend code looks as follows:
app.get("/articles", (req, res) => {
res.json([
{
"title":"Test Article One",
"timestamp":"09-01-2023",
"text":"Test text one"
},
{
"title":"Test Article Two",
"timestamp":"10-01-2023",
"text":"Test text two"
},
{
"title":"Test Article Three",
"timestamp":"11-01-2023",
"text":"Test text three"
}])
})
The React code looks as follows:
const MemberPage = () => {
const [articles, setArticles] = useState([])
const [articleKeys, setArticleKeys] = useState([])
useEffect(() => {
fetch("http://localhost:3001/articles")
.then((res) => res.json())
.then((data) => {
setArticles(data)
setArticleKeys(Object.keys(data[0]))
})
})
return (
<div id="memberpage-main-container">
<DataGrid columns={articleKeys} rows={articles} />
</div>
)
}
I get the error message TypeError: null is not an object (evaluating 'measuringCell.getBoundingClientRect') in the browser console and the page wouldn't render. I first thought, it is because the DataGrid is rendered before the useEffect fetches the data which I've red in other answers, however, when I write:
const articleKeys = []
const articles = []
it works (I'm mean, it's an empty page then, but I don't get any errors). So, I would expect it not to be a problem when setting useState([]).
Any help is appreciated.
So, I've found a solution by switching from react-data-grid to #mui/x-data-grid.
The code now looks as follows:
import React, { useState, useEffect } from "react"
import { DataGrid } from "#mui/x-data-grid"
const MemberPage = () => {
const [articles, setArticles] = useState([])
const [articleKeys, setArticleKeys] = useState([])
function parseArticleKeys(keys) {
let tableColumns = []
for (let i = 0; i < keys.length; i++) {
tableColumns.push({field: keys[i], headerName: keys[i], width: 300})
}
return tableColumns
}
useEffect(() => {
fetch("http://localhost:3001/articles")
.then((res) => res.json())
.then((data) => {
setArticles(data)
setArticleKeys(Object.keys(data[0]))
})
})
return (
<div id="memberpage-news-container">
<DataGrid columns={parseArticleKeys(articleKeys)} rows={articles} />
</div>
)
}
export default MemberPage
I tried a similar thing with react-data-grid but couldn't get it to work. If someone has an idea to accomplish that with react-data-grid, it still might be helpful for others but I personally am ok with that solution.

Cannot import meta data from mdx file in getStaticProps nextjs

I have a problem while trying to require meta data from an mdx file in my Next.js project.
MDX file example:
export const meta = {
title: 'title',
date: new Date('May 09, 2019'),
};
Content
export const getStaticProps = async context => {
const postFilenames = await recRead(process.cwd() + '/pages', ['*.tsx']);
const postMetadata = await Promise.all(
postFilenames.map(async p => {
const { meta } = require(p);
return meta;
}),
);
return {
props: {
postMetadata: postMetadata,
},
};
};
It is a modified version of this: https://sarim.work/blog/dynamic-imports-mdx. While accessing a website I get an error:
Cannot find module '/home/oliwier/webDev/oliwierwpodrozy/pages/balkany/1.mdx'.
BTW recRead is this https://www.npmjs.com/package/recursive-readdir.
What is going on? Outside of getStaticProps I can import data.
I found something ridiculous when trying to solve the problem.
// 1)console.log(postFilenamesToImport[0]);
// 2) const meta = await import('../pages/wielka-brytania/1.mdx');
// 3) const meta = await import(postFilenamesToImport[0]);
// console.log(meta.meta);
shows: ../pages/wielka-brytania/1.mdx which is a string
This one works
But this one doesn't. Shows error: Error: Cannot find module '../pages/wielka-brytania/1.mdx'
It is not a const problem. It is written for tests and i know that using 2) and 3) together would cause problem. This error occurs when 1) is commented.
You can import metadata like follows.
First, we export the metadata from within the .mdx file
// in /pages/posts/example.mdx
import Layout from "../../components/layout";
export const meta = {
title: "example",
date: "2021-12-27",
slug: "example",
};
Lorem ipsum.
export default ({ children }) => (
<Layout subtitle={meta.title}>{children}</Layout>
);
Then we use getStaticProps to scan the file system at runtime, importing each .mdx file as a module, then mapping out their metadata exports. Since we are displaying the metadata on the index page, we will pop index.js from the array.
// in /pages/posts/index.js
export const getStaticProps = async (context) => {
const postDirectory = path.join(process.cwd(), "src/pages/posts");
let postFilenames = fs.readdirSync(postDirectory);
postFilenames = removeItemOnce(postFilenames, "index.js");
const postModules = await Promise.all(
postFilenames.map(async (p) => import(`./${p}`))
);
const postMetadata = postModules.map((m) => (m.meta ? m.meta : null));
return {
props: {
postMetadata: postMetadata,
},
};
// thanks https://sarim.work/blog/dynamic-imports-mdx
};
function removeItemOnce(arr, value) {
var index = arr.indexOf(value);
if (index > -1) {
arr.splice(index, 1);
}
return arr;
// thanks https://stackoverflow.com/a/5767357/13090245
}
Here is one way of using the prop to render a list of posts
// in /pages/posts/index.js
export default function PostsIndex({ postMetadata }) {
return (
<Layout subtitle="blog index">
<ul>
{postMetadata.map(({ slug, date, title }) => (
<li key={slug}>
<Link href={`/posts/${slug}`} a={title} />
<br />
{date}
</li>
))}
</ul>
</Layout>
);
}

React-Admin: Using "getList", I am getting "Error: cannot read property 'map' of undefined"

With react-admin, i'm trying to get the users list from API Restful Node server,and I have this error:
Error: cannot read property 'map' of undefined
Here's the getUsers in server user.controller.js:
const getUsers = catchAsync(async (req, res) => {
const users = await userService.getUsers(req.query);
const data = users.map(user => user.transform());
const total = users.length;
res.type('json');
res.set('Access-Control-Expose-Headers', 'Content-Range');
res.set('Content-Range', `users 0-2/${total}`);
res.set('X-Total-Count', total);
response = '{ data: ' + JSON.stringify(data) + ', total: ' + total + ' }';
res.send(response);
});
Here the data response received:
{
data: [
{"id":"5e6f5e3b4cf60a67701deeae","email":"admin#test.com","firstname":"Ad","lastname":"Min","role":"admin"},
{"id":"5e6f5e3b4cf60a67701deeaf","email":"test#test.com","firstname":"Jhon","lastname":"Doe","role":"user"}
],
total: 2
}
In react-admin, getList in dataProvider.js:
export default {
getList: (resource, params) => {
console.log(params);
const { field, order } = params.sort;
const query = {
...fetchUtils.flattenObject(params.filter),
sortBy: field
};
const url = `${apiUrl}/${resource}?${stringify(query)}`;
return httpClient(url).then(({ headers }, json) => ({
data: json,
total: parseInt(
headers
.get("Content-Range")
.split("/")
.pop(),
10
)
}));
},
Update:
UserList.tsx
import React from "react";
import {
TextField,
Datagrid,
DateInput,
Filter,
List,
EmailField,
SearchInput
} from "react-admin";
import { useMediaQuery, Theme } from "#material-ui/core";
import SegmentInput from "./SegmentInput";
import MobileGrid from "./MobileGrid";
const UserFilter = (props: any) => (
<Filter {...props}>
<SearchInput source="q" alwaysOn />
<DateInput source="createdAt" />
<SegmentInput />
</Filter>
);
const UserList = (props: any) => {
const isXsmall = useMediaQuery<Theme>(theme => theme.breakpoints.down("xs"));
return (
<List
{...props}
filters={<UserFilter />}
sort={{ field: "createdAt", order: "desc" }}
perPage={25}
>
{isXsmall ? (
<MobileGrid />
) : (
<Datagrid optimized rowClick="edit">
<TextField source="id" />
<EmailField source="email" />
<TextField source="firstname" />
<TextField source="lastname" />
<TextField source="role" />
<DateInput source="createdAt" />
<DateInput source="updatedAt" />
</Datagrid>
)}
</List>
);
};
export default UserList;
Here the documentation with somes examples for getList:
https://marmelab.com/react-admin/DataProviders.html#writing-your-own-data-provider
I don't understand, i need help please, what wrong?
Thanks & Regards
Ludo
Here's a detailed explanation of what's happening with the map() in reactjs.
And this source is on point for resolving this in nodejs
In your case, let's take a closer look here:
// user.controller.js
const getUsers = catchAsync(async (req, res) => {
// Actually, it might be wiser to first declare users
+ let users = [];
// You await for users (querying) to be resolved or rejected, cool
- const users = await userService.getUsers(req.query);
// And we then we assign "users" as before
+ users = await userService.getUsers(req.query);
// But here, map() requires users to be defined before iteration.
// So for this to work, we need the value of users to be resolved first, right?
- const data = users.map(user => user.transform());
// Could you change the above line to this, and see?
// This doesn't because we gave map(), a synchronous parameter (function)
- const data = Promise.all(users.map(user => user.transform()));
// Ok, break this up for easier understanding
// let's declare a userList, where we give map() an "async" parameter
// At this point, userList will be an "Array of Promises"
+ const userList = users.map(async user => user.transform());
// Now we resolve all the Promises, and we should have our clean data
+ const data = await Promise.all(userList);
});
With that change, we make sure that map() runs after our users is resolved.
Does that work for you? Let me know.

Converting React Functional Component to Hook

While trying to learn full stack development I was trying out this tutorial ( https://www.freecodecamp.org/news/create-a-react-frontend-a-node-express-backend-and-connect-them-together-c5798926047c/ ) on a React-Express-Node basic app. However, it was written using functional components instead of hooks. I'm trying to convert this section to a hook:
constructor(props) {
super(props);
this.state = { apiResponse: "" };
}
callAPI() {
fetch("http://localhost:9000/testAPI")
.then(res => res.text())
.then(res => this.setState({ apiResponse: res }));
}
componentWillMount() {
this.callAPI();
}
with this in the render section:
<p className="App-intro">;{this.state.apiResponse}</p>
I tried this:
const [apiResponse, setApiResponse] = useState();
useEffect(() => {
const fetchApiResponse = async () => {
const result = await (
'http://localhost:9000/testAPI'
);
setApiResponse(result);
console.log("apiResponse " + apiResponse);
};
fetchApiResponse();
});
but the console.log of the apiResponse always shows as undefined. I know I must be doing something wrong but I can't figure it out.
You aren't far off in your attempt.
There are two problems:
Problem 1.
In order to get the same effect as componentWillMount (side note - this is a deprecated method, use componentDidMount or the constructor) you need to tell the useEffect to only run once on mount. To do this you give it an empty array of dependencies.
useEffect(() => {
// do stuff
}, []); // empty array as second argument
By not giving a second argument, the effect will run every single render.
Problem 2.
State updates are asynchronous. This means you cannot console log apiResponse immediately after updating it and expect it to contain the new value.
To get around this, just console.log inside the function body outside of the hook.
Here is a simplified example:
const {useState, useEffect} = React;
const Example = () => {
const [apiResponse, setApiResponse] = useState();
useEffect(() => {
const fetchApiResponse = () => {
const result = 'test';
setApiResponse(result);
// Will not be updated
console.log("wrong: apiResponse ", apiResponse);
}
fetchApiResponse();
}, []);
// Will be updated
console.log("right: apiResponse ", apiResponse);
return <span />
}
ReactDOM.render(<Example />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

fetch not returning data in react

I'm new to react, i'm having difficulty getting data for a single book out of list, be passed through via axios' get method.
I think it has something to do with the url, but I have been unable to get fix it.
Here's my code:
export function loadBook(book){
return dispatch => {
return axios.get('http://localhost:3000/api/books/book/:id').then(book => {
dispatch(loadBookSuccess(book.data));
console.log('through!');
}).catch(error => {
console.log('error');
});
};
}
//also tried this
export function loadBook(id){
return dispatch => {
return axios.get('http://localhost:3000/api/books/book/' + {id}).then(book => {
dispatch(loadBookSuccess(book.data));
console.log('through!');
}).catch(error => {
console.log('error');
});
};
}
Html code that contains a variable link to each individual book
<div className="container">
<h3><Link to={'/book/' + book._id}> {book.title}</Link></h3>
<h5>Author: {book.author.first_name + ' ' + book.author.family_name}</h5>
<h4>Summary: {book.summary}</h4>
<BookGenre genre={genre} />
</div>
link in Route:
<Route path="/book/:id" component={BookPage} />
Edit: code for the book component
class BookPage extends React.Component{
render(){
const book = this.props;
const genre = book.genre;
console.log(book);
return(
<div>
<div>
<h3> {book.title}</h3>
<h5>Author: {book.author.first_name + ' ' + book.author.family_name}</h5>
<h4>Summary: {book.summary}</h4>
<BookGenre genre={genre} />
</div>
</div>
);
}
}
BookPage.propTypes = {
book: PropTypes.object.isRequired
};
//setting the book with mapStateToProps
function mapStateToProps (state, ownProps){
let book = {title: '', author: '', summary: '', isbn: '', genre: []};
const bookid = ownProps.params._id;
if(state.books.length > 0){
book = Object.assign({}, state.books.find(book => book.id));
}
return {
book: book
};
}
function mapDispatchToProps (dispatch) {
return {
actions: bindActionCreators(loadBook, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(BookPage);
Instead of doing this:-
axios.get('http://localhost:3000/api/books/book/' + {id})
You should do like this:-
axios.get(`http://localhost:3000/api/books/book/${id}`)
So your action.js might look like this:-
export function loadBook(id){
const request = axios.get(`http://localhost:3000/api/books/book/${id}`);
return dispatch => {
request.then(book => {
dispatch(loadBookSuccess(book.data));
}).catch(error => {
console.log('error');
})
};
}
Since the id, you have passed it seems to be a string so it can be concatenated using ES6 template strings and make sure you wrap your strings in backtick . or you can do it by + operator, also make sure you pass id as a parameter in your loadbook function so that you can join it to your URL.
Figured out the solution to this problem.
My mistake was that I failed to send the id of the item I along with the api call.
Using componentDidMount and sending the dynamic id from the url params solved this problem for me.
Thank you, #Vinit Raj, I guess I was too much of a rookie then.

Resources