React exporting useContext causes errors - reactjs

Context.js
const GlobalContext = React.createContext();
const initState = {count:0};
const GlobalContextProvider = props => {
const [state, setState] = useState(initState);
return (
<GlobalContext.Provider value={{state:state, setState:setState}}>
{props.children}
</GlobalContext.Provider>
)
};
const GlobalContextValue = useContext(GlobalContext)
export {GlobalContextValue, GlobalContextProvider}
When I exported the GlobalContextValue, Chrome or React throws an error saying this is an invalid hook call, but I want to be able use setState in a module that's showing below.
fetchAPI.js
import { GlobalContextValue } from './GlobalContext';
const {state, setState} = GlobalContextValue;
function load() {
fetch('localhost:8000/load')
.then(res => res.json())
.then(json => setState(json));
};

You can't use hooks outside of React functional components.
You can probably do this another way though.
Disclaimer: I didn't test this code, but it should do what you want, although I don't recommend doing this at all.
const GlobalContext = React.createContext();
const globalState = { count: 0 }
let subscribers = []
export function setGlobalState(value) {
Object.assign(globalState, value)
subscribers.forEach(f => f(globalState))
}
export function subscribe(handler) {
subscribers.push(handler)
return () => {
subscribers = subscribers.filter(s => s !== handler)
}
}
const GlobalContextProvider = props => {
const [state, setState] = useState(globalState)
useEffect(() => subscribe(setState), [])
return (
<GlobalContext.Provider value={{ state: state, setState: setGlobalState }}>
{props.children}
</GlobalContext.Provider>
);
};

Related

React: setItems Error when using localStorage

When I am using the setItem() Method, i get no errors and everything is working fine. But when I try to retrieve objects via the getItem() Method, im getting the error: "src\components\UserCourses.js Line 33:8: 'setItems' is not defined no-undef"
//LocalStorage
useEffect(() => {
localStorage.setItem('basket', JSON.stringify(basket));
}, [basket]);
useEffect(() => {
const basket = JSON.parse(localStorage.getItem('basket'));
if (basket) {
setItems(basket);
}
}, []);
basket is my empty array at the start, where i put in items.
import { useStateValue } from './StateProvider'
import {useState, useEffect} from 'react'
function UserCourses ({id, name, prof, language}) {
const [{basket}, dispatch] = useStateValue();
const navigate = useNavigate()
const goCourseDetail = () => navigate(`/course/id=${id}`)
const removeFromBasket = () => {
dispatch({
type: 'REMOVE_FROM_BASKET',
id: id,
});
}
useEffect(() => {
localStorage.setItem('basket', JSON.stringify(basket));
}, [basket]);
useEffect(() => {
const basket = JSON.parse(localStorage.getItem('basket'));
if (basket) {
setItems(basket);
}
}, []);
//Provider
import React, {createContext, useContext, useReducer} from 'react';
//data layer
export const StateContext = createContext();
//Provider
export const StateProvider = ({reducer, initialState, children}) => (
<StateContext.Provider value={useReducer(reducer, initialState)}>
{children}
</StateContext.Provider>
);
//use inside of a component
export const useStateValue = () => useContext(StateContext);
I had a problem with localStorage in a shopping cart I was developing, if it helps. I was able to solve my problem, with the next code:
const initialState = []
const [carrito, setCarrito] = useState(initialState)
useEffect(() => {
const carritoLS = JSON.parse(localStorage.getItem('carrito'))
if (carritoLS) {
setCarrito(carritoLS)
}
}, [])
useEffect(() => {
if (carrito !== initialState){
localStorage.setItem('carrito', JSON.stringify(carrito))
}
}, [carrito])

Need to call an alert message component from action in react

I've created a common component and exported it, i need to call that component in action based on the result from API. If the api success that alert message component will call with a message as "updated successfully". error then show with an error message.
calling service method in action. is there any way we can do like this? is it possible to call a component in action
You have many options.
1. Redux
If you are a fan of Redux, or your project already use Redux, you might want to do it like this.
First declare the slice, provider and hook
const CommonAlertSlice = createSlice({
name: 'CommonAlert',
initialState : {
error: undefined
},
reducers: {
setError(state, action: PayloadAction<string>) {
state.error = action.payload;
},
clearError(state) {
state.error = undefined;
},
}
});
export const CommonAlertProvider: React.FC = ({children}) => {
const error = useSelector(state => state['CommonAlert'].error);
const dispatch = useDispatch();
return <>
<MyAlert
visible={error !== undefined}
body={error} onDismiss={() =>
dispatch(CommonAlertSlice.actions.clearError())} />
{children}
</>
}
export const useCommonAlert = () => {
const dispatch = useDispatch();
return {
setError: (error: string) => dispatch(CommonAlertSlice.actions.setError(error)),
}
}
And then use it like this.
const App: React.FC = () => {
return <CommonAlertProvider>
<YourComponent />
</CommonAlertProvider>
}
const YourComponent: React.FC = () => {
const { setError } = useCommonAlert();
useEffect(() => {
callYourApi()
.then(...)
.catch(err => {
setError(err.message);
});
});
return <> ... </>
}
2. React Context
If you like the built-in React Context, you can make it more simpler like this.
const CommonAlertContext = createContext({
setError: (error: string) => {}
});
export const CommonAlertProvider: React.FC = ({children}) => {
const [error, setError] = useState<string>();
return <CommonAlertContext.Provider value={{
setError
}}>
<MyAlert
visible={error !== undefined}
body={error} onDismiss={() => setError(undefined)} />
{children}
</CommonAlertContext.Provider>
}
export const useCommonAlert = () => useContext(CommonAlertContext);
And then use it the exact same way as in the Redux example.
3. A Hook Providing a Render Method
This option is the simplest.
export const useAlert = () => {
const [error, setError] = useState<string>();
return {
setError,
renderAlert: () => {
return <MyAlert
visible={error !== undefined}
body={error} onDismiss={() => setError(undefined)} />
}
}
}
Use it.
const YourComponent: React.FC = () => {
const { setError, renderAlert } = useAlert();
useEffect(() => {
callYourApi()
.then(...)
.catch(err => {
setError(err.message);
});
});
return <>
{renderAlert()}
...
</>
}
I saw the similar solution in Antd library, it was implemented like that
codesandbox link
App.js
import "./styles.css";
import alert from "./alert";
export default function App() {
const handleClick = () => {
alert();
};
return (
<div className="App">
<button onClick={handleClick}>Show alert</button>
</div>
);
}
alert function
import ReactDOM from "react-dom";
import { rootElement } from ".";
import Modal from "./Modal";
export default function alert() {
const modalEl = document.createElement("div");
rootElement.appendChild(modalEl);
function destroy() {
rootElement.removeChild(modalEl);
}
function render() {
ReactDOM.render(<Modal destroy={destroy} />, modalEl);
}
render();
}
Your modal component
import { useEffect } from "react";
export default function Modal({ destroy }) {
useEffect(() => {
return () => {
destroy();
};
}, [destroy]);
return (
<div>
Your alert <button onClick={destroy}>Close</button>
</div>
);
}
You can't call a Component in action, but you can use state for call a Component in render, using conditional rendering or state of Alert Component such as isShow.

React Context Value always default and not updated

I have created one wrapper component where I put my react context.
Inside that wrapper component I have used useEffect() hook where I fetch values from api and try to update context default values.
In my child component I tried to fetch context values but only default value of that context is fetched. So it seems that useEffect hook didnt updated my context object.
Here is wrapper component:
export const CorporateWrapper = ({ apiBaseUrl, children }) => {
const [corporateContextDefaults, setCorporateContextDefaults] = useState({});
useEffect(() => {
(async () => {
try {
const json = await fetchCorporateUserDetails(apiBaseUrl, getClientSideJwtTokenCookie());
if (json.success !== true) {
console.log(json.message);
return {
notFound: true,
};
}
console.log(json.data);
setCorporateContextDefaults({corporateId: json.data.corporate_id, corporateRole: json.data.corporate_role, corporateAdmin: json.data.corporate_role == 'Admin', corporateSuperAdmin: json.data.corporate_super_admin});
} catch (e) {
console.log(e.message);
}
})();
}, []);
return (
<CorporateProvider value={corporateContextDefaults}>
{children}
</CorporateProvider>
);
};
Here is CorporateProvider component:
import React, { useState, useContext } from "react";
const CorporateContext = React.createContext({corporateId: null, corporateRole: null,
corporateAdmin: null, corporateSuperAdmin: null});
const UpdateCorporateContext = React.createContext({});
export const useCorporateContext = () => {
return useContext(CorporateContext);
};
export const useUpdateCorporateContext = () => {
return useContext(UpdateCorporateContext);
};
export const CorporateProvider = ({ value, children }) => {
const [details, setDetails] = useState(value);
return (
<CorporateContext.Provider value={details}>
<UpdateCorporateContext.Provider value={setDetails}>
{children}
</UpdateCorporateContext.Provider>
</CorporateContext.Provider>
);
};
export default CorporateProvider;
Here is how I try to fetch context value from child component which is wrapped under wrapper component:
const { corporateId } = useCorporateContext();

converting class component to hooks : useDispatch/useSelector : where to call it and whats is wrong here?

I am trying to convert the class component I created to a functional one. Class component is working fine but when I am trying to do same thing using functional way I am not able to get the calls properly. I am trying to load data on ui from the REST call using Axios. useDispatch/useSelector. where to call it and whats is wrong here? I understand that have to use useEffect instead of componentdidmount but I think it's not getting called the way I am trying. Please advice...
Old Class Component code:
class MyClassComponent extends Component {
componentDidMount() {
const { changeRequests, listRequests } = this.props;
if (!changeRequests.fulfilled) {
listRequests();
}
}
render() {
const { changeRequests } = this.props;
if (!changeRequests.fulfilled) {
return (
<CircularProgress />
)
}
return(
// code
)
}
}
//useSelector replace mapStateToProps
const mapStateToProps = state => {// onRequest is reducer class
return {
changeRequests: state.onRequest.changeRequests
}
};
//useDispatch replaces
const mapDispatchToProps = dispatch => {// connects with Action class and then with axios DB call
return {
listRequests: () => dispatch(fetchRequests())
}
};
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(MyClassComponent));
New code I am trying looks like :
export default function MyFunctionalComponent() {
const [state, setState] = React.useState({});
useEffect(() => {
const { changeRequests, listRequests } = props;
if (!changeRequests.fulfilled) {/
listRequests();
}
},[]);
const changeRequests = useSelector(state => state.onRequest.changeRequests);
const listRequests = useDispatch(() => {
fetchPortfolioRequests();
},[]);
return(<h2>{changeRequests.data}</h2>);
I think you are using useDispatch incorrectly.
const listRequests = useDispatch(() => {
fetchPortfolioRequests();
},[]);
should be
const dispatch = useDispatch();
const listRequests = useCallback(() => dispatch(fetchPortfolioRequests()),[dispatch]);
Also in the class based component, you were using fetchRequests() but here you are trying to use fetchPortfolioRequests().
update
Your functional component should look like this:
export default function MyFunctionalComponent() {
const changeRequests = useSelector(state => state.onRequest.changeRequests);
const dispatch = useDispatch();
const listRequests = useCallback(() => dispatch(fetchPortfolioRequests()), [dispatch]);
useEffect(() => {
if (!changeRequests.fulfilled) {
listRequests();
}
}, [listRequests, changeRequests]);
return changeRequests.fulfilled ? <h2>{changeRequests.data}</h2> : <CircularProgress />;
}
update
if you once want to dispatch when the component renders then you can just use
useEffect(() => {
listRequests();
}, [listRequests]);
I was getting warning "React Hook useEffect has a missing dependency". made following changes in the above code
```
const listRequests = useCallback(() => dispatch(fetchRequests()), [dispatch]);
useEffect(() => {
if (!changeRequests.fulfilled) {
listRequests();
}
}, [listRequests, changeRequests]);
------
//removed callback from here as we can t use callback or reducer inside useEffect().
const changeRequests = useSelector(state => state.oncallRequest.changeRequests);
useEffect(() => {
if (!changeRequests.fulfilled) {
dispatch(fetchRequests(), [dispatch])// direct called method here for my action
}
}, [changeRequests.fulfilled, dispatch]);
// changeRequests.fulfilled is set to true in reducer.
Action class looks like:
fetchRequests = () => dispatch => {
dispatch({type: LIST_PENDING});
listChangeRequests().then(data => {
dispatch({type: LIST_FULFILLED, changeRequests: data})
}).catch(err => {
dispatch({type: LIST_FAILED, changeRequests: {error: ""}})
});
};

How to combine custom hook for data fetching and context?

I have a custom hook to fetch data on form submit
export const getIssues = ({ user, repo }) => {
const [issues, setIssues] = useState([]);
const handleInputChange = (e) => {
e.preventDefault();
axios.get(`https://api.github.com/repos/${user}/${repo}/issues`)
.then((response) => {
setIssues(response.data);
})
.catch((err) => console.log(err));
};
return {
issues,
onSubmit: handleInputChange,
};
};
In my component I call it like this
const response = getIssues({ user: user.value, repo: repo.value })
return (
<form className={css['search-form']} {...response}>...</form>
)
The problem is that I want to get my issues value from the hook in another component. For that I wanted to use Context. But I have no idea how to do it.
I could call this function and pass it to Provider, but I can't call it without arguments. So I kind of stuck.
All the help will be much appreciated.
You are right by saying you need React.Context to handle this situation.
You need to wrap your components into this context.
import React from "react";
const IssuesStateContext = React.createContext();
const IssuesDispatchContext = React.createContext();
function issuesReducer(state, action) {
switch (action.type) {
case "setIssues": {
return [...action.payload];
}
default: {
throw new Error(`Unhandled action type: ${action.type}`);
}
}
}
function IssuesProvider({ children }) {
const [state, dispatch] = React.useReducer(issuesReducer, []);
return (
<IssuesStateContext.Provider value={state}>
<IssuesDispatchContext.Provider value={dispatch}>
{children}
</IssuesDispatchContext.Provider>
</IssuesStateContext.Provider>
);
}
function useIssuesState() {
const context = React.useContext(IssuesStateContext);
if (context === undefined) {
throw new Error("useIssuesState must be used within a IssuesProvider");
}
return context;
}
function useIssuesDispatch() {
const context = React.useContext(IssuesDispatchContext);
if (context === undefined) {
throw new Error("useIssuesDispatch must be used within a IssuesProvider");
}
return context;
}
export { IssuesProvider, useIssuesState, useIssuesDispatch };
By using this separation in context you will be able to set issues coming from github in one component and render them in a completely different one.
Example:
App.js
ReactDOM.render(
<IssuesProvider>
<Component1 />
<Component2 />
</IssuesProvider>
)
Component 1
import React from 'react'
import { useIssuesDispatch } from './issues-context'
function Component1() {
const dispatch = useIssuesDispatch()
// fetch issues
// .then dispatch({ type: 'setIssues', payload: response })
// render
}
Component 2
import React from 'react'
import { useIssuesState } from './issues-context'
function Component2() {
const issues = useIssuesState()
// if issues.length > 0 ? render : null
}
You can write a Issues context provider that will provide {issues,useIssues} where issues are the issues and useIssues is a function that takes {user,repo}.
export const Issues = React.createContext();
export default ({ children }) => {
const [issues, setIssues] = useState([]);
const useIssues = ({ user, repo }) => {
useEffect(() => {
axios
.get(
`https://api.github.com/repos/${user}/${repo}/issues`
)
.then(response => {
setIssues(response.data);
})
.catch(err => console.log(err));
}, [user, repo]);
return issues;
};
return (
<Issues.Provider value={{ issues, useIssues }}>
{children}
</Issues.Provider>
);
};
The component that has all the components that need issues can import this issues provider:
import IssuesProvider from './IssuesProvider';
export default () => (
<IssuesProvider>
<ComponentThatNeedsIssues />
<ComponentThatSetsAndGetsIssues />
</IssuesProvider>
);
For a component that needs to set issues you can get useIssues from context:
const { useIssues } = useContext(Issues);
const issues = useIssues({user,repo});
For a component that only needs issues:
const { issues } = useContext(Issues);
To see it all work together there is a codepen here

Resources