skip to Main Content

I have a class in my react project, which I have added to a context exposing its value to all other child components. The problem I am facing is when the class value updates, none of the consumers re-render. How can I go about with this design in a way which will re-render componenets when the object is updated?

class User {
    constructor() {
        this.metadata = new Map<string, string>();
    }

    getFirstName = () => {
        return this.metadata.get("name");
    }
}

const UserContext = React.createContext(null);
const UserUpdateContext = React.createContext(null);

export default UserProvider( {children} ) {
    const [user, setUser] = React.useState(new User());

    return (
        <UserContext value={user}>
            <UserUpdateContext value={setUser}>
                { children }
            </UserUpdateContext>
        </UserContext>
    )
}

When I read in the user elsewhere in the app and update the metadata via user.metadata.set("id", 3)… this does not retrigger a re-render for all consumers. What is the way to go about this?

2

Answers


  1. Issue

    React and OOP don’t mix well at all. The components likely aren’t rerendering because you are mutating the user state instead of creating a new user state reference when you want to update part of it.

    Example, really don’t do this though:

    class User {
      constructor(init = []) {
        this.metadata = new Map<string, string>(init);
      }
    
      getFirstName = () => {
        return this.metadata.get("name");
      }
    }
    
    const UserContext = React.createContext(null);
    const UserUpdateContext = React.createContext(null);
    
    export default UserProvider({ children }) {
      const [user, setUser] = React.useState(new User());
    
      const setUserKeyValue = (key, value) => {
        const nextUser = new User(user); // <-- create new reference, copy internal state
        nextUser.metadata.set("id", 3);  // <-- update property
        setUser(nextUser);               // <-- update state
      };
    
      return (
        <UserContext value={user}>
          <UserUpdateContext value={setUserKeyValue}>
            {children}
          </UserUpdateContext>
        </UserContext>
      );
    }
    

    To effect state updates consumers should access the UserUpdateContext context value and invoke the setUserKeyValue function, passing a key and value they want to set. Consumers should not call directly the member functions of the user provided via the UserContext context.

    const setUserKeyValue = useContext(UserUpdateContext);
    
    ...
    
    setUserKeyValue("id", 3);
    
    ...
    

    Suggested Solution

    The React way would be to just store the user state directly:

    const UserContext = React.createContext(null);
    const UserUpdateContext = React.createContext(null);
    
    export default UserProvider({ children }) {
      const [user, setUser] = React.useState({});
    
      const setUserKeyValue = (key, value) => {
        setUser(user => ({
          ...user,
          [key]: value,
        }));
      };
    
      return (
        <UserContext value={user}>
          <UserUpdateContext value={setUserKeyValue}>
            {children}
          </UserUpdateContext>
        </UserContext>
      );
    }
    
    const setUserKeyValue = useContext(UserUpdateContext);
    
    ...
    
    setUserKeyValue("id", 3);
    
    ...
    
    const user = useContext(UserContext);
    
    ...
    
    user.name;
    
    ...
    
    Login or Signup to reply.
  2. Hope this one help you.

    const updateUserMetadata = (key, value) => {
      const updatedUser = { ...user };
      updatedUser.metadata.set(key, value);
      setUser(updatedUser);
    };
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search