Search code examples
javascriptreactjsgraphqlapolloapollo-client

useEffect is running when any function is running


First of all, I researched the question a lot, but I could not find a solution. I would appreciate if you help.

functional component

I add the code briefly below. this is not full code

state and props

  // blog id
  const { id } = props.match.params;

  // state
  const initialState = {
    title: "",
    category: "",
    admin_id: "",
    status: false
  };

  const [form, setState] = useState(initialState);
  const [adminList, setAdminList] = useState([]);
  const [articleText, setArticleText] = useState([]);
  const [me, setMe] = useState([]);
  const [locked, setLocked] = useState(true);
  const timerRef = useRef(null);

// queries and mutations 

  const { loading, error, data } = useQuery(GET_BLOG, {
    variables: { id }
  });

  const { data: data_admin, loading: loading_admin } = useQuery(GET_ADMINS);

  const [editBlog, { loading: loadingUpdate }] = useMutation(
    UPDATE_BLOG
  );
  const [lockedBlog] = useMutation(LOCKED_BLOG);

multiple useEffect and functions

useEffect(() => {
    if (!loading && data) {
      setState({
        title: data.blog.title,
        category: data.blog.category,
        admin_id: data.blog.admin.id,
        status: data.blog.status
      });
      setArticleText({
        text: data.blog.text
      });
    }
    console.log(data);
  }, [loading, data]);

  useEffect(() => {
    if (!loading_admin && data_admin) {
      const me = data_admin.admins.filter(
        x => x.id === props.session.activeAdmin.id
      );
      setAdminList(data_admin);
      setMe(me[0]);
    }
  }, [data_admin, loading_admin]);

  useEffect(() => {
    const { id } = props.match.params;
    lockedBlog({
      variables: {
        id,
        locked: locked
      }
    }).then(async ({ data }) => {
      console.log(data);
    });
    return () => {
      lockedBlog({
        variables: {
          id,
          locked: false
        }
      }).then(async ({ data }) => {
        console.log(data);
      });
    };
  }, [locked]);

  // if loading data
  if (loading || loading_admin)
    return (
      <div>
        <CircularProgress className="loadingbutton" />
      </div>
    );

  if (error) return <div>Error.</div>;

  // update onChange form
  const updateField = e => {
    setState({
      ...form,
      [e.target.name]: e.target.value
    });
  };

  // editor update
  const onChangeEditor = text => {
    const currentText = articleText.text;
    const newText = JSON.stringify(text);
    if (currentText !== newText) {
      // Content has changed
      if (timerRef.current) {
        clearTimeout(timerRef.current);
      }
      setArticleText({ text: newText });
      if (!formValidate()) {
        timerRef.current = setTimeout(() => {
          onSubmitAuto();
        }, 10000);
      }
    }
  };

  // auto save
  const onSubmitAuto = () => {
    if (timerRef.current) {
      clearTimeout(timerRef.current);
    }
    editBlog({
      variables: {
        id,
        admin_id,
        title,
        text: articleText.text,
        category,
        status
      }
    }).then(async ({ data }) => {
      console.log(data);
    });
  };

  // validate

  const formValidate = () => {
    const { title, category } = form;
    return !title || !articleText.text || !category;
  };

  // clear state
  const resetState = () => {
    setState({ ...initialState });
  };

  return (
    // jsx
  )

first issue, when call onSubmitAuto, first useEffect is running again. i dont want this. because I just want it to work on the first mount.

second issue, if the articleText state has changed before, when mutation it does not mutate the data in the form state. but if the form state changes first, it mutated all the data. I think this issue is the same as the first issue.

I hope I could explain the problem. :/


Solution

  • Ciao, I have an answer to the first issue: when onSubmitAuto is triggered, it calls editBlog that changes loading. And loading is on first useEffect deps list. If you don't want that, a fix could be something like that:

    const isInitialMount = useRef(true);
    //first useEffect
    useEffect(() => {
    if(isInitialMount.current) {
    if (!loading && data) {
      setState({
        title: data.blog.title,
        category: data.blog.category,
        admin_id: data.blog.admin.id,
        status: data.blog.status
      });
      setArticleText({
        text: data.blog.text
      });
    }
    console.log(data);
    if (data !== undefined) isInitialMount.current = false;
    }
    }, [loading, data]);