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.
Related
Component diagram:
"Main"
|--"Side"--"CategoryPicker"
|
|--"ItemBoard"
categoryPicker gets the chosen value.
const filterResultHandler = (e) => {
props.onFilterChange(e.target.value);}
...
onChange={filterResultHandler}
And lift up the value to Side.
const [filterState, setFilterState] = useState("all");
const onFilterChangeHandler = () => { props.onPassData(setFilterState);};
...
<CategoryPicker selected={filterState} onFilterChange={onFilterChangeHandler} />
Then I repeat to lift value to the Main.
(Up to this point I have console.log the value and it seemed OK.)
const [recData, setRecData] = useState("all");
const onFilterChangeHandler = (passedData) => {
setRecData(passedData);};
<Side onPassData={onFilterChangeHandler} selected={recData} />
Then pass it down to Itemboard as a prop.
<ItemBoard items={items} recData={recData} />
In ItemBoard I am trying to filter the array then compare to later map it and display filtered components.
const filteredProducts = props.items.filter((product) => {
return (product.cat.toString() === props.recData)})
{filteredProducts.map((product, index) => (
<Item cat={product.cat} />
))}
Warning: Cannot update a component (Side) while rendering a different component (Main). To locate the bad setState() call inside Main
Where am I loosing my logic?
PS.
Trying to focus on understanding how lifting up and passing props works, not looking for better solutions to the problem right now.
you have the bad setState use in this code:
const [recData, setRecData] = useState("all");
const onFilterChangeHandler = (passedData) => {
setRecData(passedData);};
<Side onPassData={onFilterChangeHandler} selected={recData} />
why you are passing this selected={recData} data again to Side component, you are updating the state of Main component from Side component and passing the selected={recData} again, remove this and try again
I'm working on a self-contained component with the purpose of using it in many different repositories, the problem I'm facing is passing some of the child states to the parent component to upload the data to the server. Let's take an example where we have a parent component (Form) and a child component (Input), but the child will handle its value and events.
Parent Component:
const Form = () => {
const ref = useRef(null);
const handleSubmit = () => {
uploadData(ref.current)
}
return {
<Input forwaredRef={ref} />
<Button onClick={handleSubmit}>Submit</Button>
}
}
Child Component
const Input = ({forwardRef}) => {
const ref = useRef(null);
const handleChange = (e) => {
//set input data to the ref from parent
forwardRef.current = e.target.value
}
return {
<input onChange={handleChange} />
}
}
My question is will this be a bad implementation? Will there be any downside of passing ref and use it like this case? The reason I want to use ref because I don't want the child component to re-render on every user event (in this case is user input).
Thank you for all of your inputs.
I have two React functional components Parent and Node. Parent renders list of Node components and passes click handler to each Node. When I click on the Node, in the Parent the new state is set and all components rerenders.
const Parent = (props) => {
const [clickedList, setClickedList] = useState([]);
const onNodeClick = (node) => {
let newList = [...clickedList, node];
setClickedList(newList);
}
return (
<div>
{someNodes.map(node => <Node key={node.id} node={node} onNodeClick={props.onNodeClick}/>)}
</div>
)
}
const Node = (props) => {
const handleClick = (node) => {
props.onNodeClick(node);
}
return (
<div onClick={() => handleClick(node)}>
{props.node.name}
</div>
)
}
The list of nodes contains more than 400 items. Rerendering of each component takes time. I tried to use React.memo for Node component, but the feature is that I pass the function to Node, which has different references after each rerendering of the Parent and React.memo component rerenders too. Also I tried to provide a custom comparison function for React.memo where I skip comparsion of passed function. The result is components do not rerender, but component state clickedList does not change in the click handler in the Parent.
I also tried to use useCallback hook on the click handler in the Parent, the result is similar. If I give no dependencies [] then component state clickedList does not change.
const memorizedNodeClick = useCallback(
(node) => {
onNodeClick(node);
}, [],
);
If I give state in dependencies [clickedList] then all more than 400 components rerenders
const memorizedNodeClick = useCallback(
(node) => {
onNodeClick(node);
}, [clickedList],
);
How can I prevent unnecessary component rendering and keep all clicked items in the clickedList state?
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>
}
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.