2

I have passed down a state variable and function from a context file:

UserContext:

import React, { useState } from 'react';

const UserContext = React.createContext();

function UserProvider({ children }) {
  var [userImages, setUserImages] = useState({
    avatar: '/static/uploads/profile-avatars/placeholder.jpg'
  });

  return (
    <UserContext.Provider
      value={{
        userImages,
        setUserImages
      }}
    >
      {children}
    </UserContext.Provider>
  );
}

export default UserContext;

export { UserProvider };

At this point UserImages is just an object with one prop i.e. avatar

This is my App which is being wrapped by the Provider (please disregard the redux implementation, I just wanted to try Context)

import React from 'react';
import { Provider } from 'react-redux';
import { UserProvider } from './UserContext';
import App from 'next/app';
import withRedux from 'next-redux-wrapper';
import { PersistGate } from 'redux-persist/integration/react';

import reduxStore from '../store/index';

import withReactRouter from '../with-react-router/with-react-router';


class MyApp extends App {
  static async getInitialProps({ Component, ctx }) {
    const pageProps = Component.getInitialProps
      ? await Component.getInitialProps(ctx)
      : {};
    return { pageProps };
  }
  render() {
    const { Component, pageProps, store } = this.props;

    return (
      <UserProvider>
        <Provider store={store}>
          <PersistGate persistor={store.__PERSISTOR} loading={null}>
            <Component {...pageProps} />
          </PersistGate>
        </Provider>
      </UserProvider>
    );
  }
}

I am trying to update some context using a setState function following this post

However I still get TypeError: Cannot read property 'avatar' of undefined

This is the shape of the state object:

userData:
setUserImages: ƒ ()
userImages:
avatar: "/static/uploads/profile-avatars/placeholder.jpg"

or

userData : {
  setUserImages : SetUserImages function,
  userImages : {
  avatar : "/static/uploads/profile-avatars/placeholder.jpg"
  }
 }

My component:

 function ImageUploader({ userData }) {

  var { avatar } = userData.userImages;
  var setUserAvatar = userData.setUserImages;

  function avatarUpdater(avatarPath) {
    setUserAvatar({ userData: { ...userData.userImages.avatar, avatarPath } });
    }
  }

Does anyone have an idea why this is happening?

Antonio Pavicevac-Ortiz
  • 7,239
  • 17
  • 68
  • 141

2 Answers2

1

UserProvider is the root of your app, so you can directly get it {userImages, setUserImages} in ImageUploader

function ImageUploader() {
  const {userImages, setUserImages} = useContext(UserContext)
  const { avatar } = userImages;

  function avatarUpdater(avatarPath) {
     setUserImages({ avatar: avatarPath });
  }
}
Tony Nguyen
  • 3,298
  • 11
  • 19
0

Typically its good practice to not expose the setState from your context. You wanna wrap it in an explicit method to update state, then add that method to your Provider. Something like:

const userContext = {
  avatar: userImages,
  updateAvatarUrl: (url) => {
    setUserImages(prevState => ({...prevState, avatar: url}))
  }
}

return <UserContext.Provider value={userContext}>{children}</UserContext.Provider>

Try adding a hook for your UserContext which you can consume inside your component.

In UserContext add

export const useUserContext = () => useContext(UserContext)

Then inside your component:

import { useUserContext } from '<UserContext import>'

...

function avatarUpdater(avatarPath) {
    userCtx.updateAvatarUrl(avatarPath)
}

Cleanest structure for Context in my opinion. Allows for more precise control over context state.

jmkmay
  • 1,441
  • 11
  • 21