Kian Soltani
Kian Soltani

Reputation: 97

Through a function passed in context, React state always has the initial value

I'm updating with functions that I pass from one component to its parent and then through context. I can set the state as I want but I can't access its data inside these functions. They show as the initial data. It's odd, I checked in React DevTools and the state is fine.

Here is a codeSandbox : https://codesandbox.io/s/admiring-fog-2b5gm?file=/components/Provider.tsx:859-992 Link to the page : https://2b5gm.sse.codesandbox.io/

import React, { useState, createContext } from "react";
import Provider from "./Provider";
import Consumer from "./Consumer";

export const context = createContext<AppContextType | undefined>(undefined);

type AppContextType = {
  actions: {
    addStrings?: (t: string[]) => void;
    logStrings?: () => void;
  };
};

const TestContainer = () => {
  const [actions, setActions] = useState<{
    addStrings?: (t: string[]) => void;
  }>({});

  return (
    <context.Provider
      value={{
        actions: actions
      }}
    >
      <Provider setActions={setActions} />
      <Consumer />
    </context.Provider>
  );
};

export default TestContainer;

import React, { useEffect, useState } from "react";

type ProviderProps = {
  setActions: React.Dispatch<
    React.SetStateAction<{
      addStrings?: (t: string[]) => void;
      logStrings?: () => void;
    }>
  >;
};

const Provider = ({ setActions }: ProviderProps) => {
  const [textArray, setTextArray] = useState<string[]>([]);

  function addStrings(texts: string[]) {
    console.log("PREVIOUS LINES: ", textArray);
    console.log("NEW LINES:", [...textArray, ...texts]);
    setTextArray([...textArray, ...texts]);
  }

  function logStrings() {
    console.log("Strings : ", textArray);
  }

  useEffect(() => {
    console.log("send functions to parent");
    setActions({ addStrings: addStrings, logStrings: logStrings });
  }, []);

  useEffect(() => {
    console.log("useEffect textArray changed to: ", textArray);
  }, [textArray]);

  return (
    <>
      <h1>Strings : </h1>
      {textArray.map((line, index) => (
        <p key={index}>{line}</p>
      ))}
    </>
  );
};

export default Provider;
import React, { useContext } from "react";
import { context } from "./TestContainer";

const Consumer = () => {
  const appContext = useContext(context);

  function onButtonAddClick() {
    if (appContext?.actions.addStrings)
      appContext.actions.addStrings(["test 1", "test 2", "test 3"]);
  }

  function onButtonLogClick() {
    if (appContext?.actions.logStrings) appContext.actions.logStrings();
  }

  return (
    <div>
      <button onClick={onButtonAddClick}>Add strings</button>
      <button onClick={onButtonLogClick}>Log</button>
    </div>
  );
};

export default Consumer;

The data in question is the textArray state in the Provider component. I'm calling the addStrings and the LogStrings methods from the Consumer component to control it.

Expected behavior: LogStrings should log the actual value of the state. In this case : "test 1, test 2, test 3" but it always print the state's default value.

Upvotes: 2

Views: 887

Answers (1)

juliomalves
juliomalves

Reputation: 50258

The reason you're seeing the initial state getting logged is because setActions only gets called when Provider is first mounted, when textArray still has its initial value of empty array []. That will be the value that logStrings will log, even if you change textArray externally.

To actually update logStrings with the new textArray value you pass in addStrings(["test 1", "test 2", "test 3"]), you'd need to call setActions again. This means adding textArray to the dependencies array of the useEffect that calls setActions.

useEffect(() => {
    console.log("send functions to parent");
    setActions({ addStrings: addStrings, logStrings: logStrings });
}, [textArray]);

Upvotes: 1

Related Questions