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.
Related
I have three components First, Second and Third that need to render one after the other.
My App looks like this at the moment:
function App() {
return (
<First/>
)
}
So ideally, there's a form inside First that on submission (onSubmit probably) triggers rendering the Second component, essentially getting replaced in the DOM. The Second after some logic triggers rendering the Third component and also passes a value down to it. I'm not sure how to go on about it.
I tried using the useState hook to set a boolean state to render one of the first two components but I would need to render First, then somehow from within it change the set state in the parent which then checks the boolean and renders the second. Not sure how to do that. Something like below?
function App() {
const { isReady, setIsReady } = useState(false);
return (
isReady
? <First/> //inside this I need the state to change on form submit and propagate back up to the parent which checks the state value and renders the second?
: <Second/>
);
}
I'm mostly sure this isn't the right way to do it.
Also need to figure out how to pass the value onto another component at the time of rendering it and getting replaced in the DOM. So how does one render multiple components one after the other on interaction inside each? A button click for example?
Would greatly appreciate some guidance for this.
then somehow from within it change the set state in the parent which then checks the boolean and renders the second.
You're actually on the right track.
In React, when you're talking about UI changes, you have to manage some state.
So we got that part out of the way.
Now, what we can do in this case is manage said state in the parent component and pass functions to the children components as props in-order to allow them to control the relevant UI changes.
Example:
function App() {
const { state, setState } = useState({
isFirstVisible: true,
isSecondVisible: false,
isThirdVisible: false,
});
const onToggleSecondComponent = (status) => {
setState(prevState => ({
...prevState,
isSecondVisible: status
}))
}
const onToggleThirdComponent = (status) => {
setState(prevState => ({
...prevState,
isThirdVisible: status
}))
}
return (
{state.isFirstVisible && <First onToggleSecondComponent={onToggleSecondComponent} /> }
{state.isSecondVisible && <Second onToggleThirdComponent={onToggleThirdComponent} /> }
{state.isThirdVisible && <Third/> }
);
}
Then you can use the props in the child components.
Example usage:
function First({ onToggleSecondComponent }) {
return (
<form onSubmit={onToggleSecondComponent}
...
</form
)
}
Note that there are other ways to pass these arguments.
For example, you can have one function in the parent comp that handles them all, or you can just pass setState to the children and have them do the logic.
Either way, that's a solid way of achieving your desired outcome.
Seen as your saying there are stages, rather than having a state for each stage, just have a state for the current stage, you can then just increment the stage state to move onto the next form.
Below is a simple example, I've also used a useRef to handle parent / child state, basically just pass the state to the children and the children can update the state. On the final submit I'm just JSON.stringify the state for debug..
const FormContext = React.createContext();
const useForm = () => React.useContext(FormContext);
function FormStage1({state}) {
const [name, setName] = React.useState('');
state.name = name;
return <div>
Stage1:<br/>
name: <input value={name} onChange={e => setName(e.target.value)}/>
</div>
}
function FormStage2({state}) {
const [address, setAddress] = React.useState('');
state.address = address;
return <div>
Stage2:<br/>
address: <input value={address} onChange={e => setAddress(e.target.value)}/>
</div>
}
function FormStage3({state}) {
const [hobbies, setHobbies] = React.useState('');
state.hobbies = hobbies;
return <div>
Stage3:<br/>
hobbies: <input value={hobbies} onChange={e => setHobbies(e.target.value)}/>
</div>
}
function Form() {
const [stage, setStage] = React.useState(1);
const state = React.useRef({}).current;
let Stage;
if (stage === 1) Stage = FormStage1
else if (stage === 2) Stage = FormStage2
else if (stage === 3) Stage = FormStage3
else Stage = null;
return <form onSubmit={e => {
e.preventDefault();
setStage(s => s + 1);
}}>
{Stage
? <React.Fragment>
<Stage state={state}/>
<div>
<button>Submit</button>
</div>
</React.Fragment>
: <div>
{JSON.stringify(state)}
</div>
}
</form>
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Form/>);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="root"></div>
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.
I am using Next js for an application.
The code components have a global state stored in parent and passed down to child components using ReactContext.
In one of the components, when I am trying to create a state component by destructuring a parent state it is behaving in an unpredictable manner.
At times the destructuring initializes a local state, at times it does not.
const ContextEntry = () => {
const { widget, pageStates } = useContext(WidgetContext);
const { currentLanguage } = pageStates;
// this statement produces an output..
console.log("ContextEntry Component widget context ", widget.context);
//?? why not getting destructured
// const [contextEntryData, setContextEntryData] = useState(widget.context);
const [contextEntryData, setContextEntryData] = useState({
...widget.context,
});
console.log("State object ", contextEntryData);
return (
<div className={style.container}>
{widget.fields.map((field) => {
return (
<CustomFieldWidget
field={field}
key={field.fieldId}
updateContextEntry={updateContextEntry}
/>
);
})}
</div>
);
};
export default ContextEntry;
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?
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.