How to use useNavigate() before rendering component with useState() in React
Asked Answered
P

1

6

I am using React with apollo-client. If user not the owner of note it should be redirected. But after redirecting I am receiving: Warning: Cannot update a component ('BrowserRouter') while rendering a different component ('EditNote'). To locate the bad setState() call inside 'EditNote'. As I can understand problem in useState() of <NoteForm/> component. What I do wrong?

import React, { useEffect } from "react";
import { useMutation, useQuery } from "@apollo/client";
import { useNavigate, useParams } from "react-router-dom";

import NoteForm from "../components/NoteForm";

import { GET_NOTE, GET_ME } from "../gql/query";
import { EDIT_NOTE } from "../gql/mutation";

const EditNote = () => {
    const navigate = useNavigate();
    const params = useParams();
    const id = params.id;

    const [editNote] = useMutation(EDIT_NOTE, {
        variables: {
            id
        },
        onCompleted: () => {
            navigate(`/note/${id}`, { replace: true });
        }
    });

    const { loading: noteLoading, error: noteError, data: noteData } = useQuery(GET_NOTE, { variables: { id } });
    const { loading: userLoading, error: userError, data: userData } = useQuery(GET_ME);


    if (noteLoading || userLoading) return 'Loading...';
    if (noteError || userError) return `Error! ${error.message}`;

    if (userData.me.id !== noteData.note.author.id) {
        navigate(`/note/${id}`, { replace: true });
    }

    return <NoteForm content={noteData.note.content} action={editNote} />;



};

export default EditNote;

If wrap redirect with useEffect() will receive error Uncaught TypeError: Cannot read properties of undefined (reading 'me') as requests was not finished. So maybe I should somehow 'wait' until requests will be not finished or somehow join requests in one useQuery().

    const { loading: noteLoading, error: noteError, data: noteData } = useQuery(GET_NOTE, { variables: { id } });
    const { loading: userLoading, error: userError, data: userData } = useQuery(GET_ME);

    useEffect(() => {
        if (userData.me.id !== noteData.note.author.id) {
            navigate(`/note/${id}`, { replace: true });
        }
    }, [userData, noteData]);

    if (noteLoading || userLoading) return 'Loading...';
    if (noteError || userError) return `Error! ${error.message}`;

    return <NoteForm content={noteData.note.content} action={editNote} />;

This is NoteForm component, where is useState().

import React, { Component, useState } from "react";
import styled from 'styled-components';

import Button from './Button';

const Wrapper = styled.div`
    height: 100%;
`;
const Form = styled.form`
    height: 100%;
`;
const TextArea = styled.textarea`
    width: 100%;
    height: 90%;
`;

const NoteForm = props => {
    const [value, setValue] = useState({ content: props.content || '' });

    const onChange = event => {
        setValue({
            ...value,
            [event.target.name]: event.target.value
        });
    };

    return (
        <Wrapper>
            <Form onSubmit={e => {
                e.preventDefault();
                props.action({
                    variables: { ...value }
                });
            }}
            >
                <TextArea required type="text" name="content" placeholder="Note content"
                    value={value.content}
                    onChange={onChange}
                />
                <Button type="submit">Save</Button>
            </Form>
        </Wrapper>
    );
};

export default NoteForm;
Paul answered 27/6, 2022 at 14:28 Comment(0)
B
1

I'm not sure but try this :

first update this line in the parent component :

if (userData.me.id !== noteData.note.author.id) {
    return navigate(`/note/${id}`, { replace: true });
 }

then in the child component i would like to do this :

const NoteForm = ({content = "", action}) => {
    const [value, setValue] = useState(content);

    const onChange = event => {
        setValue(event.target.value);
    };
.....

// then in the render of text area we only need a flat state contains value

<TextArea
  required
  type="text"
  name="content"
  placeholder="Note content"
  value={value}
  onChange={onChange}
/>

But if it doesn't work , try to set the state inside useEffect based on the value of content, dont't forget to give the state an initial value "":

useEffect(() => {
   if (content) {
     setValue(content);
   }
}, [content]);
Bilharziasis answered 27/6, 2022 at 14:52 Comment(5)
Not helped :( The warning is still appearPaul
@AlexeyKanarskiy did you try to set state inside useEffect ? i updated my answer just right now !Bilharziasis
with this you should initialize useState : const [value, setValue] = useState("");Bilharziasis
adding return to navigate I have already tried. adding useEffect() not helped.Paul
returning navigate solved the issue for mePersonalize

© 2022 - 2024 — McMap. All rights reserved.