Next js Strapi integration not displaying data - axios

I am trying to build a simple task website to get familiar with full stack development. I am using Next js and Strapi. I have tried all I can think of, but the data from the server just will not display on the frontend. It seems to me that the page loads too soon, before the data has been loaded in. However, I am not a full stack dev and am therefore not sure.
import axios from 'axios';
const Tasks = ({ tasks }) => {
return (
<ul>
{tasks && tasks.map(task => (
<li key={task.id}>{task.name}</li>
))}
</ul>
);
};
export async function getStaticProps() {
const res = await axios.get('http://localhost:1337/tasks');
const data = await res.data;
if (!data) {
return {
notFound: true,
}
} else {
console.log(data)
}
return {
props: { tasks: data },
};
};
export default Tasks;

I had the same issue. You need to call the api from the pages files in the pages folder. I don't know why this is but that's how it works.

Related

How to create Strapi entries in Nextjs with a Form (Apollo and GraphQL)

I am trying to create new Strapi entries in Nextjs by submitting a form using Apollo client and GraphQL.
I tried a lot of diffrent things with my limited knowledge and was not able to make it work. While researching the topic I realized that most people are using the "useMutation" hook. It never worked though (also when using "useQuery" for queries). So I used a similar approach as for queries because they work.
queries: client.query | mutations: client.mutate
What I have tried:
// GraphQL that works in the Strapi playground
const TEST1 = gql`
mutation CreateMitgliedanmeldung($name: String!) {
createMitgliedanmeldung(data: { name: $name }) {
data {
attributes {
name
}
}
}
}`;
export default function MitgliedWerden() {
const formState = {
name: ''
};
function updateFormState(key, value) {
formState[key] = value;
}
function submit2() {
console.log(formState.name);
client.mutate({
variables: { name: formState.name },
mutation: TEST1,
})
}
return (
<div>
<form onSubmit={submit2()}>
<input onChange={e => updateFormState('name', e.target.value)} id="name"></input>
<button type="submit">Mitglied werden 1</button>
</form>
</div>
)
}
The code above creates new entries but there are multiple problems:
Variable "name" is not present in the new entry but can be console logged inside "updateFormState" function
(same mutation works fine inside graphql playground)
Form is submitted when page is reloaded/loaded
When submitting the page reloads (this is fine if the other problems are gone)
To fix the reload problem I added the following and called it onSubmit.
// calling this onSubmit instead of submit2 function
const newsubmit = (e) => {
e.preventDefault();
submit2()
};
Now submitting or reloading the page does not create a new entry but I get the console.log with the correct value. It seems like the "client.mutate" is broken or can't work in those conditions.
I was not able to find a lot about the ".mutate" function from apollo and the more often used "useMutation" hook did not work at all for me. Using "client.query" works fine.
Apollo Client:
import { ApolloClient, InMemoryCache } from "#apollo/client"
const defaultOptions = {
query: {
fetchPolicy: "no-cache",
},
}
const client = new ApolloClient({
uri: process.env.STRAPI_GRAPHQL_URL,
headers: { "Authorization": process.env.STRAPI_TOKEN },
cache: new InMemoryCache(),
defaultOptions,
});
export default client
Dependencies:
"#apollo/client": "^3.6.9",
"#apollo/react-hooks": "^4.0.0",
"graphql": "^16.5.0",
"graphql-request": "^4.3.0",
"next": "12.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",

Mocking authentication when testing MSAL React Apps

Our app is wrapped in the MSAL Authentication Template from #azure/msal-react in a standard way - key code segments are summarized below.
We would like to test app's individual components using react testing library (or something similar). Of course, when a React component such as SampleComponentUnderTest is to be properly rendered by a test as is shown in the simple test below, it must be wrapped in an MSAL component as well.
Is there a proper way to mock the MSAL authentication process for such purposes? Anyway to wrap a component under test in MSAL and directly provide test user's credentials to this component under test? Any references to useful documentation, blog posts, video, etc. to point us in the right direction would be greatly appreciated.
A Simple test
test('first test', () => {
const { getByText } = render(<SampleComponentUnderTest />);
const someText = getByText('A line of text');
expect(someText).toBeInTheDocument();
});
Config
export const msalConfig: Configuration = {
auth: {
clientId: `${process.env.REACT_APP_CLIENT_ID}`,
authority: `https://login.microsoftonline.com/${process.env.REACT_APP_TENANT_ID}`,
redirectUri:
process.env.NODE_ENV === 'development'
? 'http://localhost:3000/'
: process.env.REACT_APP_DEPLOY_URL,
},
cache: {
cacheLocation: 'sessionStorage',
storeAuthStateInCookie: false,
},
system: {
loggerOptions: {
loggerCallback: (level, message, containsPii) => {
if (containsPii) {
return;
}
switch (level) {
case LogLevel.Error:
console.error(message);
return;
case LogLevel.Info:
console.info(message);
return;
case LogLevel.Verbose:
console.debug(message);
return;
case LogLevel.Warning:
console.warn(message);
return;
default:
console.error(message);
}
},
},
},
};
Main app component
const msalInstance = new PublicClientApplication(msalConfig);
<MsalProvider instance={msalInstance}>
{!isAuthenticated && <UnauthenticatedHomePage />}
{isAuthenticated && <Protected />}
</MsalProvider>
Unauthenticated component
const signInClickHandler = (instance: IPublicClientApplication) => {
instance.loginRedirect(loginRequest).catch((e) => {
console.log(e);
});
};
<UnauthenticatedTemplate>
<Button onClick={() => signInClickHandler(instance)}>Sign in</Button>
</UnauthenticatedTemplate>
Protected component
<MsalAuthenticationTemplate
interactionType={InteractionType.Redirect}
errorComponent={ErrorComponent}
loadingComponent={LoadingComponent}
>
<SampleComponentUnderTest />
</MsalAuthenticationTemplate>
I had the same issue as you regarding component's test under msal-react.
It took me a couple of days to figure out how to implement a correct auth mock.
That's why I've created a package you will find here, that encapsulates all the boilerplate code : https://github.com/Mimetis/msal-react-tester
Basically, you can do multiple scenaris (user is already logged, user is not logged, user must log in etc ...) in a couple of lines, without having to configure anything and of course without having to reach Azure AD in any cases:
describe('Home page', () => {
let msalTester: MsalReactTester;
beforeEach(() => {
// new instance of msal tester for each test
msalTester = new MsalReactTester();
// spy all required msal things
msalTester.spyMsal();
});
afterEach(() => {
msalTester.resetSpyMsal();
});
test('Home page render correctly when user is logged in', async () => {
msalTester.isLogged();
render(
<MsalProvider instance={msalTester.client}>
<MemoryRouter>
<Layout>
<HomePage />
</Layout>
</MemoryRouter>
</MsalProvider>,
);
await msalTester.waitForRedirect();
let allLoggedInButtons = await screen.findAllByRole('button', { name: `${msalTester.activeAccount.name}` });
expect(allLoggedInButtons).toHaveLength(2);
});
test('Home page render correctly when user logs in using redirect', async () => {
msalTester.isNotLogged();
render(
<MsalProvider instance={msalTester.client}>
<MemoryRouter>
<Layout>
<HomePage />
</Layout>
</MemoryRouter>
</MsalProvider>,
);
await msalTester.waitForRedirect();
let signin = screen.getByRole('button', { name: 'Sign In - Redirect' });
userEvent.click(signin);
await msalTester.waitForLogin();
let allLoggedInButtons = await screen.findAllByRole('button', { name: `${msalTester.activeAccount.name}` });
expect(allLoggedInButtons).toHaveLength(2);
});
I am also curious about this, but from a slightly different perspective. I am trying to avoid littering the code base with components directly from msal in case we want to swap out identity providers at some point. The primary way to do this is to use a hook as an abstraction layer such as exposing isAuthenticated through that hook rather than the msal component library itself.
The useAuth hook would use the MSAL package directly. For the wrapper component however, I think we have to just create a separate component that either returns the MsalProvider OR a mocked auth provider of your choice. Since MsalProvider uses useContext beneath the hood I don't think you need to wrap it in another context provider.
Hope these ideas help while you are thinking through ways to do this. Know this isn't a direct answer to your question.

Error code 500 on a 404 not found page when deploying on Vercel

I deployed my app on Vercel a few months ago, all the nav links were working fine. Past month I had made some changes in the code and now my dashboard link is not working (on deployment only, works fine locally). Moreover the error it throws is a 500 on that 404 page. I've tried redeploying with build cache cleared, but nothing changed and it still doesn't work.
Code of page in question:
const fetcher = (url) => fetch(url).then((res) => res.json())
export default function dashboard(props) {
const classes = useStyles()
const userData = props.userData
const { t } = useTranslation('dashboard')
const router = useRouter()
if (userData == undefined || !userData) {
return <p>No data</p>
} else {
return (
<>
<main className={classes.main}></main>
</>
)
}
}
export async function getServerSideProps(context) {
return {
props: {
isCreateEvent: context.query.isCreateEvent,
...(await serverSideTranslations(context.locale, ['dashboard'])),
},
}
}
And the navlink code is :
<CustomButton
href={{
pathname: '/control',
query: {
isCreateEvent: false,
},
}}
styleClass={classes.actionbuttons}
typoClass={classes.headerText}
userID={userData._id}
id='2'
>
<div onClick={() => handleBgChange(2)}>
{i18nData[locale].dashboard}
</div>
</CustomButton>

busboy-bodyparser changes my request so that GridFsStorage doesn't register the request-data in mongodb

I am a frontend developer trying to broaden my horizons, and making what will become a MERN application. I'm struggling with image uploads to mongodb.
First I used the express bodyparser:
app.use(express.urlencoded({ extended: true }));
and app.use(express.json());
when used like this I managed to upload the file fine, and the uploaded file showed up in MongoDB Compass.
I found out that this doesn't support multipart/form-data, so I've changed the bodyparser to busboy-bodyparser so that I can access both form-data and the file that is being uploaded. So I changed the bodyparser to:
app.use(busboyBodyParser());
and now it won't upload the request-data to mongodb.
My upload control looks like this:
const upload = require("../middleware/upload");
const uploadFile = async (req, res) => {
try {
req.file = req.files.file;
await upload(req, res);
if (req.file == undefined) {
return res.send(`You must select a file.`);
}
return res.send(`File has been uploaded.`);
} catch (error) {
console.log(error);
return res.send(`Error when trying upload image: ${error}`);
}
};
module.exports = {
uploadFile: uploadFile
};
the reason I've set req.file equals to req.files.file is because busboy-bodyparser sends the file from req.files.file and not req.file, and I thought that this change would make the request function properly, it did not.
My upload-middleware looks like this:
const promise = mongoose.connect(mongoURI, { useNewUrlParser: true, useUnifiedTopology: true });
const conn = mongoose.connection;
let gfs;
conn.once('open', () => {
gfs = Grid(conn, mongoose.mongo);
gfs.collection('uploads');
});
//create storage object
const storage = new GridFsStorage({
db: promise,
file: (req, file) => {
return new Promise((resolve, reject) => {
crypto.randomBytes(16, (err, buf) => {
if (err) {
return reject(err);
}
const filename = buf.toString('hex') + path.extname(file.originalname);
const fileInfo = {
filename: filename,
bucketName: 'uploads',
metadata: {
title: req.body.title,
orientation: req.body.orientation
}
};
resolve(fileInfo);
});
});
}
});
const uploadFile = multer({ storage }).single("file");
var uploadFilesMiddleware = util.promisify(uploadFile);
module.exports = uploadFilesMiddleware;
I believe this is the code that logs (node:15124) DeprecationWarning: Listening to events on the Db class has been deprecated and will be removed in the next major version.
(Use node --trace-deprecation ... to show where the warning was created)
which is another problem I'm unsure how to solve, but that's another problem for another day.
My end goal is to be able to send the file to mongodb, with the attached metadata (title and orientation).
with this code I'm able to get the "File has been uploaded" message from the upload-control, but in mongoDB compass no file/chunks has been uploaded. The uploads worked great on file-uploads (without the metadata) with the express-bodyparser, so when I changed that to the busboy-bodyparser I get both the file and the metadata as intended but it is not loaded into the db, which leads me to believe that the new bodyparser changes the request so that GridFsStorage no longer recognizes it and doesn't put the data into the db. But frankly I'm just speculating here, and I generally have very limited knowledge of backend.
I use the correct enctype on the form I believe:
<form
action="/upload"
method="POST"
enctype="multipart/form-data">
any tips or explanations is very much appreciated!
I am a complete beginner in backend, so don't be afraid to spell it our for me :)
I managed to fix it!
I'm unsure what caused it, but I believe that the req.body-fields hadn't been populated yet or something of that nature. I therefore switched out
metadata: {
title: req.body.title,
orientation: req.body.orientation
}
with: metadata: req.body and it just works.
For any other backend-newbie who might stumble upon this, also remember to name your inputs in html like this: <input name="title" type="text" /> it is the name-attribute that gets submitted with the html-form and gives the key to req.body, so that you can access for example req.body.title (which didn't work here, but still worth knowing)

axios and mongo - trying to delete using the mapped _id as the perimeter. It responds with 200 but object remains in the db

Trying to make a simple CRUD app using react, axios and mongoose.
Here is my axios:
deleteUser(id) {
axios.delete(`${rootUrl}/api/users/${this.state.id}`, {
params: { id }
})
.then(response => {
// this.setState({ users: response.data });
console.log('deleteUser response', response, this.state);
});
}
Here is the relevant API route:
router.delete('/users/', (req, res) => {
const { id } = req.body;
User.findByIdAndDelete(id, (error, data) => {
if (error) {
console.log('error in deleting!');
throw error;
} else {
console.log('user has been deleted', data);
res.status(204).json(data);
}
});
});
It returns
DELETE /api/users/?id=5b34e5b5dfef8b4sd234567 204 47.816 ms - -
But when I GET users, the deleted user remains.
Here is the render I am mapping state into. I think I am pulling the id in properly but I am not sure what else to try:
{this.state.users.map(user => {
return (
<div className="Users1" key={user._id}>
<Card>
<CardBody>
<CardTitle>{user.username}</CardTitle>
<CardText>{user._id}</CardText>
<Button onClick={() => this.deleteUser(user._id)}
key={user._id}
type="button"
color="danger"
>
X
</Button>
</CardBody>
</Card>
</div>
);
})}
and here is state:
state = {
users: [],
id: ''
};
I had a similar problem. I had success by looking at the req.query property on the API server, rather than req.body:
const { id } = req.query;
I figured it out by dumping the entire req object to the console.
On the client side, my request did not include the id in the URL. I just included it in the params object. This seemed to help. So the equivalent call in your code would look something like this:
axios.delete(`${rootUrl}/api/users/`, {
params: { id }
})
I hope this helps. I'm still just learning React Native, so I'm hardly an expert. But I wanted to share what worked for me in case it helps others.