Calling react context prop (function) from components function - reactjs

I have a Context Consumer which works like this:
<ToastConsumer>
{({ openToast }) => (
<button onClick={() => openToast('You clicked Button A!')}>
Button A
</button>
)}
</ToastConsumer>
But I want to add some extra logic on the click handler and move the openToast Consumer function like this:
upVote = () => {
if (!this.state.hasVoted) {
this.setState({
hasVoted: true,
rating: this.state.rating + 1,
});
this.vote(this.state.rating + 1);
}
this.openToast // not working???
};
<ToastConsumer>
{({ openToast }) => (
<div className="vote-button">
<span
className="vote-up vote-action cursor-pointer"
onClick={this.upVote}
>
šŸ‘ +1...
All of the examples provided for the Context API seem to be a simple click handler and I cant workout how to acheive a more complex example like this one.

One way to achieve this is to pass your openToast function into your new handler. You can do this either by wrapping the onClick in a function, or by currying your upVote function.
Examples:
Wrapping in a function:
upVote = (openToast) => {
onClick={() => this.upVote(openToast)}
Currying upVote:
upVote = (openToast) => () => {
onClick={this.upVote(openToast)}

openToast needs to be provided to upVote as an argument (as another answer already mentions), upVote becomes higher-order function:
upVote = openToast => () => {
// ...
openToast();
}
And used like:
<span onClick={this.upVote(openToast)}>
A way to avoid this complexity with context consumers is to make a context available in class instance. This can be done with contextType:
static contextType = ToastContext;
upVote = openToast => () => {
// ...
this.context.openToast();
}
A downside is that this restricts a component to be used with one context.
Or a context can be provided to a component with a HOC:
const withToast = Comp => props => (
<ToastConsumer>
{({ openToast }) => <Comp openToast={openToast} ...props/>}
</ToastConsumer>
);
Then a component that was connected to a context with withToast(MyComponent) receives openToast as a prop:
upVote = openToast => () => {
// ...
this.props.openToast();
}

Related

Is there a good way to use React hooks inside a callback or render prop?

Often times I find myself creating or using components where callbacks are used to create part of the component tree, as in these two simplified examples:
// Example 1: Render an array of components
const ExampleComponent1 = () => (
<ul>
{items.map((item) => <li key={item.id} style={{ paddingTop: item.paddingTop }}>{item.label}</li>)}
</ul>
);
// Example 2: Use render props
const ExampleComponent2 = () => (
<Dialog
renderHeader={useCallback((dialogCtx) => 'Header', [])}
renderBody={useCallback((dialogCtx) => <button onClick={() => dialogCtx.closeDialog(null)}>Test</button>, [])}
/>
);
As you can see, the callback results in both cases are dependent on the callback argument. In order to avoid a new style object (in example 1) and a new onClick function (in example 2) to be created on every render, I should wrap them in useMemo/useCallback, but that is not possible here because they are created inside the callbacks rather than on the root level of the component.
The only way that I'm aware of to work around this problem is to create designated components for each callback:
// Example 1: Render an array of components
const ExampleComponent1 = () => (
<ul>
{items.map((item) => <ExampleComponent1Item key={item.id} item={item}/>)}
</ul>
);
const ExampleComponent1Item = ({ item }) => <li style={useMemo(() => ({ paddingTop: item.paddingTop }), [item.paddingTop])}>{item.label}</li>;
// Example 2: Use render props
const ExampleComponent2 = () => (
<Dialog
renderHeader={useCallback((dialogCtx) => <ExampleComponent2Header dialogCtx={dialogCtx}/>, [])}
renderBody={useCallback((dialogCtx) => <ExampleComponent2Body dialogCtx={dialogCtx}/>, [])}
/>
);
const ExampleComponent2Header = ({ dialogCtx }) => 'Header';
const ExampleComponent2Body = ({ dialogCtx }) => <button onClick={useCallback(() => dialogCtx.closeDialog(null), [dialogCtx.closeDialog])}>Test</button>;
You can already see in this simplified example how splitting up the app in such a way creates a lot of additional code and makes the app much harder to read. In more complex scenarios where a lot of props from the parent component need to be reused in the sub component, the sub components will become even more bulky, particularly when prop types need to be defined as well.
It seems to me that both of these example are rather common use cases, since callbacks are the only way to generate a list of components from an array in React, and render props are a common pattern in more complex components. I'm wondering:
Is there any way how I could write the above example without splitting off the callback results into separate components? For example some way to use hooks directly inside the callbacks.
Is there an alternative pattern for mapping an array to a list of components that doesn't rely on callbacks, so hooks can be used?
Is there an alternative pattern to render props that makes it easier to use hooks?
Update: To make it clear, Iā€™m looking for a programming pattern, not specific problems in the simplified example code above.
I don't understand what's wrong with example 1.
const ExampleComponent1 = () => (
<ul>
{items.map((item) => <li key={item.id} style={{ paddingTop: item.paddingTop }}>{item.label}</li>)}
</ul>
);
This is perfectly fine in React. Until you can see that the style prop is causing performance issues, you shouldn't try to optimize it.
Version with hooks, in case you somehow really need it:
const ExampleComponent1Item = ({ item }) => {
const style = useMemo(() => ({ paddingTop: item.paddingTop }), [item.paddingTop]);
return (<li style={style}>{item.label}</li>);
};
const ExampleComponent1 = () => (
<ul>
{items.map((item) => <ExampleComponent1Item key={item.id} item={item}/>)}
</ul>
);
The same goes for example 2:
const ExampleComponent2 = () => {
const renderHeader = useCallback((dialogCtx) => 'Header', []);
const renderBody = useCallback((dialogCtx) => <button onClick={() => dialogCtx.closeDialog(null)}>Test</button>, []);
return (<Dialog renderHeader={renderHeader} renderBody=renderBody}/>);
};
If you want to avoid the onClick function to be recreated each time renderBody is called, you can do:
const ExampleComponent2Body = {( dialogCtx }) => {
const onClick = useCallback(() => dialogCtx.closeDialog(null), [dialogCtx.closeDialog]);
return (<button onClick={onClick}>Test</button>);
};
const ExampleComponent2 = () => {
const renderHeader = useCallback((dialogCtx) => 'Header', []);
const renderBody = useCallback((dialogCtx) => <ExampleComponent2Body dialogCtx={dialogCtx}/>, []);
return (<Dialog renderHeader={renderHeader} renderBody=renderBody}/>);
};

Handle button click with two different handler in different condition - ReactJS

I want to handle the click of my custom button component in two separate conditions.
This is the custom-button component:
function CustomButton({status, onStop, onClick}) {
return (
// ...
<button onClick={status ? onStop : onClick}/>
// ...
)
}
This code is just a simple example to explain my question. and I can't separate my component according to the value of status.
My question is, Is this an anti-pattern or bad practice in a component? If yes, what's the best practice to do?
It's not an anti-pattern and more like a bad practice, such code isn't maintainable for when more conditions and callbacks will be added.
Components should be simple, readable and reusable while using all provided props as possible (i.e don't add unused props to component's logic):
const NOOP = () => {}
function CustomButton({ onClick = NOOP }) {
return <button onClick={onClick}>Cool Button</button>;
}
Better practice is to handle condition in parent's logic:
function Parent() {
const onContinue = // ...
const onStop = // ...
return <CustomButton onClick={status ? onStop : onContinue} />;
}
I think that is better to pass a single callback onClick and handle the business logic inside it.
Create a function handleClick inside CustomButton component before return and use if-else blocks to achieve this functionality. It is the right way, according to me.
Create your custom button and use it into any place with any props
import React from 'react';
const CustomButton = ({ children, type = 'button', onClick }) => (
<button
type={type}
onClick={onClick}
>
{children}
</button>
);
const Parent = ({ status }) => {
const stop = useCallback(() => {}, []);
const onClick = useCallback(() => {}, []);
return (
status
? <CustomButton onClick={useCallback}>stop</CustomButton>
: <CustomButton onClick={onClick}>click</CustomButton>
);
}

How to render a different component with React Hooks

I have a parent component with an if statement to show 2 different types of buttons.
What I do, on page load, I check if the API returns an array called lectures as empty or with any values:
lectures.length > 0 ? show button A : show button B
This is the component, called main.js, where the if statement is:
lectures.length > 0
? <div onClick={() => handleCollapseClick()}>
<SectionCollapse open={open} />
</div>
: <LectureAdd dataSection={dataSection} />
The component LectureAdd displays a + sign, which will open a modal to create a new Lecture's title, while, SectionCollapse will show an arrow to show/hide a list of items.
The logic is simple:
1. On page load, if the lectures.lenght > 0 is false, we show the + sign to add a new lecture
OR
2. If the lectures.lenght > 0 is true, we change and show the collpase arrow.
Now, my issue happens when I add the new lecture from the child component LectureAdd.js
import React from 'react';
import { Form, Field } from 'react-final-form';
// Constants
import { URLS } from '../../../../constants';
// Helpers & Utils
import api from '../../../../helpers/API';
// Material UI Icons
import AddBoxIcon from '#material-ui/icons/AddBox';
export default ({ s }) => {
const [open, setOpen] = React.useState(false);
const [ lucturesData, setLecturesData ] = React.useState(0);
const { t } = useTranslation();
const handleAddLecture = ({ lecture_title }) => {
const data = {
"lecture": {
"title": lecture_title
}
}
return api
.post(URLS.NEW_COURSE_LECTURE(s.id), data)
.then(data => {
if(data.status === 201) {
setLecturesData(lucturesData + 1) <=== this doesn't trigger the parent and the button remains a `+` symbol, instead of changing because now `lectures.length` is 1
}
})
.catch(response => {
console.log(response)
});
}
return (
<>
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
<AddBoxIcon />
</Button>
<Form
onSubmit={event => handleAddLecture(event)}
>
{
({
handleSubmit
}) => (
<form onSubmit={handleSubmit}>
<Field
name='lecture_title'
>
{({ input, meta }) => (
<div className={meta.active ? 'active' : ''}>
<input {...input}
type='text'
className="signup-field-input"
/>
</div>
)}
</Field>
<Button
variant="contained"
color="primary"
type="submit"
>
ADD LECTURE
</Button>
</form>
)}
</Form>
</>
)
}
I've been trying to use UseEffect to trigger a re-render on the update of the variable called lucturesData, but it doesn't re-render the parent component.
Any idea?
Thanks Joe
Common problem in React. Sending data top-down is easy, we just pass props. Passing information back up from children components, not as easy. Couple of solutions.
Use a callback (Observer pattern)
Parent passes a prop to the child that is a function. Child invokes the function when something meaningful happens. Parent can then do something when the function gets called like force a re-render.
function Parent(props) {
const [lectures, setLectures] = useState([]);
const handleLectureCreated = useCallback((lecture) => {
// Force a re-render by calling setState
setLectures([...lectures, lecture]);
}, []);
return (
<Child onLectureCreated={handleLectureCreated} />
)
}
function Child({ onLectureCreated }) {
const handleClick = useCallback(() => {
// Call API
let lecture = callApi();
// Notify parent of event
onLectureCreated(lecture);
}, [onLectureCreated]);
return (
<button onClick={handleClick}>Create Lecture</button>
)
}
Similar to solution #1, except for Parent handles API call. The benefit of this, is the Child component becomes more reusable since its "dumbed down".
function Parent(props) {
const [lectures, setLectures] = useState([]);
const handleLectureCreated = useCallback((data) => {
// Call API
let lecture = callApi(data);
// Force a re-render by calling setState
setLectures([...lectures, lecture]);
}, []);
return (
<Child onLectureCreated={handleLectureCreated} />
)
}
function Child({ onLectureCreated }) {
const handleClick = useCallback(() => {
// Create lecture data to send to callback
let lecture = {
formData1: '',
formData2: ''
}
// Notify parent of event
onCreateLecture(lecture);
}, [onCreateLecture]);
return (
<button onClick={handleClick}>Create Lecture</button>
)
}
Use a central state management tool like Redux. This solution allows any component to "listen in" on changes to data, like new Lectures. I won't provide an example here because it's quite in depth.
Essentially all of these solutions involve the same solution executed slightly differently. The first, uses a smart child that notifies its parent of events once their complete. The second, uses dumb children to gather data and notify the parent to take action on said data. The third, uses a centralized state management system.

useLoopCallback -- useCallback hook for components created inside a loop

I'd like to start a discussion on the recommended approach for creating callbacks that take in a parameter from a component created inside a loop.
For example, if I'm populating a list of items that will have a "Delete" button, I want the "onDeleteItem" callback to know the index of the item to delete. So something like this:
const onDeleteItem = useCallback(index => () => {
setList(list.slice(0, index).concat(list.slice(index + 1)));
}, [list]);
return (
<div>
{list.map((item, index) =>
<div>
<span>{item}</span>
<button type="button" onClick={onDeleteItem(index)}>Delete</button>
</div>
)}
</div>
);
But the problem with this is that onDeleteItem will always return a new function to the onClick handler, causing the button to be re-rendered, even when the list hasn't changed. So it defeats the purpose of useCallback.
I came up with my own hook, which I called useLoopCallback, that solves the problem by memoizing the main callback along with a Map of loop params to their own callback:
import React, {useCallback, useMemo} from "react";
export function useLoopCallback(code, dependencies) {
const callback = useCallback(code, dependencies);
const loopCallbacks = useMemo(() => ({map: new Map(), callback}), [callback]);
return useCallback(loopParam => {
let loopCallback = loopCallbacks.map.get(loopParam);
if (!loopCallback) {
loopCallback = (...otherParams) => loopCallbacks.callback(loopParam, ...otherParams);
loopCallbacks.map.set(loopParam, loopCallback);
}
return loopCallback;
}, [callback]);
}
So now the above handler looks like this:
const onDeleteItem = useLoopCallback(index => {
setList(list.slice(0, index).concat(list.slice(index + 1)));
}, [list]);
This works fine but now I'm wondering if this extra logic is really making things faster or just adding unnecessary overhead. Can anyone please provide some insight?
EDIT:
An alternative to the above is to wrap the list items inside their own component. So something like this:
function ListItem({key, item, onDeleteItem}) {
const onDelete = useCallback(() => {
onDeleteItem(key);
}, [onDeleteItem, key]);
return (
<div>
<span>{item}</span>
<button type="button" onClick={onDelete}>Delete</button>
</div>
);
}
export default function List(...) {
...
const onDeleteItem = useCallback(index => {
setList(list.slice(0, index).concat(list.slice(index + 1)));
}, [list]);
return (
<div>
{list.map((item, index) =>
<ListItem key={index} item={item} onDeleteItem={onDeleteItem} />
)}
</div>
);
}
Performance optimizations always come with a cost. Sometimes this cost is lower than the operation to be optimized, sometimes is higher. useCallback it's a hook very similar to useMemo, actually you can think of it as a specialization of useMemo that can only be used in functions. For example, the bellow statements are equivalents
const callback = value => value * 2
const memoizedCb = useCallback(callback, [])
const memoizedWithUseMemo = useMemo(() => callback, [])
So for now on every assertion about useCallback can be applied to useMemo.
The gist of memoization is to keep copies of old values to return in the event we get the same dependencies, this can be great when you have something that is expensive to compute. Take a look at the following code
const Component = ({ items }) =>{
const array = items.map(x => x*2)
}
Uppon every render the const array will be created as a result of a map performed in items. So you can feel tempted to do the following
const Component = ({ items }) =>{
const array = useMemo(() => items.map(x => x*2), [items])
}
Now items.map(x => x*2) will only be executed when items change, but is it worth? The short answer is no. The performance gained by doing this is trivial and sometimes will be more expensive to use memoization than just execute the function each render. Both hooks(useCallback and useMemo) are useful in two distinct use cases:
Referencial equality
When you need to ensure that a reference type will not trigger a re render just for failing a shallow comparison
Computationally expensive operations(only useMemo)
Something like this
const serializedValue = {item: props.item.map(x => ({...x, override: x ? y : z}))}
Now you have a reason to memoized the operation and lazily retrieve the serializedValue everytime props.item changes:
const serializedValue = useMemo(() => ({item: props.item.map(x => ({...x, override: x ? y : z}))}), [props.item])
Any other use case is almost always worth to just re compute all values again, React it's pretty efficient and aditional renders almost never cause performance issues. Keep in mind that sometimes your efforts to optimize your code can go the other way and generate a lot of extra/unecessary code, that won't generate so much benefits (sometimes will only cause more problems).
The List component manages it's own state (list) the delete functions depends on this list being available in it's closure. So when the list changes the delete function must change.
With redux this would not be a problem because deleting items would be accomplished by dispatching an action and will be changed by a reducer that is always the same function.
React happens to have a useReducer hook that you can use:
import React, { useMemo, useReducer, memo } from 'react';
const Item = props => {
//calling remove will dispatch {type:'REMOVE', payload:{id}}
//no arguments are needed
const { remove } = props;
console.log('component render', props);
return (
<div>
<div>{JSON.stringify(props)}</div>
<div>
<button onClick={remove}>REMOVE</button>
</div>
</div>
);
};
//wrap in React.memo so when props don't change
// the ItemContainer will not re render (pure component)
const ItemContainer = memo(props => {
console.log('in the item container');
//dispatch passed by parent use it to dispatch an action
const { dispatch, id } = props;
const remove = () =>
dispatch({
type: 'REMOVE',
payload: { id },
});
return <Item {...props} remove={remove} />;
});
const initialState = [{ id: 1 }, { id: 2 }, { id: 3 }];
//Reducer is static it doesn't need list to be in it's
// scope through closure
const reducer = (state, action) => {
if (action.type === 'REMOVE') {
//remove the id from the list
return state.filter(
item => item.id !== action.payload.id
);
}
return state;
};
export default () => {
//initialize state and reducer
const [list, dispatch] = useReducer(
reducer,
initialState
);
console.log('parent render', list);
return (
<div>
{list.map(({ id }) => (
<ItemContainer
key={id}
id={id}
dispatch={dispatch}
/>
))}
</div>
);
};

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