Setting values in a child component in react - javascript

In following scenario I have a very simple child component (arrow component) that acts like a counter. The counter can increment a simple value. What if the parent wants to set that counter to x? The obvious answer would be just have a parameter that you set to x. But as my variable in the parent component is a hook, it would be read only. Would you then have to add a parameter for both the getter and setter variable on the child? And then the child would work on those two variables? Or what is best practice here? Or is there some way to transfer the complete hook to the child? It is really not beautiful that you must add two parameters to the child in my world.
const App = props => {
const resetChildCounter = () => {
alert("what to do here");
};
return (
<div className="App">
<Child />
<button onClick={resetChildCounter}>
Reset child counter from parent
</button>
<button onClick={resetChildCounter}>Set child counter to 5</button>
</div>
);
};
const Child = props => {
const [counter, setCounter] = useState(0);
return (
<div>
<span>Counter is: {counter}</span>
<button
onClick={() => {
setCounter(counter + 1);
}}
>
Increment
</button>
</div>
);
};

The Child component acts like a "hybrid" component, it acts as a controlled component and an uncontrolled component.
A simple implementation of a hybrid component looks like this:
const NOOP = () => {}
const Child = ({ initial = 0, onChange = NOOP }) => {
const [counter, setCounter] = useState(initial);
useEffect(() => {
setCounter(initial);
}, [initial]);
useEffect(() => {
onChange(counter);
}, [onChange, counter]);
return (
<div>
<span>Counter is: {counter}</span>
<button onClick={() => setCounter(p => p + 1)}>Increment</button>
</div>
);
};
export default Child;
In this case, as stated by the OP:
I would like to make a self-maintained child component with much more stuff than a simple button.
You get a stand-alone component with a controlled feature.

You're correct.
If you need to set it from the parent, you need to store it as a state of the parent, not the child. And if you need to use it on the child, you need to pass it down to the child. Finally, if you need to set it from the child as well, you don't do it directly, but through a setter prop passed down from the parent.
It is really not beautiful that you must add two parameters to the child in my world.
If you have a state in either parent or child, and then if it's possible for you to mutate it from both sides, that is more ugly. It will break the philosophy of React.

Related

Passing props from parent to child using hooks

I am new to react and I am trying to pass props from a parent function to a child. The parameters I am passing "square_state" and "setSquare_state" are not being recognized in the useSquare or handle_square_click function. I am using the following https://designcode.io/react-hooks-handbook-props as a reference.
const handle_square_click = (props) => {
props.setSquare_state(player)
setGetplayer(true)
}
const useSquare = (square_state, setSquare_state) => {
// Hook for square state management and rendering
return (
<button className="square" onClick={<handle_square_click setSquare_state={setSquare_state}/> }>
{square_state}
</button>
);
}
// ------------------------------------------------------------------
// Board Function
const Board = ({player}) => {
let status = "Next Player : " + player
const [square1_state, setSquare1_state] = useState(1);
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
<useSquare
square_state={square1_state}
setSquare_state={setSquare1_state}
/>
There are a number of problems here.
Functions prefixed with use should be reserved for custom hooks. If you have a function that returns a React component, you should follow the React standards and give it an upper-case name, one that doesn't start with use - for example, you could call the square function Square.
When you pass props like
square_state={square1_state}
setSquare_state={setSquare1_state}
The child then sees them as the properties of an object, where that object is the first argument to the function - like you're doing with handle_square_click. So
const useSquare = (square_state, setSquare_state) => {
should be
const Square = ({ squareState, setSquareState }) => {
(using the very common camelCasing convention)
handle_square_click is a plain function, not a component, so onClick={<handle_square_click makes no sense. Declare the function inside Square, and reference just that function when passing the onClick prop. Declare the function inside Square to avoid having to pass things around.
The click handler attempts to reference player, which is not in scope. You need to pass it down from the parent. (setGetplayer probably needs to be passed down too, but its declaration isn't shown in the code in the question)
const Board = ({ player }) => {
const [squareState, setSquareState] = useState(1);
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
<Square
squareState={squareState}
setSquareState={setSquareState}
player={player}
/>
and
const Square = ({ squareState, setSquareState, player }) => {
const handleSquareClick = () => {
setSquareState(player);
// setGetplayer(true);
};
return (
<button className="square" onClick={handleSquareClick}>
{squareState}
</button>
);
};
<useSquare
square_state={square1_state}
setSquare_state={setSquare1_state}
/>
This not how to use child component in React or hooks!
You should name your (custom) component starting with capital letter and call hook inside the component.
So as the (prev answer that appeared as I was typing this), you should refactor code.

What is the point to use useCallback in a component without React.memo?

Consider example:
const Child = () => {
console.log("I did re-render!");
return null;
};
const App = () => {
const [_, setValue] = useState();
const fn = useCallback(() => {
// do something
}, []);
return (
<div>
<button onClick={() => setValue(Math.random)}>click</button>
<Child fn={fn} />
</div>
);
};
With every state change in App (click the button), the Child component re-renders, even if the passed prop fn is wrapped with useCallback. However, if I wrap Child with React.memo, it starts to work correctly - it does not re-render when parent re-renders.
My question: What's the point of using useCallbacks without React.memo?? Should I always use React.memo if I dont want the component to always re-render if its parent re-renders?
Should useCallbacks always be used with React.memo? Because it seems like they are senseless and useless without React.memo.
Playground: https://codesandbox.io/s/peaceful-khorana-nrojpb?file=/src/App.js
My question: What's the point of using useCallbacks without React.memo??
There is none unless you otherwise tell React to compare based on refernece down the line.
Should I always use React.memo if I dont want the component to always re-render if its parent re-renders?
Yes, or something equivalent.
Should useCallbacks always be used with React.memo? Because it seems like they are senseless and useless without React.memo.
Yes, unless you do something equivalent.
Just to elaborate - other than React.memo you can always wrap a sub-render with useMemo:
const App = () => {
const [_, setValue] = useState();
const fn = useCallback(() => {
// do something
}, []);
const child = useMemo(() => <Child fn={fn} />, [fn]);
return (
<div>
<button onClick={() => setValue(Math.random)}>click</button>
{child}
</div>
);
};
Or "roll your own" with useRef+useEffect or use class components and override shouldComponentUpdate or inherit from React.PureComponent.

Can I setState in Parent from Child component?

I would like to clean my code a bit so instead of having one long component I would like to create child component.
In my Parent I have some states I would like to update onClick in Child.
Parent:
const [plan, setPlan] = useState('myPlan');
const [months, setMonths] = useState('2');
const [price, setPrice] = useState(200);
<PlanSelection props={props}
item={selectedData}
itemPlan={plan}
/>
Child
const PlanSelection = ({ props, item, itemPlan }) => {
function handleSubsribe() {
props.setPlan('Subsribe Only');
props.setPrice(item.price);
props.setMonths('24+');
}
function handlePay() {
props.setPlan('Pay to Own');
props.setPrice(item.pay);
props.setMonths('12-24');
}
And just trying to call the functions (in Child component)
<button onClick={handleSubscribe} />
<button onClick={handlePay} />
Using the code above throws error after clicking in one of the buttons:
TypeError: props.setPlan is not a function
But if I don't pass props, setPlan, setPrice, .... will be undefined. Is there a way how to handle it ?
Problem
<PlanSelection props={props}
item={selectedData}
itemPlan={plan}
/>
You did not pass setPlan to child, you have only passed props, which props has nothing to do with state, selectedData which I'm not sure what's that, and plan, which is the state. In summary you did not pass anything about setState to child component
Solution
Parent
const [plan, setPlan] = useState('myPlan');
<PlanSelection
setPlan={setPlan}
/>
Child
const PlanSelection = ({ setPlan }) => {
function handleSubsribe() {
setPlan('Subsribe Only');
}
function handlePay() {
setPlan('Pay to Own');
}
In the code above I've only used setPlan as an example to show you how to setState from child component, you can apply same logic to the rest.
UPDATES
Just realized I've made a mistake which you should be worth noting. In your child component you have ({ setPlan }), this is known as destructuring, which is you are extracting setPlan out of props, hence in your child component, you should not be accessing props.setPlan but setPlan instead. Do take a look at the answer above again
You can simply pass your setState functions as props to your child component. One simple way to do that is:
const [plan, setPlan] = useState('myPlan');
const [months, setMonths] = useState('2');
const [price, setPrice] = useState(200);
<PlanSelection setPlan={setPlan}
item={selectedData}
itemPlan={plan}
/>
Here you will be able to update plan state from the child component.
If you want to pass all your setStates to the child, try this.
const handlers = {setPlan, setMonths, setPrice}
<PlanSelection handlers={handlers}
item={selectedData}
itemPlan={plan}
/>
const PlanSelection = ({ handlers, item, itemPlan }) => {
You can use setPlan as handlers.setPlan instead of props.setPlan.

How do I pass a prop to a react component yet not update that prop in the child when parent changes?

const Parent = () => {
const [thing, setThing] = useState('a string');
// code to update thing
return <Child thing={thing} />
}
const Child = props => {
return <div>I want {props.thing} to be initial value without updating</div>
}
If I want 'thing' to be passed from parent to child but not update when parent changes it, how do I accomplish this?
I've tried useEffect, tried cloning 'thing' to a constant within Child...
I would use useEffect with the empty [] for dependencies, so that it only runs once.
From Reactjs.org:
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run.
const Child = props => {
let thing = props.thing;
let [initialValue, setInitialValue] = useState("");
useEffect(() => {
setInitialValue(thing);
}, []);
return (
<div>
I want <strong>{initialValue}</strong> to be initial value without
updating
</div>
);
};
CodeSandbox
Maybe you can try setting the thing to a variable in the child component when it's null. So when the parent update, you don't have to update the child.
let childThing = null;
const Child = props => {
if(childThing === null)
childThing = props.thing
return <div>I want {childThing} to be initial value without updating</div>
}

Is it ok to define nested component with React Hooks?

I am using Hooks + Render Props in my Preact/React application. Instead of passing simple state to the render(), I am passing a component function that can be used by the user of this component.
Also, my component is Hybrid in a sense that that SortDropdown is not UI-Less like we typically have with Render Prop components. It provides some basic UI in the form of anchor element which triggers the actual drop down and shows the custom content via Render Props render function. This is how I have implemented it:
export function SortDropdown(props: SortDropdownProps) {
const { render, onSelect, value } = props;
const anchor = (ref: Ref<Element>, open: VoidFunction) => (
<SortButton buttonElm={ref} onClick={open} />
);
const surfaceContent = (ref: Ref<any>, select: SortSelectFn) => {
// NESTED INNER COMPONENT - IS IT RIGHT?
function SortMenuItem(props: SortMenuItemProps) {
const { children, field } = props;
// THE HOOK IN QUESTION
const [someState, setSomeState] = useState(false);
const isSelected = value.key === field.key;
const onClick = () => select(field);
return (
<SortMenuButton canTrigger={someState} selected={someState} onClick={onClick}>
{children}
</SortMenuButton>
);
}
return (
<div ref={ref}>{render(SortMenuItem, select)}</div>
);
};
return (
<BaseDropdown anchor={anchor} surface={surfaceContent} onSelect={onSelect as any} />
);
}
Here I have two questions. First, does defining an inner nested component, here - SortMenuItem which is then passed to the render function violates the rules of Hooks? The component may be called n-times or may not be used at all by the calling component.
Second, is it a right practice to define a nested Higher-Order Component, here - SortMenuItem being an abstraction over SortMenuButton?

Categories

Resources