Child state not updated while child props updated - reactjs

I have a Parent component which generates (and renders) a list of Child components like this:
const Parent= ({ user }) => {
const [state, setState] = useState ({selectedGames: null
currentGroup: null})
...
let allGames = state.selectedGames ? state.selectedGames.map(g => <Child
game={g} user={user} disabled={state.currentGroup.isLocked && !user.admin} />) : null
...
return ( <div> {allGames} </div>)
}
The Child component is also a stateful component, where the state (displayInfo) is only used to handle a toggle behaviour (hide/display extra data):
const Child = ({ game, user, disabled = false }) => {
const [displayInfo, setDisplayInfo] = useState(false)
const toggleDisplayInfo = () =>
{
const currentState = displayInfo
setDisplayInfo(!currentState)
}
...
<p className='control is-pulled-right'>
<button class={displayInfo ? "button is-info is-light" : "button is-info"} onClick = {toggleDisplayInfo}>
<span className='icon'>
<BsFillInfoSquareFill />
</span>
</button>
</p>
...
return ({displayInfo ? <p> Extra data displayed </p> : null})
}
When state.selectedGames is modified (for example when a user interacts with the Parent component through a select), state.selectedGames is correctly updated BUT the state of the i-th Child component stay in its previous state. As an example, let's say:
I clicked the button from the i-th Child component thus displaying "Extra data displayed" for this i-th Child only
I interact with the Parent component thus modifying state.selectedGames (no common element between current and previous state.selectedGames)
I can see that all Child have been correctly updated according to their new props (game, user, disable) but the i-th Child still has its displayInfo set to true (its state has thus not been reset contrary to what I would have (naively) expected) thus displaying "Extra data displayed".
EDIT:
After reading several SO topics on similar subjects, it appears using a key prop could solves this (note that each game passed to the Child component through the game props has its own unique ID). I have also read that such a key prop is not directly expose so I'm a bit lost...
Does passing an extra key prop with key={g.ID} to the Child component would force a re-rendering?

Related

React - child not reverting to parent state when child state not saved

I fear that I've made a complete mess of my React structure.
I have a parent component/view that contains an object of attributes that are the key element of the content on the page. The ideal is to have a component that allows the user to update these attributes, and when they do so the content on the page updates. To avoid having the content update on every single attribute update/click, I want to implement a 'revert'/'save' button on the attribute update screen.
The problem I'm having is that I'm passing the state update function from the main parent to the child components, and the save is updating the parent and then the child switches out of 'edit mode' and displays the correct value, but when I click revert it doesn't update the parent (good) but it still maintains the local state of the child rather than rerendering to the 'true' parent state so the child implies it has been updated when it actually hasn't.
I'm a hobbyist React developer so I'm hoping that there is just something wrong with my setup that is easily rectifiable.
export const MainViewParent = () => {
const [playerAttributes, updatePlayerAttributes] = useState(basePlayerAttributes);
const revertPlayerAttributes = () => {
updatePlayerAttributes({...playerAttributes});
};
// Fill on first mount
useEffect(() => {
// Perform logic that uses attributes here to initialise page
}, []);
const adjustPlayerAttributes = (player, newAttributes) => {
// Update the particular attributes
playerAttributes[player] = newAttributes;
updatePlayerAttributes({...playerAttributes});
};
return (
<PlayerAttributes
playerAttributes={playerAttributes}
playerNames={["Player 1"]}
playerKeys={["player1"]}
updatePlayerAttributes={adjustPlayerAttributes} // Update the 'base' attributes, requires player key - uses function that then calls useState update function
revertPlayerAttributes={revertPlayerAttributes}
/>
);
}
// Child component one - renders another child component for each player
export const PlayerAttributes = ({
updatePlayerAttributes
}) => {
return (
<AnotherChildComponent />
);
};
// Child component 2
const AnotherChildComponent = ({ ...someThings, updatePlayerAttributes }) => {
const [editMode, updateEditMode] = useState(false); // Toggle for showing update graph elements
const [local, updateLocal] = useState({...playerAttributes}); // Store version of parent data to store changes prior to revert/save logic
// Just update the local state as the 'save' button hasn't been pressed
const updateAttributes = ( attributeGroup, attributeKey, attributeValue) => {
local[attributeGroup][attributeKey] = attributeValue;
updateLocal({...local});
};
const revertAttributes = () => {
// Need to update local back to the original state of playerAttributes
updateLocal(playerAttributes);
// revertPlayerAttributes();
updateEditMode(false); // Toggle edit mode back
};
const saveDetails = () => {
updatePlayerAttributes(playerKey, local);
updateEditMode(false);
};
return (
<FormElement
editMode={editMode}
// Should pass in a current value
playerAttributes={playerAttributes}
attributeOptions={[0.2, 0.3, 0.4, 0.5, 0.6]}
updateFunction={updateAttributes} // When you click update the local variable
/> // This handles conditional display of data depending on editMode above
);
}
// Child component 3...
const FormElement = ({ editMode, playerAttributes, attributeOptions, updateFunction, attributeGroup, attributeKey }) => {
if (editMode) {
return (
<div>
{attributeOptions.map(option =>
<div
key={option}
onClick={() => updateFunction(attributeGroup, attributeKey, option)}
>
{option)
</div>
)}
</div>
);
}
return (
<div>{//attribute of interest}</div>
);
};
I'm just a bit confused about as to why the revert button doesn't work here. My child displays and updates the information that is held in the parent but should be fully controlled through the passing of the function and variable defined in the useState call at the top of the component tree shouldn't it?
I've tried to cut down my awful code into something that can hopefully be debugged! I can't help but feel it is unnecessary complexity that is causing some issues here, or lifecycle things that I don't fully grasp. Any advice or solutions would be greatly appreciated!

react TextField material UI value

I'm using materiel UI form component in a modal.
This modal can be opened for add or edit an item, so values can be empty or not.
I put default props values in a state but this is always empty and never get previous values...
Here is my code :
const Comp = (props) => {
const { edit, values } = props // edit props for editing user
// values is :
{
prenom: 'name',
nom: 'name'
}
// ...
const [nom, setNom] = React.useState(edit ? values.nom : '')
const [prenom, setPrenom] = React.useState(edit ? values.prenom : '')
// ...
return (
<form>
<TextField
id="prenom"
value={prenom}
label="Prénom"
variant="outlined"
onChange={(event) => setPrenom(event.target.value)}
/>
<TextField
id="nom"
value={nom}
label="Nom"
variant="outlined"
onChange={(event) => setNom(event.target.value)}
/>
</form>
)
}
Thanks for your help
I'm guessing that you have your Comp used on the parent but not visible till some state changes, something like isDialogOpen. Then once the user wants to edit some object you do something like
setIsDialogOpen(true);
setDialogEditMode(true);
setValuesToEdit({nom: 'Foo', prenom: 'Bar'});
You have to understand that once you use the component (<Comp prop='value' />) React renders it, even that nothing gets to the actual Dom, the Comp function will be called! so at first it's being called with some empty values, then you want to let the user do the editing, you change the parent state. BUT the Comp state is still the state that it was created with (Empty values).
Remember this: useState acts in two ways:
On the first render it returns the value from the given parameter.
Any future renders it ignores the parameter and returns the saved state.
So in order to change the saved state you have to declare a reaction/effect when the props change, you do that with useEffect inside your Comp
for example:
useEffect(() => {
setNom(values.nom);
setPrenom(values.prenom);
}, [values])

React hooks - send updated value from child to parent component when using onChange and prop of child toghether

I've two components - Parent and Child using react hooks.
Here, collected is a state variable of parent component. And it's being passed to TenderInput component.
const handleBlur = (val) => {
console.log('::: handleBlur :::');
console.log(val);
setCollected(Number(val).toFixed(2));
}
<TenderedInput
default={collected}
focus={focusInput}
// onChange={handleBlur}
/>
In TenderInput
const TenderedInput = (props) => {
const [tendered, updateTendered] = useState(props.default);
const handleChange = (event) => {
const val = convertToCurrency(event.target.value);
updateTendered(val);
// props.onChange(event.target.value); this line causes an issue when I try to update state of parent with this call
}
return (
<div className="collected__amount">
<input
type="text"
className="form__input"
value={tendered}
onChange={event => handleChange(event)}
onFocus={event => event.currentTarget.select()}
autoFocus={props.focus}
tabIndex="2"
/>
</div>
)
}
TenderInput's textbox and it's onChange event is working fine and updating tendered state of TenderInput component. But at the same I need to update parent's state. Now, collected is in parent and if I add props.onChange(event.target.value), collected is getting updated every time we enter something in textbox and re-renders the component and doesn't allow to enter the correct value.
I even tried to add props.onChange(event.target.value) in onBlur on TenderInput's textbox but then I need to click on a button twice to make it work.
**How do I handle updating child component's state and at the same time update parent's state? **
If you are going to need the same value of the state for both (parent and child), then why don't you declare and manage that state from the parent instead of from the child? So in your parent component you would have:
const [tendered, updateTendered] = useState(props.default);
const handleChange = (event) => {
const val = convertToCurrency(event.target.value);
updateTendered(val);
}
return (
<TenderedInput
tendered={tendered}
focus={focusInput}
onChange={handleChange}
/>
)
And in your child you would have:
const TenderedInput = (props) => {
return (
<div className="collected__amount">
<input
type="text"
className="form__input"
value={props.tendered}
onChange={event => props.handleChange(event)}
onFocus={event => event.currentTarget.select()}
autoFocus={props.focus}
tabIndex="2"
/>
</div>
)
}
So this way you will have the state managed from the parent instead of the child, and both will be handled simultaneously.
you can simply pass the parentStateSetter function into the child component as props and update the state externally from the child component,make the onChangeHandler in child component update both parent and child state
handlechange = (e) => {setChildState(...);setParentState(...)}
just pass the textfield value into both stateSetters and it will update both states as you want.

why child state value is not updating in parent callback function at first time?

why child state value is not updating in parent callback function at first time? i want to make my input-Field disable based on state.
Sandbox with full example: https://z3wu6.csb.app/
Issue
PageHeader
Initial state is true, when you click the the button the editViewHandler is called. It toggles the edit state of this component but then calls the editCallback callback with the current value of edit which is initially true. You're sending the non-updated value to the callback! (you set it to true again in UserProfile) You can fix this by also inverting the edit value sent to editCallback.
const [edit, setEditView] = useState(true);
const editViewHandler = () => {
setEditView(!edit);
editCallback(!edit); // <-- send the same value you update state to
};
I see you've also duplicated this edit state in UserProfile. You shouldn't duplicate state. You want a single source of truth.
You already pass editCallback from UserProfile so just attach that as the callback to the button.
Suggestion Solution
Toggle the value in the source callback in UserProfile
const UserProfile = () => {
const [edit, setEdit] = useState(true);
const editCallback = () => setEdit(edit => !edit);
return (
<>
<PageHeader
button
editCallback={editCallback}
title="User Profile"
subtitle="Overview"
/>
<UserAccountDetails edit={edit} />
</>
);
};
And attach to button's onClick handler
const PageHeader = ({ title, subtitle, button, editCallback }) => (
<div className="page-header py-4 withBtn d-flex align-items-center">
{button ? <Button onClick={editCallback}>Edit Detail</Button> : null}
</div>
);

How to handle the state of multiple checkboxes in child component by the parent in react?

I have multiple check-boxes which are dynamic. These check boxes are placed (assume) in "B" component.
I have managed the state of these check boxes in "A" component which is the parent of "B".
When one or all the check boxes are clicked I want to render a div
expected result : render div if one or all the check boxes are clicked. If none is selected the div should be hidden. Basically the div should be visible even if a single checkbox is checked.
current result : When one check box is clicked the div gets rendered but if another checkbox is selected the div disappears.
Here's my A component (Parent)
const A = () => {
const [isChecked, setIsChecked] = useState(false) // state that holds state of check boxes
return (
{isChecked ? <div>Some Content..</div> : null} // rendering the div according to the state of the checkboxes
<Child isChecked={isChecked} setIsChecked={setIsChecked} />
)
}
Code of Child Component
const Child = props => {
return (
{checkBoxList.map((box, index) => (
<CustomCheckBox
key={index}
name={`check-box-${index}`} // to uniquely identify each check box
isChecked={props.isChecked}
setIsChecked={props.setIsChecked} />
))}
)
}
CustomCheckBox component :
const CustomCheckBox = props => {
const onChangeHandler = (event) => {
props.isChecked ? props.setIsChecked(false) : props.setIsChecked(true)
}
return <input name={props.name} type="checkBox" onChange={onChangeHandler} />;
};
CustomCheckBox.propTypes = {
name: string
}
I'm aware that the parent component state is only capable of handling a single check box right now.
The reason for the current result is because of the condition that I have included in onChangeHandler function.
props.isChecked ? props.setIsChecked(false) : props.setIsChecked(true)
I'm confused about how to handle the state of multiple check boxes in the above stated scenario using a single handler function !
you can keep track of checkbox indexes that you click instead of if it was clicked
const [isChecked, setIsChecked] = useState([]) // state that holds state of checked boxes
then handler would become:
const onChangeHandler = (index) => {
const result = props.isChecked;
var i = result.indexOf(index);
if (i> -1) {
result.splice(i, 1);
}
else {
result = [...result, index]
}
props.setIsChecked(result)
}
so in the parent you would check if isChecked.length> 0
Hope this helps

Resources