javascriptreactjsreact-nativereact-contextreact-state

setState function not recognized as function


In my React Native app, I'm having a problem where the setState function is not being recognized as a function.

I already inserted the context provider on the App root page and exported the context properties, and it still doesn't work.

I'm trying to call it on a component, and still getting the "setAppCompany is not a function"

App.js

import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import AppProviders from './mobile/src/providers/AppProviders';
import Route from './mobile/src/navigations/Route';
import Layout from './mobile/src/layout/Layout';

export default function App() {
  return (
    <AppProviders>
      <NavigationContainer>
        <Layout>
          <Route />
        </Layout>
      </NavigationContainer>
    </AppProviders>
  );
}

AppProviders.js

import React from 'react';
import CompanyProvider from '../contexts/CompanyContext';
import UserProvider from '../contexts/UserContext';

const AppProviders = ({ children }) => {
  return (
    <CompanyProvider>
      <UserProvider>{children}</UserProvider>
    </CompanyProvider>
  );
};

export default AppProviders;

CompanyContext.js

import React, { createContext, useState, useContext } from 'react';
export const CompanyContext = createContext();

export default function CompanyProvider({ children }) {
  const [appCompanyId, setAppCompany] = useState(null);

  return (
    <CompanyContext.Provider value={(appCompanyId, setAppCompany)}>
      {children}
    </CompanyContext.Provider>
  );
}

export const useCompany = () => {
  const context = useContext(CompanyContext);
  const { appCompanyId, setAppCompany } = context;
  return { appCompanyId, setAppCompany };
}

UserContext.js

import React, { createContext, useState, useContext } from 'react';
export const UserContext = createContext();

export default function UserProvider({ children }) {
  const [logged, setLogged] = useState(false);
  const [userId, setUserId] = useState(null);
  const [token, setToken] = useState('');
  const [admin, setAdmin] = useState(false);

  return (
    <UserContext.Provider
      value={
        (logged, setLogged, userId, setUserId, token, setToken, admin, setAdmin)
      }>
      {children}
    </UserContext.Provider>
  );
}

export function useUser() {
  const context = useContext(UserContext);
  const {
    logged,
    setLogged,
    userId,
    setUserId,
    token,
    setToken,
    admin,
    setAdmin,
  } = context;
  return {
    logged,
    setLogged,
    userId,
    setUserId,
    token,
    setToken,
    admin,
    setAdmin,
  };
}

TopBar.js (Component using context properties)

import React, { useState, useEffect } from 'react';
import { View, StyleSheet, Text } from 'react-native';
import { Picker } from '@react-native-picker/picker';
import { getCompaniesByUserId } from '../services/api/company.services';
import { useUser } from '../contexts/UserContext';
import { useCompany } from '../contexts/CompanyContext';

const TopBar = () => {
  // Hooks ------------------------------------->
  const { userId } = useUser();
  const { setAppCompany } = useCompany();

  const [selectedCompany, setSelectedCompany] = useState(null);
  const [avaliableCompanies, setAvailableCompanies] = useState([
    { idEmpresa: 1, nomeEmpresa: 'Rooftop Bar' },
    { idEmpresa: 2, nomeEmpresa: 'Restaurante Brilhante' },
  ]);

  useEffect(() => {
    if (avaliableCompanies == null)
      setAvailableCompanies(getCompaniesByUserId(userId));

    if (avaliableCompanies != null && selectedCompany == null)
      setSelectedCompany(avaliableCompanies[0]);
  }, [avaliableCompanies, selectedCompany, userId]);

  useEffect(() => {
    if (selectedCompany != null) setAppCompany(selectedCompany);
  }, [selectedCompany, setAppCompany]);

  // JSX ------------------------------------->
  return (
    <View style={styles.topBar}>
      <Text style={styles.title}>RoofStock</Text>
      <View style={styles.selectorContainer}>
        <Text style={styles.label}>Company:</Text>
        <Picker
          selectedValue={selectedCompany}
          onValueChange={setSelectedCompany}
          style={styles.picker}
          dropdownIconColor="#fff">
          {avaliableCompanies.map((company) => (
            <Picker.Item
              label={company.nomeEmpresa}
              value={company.idEmpresa}
            />
          ))}
        </Picker>
      </View>
    </View>
  );
};

I tried to use the setState function in the context created and couldn't. I was expecting to use the setState function to control and manage state based on this data around the app.


Solution

  • Issue

    The problem here is that you have malformed the context value:

    value={(appCompanyId, setAppCompany)}
    

    The code, written this way, uses the Comma operator:

    The comma (,) operator evaluates each of its operands (from left to right) and returns the value of the last operand. This is commonly used to provide multiple updaters to a for loop's afterthought.

    setAppCompany itself is returned as the entire context value, and the consumers attempt to destructure property values from it what are undefined and not callable as functions.

    Solution Suggestion

    You should pass either an object or an array of values.

    value={{ appCompanyId, setAppCompany }}
    
    const { appCompanyId, setAppCompany } = useContext(CompanyContext);
    

    or

    value={[appCompanyId, setAppCompany]}
    
    const [appCompanyId, setAppCompany] = useContext(CompanyContext);