Passing useState Hook into another function on React - reactjs

I want to pass in a boolean value in a useState hook that opens and closes a modal on click between two functions. However, I keep getting this error message: Cannot destructure property 'setOpenModal' of 'props' as it is undefined.
Main.js
import React, { useState, useEffect } from "react";
import * as Materials from "../components/Materials"; // <- Material.js
const Main = () => {
const [openModal, setOpenModal] = useState(false); //<- Opens (True) and Closes (False) Modal
const { MaterialContainer } = Materials.MaterialTable(); // <-Calling Function Under MaterialTable
return (
<MaterialContainer
openModal={openModal}
setOpenModal={setOpenModal}
/>
// This is how I am passing in Open/Close useState.
}
Material.js
export const MaterialTable = (props) => {
const { openModal, setOpenModal } = props; // <- Pointed in Error Message.
const openMaterialModal = (item) => {
console.log("Button Clicked");
setOpenModal(true); // <- Where I am passing in a true statement.
};
const MaterialContainer = () => (
<>
<Table>Stuff</Table>
</>
);
return {
MaterialContainer
}
}
Thanks in advance.

The MaterialTable component is entirely malformed from a React perspective, though valid JavaScript. It's just a normal function that defines a couple of constants and then returns nothing. (Well, in the original question it returned nothing. Now it returns an object.)
And when you call that function you indeed don't pass anything to it:
const { MaterialContainer } = Materials.MaterialTable();
So the props will be undefined.
Make MaterialTable itself a React component:
export const MaterialTable = (props) => {
// destructure the props passed to the component
const { openModal, setOpenModal } = props;
// a function I assume you plan to use in the JSX below later?
const openMaterialModal = (item) => {
console.log("Button Clicked");
setOpenModal(true);
};
// the rendering of the component
return (
<>
<Table>Stuff</Table>
</>
);
}
Then just import and use that component, without trying to destructure anything from it or invoke it manually:
import React, { useState, useEffect } from "react";
// import the component
import { MaterialTable } from "../components/Materials";
const Main = () => {
// use the state hook
const [openModal, setOpenModal] = useState(false);
// render the component, passing props
return (
<MaterialTable
openModal={openModal}
setOpenModal={setOpenModal}
/>
);
}

Related

How do I trigger React UseEffect on only the first render?

In am using React and trying to trigger a function only once, when the page initially loads. Currently, the below code triggers the console message twice at page load.
import { useState, useEffect, useRef } from "react";
export default function TestRef(){
const [inputValue, setInputValue] = useState("");
const count = useRef(null);
const myFunc = () => {
console.log('Function Triggered');
}
useEffect(() => {
if(!count.current){
count.current = 1;
myFunc();
}
return () => { count.current = null; }
}, []);
return (
<>
<p>Page content</p>
</>
);
}
I have read up on how React 18 intentionally double-renders elements, which is my reason for using useRef and for returning the cleanup function in the useEffect hook, but it still doesn't seem to work.
hi please make sure you didn't invoke TestRef componenet twice in your page!
for debug and find rerenders you can use react profiler extention on chrome then remove extra rerender by using momo and useMemo and useCallback
Finally got it to work by doing this. This appears to only run myFunc() once, on the initial rendering of the component.
import { useState, useEffect, useRef } from "react";
export default function TestRef(){
const [inputValue, setInputValue] = useState("");
const count = useRef(null);
const myFunc = () => {
console.log('Function Triggered');
}
useEffect(() => {
if(count.current == null){
myFunc();
}
return () => { count.current = 1; }
}, []);
return (
<>
<p>Page content</p>
</>
);
}

React hook "useMemo" with array as dependency

I am new to react (that I use with typeScript) and I am facing an issue with the use of the useMemo hook.
Here is my fetching service:
export default class FetchingService {
datas: Data[] = [];
constructor() {
this.fetch();
}
async fetch(): Promise<Data[]> {
const d = // await an async array from an api, using Array.flat()
this.datas = d;
console.log(this.datas);
return d;
}
}
In a component, I try to watch for change of the datas attribute of my service:
import fetchingService from '../services/fetchingService.ts';
const Home: React.FC = () => {
const ds: Data[];
const [datas, setDatas] = useState(ds);
const fetchDatas = useMemo(() => {
console.log('Render datas', fetchingService.datas?.length)
setDatas(fetchingService.datas);
return fetchingService.datas;
}, [fetchingService.datas]);
return (
<ul>{datas.map(d => {
return (
<li key={d.id}>{d.id}</li>
);
</ul>
);
}
The problem I am facing is that the useMemo hook is not recompouted when the datas attribute changes within my fetchService. I am pretty sure that my FetchingService.fetch() function works because the console.log within the fetch function always display the fetched datas.
The observed behavior is that sometimes datas are well rendered (when fetch ends before rendering ...), but sometimes it isn't.
The expected one is that datas are rendered every time and only on refresh, exept when datas are modified
I also tried to put the length of the data array as a dependency in useMemo, but in both cases it doesn't work and I have a warning in my IDE, telling me it is an unnecessary dependency.
I don't really understand if it is a typescript or a specific react behavior issue. I think the reference of the datas attribute should change at the end of the fetch (or at least its length attribute ...), but tell me if I am wrong.
I do appreciate every help !
in fetchingService, when datas change, probably the dependency cannot be accepted. You can use a custom hook in stead of it.
You can use this source about useMemo: useMemo with an array dependency?
import { useState, useLayoutEffect, useCallback } from "react";
export const useFetchingService = () => {
const [fetchedData, setFetchedData] = useState([]);
const fetch = useCallback(async () => {
const d = await new Promise((res, rej) => {
setTimeout(() => {
res([1, 2, 3]);
}, 5000);
}); // await an async array from an api, using Array.flat()
setFetchedData(d);
}, []);
useLayoutEffect(() => {
fetch();
}, []);
return [fetchedData];
};
useLayoutEffect runs before rendering
using:
const [fetchData] = useFetchingService();
const fetchDatas = useMemo(async () => {
console.log("Render datas", fetchData.length);
setDatas(fetchData);
return fetchData;
}, [fetchData]);
You can also use this directly without 'datas' state.
I hope that this will be solution for you.
So I put together a codesandbox project that uses a context to store the value:
App.tsx
import React, { useState, useEffect, createContext } from "react";
import Home from "./Home";
export const DataContext = createContext({});
export default function App(props) {
const [data, setData] = useState([]);
useEffect(() => {
const get = async () => {
const d = await fetch("https://dummyjson.com/products");
const json = await d.json();
const products = json.products;
console.log(data.slice(0, 3));
setData(products);
return products;
};
get();
}, []);
return (
<div>
Some stuff here
<DataContext.Provider value={{ data, setData }}>
<Home />
</DataContext.Provider>
</div>
);
}
Home.tsx
import React, { FC, useMemo, useState, useEffect, useContext } from "react";
import { DataContext } from "./App";
import { Data, ContextDataType } from "./types";
const Home: FC = () => {
const { data, setData }: ContextDataType = useContext(DataContext);
return (
<>
<ul>
{data.map((d) => {
return (
<li key={d.id}>
{d.title}
<img
src={d.images[0]}
width="100"
height="100"
alt={d.description}
/>
</li>
);
})}
</ul>
</>
);
};
export default Home;
This was my first time using both codesandbox and typescript so I apologize for any mistakes

React Hook access in an onclick function

I need to refresh a component using a button and a HTTP fetch request, so I've a hook that calls the function but I need to fire it from an onClick handler in a button:
const {useEffect, useState, useRef} = React;
const useRefreshArtifacts = (setArtifactsStore) => {
const refresh = () => {
console.log("do some stuff with state");
const [match, setMatch] = useState({});
};
return {refresh};
};
function ArtifactApp(props) {
const {refresh} = useRefreshArtifacts("");
return (<div><button onClick={refresh}>Hello</button></div>);
}
const AppContainer = () => {
return (<div><ArtifactApp /></div>);
}
ReactDOM.render(<AppContainer />, document.getElementById("root"));
https://jsfiddle.net/jre2cwbf/
Which gives the usual Invalid hook call. Hooks can only be called inside of the body of a function component but I can't figure out how to externalise the hook call, I've tried a useCallback but not got any close.
Any help appreciated.
Modify your code in this way:
const {useEffect, useState, useRef} = React;
const useRefreshArtifacts = (setArtifactsStore) => {
const [match, setMatch] = useState("Hello");
const refresh = () => {
console.log("do some stuff with state");
setMatch("refresh")
};
return {match, refresh};
};
function ArtifactApp(props) {
const {match, refresh} = useRefreshArtifacts("");
return (<div><button onClick={refresh}>{match}</button></div>);
}
const AppContainer = () => {
return (<div><ArtifactApp /></div>);
}
ReactDOM.render(<AppContainer />, document.getElementById("root"));
As you can see I have:
move useState outside refresh function;
set match on refresh function;
returned match from custom hook (useRefreshArtifacts) to use it on component.
Here your fiddle modified.

React function components naming issue

When my component name is WithErrorHandler I get the following error:
React Hook "useState" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function.
But when I change it to withErrorHandler it works fine. (first letter is lower cased)
Can someone please explain what am I doing wrong here?
import React, { useState, useEffect } from 'react';
import Modal from '../../components/UI/Modal/Modal';
import WrapperComponent from '../WrapperComponent/WrapperComponent';
const WithErrorHandler = (WrappedComponent, axios) => {
return props => {
const [error, setError] = useState(null);
const reqInterceptor = axios.interceptors.request.use(req => {
setError(null);
return req;
});
const resInterceptor = axios.interceptors.response.use(res => res, error => {
setError(error);
});
useEffect(() => {
return () => {
axios.interceptors.request.eject(reqInterceptor);
axios.interceptors.response.eject(resInterceptor);
};
}, [reqInterceptor, resInterceptor]);
const closeModalHandler = () => setError(null);
return (
<WrapperComponent>
<Modal show={error} hide={closeModalHandler}>
{error ? error.message : null}
</Modal>
<WrappedComponent {...props} />
</WrapperComponent>
)
}
}
export default WithErrorHandler;
It looks like it's tripping the safeguard about hooks even though it's still valid code.
Just name it withErrorHandler as it's not a component, it's a function returning a component, known as an Higher-Order Component (HOC).
You could also give a name to the returned component.
// Use camelCase for the HOC function.
const withErrorHandler = (WrappedComponent, axios) => {
// Use PascalCase for the name of the component itself (optional but encouraged).
return function WithErrorHandler(props) => {
// This hook is at the right place already!
const [error, setError] = useState(null);
// ...
return /*...*/
}
}
export default withErrorHandler;

REACT - using localstorage for updating a component

How to update a component by changing localstorage from another component?
for example with react hooks I want to call a function by changing localstorage value, but doesn't work:
React.useEffect(() => {
//call a function by changing id value in localstorage
}, [localStorage.getItem("id")])
You need to use ContextProvider to share same hooks and data between different components.
import React, { useContext, useEffect, useState } from 'react';
import PropTypes from 'prop-types';
const MyContext = React.createContext();
const useMyHookEffect = (initId) => {
const [id, setId] = useState(initId);
const saveId = (id) => {
window.localStorage.setItem('id', id);
setId(id);
};
useEffect(() => {
//Now you can get the id from the localStorage
const myId = window.localStorage.getItem('id');
setId(myId);
}, []);
return { id, saveId };
};
// Provider component that wraps app and makes themeMode object
export function MyHookProvider({ children, id }) {
const myEffect = useMyHookEffect(id);
return (
<MyContext.Provider value={myEffect}>
{children}
</MyContext.Provider>
);
}
MyHookProvider.defaultProps = {
children: null,
id: null,
};
MyHookProvider.propTypes = {
children: PropTypes.node,
id: PropTypes.string,
};
export const useMyHook = () => useContext(MyContext);
And you need to call it as provider outside of your components.
<MyHookProvider>
<ComponentA />
<ComponentB />
</MyHookProvider>
Now you can use shared hook between your components.
export function ComponentA(){
const { id, saveId } = useMyHook(null);
return (<div>{id}<button onClick={() => saveId(2)}></button></div>);
}
You can use window.addEventListener('storage ...
React.useEffect(() => {
function example() {
//call a function by changing id value in localstorage
}
window.addEventListener('storage', example)
return () => window.removeEventListener('storage', example)
} , [ ])
inside example you might check that id is the localStorage piece make the function run
As you want to re-render an element you should use states. By using states every element using this variable will automatically update. You can use the hook useState().
import React, { useState, useEffect } from 'react';
const [ id, setId ] = useState(initalValue);
useEffect(() => {
setId(localStorage.getItem('id'));
}, [localStorage.getItem('id')]);
return(
'Your code and the element that should update'
);

Resources