Set React Context inside function-only component - reactjs

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 />}
</>
)
}

Related

Accessing state or props from functional component

i'm trying to accessing variable/state in functional component from other component. i found out i can save the in some global state like redux or local storage. but i don't want to use any of them. i also found out about lifting state up but can't understand about it.
below is an example for my case. let's say i want to generate a lucky number from functional component GenerateLuckyNumber. and i call the component from showLuckyNumber. but i need to save all the list of lucky number that have been generated. i create a hook state to store all lucky number. but i dont know how to retrieve them from GenereateLuckyNumber
any idea how to to it?
import React from 'react';
import { Text, TouchableOpacity } from 'react-native';
import { useState, useEffect } from 'react';
export const GenerateLuckyNumber = () => {
const [luckNo, setluckNo] = useState(1)
const changeCatName = () => {
setluckNo(Math.floor(Math.random() * 100))
}
return (
<>
<TouchableOpacity onPress={() => changeCatName()}>
<Text>Hello, Your Lucky Number is {luckNo}!</Text>
</TouchableOpacity>
</>
);
}
const showLuckyNumber = () => {
const [listLuckyNumber, setlistLuckyNumber] = useState()
//how to save all generated number from functional component to state in this component
return (
<>
<GenerateLuckyNumber/>
<GenerateLuckyNumber/>
<GenerateLuckyNumber/>
</>
);
}
export default showLuckyNumber;
Your component can provide an optional callback function as a prop, which it would invoke whenever it "generates a lucky number". For example:
export const GenerateLuckyNumber = ({onGenerate}) => {
And within the component:
const changeCatName = () => {
const luckyNumber = Math.floor(Math.random() * 100);
setluckNo(luckyNumber);
onGenerate && onGenerate(luckyNumber); // invoke the callback
}
Then any component which renders a <GenerateLuckyNumber/> can optionally supply it with a callback:
const showLuckyNumber = () => {
const [listLuckyNumber, setlistLuckyNumber] = useState()
const handleNewLuckyNumber = (num) => {
// do whatever you like with the newly generated number here
};
return (
<>
<GenerateLuckyNumber onGenerate={handleNewLuckyNumber}/>
<GenerateLuckyNumber onGenerate={handleNewLuckyNumber}/>
<GenerateLuckyNumber onGenerate={handleNewLuckyNumber}/>
</>
);
}

REACT: How to set the state in the child and access it in the parent, receiving undefined

I am building this project to try and improve my understanding of react :), so I am a n00b and therefore still learning the ropes of extracting components, states, props etc =)
I have a child Component DescriptionDiv, its parent component is PlusContent and finally the parent component is PlusContentHolder. The user types some input into the DescriptionDiv which then, using a props/callback passes the user input to the PlusContent.
My question/problem is: after setting useState() in the PlusContent component, I am after a button click in the PlusContentHolder component, returned with an undefined in the console.log.
How come I cannot read the useState() in the next parent component, the PlusContentHolder?
I know that useState() is async so you cannot straight up call the value of the state in the PlusContent component, but shouldn't the state value be available in the PlusContentHolder component?
below is my code for the DescriptionDiv
import './DescriptionDiv.css';
const DescriptionDiv = props => {
const onDescriptionChangeHandler = (event) => {
props.descriptionPointer(event.target.value);
}
return (
<div className='description'>
<label>
<p>Description:</p>
<input onChange={onDescriptionChangeHandler} type='text'></input>
</label>
</div>);
}
export default DescriptionDiv;
Next the code for the PlusContent comp
import React, { useState } from "react";
import DescriptionDiv from "./div/DescriptionDiv";
import ImgDiv from "./div/ImgDiv";
import "./PlusContent.css";
import OrientationDiv from "./div/OrientationDiv";
const PlusContent = (props) => {
const [classes, setClasses] = useState("half");
const [content, setContent] = useState();
const [plusContent, setPlusContent] = useState({
orientation: "left",
img: "",
description: "",
});
const onOrientationChangeHandler = (orientationContent) => {
if (orientationContent == "left") {
setClasses("half left");
}
if (orientationContent == "right") {
setClasses("half right");
}
if (orientationContent == "center") {
setClasses("half center");
}
props.orientationInfo(orientationContent);
};
const onDescriptionContentHandler = (descriptionContent) => {
props.descriptionInfo(setPlusContent(descriptionContent));
console.log(descriptionContent)
};
const onImageChangeHandler = (imageContent) => {
props.imageInfo(imageContent);
setContent(
<>
<OrientationDiv
orientationPointer={onOrientationChangeHandler}
orientationName={props.orientationName}
/> {/*
<AltDiv altPointer={onAltDivContentHandler} />
<TitleDiv titlePointer={onTitleDivContentHandler} /> */}
<DescriptionDiv descriptionPointer={onDescriptionContentHandler} />
</>
);
};
return (
<div className={classes}>
<ImgDiv imageChangeExecutor={onImageChangeHandler} />
{content}
</div>
);
};
export default PlusContent;
and lastly the PlusContentHolder
import PlusContent from "../PlusContent";
import React, { useState } from "react";
const PlusContentHolder = (props) => {
const onClickHandler = (t) => {
t.preventDefault();
descriptionInfoHandler();
};
const descriptionInfoHandler = (x) => {
console.log(x) // this console.log(x) returns and undefined
};
return (
<div>
{props.contentAmountPointer.map((content) => (
<PlusContent
orientationInfo={orientationInfoHandler}
imageInfo={imageInfoHandler}
descriptionInfo={descriptionInfoHandler}
key={content}
orientationName={content}
/>
))}
<button onClick={onClickHandler}>Generate Plus Content</button>
</div>
);
};
export default PlusContentHolder;
The reason why the descriptionInfoHandler() function call prints undefined in its console.log() statement when you click the button, is because you never provide an argument to it when you call it from the onClickHandler function.
I think that it will print the description when you type it, however. And I believe the problem is that you need to save the state in the PlusContentHolder module as well.
I would probably add a const [content, setContent] = useState() in the PlusContentHolder component, and make sure to call setContent(x) in the descriptionInfoHandler function in PlusContentHolder.
Otherwise, the state will not be present in the PlusContentHolder component when you click the button.
You need to only maintain a single state in the PlusContentHolder for orientation.
Here's a sample implementation of your use case
import React, { useState } from 'react';
const PlusContentHolder = () => {
const [orientatation, setOrientation] = useState('');
const orientationInfoHandler = (x) => {
setOrientation(x);
};
const generateOrientation = () => {
console.log('orientatation', orientatation);
};
return (
<>
<PlusContent orientationInfo={orientationInfoHandler} />
<button onClick={generateOrientation}>generate</button>
</>
);
};
const PlusContent = ({ orientationInfo }) => {
const onDescriptionContentHandler = (value) => {
// your custom implementation here,
orientationInfo(value);
};
return <DescriptionDiv descriptionPointer={onDescriptionContentHandler} />;
};
const DescriptionDiv = ({ descriptionPointer }) => {
const handleChange = (e) => {
descriptionPointer(e.target.value);
};
return <input type="text" onChange={handleChange} />;
};
I would suggest to maintain the orientation in redux so that its easier to update from the application.
SetState functions do not return anything. In the code below, you're passing undefined to props.descriptionInfo
const onDescriptionContentHandler = (descriptionContent) => {
props.descriptionInfo(setPlusContent(descriptionContent));
};
This shows a misunderstanding of the use of state. Make sure you're reading about "lifting state" in the docs.
You're also declaring needless functions, e.g. onDescriptionContentHandler in your PlusContent. The PlusContent component could just pass the descriptionInfoHandler from PlusContentHolder prop directly down to DescriptionDiv, since onDescriptionContentHandler doesn't do anything except invoke descriptionInfoHandler.
You may want to consider restructuring your app so plusContent state is maintained in PlusContentHolder, and pass that state down as props. That state would get updated when DescriptionDiv invokes descriptionInfoHandler. It'd subsequently pass the updated state down as props to PlusContent.
See my suggested flowchart.

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

react functional component with ag grid cannot call parent function via context

I am using ag-grid-react and ag-grid-community version 22.1.1. My app is written using functional components and hooks. I have a cellrenderer component that is attempting to call a handler within the parent component using the example found here. Is this a bug in ag-grid? I have been working on this application for over a year as I learn React, and this is my last major blocker so any help or a place to go to get that help would be greatly appreciated.
Cell Renderer Component
import React from 'react';
import Button from '../../button/button';
const RowButtonRenderer = props => {
const editClickHandler = (props) => {
let d = props.data;
console.log(d);
props.context.foo({d});
//props.editClicked(props);
}
const deleteClickHandler = (props) => {
props.deleteClicked(props);
}
return (<span>
<Button classname={'rowbuttons'} onClick={() => { editClickHandler(props) }} caption={'Edit'} />
<Button classname={'rowbuttons'} onClick={() => { deleteClickHandler(props) }} caption={'Delete'} />
</span>);
};
export default RowButtonRenderer;
Parent Component
function Checking() {
function foo(props) {
let toggle = displayModal
setNewData(props);
setModalDisplay(!toggle);
}
const context = {componentParent: (props) => foo(props)};
const gridOptions = (params) => {
if (params.node.rowIndex % 2 === 0) {
return { background: "#ACC0C6" };
}
};
const frameworkComponents = {
rowButtonRenderer: RowButtonRenderer,
};
.
.
.
return (
<>
<AgGridReact
getRowStyle={gridOptions}
frameworkComponents={frameworkComponents}
context = {context}
columnDefs={columnDefinitions}
rowData={rowData}
headerHeight="50"
rowClass="gridFont"
></AgGridReact>
</>
);
}
When clicking the edit button on a row, the debugger says that there is a function.
This error is received though:
You are passing the context object in this code section:
const context = {componentParent: (props) => foo(props)};
...
<AgGridReact
context={context}
{...}
></AgGridReact>
And in your cell renderer you call this
props.context.foo({d});
While it should be this
props.context.componentParent({d});
Also you can assign your callback directly since it receives the same parameter and returns the same result (if any)
function foo(props) {
let toggle = displayModal
setNewData(props);
setModalDisplay(!toggle);
}
const context = {componentParent: foo};
You can also use this shorthand syntax from ES6 when assigning object property
function componentParent(props) {
let toggle = displayModal
setNewData(props);
setModalDisplay(!toggle);
}
const context = {componentParent};
Live Demo

Method within React functional component

Hello I had to add a method inside a component, that was stateless functional component. Now I am wondering if it can stay like this or should it be a class component now. My component:
const Pagination = ({ changePage }) => {
function changePageNumber(event) {
changePage(event.currentTarget.dataset.num);
}
return (
<button
className={css.button}
data-num={num}
onClick={changePageNumber}
>
{num}
</button>
);
};
Can it be like this?
Yes, you can write methods like this. Also you can use arrow functions, like:
const Pagination = ({ changePage }) => {
const changePageNumber = event => {
changePage(event.currentTarget.dataset.num);
}
return (
<button
className={css.button}
data-num={num}
onClick={changePageNumber}
>
{num}
</button>
);
};
Bonus: It's not necessary to name component that exported default, just:
export default ({ changePage }) => {
...
}
And in another file:
import AnyName from './Pagination'
You can change it to be like
const changePageNumber = (event) = () => {
changePage(event.currentTarget.dataset.num);
}
const Pagination = ({ changePage }) => {
return (
<button
className={css.button}
data-num={num}
onClick={changePageNumber}
>
{num}
</button>
);
};
changePageNumber is a function not a method. It is perfectly fine to stay there. It is there as a utility/helper function for that Pagination component. Such functions can be deployed to improve the readability of the code. Current state of your code perfectly fine.
Also you don't need to turn it into Class components, we use them when we think we need to store state, not to store methods.

Resources