million icon indicating copy to clipboard operation
million copied to clipboard

bug: Not working on Nextjs 13 app-directory

Open acf77 opened this issue 2 years ago • 9 comments

Trying to use million on a Dialog component on nextjs13 with app directory.

Here's the next.config.mjs

import million from "million/compiler"

/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    appDir: true,
  },
}

export default million.next(nextConfig);

And heres is the component

import React, { useState } from "react"
import { block } from "million/react"

import { Dialog, Stack, Typography } from "@mui/material"
import { FileWithPath, useDropzone } from "react-dropzone"
import { PrimaryButton } from "../../buttons/primary-button"
import { SecondaryButton } from "../../buttons/secondary-button"

import styles from "./index.module.scss"
import { useDispatch } from "react-redux"
import { AppDispatch, RootState } from "../../../lib/redux/store"
import { uploadPDFS } from "../../../lib/redux/actions/async"
import { useSelector } from "react-redux"

function Component() {

  const [magicModeDialogOpen, setMagicModeDialogOpen] = useState(false)
  const [error, setError] = useState("")
  const [files, setFiles] = useState<any>()

  const dispatch = useDispatch<AppDispatch>()
  const { userData } = useSelector((state: RootState) => state.centralProjectData)


  const onDrop = React.useCallback((acceptedFiles: FileWithPath[]): void => {

    if (acceptedFiles.length > 4) {
      setError("You can only add up to 4 articles")
      setTimeout(() => {
        setError("")
      }, 4000)
      return
    }

    if (acceptedFiles.some((f: any) => f.type != "application/pdf")) {
      setError("Only .pdf files are accepted")
      setTimeout(() => {
        setError("")
      }, 4000)
      return
    }

    setFiles(acceptedFiles)

  }, [])

  const handleUploadFiles = () => {
    console.log(files)

    dispatch(uploadPDFS({ userId: userData.userId, files }))

  }


  const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop })

  const handleDropzoneText = ({ isDragActive, error, files }: { isDragActive: boolean, error: string, files: boolean }) => {
    if (isDragActive) {
      return <strong>Drop it like its hot 🔥</strong>
    }
    if (error) {
      return <Typography variant="caption" color="error"><strong>{error}</strong></Typography>
    }
    if (files) {
      return <strong>Click on Upload files to start loading</strong>
    }
    return "Drag and Drop your articles here"
  }

  return (
    <>
      <Stack gap={1} className={styles.gradientBox} sx={{ p: "16px 0", border: "1px solid #e7e7e7", borderRadius: '16px' }} justifyContent="center" alignItems='center'>
        <Typography variant="caption">
          Add articles to<br />✨<strong>Magic Mode</strong>
        </Typography>
        <PrimaryButton onClick={() => setMagicModeDialogOpen(true)}>Add articles</PrimaryButton>
      </Stack>
      <Dialog open={magicModeDialogOpen} onClose={() => setMagicModeDialogOpen(false)}>
        <Stack gap={1} sx={{ p: 4, maxWidth: "500px" }}>
          <Typography variant="h6">
            Add articles to ✨<strong>Magic Mode</strong>
          </Typography>
          <Typography variant="caption" >
            Add up to 4 articles to automatically generate suggestions tailored to a specific scientific writing style or subject. <strong>Up to 4 .pdf files are allowed.</strong>
          </Typography>
          <Stack className={styles.dropzone} {...getRootProps()} >
            <input {...getInputProps()} accept="application/pdf" />
            <Typography variant="caption">
              {handleDropzoneText({ isDragActive, error, files: Boolean(files) })}
            </Typography>
          </Stack>
          <Stack direction='row' justifyContent='space-between' alignItems='center' >
            <SecondaryButton disabled={!files} onClick={() => setFiles("")}>Cancel</SecondaryButton>
            <PrimaryButton disabled={!files} onClick={handleUploadFiles}>Upload files</PrimaryButton>
          </Stack>
        </Stack>
      </Dialog>
    </>
  )
}

const MagicModeDialog = block(Component)

export default MagicModeDialog;

And the error

Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.

The error points specifically to the useState hooks.

Is this expected?

Good job! Love these projects, wish I had the intelligence to do something like this.

acf77 avatar Apr 25 '23 00:04 acf77

Ah, there are several problems here. Currently Million.js is limited to "deterministic" returns, so no early / conditional returns of different shape. Furthermore, I'm not sure if mui components can be optimized in this case.

I think in your case it would be best to try optimizing at a more granular level

aidenybai avatar Apr 25 '23 00:04 aidenybai

I think one area we can improve on this is actually useful errors. Something to invest in

aidenybai avatar Apr 25 '23 00:04 aidenybai

Granular level you mean any component inside the return? Like a separate component just for the dropzone? Also, the problem might be in the onDrop function that has conditionals?

I will play around with it and see how I can use it on my current project. Let me know if I can help you in any way!

acf77 avatar Apr 25 '23 01:04 acf77

Yep, exactly.

aidenybai avatar Apr 25 '23 03:04 aidenybai

Any updates?

tobySolutions avatar Apr 26 '23 10:04 tobySolutions

I think you should use this import instead:

import { block } from "million/next"

tobySolutions avatar Apr 28 '23 15:04 tobySolutions

Ah, there are several problems here. Currently Million.js is limited to "deterministic" returns, so no early / conditional returns of different shape. Furthermore, I'm not sure if mui components can be optimized in this case.

I think in your case it would be best to try optimizing at a more granular level

So wrapping the App using block is useless if it uses a component that violates the Million.js Rules of Blocks inside it?

lamualfa avatar Jun 11 '23 04:06 lamualfa

Does million.js work only with client site components?

emrekardaslar avatar Jun 13 '23 20:06 emrekardaslar

Hey Do you have some updates related to million and mui components?

igor90 avatar Jul 03 '23 13:07 igor90

I think you should use this import instead:

import { block } from "million/next"

I don't think that exists

Module not found: Package path ./next is not exported from package \node_modules\million

sam-hieken avatar Jul 11 '23 21:07 sam-hieken

@emrekardaslar yes @igor90 not yet @sam-hieken import from million/react

closing for inactivity. please open a new issue w/ a reproduction if you have a separate issue

aidenybai avatar Jul 14 '23 04:07 aidenybai

Thanks @aidenybai for the answers. But how about this:

image

lamualfa avatar Jul 14 '23 08:07 lamualfa

Yes, it would

aidenybai avatar Jul 14 '23 09:07 aidenybai

I am facing the same basic issue, any help would be appreciated!

VisileanRushiC avatar Dec 20 '23 07:12 VisileanRushiC

Hey i am facing the same issue and i am using Shadcn ui, any help would be appreciated

Rishavraaj avatar Jun 04 '24 12:06 Rishavraaj