Passed function as prop and is undefined - reactjs

I'm trying to passed function toggle and is undefined. How to fix that ?
function page() {
const [open, setOpen] = React.useState(false);
const handleToggle = () => {
setOpen(!open);
};
return (
<div>
<Sidebar state={open} callback={handleToggle} />
</div>
);
}
export default page;
//Passing to Sidebar component //
const Sidebar = ({callback,state}:any) => {
}

I would consider just passing props on the header of Sidebar component (something like):
const Sidebar = (props) => {...}
Then you can access props.callback and props.state inside your code.
Another piece of advice I could give you is to not use the state itself to change on the handler, but use the previous snapshot, for example:
const handleToggle = () => {
setOpen(prevIsOpen => !prevIsOpen);
};
This way you could avoid some random bugs. (See https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous)
Don't know if this was of any help but hope it was (not a lot of experience in React).

Related

Returning a function vs setting a const equal to a function in React

I'm relatively new to React and was wondering why I'm getting a Functions are not valid as a React child. This may happen if you return a Component instead of from render error.
In my code there is an existing:
const SomeConst = SomeFunctionThatReturnsAComponent();
Now I am trying to update that const to have some logic before calling the function like so:
const SomeConst = (props) => {
//Some logic
return (SomeFunctionThatReturnsAComponent()());
}
Here is SomeFunctionThatReturnsAComponent():
const Template = component => (props) => {
const ReturnThisComponent = props => <Component component={component}, {props} />
return ReturnThisComponent;
}
The usage of SomeConst remains the same as the original implementation:
const SomeComponent = (props) => {
return (<SomeConst prop1="foo"/>)
}
I am wondering why this is not functionally the same thing and why I am getting this error. I have not been able to find a post that gets this error with the way I have it implemented. They are different implementations that arrive at this same error so they were not that much of a help to me.
If there is another post, I would kindly ask to link me to it and I will quickly remove this post and link it to the other post for future people.
Please let me know if I can clarify anything, I have tried to create this post as a repex.
tl;dr - you are missing one function call.
SomeFunctionThatReturnsAComponent()();
what does each parentheses do?
The first parens are calling the first function, that returns a function that returns ReturnThisComponent. If you call this function (Template) only once, you will still return a function (props) => { ... };
const Template = component => (props) => {
const ReturnThisComponent = props => <Component component={component}, {props} />
return ReturnThisComponent;
}
So either - call the function twice, or - remove the second curried function (props) => (and I think this is what you wanted to achieve). So it will look like:
const Template = (component) => {
const Component = component;
const ReturnThisComponent = (props) => <Component {...props} />;
return ReturnThisComponent;
}

React, Too many rerenders

Given the following code:
export default function CrearContacto({setOpening}) {
const [open, setOpen] = useState(false);
setOpen(setOpening);
useEffect(() => {
console.log(setOpening);
setOpen(setOpening);
}, [setOpening]);
const handleClose = () =>{setOpening=false};
return (
<div>
<Dialog open={open} onClose={handleClose}>
//code continues
I'm getting a Too many re-renders error, however I'm unable so see where is the loop here. {setOpening} is being used in a button in another component, when clicked, true is sent. setOpen sets new open state as true, so Dialog can pop-up. At the same time useEffect is watching setOpening for changes (probably this is also wrong, I'm trying to make this work without success). If closed, handleClose is triggered and now setOpening equals false, so useEffect detects the change and executes setOpen to false. I don't see the loop, but also the code isn't working as intended.
Edit - Added parent relative code
function Agenda() {
const MiContexto = useOutletContext();
return (<CrearContacto setOpening={MiContexto.modalState}/>
//code continues
MainLayout class:
import Navbar from "./Navbar"
import Topbar from "./Topbar"
import React, {useReducer} from "react";
import { Outlet, useOutletContext } from "react-router-dom";
export const MainContexto = React.createContext({modalState:false});
const initialState=false;
const estadosPosiblesModal = (state, action) =>{
switch(action){
case false:
return false;
case true:
return true;
default:
return state;
}
}
const MainLayout = ()=> {
const [EstadoModal, dispatch] = useReducer(estadosPosiblesModal,initialState);
return (
<MainContexto.Provider value={{modalState:EstadoModal,modalDispatch:dispatch}}>
<div className="mainlayout">
<Navbar/>
<main className="mainlayout_topPlusContent">
<Topbar className="TopBar" prueba={EstadoModal}/>
<Outlet context={{modalState:EstadoModal,modalDispatch:dispatch}}/>
</main>
</div>
</MainContexto.Provider>
);
};
export default MainLayout;
I had to use simultaneously a custom context and the Router outlet context as my context wasn't going through the outlet.
Topbar contains:
const micontext = useContext(MainContexto);
...
<Button variant="contained" onClick={()=>micontext.modalDispatch(true)}>
Your problem is you call setOpen(setOpening) on the component level CrearContacto which is causing the infinite re-renderings on that component. You can imagine this cycle: state update (setOpen(setOpening)) > re-rendering > call state update again > ...
And another problem is you cannot set setOpening=false directly, it needs to be set with a state setter from the parent component.
export default function CrearContacto({setOpening}) {
const [open, setOpen] = useState(setOpening);
//setOpen(setOpening); //remove this part
useEffect(() => {
setOpen(setOpening)
},[setOpening])
const handleClose = () =>{setOpen(false)}; //set `open` state internally instead
return (
<div>
<Dialog open={open} onClose={handleClose}>
</div>)
}
If you want to update the parent component's state, you should pass the state setter and state value to your child component which is CrearContacto too
I'd assume that you have this state on the parent component
const [opening, setOpening] = useState(false);
Note that setOpening is following the naming convention of the state setter, so I'd prefer to do it this way instead
You can try to modify your component like below
export default function CrearContacto({setOpening, opening}) {
const [open, setOpen] = useState(opening);
//setOpen(setOpening); //remove this part
useEffect(() => {
setOpen(opening)
},[opening])
const handleClose = () =>{setOpening(false)};
return (
<div>
<Dialog open={open} onClose={handleClose}>
</div>)
}
If you only use open state for Dialog, you can remove that state declaration too
export default function CrearContacto({setOpening, opening}) {
const handleClose = () =>{setOpening(false)};
return (
<div>
<Dialog open={opening} onClose={handleClose}>
</div>)
}
To expand my comment:
From inside (or a child) component, you can change the open value by using the state, so call the setOpen function.
From a parent component you can change the value of open by changing the prop setOpening (rename this prop as it sounds like it's a function), so you would have:
export default function CrearContacto({setOpening}) {
const [open, setOpen] = useState(setOpening);
useEffect(() => {
console.log(setOpening);
setOpen(setOpening);
}, [setOpening]);
const handleClose = () =>{setOpen(false)};
return (
<div>
<Dialog open={open} onClose={handleClose}>

Set React Context inside function-only component

My goal is very simple. I am just looking to set my react context from within a reusable function-only (stateless?) react component.
When this reusable function gets called it will set the context (state inside) to values i provide. The problem is of course you can't import react inside a function-only component and hence I cannot set the context throughout my app.
There's nothing really to show its a simple problem.
But just in case:
<button onCLick={() => PlaySong()}></button>
export function PlaySong() {
const {currentSong, setCurrentSong} = useContext(StoreContext) //cannot call useContext in this component
}
If i use a regular react component, i cannot call this function onClick:
export default function PlaySong() {
const {currentSong, setCurrentSong} = useContext(StoreContext) //fine
}
But:
<button onCLick={() => <PlaySong />}></button> //not an executable function
One solution: I know i can easily solve this problem by simply creating a Playbtn component and place that in every song so it plays the song. The problem with this approach is that i am using a react-player library so i cannot place a Playbtn component in there...
You're so close! You just need to define the callback inside the function component.
export const PlaySongButton = ({...props}) => {
const {setCurrentSong} = useContext(StoreContext);
const playSong = () => {
setCurrentSong("some song");
}
return (
<button
{...props}
onClick={() => playSong()}
/>
)
}
If you want greater re-usability, you can create custom hooks to consume your context. Of course where you use these still has to follow the rules of hooks.
export const useSetCurrentSong = (song) => {
const {setCurrentSong} = useContext(StoreContext);
setCurrentSong(song);
}
It is possible to trigger a hook function by rendering a component, but you cannot call a component like you are trying to do.
const PlaySong = () => {
const {setCurrentSong} = useContext(StoreContext);
useEffect( () => {
setCurrentSong("some song");
}, []
}
return null;
}
const MyComponent = () => {
const [shouldPlay, setShouldPlay] = useState(false);
return (
<>
<button onClick={() => setShouldPlay(true)}>Play</button>
{shouldPlay && <PlaySong />}
</>
)
}

Create a hook with a consumer (provider/consumer)

How can I create a hook based on a consumer and provider?
The reason for why I am asking this is because the Provider and Consumer is exported from another library, so I can not extract that part more than that, and I would like a neat way to just add a value to the useHook to populate wanted values.
// I have a provider somewhere above this part, and here is the consumer:
<MyConsumer>
{props => <Values {...props} />}
</MyConsumer>
I would like to create a hook that returns the values I get inside the Values component. And maybe the best is to include the MyConsumer inside that hook so I would not need "two lines of code" where I want to use the hook.
I am imagining something like:
const useValues = (props) => {
const [state, setState] = useState({});
// do stuff
return state;
}
As mention, I do not know what I should do about the MyConsumer part.
Thank you for any help
You can do this by using your own context to wrap the provider and consumer.
const MyStateCtx = createContext();
const useMyState = () => useContext(MyStateCtx);
// LibraryProvider and LibraryConsumer are the provider and consumer
// supplied by the library you're using.
const MyStateProvider = ({ children }) => {
return (
<LibraryProvider>
<LibraryConsumer>
{(props) => (
<MyStateCtx.Provider value={props}>{children}</MyStateCtx.Provider>
)}
</LibraryConsumer>
</LibraryProvider>
);
};
const ConsumingComponent = () => {
// You can consume state here with this hook ✨
const myState = useMyState();
return <div />;
};
export default function App() {
return (
<MyStateProvider>
<div>
<ConsumingComponent />
</div>
</MyStateProvider>
);
}
Codesandbox link

How to target a specific item to toggleClick on using React Hooks?

I have a navbar component with that actual info being pulled in from a CMS. Some of the nav links have a dropdown component onclick, while others do not. I'm having a hard time figuring out how to target a specific menus index with React Hooks - currently onClick, it opens ALL the dropdown menus at once instead of the specific one I clicked on.
The prop toggleOpen is being passed down to a styled component based on the handleDropDownClick event handler.
Heres my component.
const NavBar = props => {
const [links, setLinks] = useState(null);
const [notFound, setNotFound] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const fetchLinks = () => {
if (props.prismicCtx) {
// We are using the function to get a document by its uid
const data = props.prismicCtx.api.query([
Prismic.Predicates.at('document.tags', [`${config.source}`]),
Prismic.Predicates.at('document.type', 'navbar'),
]);
data.then(res => {
const navlinks = res.results[0].data.nav;
setLinks(navlinks);
});
}
return null;
};
const checkForLinks = () => {
if (props.prismicCtx) {
fetchLinks(props);
} else {
setNotFound(true);
}
};
useEffect(() => {
checkForLinks();
});
const handleDropdownClick = e => {
e.preventDefault();
setIsOpen(!isOpen);
};
if (links) {
const linkname = links.map(item => {
// Check to see if NavItem contains Dropdown Children
return item.items.length > 1 ? (
<Fragment>
<StyledNavBar.NavLink onClick={handleDropdownClick} href={item.primary.link.url}>
{item.primary.label[0].text}
</StyledNavBar.NavLink>
<Dropdown toggleOpen={isOpen}>
{item.items.map(subitem => {
return (
<StyledNavBar.NavLink href={subitem.sub_nav_link.url}>
<span>{subitem.sub_nav_link_label[0].text}</span>
</StyledNavBar.NavLink>
);
})}
</Dropdown>
</Fragment>
) : (
<StyledNavBar.NavLink href={item.primary.link.url}>
{item.primary.label[0].text}
</StyledNavBar.NavLink>
);
});
// Render
return (
<StyledNavBar>
<StyledNavBar.NavContainer wide>
<StyledNavBar.NavWrapper row center>
<Logo />
{linkname}
</StyledNavBar.NavWrapper>
</StyledNavBar.NavContainer>
</StyledNavBar>
);
}
if (notFound) {
return <NotFound />;
}
return <h2>Loading Nav</h2>;
};
export default NavBar;
Your problem is that your state only handles a boolean (is open or not), but you actually need multiple booleans (one "is open or not" for each menu item). You could try something like this:
const [isOpen, setIsOpen] = useState({});
const handleDropdownClick = e => {
e.preventDefault();
const currentID = e.currentTarget.id;
const newIsOpenState = isOpen[id] = !isOpen[id];
setIsOpen(newIsOpenState);
};
And finally in your HTML:
const linkname = links.map((item, index) => {
// Check to see if NavItem contains Dropdown Children
return item.items.length > 1 ? (
<Fragment>
<StyledNavBar.NavLink id={index} onClick={handleDropdownClick} href={item.primary.link.url}>
{item.primary.label[0].text}
</StyledNavBar.NavLink>
<Dropdown toggleOpen={isOpen[index]}>
// ... rest of your component
Note the new index variable in the .map function, which is used to identify which menu item you are clicking.
UPDATE:
One point that I was missing was the initialization, as mention in the other answer by #MattYao. Inside your load data, do this:
data.then(res => {
const navlinks = res.results[0].data.nav;
setLinks(navlinks);
setIsOpen(navlinks.map((link, index) => {index: false}));
});
Not related to your question, but you may want to consider skipping effects and including a key to your .map
I can see the first two useState hooks are working as expected. The problem is your 3rd useState() hook.
The issue is pretty obvious that you are referring the same state variable isOpen by a list of elements so they all have the same state. To fix the problems, I suggest the following way:
Instead of having one value of isOpen, you will need to initialise the state with an array or Map so you can refer each individual one:
const initialOpenState = [] // or using ES6 Map - new Map([]);
In your fetchLink function callback, initialise your isOpen state array values to be false. So you can put it here:
data.then(res => {
const navlinks = res.results[0].data.nav;
setLinks(navlinks);
// init your isOpen state here
navlinks.forEach(link => isOpen.push({ linkId: link.id, value: false })) //I suppose you can get an id or similar identifers
});
In your handleClick function, you have to target the link object and set it to true, instead of setting everything to true. You might need to use .find() to locate the link you are clicking:
handleClick = e => {
const currentOpenState = state;
const clickedLink = e.target.value // use your own identifier
currentOpenState[clickedLink].value = !currentOpenState[clickedLink].value;
setIsOpen(currentOpenState);
}
Update your component so the correct isOpen state is used:
<Dropdown toggleOpen={isOpen[item].value}> // replace this value
{item.items.map(subitem => {
return (
<StyledNavBar.NavLink href={subitem.sub_nav_link.url}>
<span>{subitem.sub_nav_link_label[0].text}</span>
</StyledNavBar.NavLink>
);
})}
</Dropdown>
The above code may not work for you if you just copy & paste. But it should give you an idea how things should work together.

Resources