Don't update child component - reactjs

I have two children Components, when I onChange in first children, then the second children re render, I don't want to the second children re render. Online code example:
https://codesandbox.io/s/ji-ben-antd-4-24-0-forked-efg56l?file=/demo.tsx
const ChildA = (props: {
name: string;
changeValue: (key: string, value: any) => void;
}) => {
const { name, changeValue } = props;
return (
<Input
value={name}
onChange={(e) => {
changeValue("name", e.target.value);
}}
/>
);
};
const ChildB = (props: {
age: number;
changeValue: (key: string, value: any) => void;
}) => {
const { age, changeValue } = props;
console.log("==when I change name====, this component re-render");
return (
<InputNumber
value={age}
onChange={(e) => {
changeValue("age", e);
}}
/>
);
};
const App: React.FC = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const [saveValue, setSaveValue] = useState({
name: "wang",
age: 18
});
const showModal = () => {
setIsModalOpen(true);
};
const handleOk = () => {
// send value
console.log("====saveValue==", saveValue);
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
const changeValue = (key: string, value: any) => {
const newValue = JSON.parse(JSON.stringify(saveValue));
newValue[key] = value;
setSaveValue(newValue);
};
return (
<>
<Button type="primary" onClick={showModal}>
Open Modal
</Button>
<Modal
title="Basic Modal"
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
>
<ChildA name={saveValue?.name} changeValue={changeValue} />
<ChildB age={saveValue?.age} changeValue={changeValue} />
</Modal>
</>
);
};
When I change the name ,I don`t want to Child B re-render
The actual situation is that there are many sub-components in a Modal. When you click OK, the value of the sub-component is obtained, saved and sent to the server. If you have good design component ideas, please share

I don't want to the second children re render.
Wrap ChildB with React.memo for a basic memoization.
const ChildB = memo(...);
Wrap the changeValue function with React.useCallback to persist the instance.
const changeValue = useCallback(...);
Slightly modify the changeValue function so it does not use the saveValue as a dependency.
setSaveValue((prev) => {
const newValue = JSON.parse(JSON.stringify(prev));
newValue[key] = value;
return newValue;
});
Codesandbox demo

Related

React Send Data from Child to Parent with Typescript

I am trying to send data from Child Component to Parent component, using Typescript. There are lot of resources, but many don't explore typescript concept.
On the Parent, how would I set the ProductType event? Is this the proper way using React.ChangeEvent? We are using a dropdown selector with Material UI.
onProductTypeChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setProductType(e.target.value)}
Full Code:
Parent:
const ProductExport = () => {
const [productType, setProductType] = useState<undefined | string>('');
return (
<>
<ProductFilters
productTypeValue={productType}
onProductTypeChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setProductType(e.target.value)}
/>
Child:
type Props = {
productTypeValue?: string;
onProductTypeChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
};
const ProductFilters = ({
productTypeValue,
onProductTypeChange
}: Props) => {
return (
<Box>
<Accordion>
<AccordionSummary>
<Typography>Products</Typography>
</AccordionSummary>
<AccordionDetails>
<Box >
<Box>
<TextField
select
value={productTypeValue}
onChange={onProductTypeChange}
label={'Select Product Type'}
>
Usually you hide the internal details in the Child (the actual event), and expose to the parent only the result (new data):
// Parent
const ProductExport = () => {
const [productType, setProductType] = useState<undefined | string>('');
return (
<ProductFilters
productTypeValue={productType}
onProductTypeChange={setProductType}
/>
);
}
// Child
type Props = {
productTypeValue?: string;
onProductTypeChange?: (newType: string) => void;
};
const ProductFilters = ({
productTypeValue,
onProductTypeChange
}: Props) => {
return (
<TextField
select
value={productTypeValue}
onChange={(event) => onProductTypeChange?.(event.target.value)}
label={'Select Product Type'}
/>
);
}
React has a preprepared type for multiple event handlers, such ChangeEventHandler (TS playground):
type Props = {
productTypeValue?: string;
onProductTypeChange: ChangeEventHandler<HTMLInputElement>;
};
const ProductFilters = ({
productTypeValue,
onProductTypeChange
}: Props) => null;
const ProductExport = () => {
const [productType, setProductType] = useState<undefined | string>('');
const onProductTypeChange: ChangeEventHandler<HTMLInputElement> = e => setProductType(e.target.value);
return (
<ProductFilters
productTypeValue={productType}
onProductTypeChange={onProductTypeChange}
/>
);
};

Child context undefined

In the following code I can access a context at the parent and then it's undefined in a child. It works locally with simple FC setups, but fails downstream in a class component.
const HookDialog = () => {
const { data, setData } = useDialog(1); // I work fine
return (
<DialogHook>
<DialogContent>
<h1>Value: {data}</h1>
</DialogContent>
<Footer>
<FooterButton name="positive">Positive</FooterButton>
</Footer>
</DialogHook>
);
}
export const FooterButton: React.FC<FooterButtonProps> = (
{
children,
name,
className,
...props
}) => {
const dialogHook = useDialog(); // I'm undefined!
return(
<Button {...props} className={cssNames} ...>
{children}
</Button>
);
}
export const DialogProvider = props => {
const [dialog, setDialog] = useState<ReactElement<typeof Dialog>>();
const [data, setData] = useState<any>();
return (
<DialogContextHook.Provider value={{ dialog, setDialog, data, setData }} {...props} >
{props.children}
{dialog}
</DialogContextHook.Provider>
)
}
type CloseEvent = (source: string, data:any) => void;
interface useDialog extends DialogContextHook {
close: (source: string) => void;
}
export const useDialog = (
initialValue?: any,
onClose?: CloseEvent) => {
const context = useContext(DialogContextHook);
if (!context) {
throw new Error('useDialog must be used within a DialogProvider');
}
useEffect(() => {
context.setData(initialValue);
},[])
const close = (source: string) => {
context.setDialog(undefined);
onClose?.(source, context.data);
}
return { ...context, close };
}
<DialogProvider>
<VOITable/>
</DialogProvider>
Update
I recreated FooterButton in the downstream project and the same code works, just not when imported.

React Functional Component - Binding handler to an array

I am trying to bind a handler to an array position that prints the state of the object. The function is being called but the state is always in the initial state.
import React from "react";
export default function MyForm() {
const [state, setState] = React.useState({
buttons: [],
object: null
});
const stateRef = React.useRef(null);
const onExecButtonHandler = (index) => {
console.log("[MyForm][execButton]: " + index);
alert(JSON.stringify(state.object));
alert(JSON.stringify(stateRef.current.object));
};
const onChangeHandler = (event)=>{
let obj = state.object
obj['description'] = event.target.value
console.log(obj)
setState((state) => ({ ...state, object: obj }));
stateRef.current = state;
}
const generateButtons = () => {
let buttons = [];
//buttons.push([name,(event)=>onExecButtonHandler(event,i),obj[i].icon,obj[i].jsFunction])
buttons.push([
"Show Hi",
onExecButtonHandler.bind(this, 1),
"icon",
"custom function"
]);
return buttons;
};
React.useEffect(() => {
console.log("[MyForm][useEffect]");
let buttons = generateButtons();
setState({ buttons: buttons, object: { description: "hi" } });
stateRef.current = state;
}, []);
return (
<>
{state.object && (
<form>
<input text="text" onChange={onChangeHandler} value={state.object.description} />
<input
type="button"
value="Click me"
onClick={() => {
state.buttons[0][1]();
}}
/>
</form>
)}
</>
);
}
You can test here: https://codesandbox.io/s/charming-robinson-g1yuo?fontsize=14&hidenavigation=1&theme=dark
I was able to access the latest state of the object using the "useRef" hook. I'd like to access using the state though. Is there some way to do so?

How to select / deselect checkbox in react typescript?

I am working with checkboxes in react typescript. I am handling onChange event but it's not working properly.
code for parent component -
import { myCheckboxData } from "./constant";
export const ParentComponent = (props: Test) => {
const myCheckboxDataAPI = myCheckboxData!.map((data) => Object.assign({}, data));
const initialData: InterfaceA = {
myType: myCheckboxDataAPI!,
};
const [myInitialState, setMyInitialState] = React.useState(initialData);
const onChange = (type: string, selectedValue: boolean) => {
const index: number = myCheckboxDataAPI!.findIndex((x) => x.label === selectType);
if (index > -1) {
myCheckboxDataAPI![index].checked = !selectedValue;
setMyInitialState({
...myInitialState,
myType: myCheckboxDataAPI,
});
}
};
return <FirstChild onChange={onChange} checkboxData={myInitialState} />;
};
code for FirstChild Component -
import react from "react";
interface Test1 {
checkboxData: Array<ICheckboxProps>;
onChange: (type: string, value: boolean) => void;
}
export const FirstChild = (props: Test1) => {
return (
<Grid container wrap='nowrap'>
{myData.map((item) => {
return (
<div key={`new_${item.label}`}>
<Checkbox
id={`new_${item.label}`}
checked={item.checked}
label={item.label}
onChange={(event: React.ChangeEvent<HTMLInputElement>): void => {
onChange(String(item.label), item.checked);
}}
/>
</div>
);
})}
</Grid>
);
};
myCheckboxData has 2 values in it. I am able to select both values one by one. I am also able to select/deselect a particular value. But if I want to deselect both values then it's not working. How can I make it work perfectly ?

How to edit input with default value in ReactJS

So, are there any other ways, rather than changing value to defaultValue to change the input value manually? (When using defaultValue My program doesn't work properly)
ChildComponent = React.memo(
({ name, index, onChange, onDelete
}) => {
return (
<div>
<input value={name} onChange={onChange(index, name)} />
<button onClick = {() => onDelete(index)}>delete</button>
</div>
);
}
);
function App() {
const [names, setNames] = React.useState(["First", "Second"]);
const newName = React.useRef();
const onNameChange = (index: number, newName: string) => (event: {
target: { value: string };
}) => {
names[index] = event.target.value;
setNames(names);
};
function onNameDelete(index: number) {
const nameArr = [...names];
nameArr.splice(index, 1);
setNames(nameArr);
}
return (
<div>
{names.map((name, index) => (
<ChildComponent
key={index}
name={name}
index={index}
onChange={onNameChange}
onDelete={onNameDelete}
/>
))}
</div>
);
}
The issue is in your onChange input handler in your ChildComponent. You are not using passed value by user to input at all. You need to write it similarly to your onDelete handler (with use the new value, in my snippet stored in event.target.value):
ChildComponent = React.memo(
({ name, index, onChange, onDelete
}) => {
return (
<div>
<input value={name} onChange={(event) => onChange(index, event.target.value)} />
<button onClick = {() => onDelete(index)}>delete</button>
</div>
);
}
);
Look also on the definition of the input change handler in Html documentation.
EDIT:
Another issue is your handler in your parent controller:
const onNameChange = (index: number, newName: string) => (event: {
target: { value: string };
}) => {
names[index] = event.target.value; //this is BAD, do not change your state value directly, moreover, the event is undefined here
setNames(names);
};
You need to update item in the array immutably (source Updating an Item in an Array):
const onNameChange = (index: number, newName: string) => (event: {
target: { value: string };
}) => {
const newNames = names.map((item, itemIndex) => {
if (itemIndex !== index) {
// This isn't the item we care about - keep it as-is
return item
}
// Otherwise, this is the one we want - return an updated value
return newName;
});
setNames(newNames);
};

Resources