最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Next.js, using random properties without triggering "did not match on serverclient" error - Stack

programmeradmin7浏览0评论

I'd like to randomly generate the id property of form inputs, to prevent them from potentially conflicting with other inputs with the same id. This could happen if I have two login forms on the same page, each with an email field. The reason I want/need to set the id property is so that I can set the for property on the label corresponding to that input. The problem is that this randomly generated id is different on the server and the client, and so next.js throws an error. Here's some code:

function uniqueId() {
    let first = (Math.random() * 46656) | 0
    let second = (Math.random() * 46656) | 0
    first = ('000' + first.toString(36)).slice(-3)
    second = ('000' + second.toString(36)).slice(-3)
    return first + second
}

const Login = () => {
    const [emailId] = useState(uniqueId())

    return (
        <form>
            <label htmlFor={emailId}>Email</label>
            <input id={emailId} name='email' type='email' />
        </form>
    )
}

This is the error I get:

Warning: Prop 'htmlFor' did not match. Server: "email-txdmls" Client: "email-htte8e"

Any idea how to generate a random id that's consistent on the server/client? Or maybe a different way of doing it without random ids?

I'd like to randomly generate the id property of form inputs, to prevent them from potentially conflicting with other inputs with the same id. This could happen if I have two login forms on the same page, each with an email field. The reason I want/need to set the id property is so that I can set the for property on the label corresponding to that input. The problem is that this randomly generated id is different on the server and the client, and so next.js throws an error. Here's some code:

function uniqueId() {
    let first = (Math.random() * 46656) | 0
    let second = (Math.random() * 46656) | 0
    first = ('000' + first.toString(36)).slice(-3)
    second = ('000' + second.toString(36)).slice(-3)
    return first + second
}

const Login = () => {
    const [emailId] = useState(uniqueId())

    return (
        <form>
            <label htmlFor={emailId}>Email</label>
            <input id={emailId} name='email' type='email' />
        </form>
    )
}

This is the error I get:

Warning: Prop 'htmlFor' did not match. Server: "email-txdmls" Client: "email-htte8e"

Any idea how to generate a random id that's consistent on the server/client? Or maybe a different way of doing it without random ids?

Share Improve this question asked Jan 29, 2020 at 22:16 CullyCully 6,9556 gold badges42 silver badges63 bronze badges
Add a comment  | 

3 Answers 3

Reset to default 17

UPDATE: React 18 added the useId hook that will likely work for this. I haven't tried it but it looks like it's basically a drop-in replacement for the code in this answer.

I found a workaround to this. I'm not sure if it's a great solution (see explanation below). Seems like a lot of trouble just to essentially suppress a warning message. Still very curious to hear alternate solutions. Honestly even a way to tell next.js to ignore the difference and not issue a warning would work fine (it doesn't matter that the ids differ on SSR and client).

So what I did is generate the id in a useEffect hook. The problem is that initial server-side rendered HTML doesn't have an id on the input. It's not until all the JS is processed that it gets an id. Not ideal.

const Login = () => {
    const [emailId, setEmailId] = useState(null)

    useEffect(() => {
        setEmailId(uniqueId())
    }, [])

    return (
        <form>
            <label htmlFor={emailId}>Email</label>
            <input id={emailId} name='email' type='email' />
        </form>
    )
}

It should be noted that the id will be null on the first render. In this example it isn't an issue since the purpose is mostly to associate a label with an input, which will happen quickly enough on the second render. However, if you're using this idea in another situation, just keep it in mind.

If you want to encapsulate this into a custom hook, and clean up your component a bit:

const useUniqueId = () => {
    const [id, setId] = useState(null)

    useEffect(() => {
        setId(uniqueId())
    }, [])

    return id
}

const Login = () => {
    const emailId = useUniqueId()
    const nameId = useUniqueId()

    return (
        <form>
            <label htmlFor={nameId}>Name</label>
            <input id={nameId} name='name' type='text' />

            <label htmlFor={emailId}>Email</label>
            <input id={emailId} name='email' type='email' />
        </form>
    )
}

My solution was to use a seeded random number generator instead of Math.random(). Since I use the same seed on both frontend and backend, they both end up getting the same ID-s.

// https://stackoverflow.com/a/47593316/2405595
function createRandomSeedGenerator(str) {
  let h = 1779033703 ^ str.length;
  for (let i = 0; i < str.length; i++) {
    h = Math.imul(h ^ str.charCodeAt(i), 3432918353);
    h = (h << 13) | (h >>> 19);
  }

  return () => {
    h = Math.imul(h ^ (h >>> 16), 2246822507);
    h = Math.imul(h ^ (h >>> 13), 3266489909);
    return (h ^= h >>> 16) >>> 0;
  };
}

// https://stackoverflow.com/a/47593316/2405595
function createDeterministicRandom(seedString) {
  const generateSeed = createRandomSeedGenerator(seedString);
  let a = generateSeed();
  let b = generateSeed();
  let c = generateSeed();
  let d = generateSeed();

  return () => {
    a >>>= 0;
    b >>>= 0;
    c >>>= 0;
    d >>>= 0;
    var t = (a + b) | 0;
    a = b ^ (b >>> 9);
    b = (c + (c << 3)) | 0;
    c = (c << 21) | (c >>> 11);
    d = (d + 1) | 0;
    t = (t + d) | 0;
    c = (c + t) | 0;
    return (t >>> 0) / 4294967296;
  };
}


const deterministicRandomNumber = createDeterministicRandom(process.env.NODE_ENV);

function uniqueId() {
    let first = (deterministicRandomNumber() * 46656) | 0
    let second = (deterministicRandomNumber() * 46656) | 0
    first = ('000' + first.toString(36)).slice(-3)
    second = ('000' + second.toString(36)).slice(-3)
    return first + second
}

Of course, you should NOT do this if you need random numbers for security purposes.

getServerSideProps() could work.

https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props#using-getserversideprops-to-fetch-data-at-request-time

It can do some logic in the server and then pass it to the client. So you can make a random ID consistent on the server and client.

function Page({ randNum }) {
  return <div>{randNum}</div>;
}

// This gets called on every request
export async function getServerSideProps() {
  // Get random number
  randNum = Math.floor(Math.random() * 1000);

  // Pass data to the page via props
  return { props: { randNum } };
}

export default Page;

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论