How to add a function in const Target = props => { - reactjs

How do I add a function to connect to one of my components onChange? Creating a function like this returns an error code of 'cardActionResponse' is not defined.
What the benefit of using a const class like this?
const Target = props => {
const { markAsDone } = useContext(ItemContext);
const [{ isOver }, drop] = useDrop({
accept: 'Item',
drop: (item, monitor) => console.log(item),
collect: monitor => ({
isOver: !!monitor.isOver()
})
})
//Cannot do this. How else can I make a function to connect to CreateVideoCard?
cardActionResponse = (event) => {
console.log(event);
}
return (
<div className="target top80 right30" ref={drop} style={{ backgroundColor: isOver ? 'black' : '' }} >
<TitleDescription class="z1"/>
<div class="right10 left10">
<CreateVideoCard onChange={this.cardActionResponse} />
<CreateDescriptionCard></CreateDescriptionCard>
<CreateAudioCard></CreateAudioCard>
<CreateTermsCard></CreateTermsCard>
</div>
</div>
);
};
export default Target;

Functional components don't have it's own context (this), so you should simply use const variable.
Please use
const cardActionResponse = (event) => {
console.log(event);
}
and then
<CreateVideoCard onChange={cardActionResponse} />

Related

How can I implement not only one but multi setting toggles?

I use Shopify Polaris's setting toggle.https://polaris.shopify.com/components/actions/setting-toggle#navigation
And I want to implement not only one but multi setting toggles.But I don't want to always duplicate same handleToggle() and values(contentStatus, textStatus) like below the sandbox A,B,C...
import React, { useCallback, useState } from "react";
import { SettingToggle, TextStyle } from "#shopify/polaris";
export default function SettingToggleExample() {
const [activeA, setActiveA] = useState(false);
const [activeB, setActiveB] = useState(false);
const handleToggleA = useCallback(() => setActiveA((active) => !active), []);
const handleToggleB = useCallback(() => setActiveB((active) => !active), []);
const contentStatusA = activeA ? "Deactivate" : "Activate";
const contentStatusB = activeB ? "Deactivate" : "Activate";
const textStatusA = activeA ? "activated" : "deactivated";
const textStatusB = activeB ? "activated" : "deactivated";
const useHandleToggle = (active, setActive) => {
const handleToggle = useCallback(() => setActive((active) => !active), []);
const contentStatus = active ? "Disconnect" : "Connect";
const textStatus = active ? "connected" : "disconnected";
handleToggle();
return [contentStatus, textStatus];
};
useHandleToggle(activeA, setActiveA);
return (
<>
<SettingToggle
action={{
content: contentStatusA,
onAction: handleToggleA
}}
enabled={activeA}
>
This setting is <TextStyle variation="strong">{textStatusA}</TextStyle>.
</SettingToggle>
<SettingToggle
action={{
content: contentStatusB,
onAction: handleToggleB
}}
enabled={activeB}
>
This setting is <TextStyle variation="strong">{textStatusB}</TextStyle>.
</SettingToggle>
</>
);
}
https://codesandbox.io/s/vigorous-pine-k0dpib?file=/App.js
So I thought I can use a custom hook. But it's not working. So it would be helpful if you give me some advice.
Using simple Booleans for each toggle
If you combine your active state objects into a single array, then you can update as many settings as you would like dynamically. Here's an example of what that might look like:
import React, { useCallback, useState } from "react";
import { SettingToggle, TextStyle } from "#shopify/polaris";
export default function SettingToggleExample() {
// define stateful array of size equal to number of toggles
const [active, setActive] = useState(Array(2).fill(false));
const handleToggle = useCallback((i) => {
// toggle the boolean at index, i
setActive(prev => [...prev.slice(0,i), !prev[i], ...prev.slice(i+1)])
}, []);
return (
<>
{activeStatuses.map((isActive, index) =>
<SettingToggle
action={{
content: isActive ? "Deactivate" : "Activate",
onAction: () => handleToggle(index)
}}
enabled={isActive}
>
This setting is <TextStyle variation="strong">{isActive ? "activated" : "deactivated"}</TextStyle>.
</SettingToggle>
}
</>
);
}
Of course, you will likely want to add a label to each of these going forward, so it may be better to define a defaultState object outside the function scope and replace the Array(2).fill(false) with it. Then you can have a string label property for each toggle in addition to a boolean active property which can be added next to each toggle in the .map(...).
With labels added for each toggle
Per your follow up, here is the implementation also found in the CodeSandbox for a state with labels for each toggle (including here on the answer to protect against link decay):
import React, { useCallback, useState } from "react";
import { SettingToggle, TextStyle } from "#shopify/polaris";
const defaultState = [
{
isActive: false,
label: "A"
},
{
isActive: false,
label: "B"
},
{
isActive: false,
label: "C"
}
];
export default function SettingToggleExample() {
const [active, setActive] = useState(defaultState);
const handleToggle = useCallback((i) => {
// toggle the boolean at index, i
setActive((prev) => [
...prev.slice(0, i),
{ ...prev[i], isActive: !prev[i].isActive },
...prev.slice(i + 1)
]);
}, []);
return (
<div style={{ height: "100vh" }}>
{active?.map(({ isActive, label }, index) => (
<SettingToggle
action={{
content: isActive ? "Deactivate" : "Activate",
onAction: () => handleToggle(index)
}}
enabled={isActive}
key={index}
>
This {label} is 
<TextStyle variation="strong">
{isActive ? "activated" : "deactivated"}
</TextStyle>
.
</SettingToggle>
))}
</div>
);
}
My first attempt to refactor would use a parameter on the common handler
const handleToggle = useCallback((which) => {
which === 'A' ? setActiveA((activeA) => !activeA)
: setActiveB((activeB) => !activeB)
},[])
...
<SettingToggle
action={{
content: contentStatusA,
onAction: () => handleToggle('A')
}}
enabled={activeA}
>
It functions, but feels a bit naïve. For something more React-ish, a reducer might be the way to go.
With a reducer
This seems cleaner, and is definitely more extensible if you need more toggles.
function reducer(state, action) {
switch (action.type) {
case "toggleA":
const newValueA = !state.activeA;
return {
...state,
activeA: newValueA,
contentStatusA: newValueA ? "Deactivate" : "Activate",
textStatusA: newValueA ? "activated" : "deactivated"
};
case "toggleB":
const newValueB = !state.activeB;
return {
...state,
activeB: newValueB,
contentStatusB: newValueB ? "Deactivate" : "Activate",
textStatusB: newValueB ? "activated" : "deactivated"
};
default:
throw new Error();
}
}
const initialState = {
activeA: false,
activeB: false,
contentStatusA: "Activate",
contentStatusB: "Activate",
textStatusA: "deactivated",
textStatusB: "deactivated"
};
export default function SettingToggleExample() {
const [state, dispatch] = useReducer(reducer, initialState)
return (
<>
<SettingToggle
action={{
content: state.contentStatusA,
onAction: () => dispatch({type: 'toggleA'})
}}
enabled={state.activeA}
>
This setting is <TextStyle variation="strong">{state.textStatusA}</TextStyle>.
</SettingToggle>
<SettingToggle
action={{
content: state.contentStatusB,
onAction: () => dispatch({type: 'toggleA'})
}}
enabled={state.activeB}
>
This setting is <TextStyle variation="strong">{state.textStatusB}</TextStyle>.
</SettingToggle>
</>
);
}
With a wrapper component
A child component can eliminate the 'A' and 'B' suffixes
function reducer(state, action) {
switch (action.type) {
case "toggle":
const newValue = !state.active;
return {
...state,
active: newValue,
contentStatus: newValue ? "Deactivate" : "Activate",
textStatus: newValue ? "activated" : "deactivated"
};
default:
throw new Error();
}
}
const initialState = {
active: false,
contentStatus: "Activate",
textStatus: "deactivated",
};
const ToggleWrapper = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<SettingToggle
action={{
content: state.contentStatus,
onAction: () => dispatch({ type: "toggle" })
}}
enabled={state.active}
>
This setting is <TextStyle variation="strong">{state.textStatus}</TextStyle>.
</SettingToggle>
)
}
export default function SettingToggleExample() {
return (
<>
<ToggleWrapper />
<ToggleWrapper />
</>
);
}

Options not showing when using custom input in React-Bootstrap-TypeAhead

I am using React-Bootstrap-TypeAhead's latest version in my React project. The main goal is to display the options menu when the user types. The menu is displayed when using the default input component but once I use the render input method for customization the options menu stops showing:
working example
import React, { useState } from 'react';
import { AsyncTypeahead } from 'react-bootstrap-typeahead';
/* example-start */
const BasicExample = ({ key, label }) => {
const [singleSelections, setSingleSelections] = useState([]);
const [multiSelections, setMultiSelections] = useState([]);
const [query, setQuery] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [options, setOptions] = useState([]);
const PER_PAGE = 50;
const SEARCH_URI = 'https://api.github.com/search/users';
function makeAndHandleRequest(query, page = 1) {
return fetch(`${SEARCH_URI}?q=${query}+in:login&page=${page}&per_page=50`)
.then((resp) => resp.json())
.then(({ items, total_count }) => {
/* eslint-disable-line camelcase */
const options = items.map((i) => ({
avatar_url: i.avatar_url,
id: i.id,
login: i.login,
}));
return { options, total_count };
})
.catch((err) => console.log(err));
}
const _handleInputChange = (query) => {
setQuery(query);
};
const _handlePagination = (e, shownResults) => {
const { query } = this.state;
const cachedQuery = this._cache[query];
// Don't make another request if:
// - the cached results exceed the shown results
// - we've already fetched all possible results
if (cachedQuery.options.length > shownResults || cachedQuery.options.length === cachedQuery.total_count) {
return;
}
setIsLoading(true);
const page = cachedQuery.page + 1;
makeAndHandleRequest(query, page).then((resp) => {
const options = cachedQuery.options.concat(resp.options);
// this._cache[query] = { ...cachedQuery, options, page };
setIsLoading(false);
setOptions(options);
});
};
const _handleSearch = (query) => {
setIsLoading(true);
makeAndHandleRequest(query).then((resp) => {
setIsLoading(true);
setOptions(resp?.options || []);
});
};
return (
<>
<AsyncTypeahead
{...{ query, isLoading, options }}
id="async-pagination-example"
labelKey="login"
maxResults={PER_PAGE - 1}
minLength={2}
onInputChange={_handleInputChange}
onPaginate={_handlePagination}
onSearch={_handleSearch}
renderInput={({ inputRef, referenceElementRef, ...inputProps }) => (
<div className="form-group h-64">
<label>Job Category</label>
<div className="input-group">
<input
type="text"
{...inputProps}
ref={(input) => {
inputRef(input);
// referenceElementRef(input);
}}
className="form-control"
placeholder=""
/>
</div>
</div>
)}
paginate
placeholder="Search for a Github user..."
renderMenuItemChildren={(option) => (
<div key={option.id}>
<img
alt={option.login}
src={option.avatar_url}
style={{
height: '24px',
marginRight: '10px',
width: '24px',
}}
/>
<span>{option.login}</span>
</div>
)}
useCache={false}
/>
</>
);
};
/* example-end */
export default BasicExample;
The reason you're not seeing any results rendered is that _handleInputChange is triggering a re-render and resetting the debounced onSearch handler before it can fire.
You can wrap _handleSearch with useCallback to fix that:
const _handleSearch = useCallback((query) => {
setIsLoading(true);
makeAndHandleRequest(query).then((resp) => {
setIsLoading(false);
setOptions(resp?.options || []);
});
}, []);

Context variable not updating in child component React Context

I have multiple child components (component generate through recursion) inside my main component. Now the problem is when I updated the context variable in the parent component the child component doesn't render the updated value
here is my context provider
function MainLayoutProvider({ children }) {
const [mainJson, setMainJson] = useState([
{
component:'section',
id:'1111',
content:null,
type:'section',
cmType:'normal',
class:'',
style:{},
props:{}
}
]);
return (
<MainLayout.Provider value={mainJson}>
<MainDispatchLayout.Provider value={setMainJson}>
{children}
</MainDispatchLayout.Provider>
</MainLayout.Provider>
);
}
Here I included it on my main component
function App() {
return (
<DndProvider backend={HTML5Backend}>
<MainLayoutProvider>
<PlayGround></PlayGround>
</MainLayoutProvider>
</DndProvider>
);
}
inside the PlayGround component, there is another component name DropSection, where I am updating the 'mainJson' value
function DropSection() {
const board = useContext(MainLayout);
const setBoard = useContext(MainDispatchLayout);
const [{ isOver }, drop] = useDrop(() => ({
accept: "image",
drop(item, monitor) {
const didDrop = monitor.didDrop();
if (!didDrop ) {
addItemToBoard(item.sectionName)
}
},
collect: (monitor) => ({
isOver: !!monitor.isOver(),
}),
}));
const addItemToBoard = (sectionName) => {
let newJson = {
component:sectionName,
id:IdGenerator(),
content:null,
type:'text',
cmType:'normal',
class:'',
style:{},
props:{}
}
setBoard((board) => [...board, newJson]);
};
return (
<div ref={drop}>
<h4 className="text-center">DropZone</h4>
{board.map((config,index) => <RenderCard key={config.id} config={config} />)}
</div>
);
}
but in the RenderCard component, the value of 'mainJson' is not updating or rendering, I am getting the old value which initializes in MainLayoutContext
function RenderCard({config}) {
const board =useContext(MainLayout);
const setBoard = useContext(MainDispatchLayout);
const [{ isOver }, drop] = useDrop(() => ({
accept: "image",
drop(item, monitor) {
const didDrop = monitor.didDrop();
if (!didDrop ) {
addItemToBoard(item.sectionName)
}
},
collect: (monitor) => ({
isOver: !!monitor.isOver(),
}),
}));
const addItemToBoard = async (sectionName) => {
let newJson = {
component:sectionName,
id:IdGenerator(),
content:null,
type:'text',
cmType:'normal',
class:'',
style:{},
props:{}
}
setBoard((board) => [...board, newJson]);
};
if(config.cmType == 'complex'){
return RenderComplexCard(config)
}
var configProperty = {
style: config.style,
id:config.id,
};
return React.createElement(
config.component,
configProperty,
config.content &&
( config.type == "section" && Array.isArray(config.content) ? config.content.map(c => <RenderCard key={c.id} config={c} />) : config.content )
);
}
I had similar issue and solved by using useRef hook, if you do not want to use useState.
Do not forget to reference the variable by using .current when accessing it.

useCallback and memoization

How the memorized callback function works? In some articles I read that the function is recreated if we do not use useCallback. But if it is recreated, should it be different from the prev version? In my code I didn't notice that there was a difference in callback functions.
My question is: Why in both cases, my set size is 1?
from off doc useCallback
Returns a memoized callback.
Pass an inline callback and an array of dependencies. useCallback will
return a memoized version of the callback that only changes if one of
the dependencies has changed. This is useful when passing callbacks to
optimized child components that rely on reference equality to prevent
unnecessary renders (e.g. shouldComponentUpdate).
import { useCallback } from "react";
const dataSource = [
{
id: 1,
model: "Honda",
color: "red",
},
{
id: 2,
model: "Mazda",
color: "yellow",
},
{
id: 3,
model: "Toyota",
color: "green",
},
];
const Car = ({ model, color, set, onCarClick }) => {
const onClick = () => onCarClick(model, color);
set.add(onCarClick);
console.log(set.size);
return (
<div onClick={onClick}>
Model: {model} Color: {color}
</div>
);
};
const CarsCallback = ({ cars, set }) => {
const onCarClick = (model, color) => {
console.log(model, color);
};
console.log("CarsCallback");
return (
<>
{cars.map((car) => {
return (
<Car
key={car.id}
set={set}
{...car}
onCarClick={onCarClick}
/>
);
})}
</>
);
};
const CarsUseCallback = ({ cars, set }) => {
const onCarClick = useCallback((model, color) => {
console.log(model, color);
}, []);
console.log("CarsUseCallback");
return (
<>
{cars.map((car) => {
return (
<Car
key={car.id}
{...car}
set={set}
onCarClick={onCarClick}
/>
);
})}
</>
);
};
export default function App() {
return (
<div className="App">
<CarsCallback cars={dataSource} set={new Set()} />
<CarsUseCallback cars={dataSource} set={new Set()} />
</div>
);
}
Because CarsUseCallback and CarsCallback was triggered once.
We can see the only one log of CarsUseCallback and CarsCallback.
If we re-render the CarsUseCallback and CarsCallback, we can see the size is 1 and 2.
const CarsCallback = ({ cars, set }) => {
const [count, setCount] = useState(1);
console.log('CarsCallback');
useEffect(() => {
setCount(2);
}, []);
// ...
};
const CarsUseCallback = ({ cars, set }) => {
const [count, setCount] = useState(1);
console.log('CarsUseCallback');
useEffect(() => {
setCount(2);
}, []);
// ...
}

Looking for a clever way to render multiple children component dynamically

I have a Modal component composed of a <Header> and a <Content>.
I want both children <Header> and <Content> to be rendered dynamically based on a given value ('modal type').
My first intuition was to create an object storing modal components like:
export const useModalDetails = (onClose = () => {}) => {
const [ modalDetails, setModalDetails ] = useMergeState({
Header: () => <div/>,
Content: () => <div/>,
onClose,
});
return [modalDetails, setModalDetails];
};
Then a function to set modal details base on the value type:
const onChangeModalType = (type) => {
if (type === 'type1') {
setModalDetails({
Header: () => <div>Modal Header Type 1</dov>,
Content: () => <div>Modal Content Type 1</div>,
open: true
})
} else if (type === 'type2') {
setModalDetails({
Header: () => <div>Modal Header Type 2</dov>,
Content: () => <div>Modal Content Type 2</div>,
open: true
})
}
}
Then I can use the react-hook in the <Modal> component:
import SemanticModal from 'semantic-ui-react'
const Modal = () => {
const [ modalDetails, setModalDetails ] = useMergeState({
Header: () => <></>,
Content: () => <></>,
open: false,
});
return [modalDetails, setModalDetails];
onChangeModalType('type2')
return (
<>
<SemanticModal
Header={modalDetails.Header}
Content={modalDetails.Content}
open={modalDetails.open}
/>
<button onClick={() => onChangeModalType('type1')}>load type 1</button>
<button onClick={() => onChangeModalType('type2')}>load type 2</button>
</>
)
}
Bear in mind I have 12 different types of content, and header for a <Modal>, that means 12 if, else statements for each.
Do you have any idea how I could develop that in a smarter, clever, more readable, maintainable... WAY?
If you're decided on having an individual modal component that switches behaviour based on state/props you could approach it like this:
const modals = {
type1: {
Header: <div>Modal1 Header</div>,
Content: <div>Modal1 Content</div>,
open: true,
},
type2: {
Header: <div>Modal2 Header</div>,
Content: <div>Modal2 Content</div>,
open: true,
},
};
const Modal = ({type}) => {
const [modalType, setModalType] = useState(type);
const { Header, Content, open } = modals[modalType];
return (
<>
<SemanticModal
Header={Header}
Content={Content}
open={open}
/>
</>
);
};
An alternative to consider is creating a reusable BaseModal component which you extend into a seperate component for each use case:
const BaseModal = ({ Header, Content, open }) => {
return (
<>
<SemanticModal Header={Header} Content={Content} open={open} />
</>
);
};
const Modal1 = () => {
return (
<BaseModal
Header={<div>Modal1 Header</div>}
Content={<div>Modal1 Content</div>}
open={true}
/>
);
};
The nice thing about this pattern is that you still get to share the common elements of all modals whilst selectively injecting the parts that need to differ

Resources