React Props wont update in Child Components - javascript

Following Situation.
I have a functional Parent Component like this:
function TestAutomationTab() {
const theme = createMuiTheme({
typography: {
htmlFontSize: 10,
useNextVariants: true,
},
});
const [szenarios, setSzenarios] = useState([]);
const [filterSzenario, setFilterSzenario] = useState('ALL');
const [data, setData] = useState([{}]);
const [runAll, setRunAll] = useState(false);
const [runAllButton, setRunAllButton] = useState('RUN ALL');
useEffect(() => {
fetchDistinctSzenarios();
fetchTestfaelle();
}, []);
async function fetchDistinctSzenarios() {
const response = await Api.getDistinctTestautoSzenarios();
setSzenarios(response.data);
setSzenarios(oldState => [...oldState, 'ALLE']);
}
function handleFilterChange(event) {
setFilterSzenario(event.target.value);
fetchTestfaelle();
}
async function fetchTestfaelle() {
const response = await Api.getAllOeTestfaelle();
response.data.forEach((e) => {
e.status = 'wait';
e.errorStatus = '';
e.statusText = '-';
});
setData(response.data);
}
function sendSingleCase(id) {
data.forEach((e) => {
if(e.id === id){
e.status = 'sending';
}
})
}
return (
<React.Fragment>
<MuiThemeProvider theme={theme}>
<div style={styles.gridContainer}>
<Upload />
<TestautomationSzenarioFilter
/>
<DocBridgePieChart />
<div style={styles.uebersicht}>
{filterSzenario.length ? <OeTestfallAccordion
choosenFilter={filterSzenario}
testData={data}
runAll={runAll}
sendSingleCase={sendSingleCase}
/> : <div>Wähle Szenario</div>}
</div>
</div>
</MuiThemeProvider>
</React.Fragment>
);
}
OeTestfallAccordion
function OeTestfallAccordion(props) {
const data = props.testData;
return (
<React.Fragment>
{data.map(e => (<OeTestfall
key={e.id}
szenario={e.szenario}
testid={e.testfallid}
json={e.json}
status={e.status}
runAll={props.runAll}
errorStatus={e.errorStatus}
statusText={e.statusText}
sendSingleCase={props.sendSingleCase}
/>))}
</React.Fragment>
);
}
OeTestfall
function OeTestfall(props) {
const { szenario, testid, json } = props;
const [open, setOpen] = useState(false);
function handleOpen(event) {
event.stopPropagation();
setOpen(true);
}
function handleClose() {
setOpen(false);
}
return (
<ExpansionPanel>
<ExpansionPanelSummary expandIcon={<ExpandMoreOutlined />}>
<OeTestfallSummary
szenario={szenario}
testid={testid}
json={json}
status={props.status}
handleClose={handleClose}
handleOpen={handleOpen}
open={open}
statusText={props.statusText}
errorStatus={props.errorStatus}
sendSingleCase={props.sendSingleCase}
/>
</ExpansionPanelSummary>
<ExpansionPanelDetails>
<div>ForNoError</div>
</ExpansionPanelDetails>
<ExpansionPanelActions>
<Button
variant="outlined"
color="primary"
>
Bearbeiten
</Button>
<Button
variant="outlined"
color="secondary"
>
Löschen
</Button>
</ExpansionPanelActions>
</ExpansionPanel>
);
}
OeTestfallSummery
function OeTestfallSummary(props) {
const { handleOpen } = props;
const [status, setStatus] = useState('');
const [statusText, setStatusText] = useState('');
const [errorStatus, setErrorStatus] = useState('');
useEffect(() => {
setErrorStatus(props.errorStatus);
setStatusText(props.statusText);
setStatus(props.status);
}, []);
return (
<div style={styles.summaryWrapper}>
<Typography align="center" variant="subtitle1">
TestID: {props.testid}
</Typography>
<Typography align="center" variant="subtitle1" style={{ fontWeight: 'bold' }}>
{props.szenario}
</Typography>
<Button
size="small"
variant="outlined"
color="primary"
onClick={handleOpen}
>
JSON
</Button>
<Tooltip title="VorneTooltip" style={styles.lightTooltip} placement="left">
<Chip
color="secondary"
variant="outlined"
label={status}
/>
</Tooltip>
<StatusChip
status={errorStatus}
/>
<OeJsonViewer json={JSON.parse(props.json)} open={props.open} handleClose={props.handleClose} stopEventPropagation />
<Tooltip
title="ToolTipTitel"
style={styles.lightTooltip}
placement="top"
>
<Chip
color="primary"
variant="outlined"
label={statusText}
/>
</Tooltip>
<Button variant="contained" color="primary" onClick={() => props.sendSingleCase(props.testid)} >
Run
</Button>
<Button variant="contained" color="primary" onClick={() => console.log(status)} >
test
</Button>
</div>
);
}
In my OeTestfallAccordion the prop testData does not update. If i try to console.log it inside my childComponent it has the old Value like before i execute the sendSinglecase function. What do i need to do, that i update the Data correctly that my child component gets notified that the props had changed and it has to rerender.
EDIT:
I tried some new things and can narrow down the problem. In my TestAutomationTab Component i send the whole data State to the OeTestfallAccordion Child Component. In this OeTestfallAccordion Component i split up the Array of Data which consists of multiple Objects like:
0: {id: 41, testfallid: 1, json: "{\"testCaseData\":{\"baseData\":{\"Check\":\"Thing…e\":\"alle\",\"tuwid\":\"2909\"}},\"testType\":\"Test\"}}", ID: null, businessId: null, …}
1: {id: 42, testfallid: 2, json: "{\"testCaseData\":{\"baseData\":{\"testfallid\":\"1…e\":\"alle\",\"tuwid\":\"2909\"}},\"testType\":\"Test\"}}", edcomAuftragsId: null, businessId: null, …}
When i hit the function sendSingleCase in my Parent Component TestAutomationTab i just change one single Parameter of the Object. The whole construct of Data keeps the same. The Child Component doesnt recognize that i changed something in the Object of Data.
But i dont know why? I also tried to useEffect on Props change in my Child COmponent when the props are changed. But it never gets executed even tho some attributes got updated inside the props.data.
function OeTestfallAccordion(props) {
const testData = props.testData;
const [data, setData] = useState(testData);
useEffect(() => {
setData(testData);
console.log("triggered");
}, [props]);
...
}

Okay things worked out a bit.
I changed the sendSingleCase function to first Copy the whole state in a Temp variable. Change one Attribute inside an Object and then setData (inside useState) with the tempData Variable. So the whole State gets renewed and the child components recognize the change and rerender.
But it seems not to be very fast. Always to copy the whole Data in a new Variable and then reassign it is very Ressource heavy. Is there a better solution?
function sendSingleCase(id) {
const tempState = [...data];
tempState.forEach((e) => {
if (e.testfallid === id) {
e.status = "pressed";
console.log(e.status);
}
});
setData(tempState);
}

Related

Setting the values of an object in an array in React?

I am building a swipeable card in React. The card contains 4 slides. The values displayed in the card rely on the user input.
First I am defining a sample object like this:
const initialState = { id: '', title: '', name: '', image: ''};
Inside my component, I am defining the array state like:
const [card, setCard] = useState([initialState]);
I am displaying the card side by side along with the user input fields for users to view the cards as they compose. So whenever the user adds/edits a specific value of the card he can view it live on the card.
We can set the state of an object for each field like this:
<Input id='title' name='title' placeholder="Enter Title" type='text' value={card.title} onChange={handleChange}/>
Handle Change function:
const handleChange = (e) => {
setCard({ ...card, [e.target.name]: e.target.value });
}
But this is not possible for the above-mentioned array of objects. So how to handle this situation?
Whenever a user swipes the previous/next card the fields must be populated with the appropriate values so that he can edit them. Simply, a user must be able to edit any field at any time. Whenever a user adds a new card a new object must be pushed to the array state.
Full code:
const initialState = { id: '', title: '', name: '', image: ''};
const Home = () => {
const [card, setCard] = useState([initialState]);
const isdisabled = true;
const handleChange = (e) => {
setCard({ ...card, [e.target.name]: e.target.value });
}
const handleAdd = () => {
//TODO
}
return (
<Flex>
<Center>
<Flex bg="white" w="lg" h="420" borderRadius="lg" m="7" p="2" alignItems="center">
<Box w="48" align="center">
<IconButton aria-label='Go to previous' disabled borderRadius="full" bg='gray.200' color='black' icon={<ChevronLeftIcon w={6} h={6}/>} />
</Box>
<Box>
<Image src={card[0].image} w="full" h="44" objectFit="cover" objectPosition="0 0" borderRadius="lg" />
<Heading color="black" size='lg'>{card[0].title}</Heading>
<Text color="black" size='40'>{card[0].namee}</Text>
</Box>
<Box w="48" align="center">
<IconButton aria-label='Go to previous' disabled borderRadius="full" bg='gray.200' color='black' icon={<ChevronRightIcon w={6} h={6}/>} />
</Box>
</Flex>
</Center>
<Flex direction="column" w="lg" gap="4" m="7">
<Input placeholder="Enter Title" value={card[0].title} onChange={handleChange}/>
<Input placeholder="Enter Name" value={card[0].name} onChange={handleChange}/>
<Button onClick={handleClick}>Upload Image</Button>
<Button onClick={handleAdd}>Add another slide</Button>
<Button colorScheme="blue">Done</Button>
</Flex>
</Flex>
)
}
export default Home
How to seamlessly do this? Any help would be appreciated. Thank you.
your card state is array of objects need to update array first object
const handleChange = (e) => {
const arr = [...card]
arr[0] = {...arr[0], [e.target.name]: e.target.value }
setCard(arr);
}
#Gabriele Petrioli's answer is the perfect solution to my problem except it needs a little tweaking:
Add activeCardIndex to both navigation handlers' dependency list:
const handleGotoNext = useCallback(() => {
// you need to also handle not allowing to go beyond the max
if(activeCardIndex < cards.length-1){
setActiveCardIndex(prevActive => prevActive + 1);
}
}, [activeCardIndex]);
const handleGotoPrevious = useCallback(() => {
// you need to also handle not allowing to go below 0
if(activeCardIndex > 0){
setActiveCardIndex(prevActive => prevActive - 1);
}
}, [activeCardIndex]);
And the handleChange function:
const handleChange = useCallback((e) => {
setCards(prevCards => prevCards.map((card, index) => {
if (index === activeCardIndex) {
return { ...card,
[e.target.name]: e.target.value
}
}else {
return card;
}
}));
}, [activeCardIndex]);
You would likely need an additional state variable, specifying the active card
something like
const [cards, setCards] = useState([initialState]);
const [activeCardIndex, setActiveCardIndex] = useState(0);
handleGotoNext = useCallback(() => {
// you need to also handle not allowing to go beyond the max
setActiveCardIndex(prevActive => prevActive + 1);
}, []);
const handleGotoPrevious = useCallback(() => {
// you need to also handle not allowing to go below 0
setActiveCardIndex(prevActive => prevActive - 1);
}, []);
const handleChange = useCallback((e) => {
setCards(prevCards => prevCards.map((card, index) => {
if (index === activeCardIndex) {
return { ...card,
[e.target.name]: e.target.value
}
}
return card;
}));
}, [activeCardIndex]);
const handleAdd = useCallback(() => {
const newCards = [...cards, { ...initialState
}];
setCards(newCards);
setActiveCardIndex(newCards.length - 1);
}, [cards]);
const activeCard = cards[activeCardIndex];
// for the rendering you should use the activeCard constant, instead of cards[n]
return (
<Flex>
...
<Image src={activeCard.image} w="full" h="44" objectFit="cover" objectPosition="0 0" borderRadius="lg" />
...
</Flex>
)

Table - Ant Design, onRow DoubleClick

I want open modal window, when i doubleClick onRow in Table antd. And pass property to modal. Like this:
<Table
columns={columns}
components={components}
dataSource={dataSource}
onRow={(record) => {
return {
onDoubleClick: () => {
return <ModalWindow props={record} />
// or
return <ModalWindow> {record.name} </ModalWindow>
}
}
}} />
The problem is that no jsx is returned. I can save in state, like here:
const [visible, setVisible] = useState(false);
const [record, setRecord] = useState([]);
<Table
columns={columns}
components={components}
dataSource={dataSource}
onRow={(record) => {
return {
onDoubleClick: () => {
setRecord(record);
setVisible(true);
}
}
}} />
{visible &&
<ModalWindow record={record} />
}
but that's not my way. Help me, please.
You can handle your requirement by using one Modal and updating content of modal with double-click on every row, like this:
function App() {
// ...
const [isModalVisible, setIsModalVisible] = useState(false);
const [activeRecord, setActiveRecord] = useState(null);
const closeModal = () => {
setIsModalVisible(false);
};
return (
<>
<Table
columns={columns}
dataSource={data}
onRow={(record) => {
return {
onDoubleClick: () => {
setActiveRecord(record);
setIsModalVisible(true);
},
};
}}
/>
<Modal
title="User info"
visible={isModalVisible}
onCancel={closeModal}
footer={null}
>
{/* render whatever you want based on your record */}
<p>{activeRecord?.name} </p>
</Modal>
</>
);
}
I've implemented an example Here on stackBlitz, you can check it out.

React component isn't updating based on the response of an async call

I have a component card on the side of a page of my app that displays a user's current fitness track:
When the page loads the name of the track is blank because it hasn't been loaded from the server yet. However, I would expect that it would update the component and rerender as soon as the result was returned. Likewise, when you click the arrow of the component, a modal pops up asking if you are ready to advance to the next track. If you confirm, it calls a switchTrack() function that calls the API, and if the result is successful the user is updated and the modal closes. However, even though the track has in fact been updated, the track name doesn't refresh until you refresh the page. How do I make sure this component re-rerenders with the new text when the result comes back? (Note: You'll see I tried to force this with a reload state, but this hasn't worked).
Component Card (simplified):
const CurrentPlanCard = props => {
const { open, launchModal, closeModal } = useModal(false);
const { user } = useSelector(mapState);
const [currentTrackName, setCurrentTrackName] = useState('');
const [nextTrackName, setNextTrackName] = useState('');
const [nextTrackId, setNextTrackId] = useState('');
const [reload, setReload] = useState(false);
useEffect(() => {
if (user) {
getTrack(user.fitnessTrack)
.then(currentTrack => {
setCurrentTrackName(currentTrack.name);
setNextTrackName(currentTrack?.nextTrack.name);
setNextTrackId(currentTrack?.nextTrack._id);
})
.catch(err => {
console.error(err);
});
}
}, [user, reload, setCurrentTrackName, setNextTrackName, setNextTrackId]);
return (
<FlexContainer justify="space-between">
<LeftContainer flexDirection="column" justify="center">
<Label>Current Fitness Track:</Label>
<PlanName>{currentTrackName}</PlanName>
<Text>Ready to move to the next stage? Switch your plan now!</Text>
</LeftContainer>
<RightContainer flexDirection="column" justify="center">
<ArrowBlue onClick={launchModal} />
</RightContainer>
<SwitchPlanModal
open={open}
handleClose={closeModal}
userId={user._id}
currentTrackName={currentTrackName}
nextTrackId={nextTrackId}
nextTrackName={nextTrackName}
reload={reload}
setReload={setReload}
{...props}
/>
</FlexContainer>
);
};
Modal to switch the plan:
const SwitchPlanModal = ({
open,
handleClose,
userId,
currentTrackName,
nextTrackName,
nextTrackId,
setReload,
reload,
...props
}) => {
const [error, setError] = useState('');
const handleClick = async e => {
try {
if (nextTrackId) {
await switchTrack(userId, nextTrackId);
setReload(!reload);
setError('');
handleClose();
} else {
setError('No next track defined');
}
} catch (err) {
console.error(err);
if (err.message) {
setError(err.message);
} else if (err.error.message) {
setError(err.error.message);
}
}
};
return (
<div>
<Modal open={open} onClose={handleClose}>
<StyledDialogContent>
<Container justify="center" flexDirection="column">
<NavBar justify="space-between" alignItems="center">
<CloseRight>
<CloseIcon handleClose={handleClose} />
</CloseRight>
</NavBar>
<ModalBody
justify="space-evenly"
alignItems="center"
flexDirection="column"
>
<StyledAvatar src={`${fileStorage}/AddExercise.png`} />
<FlexContainer flexDirection="column" alignItems="center">
<Header>Ready for the next stage?</Header>
</FlexContainer>
<FlexContainer flexDirection="column" alignItems="center">
<Button
buttonText="Let's Go!"
onClick={handleClick}
/>
<Link onClick={handleClose}>Nope, not yet</Link>
{error && <ErrorMessage>{error}</ErrorMessage>}
</FlexContainer>
</ModalBody>
</Container>
</StyledDialogContent>
</Modal>
</div>
);
};

To do list making with React hooks

I'm a bit new to React. I'm trying to make simple To do list with react hooks and struggling to make "delete all button". I thought it could be work to using setState [] or return []
but it didn't work...
and also it's showing an error.
TypeError: tasks.map is not a function
Does anyone know how it figure out?
Here is my code
import React, {useState} from 'react'
let INITIAL_TASK = {
title: 'React',
doing: false,
}
const App = () => {
const [tasks, setTasks] = useState([INITIAL_TASK])
const [task_title, setTask_title] = useState('')
const handleTextFieldChanges = e => {
setTask_title(e.target.value)
}
const resetTextField = () => {
setTask_title('')
}
const isTaskInclude = () => {
return tasks.some(task => task.title === task_title)
}
const addTask = () => {
setTasks([...tasks, {
title: task_title,
doing: false,
}])
resetTextField()
}
const deleteTask = (task) => {
setTasks(tasks.filter(x => x !== task))
}
const deleteAllTask = () => {
-------------
}
const handleCheckboxChanges = task => {
setTasks(tasks.filter(x => {
if (x === task) x.doing = !x.doing
return x
}))
}
return (
<React.Fragment>
<Container component='main' maxWidth='xs'>
<CssBaseline/>
<Box
mt={5}
display='flex'
justifyContent='space-around'
>
<TextField
label='title'
value={task_title}
onChange={handleTextFieldChanges}
/>
<Button
disabled={task_title === '' || isTaskInclude()}
variant='contained'
color='primary'
onClick={addTask}
href=''
>
add
</Button>
<Button
// disabled={task_title === '' || isTaskInclude()}
variant='contained'
color='secondary'
onClick={deleteAllTask}
href=''
>
all delete
</Button>
</Box>
<List
style={{marginTop: '48px'}}
component='ul'>
{tasks.map(task => (
<ListItem key={task.title} component='li'>
<Checkbox
checked={task.doing}
value='primary'
onChange={() => handleCheckboxChanges(task)}
/>
<ListItemText>{task.title}</ListItemText>
<Button
href=''
onClick={() => deleteTask(task)}
>
delete
</Button>
</ListItem>
))}
</List>
</Container>
</React.Fragment>
)
}
export default App
You can try doing below
const deleteAllTask = () => {
setTasks([]);
};
or if you want it to set to initial value, you can do below
const deleteAllTask = () => {
setTasks([INITIAL_TASK]);
};

React does not change state after updating state in usestate hook (in custom function form)

I am using usestate hook in react 16.10.2, but after updating initial state using custom function in usetate hook, react does not trigger a re-render(OtherComponent is not rendered), Here is my react component:
import React, { useState, useEffect } from 'react';
import OtherComponent from "./OtherComponent";
function Component(props) {
const [render, setRender] = useState({0:false, 1:false});
const display_data = (index) => {
setRender((prevState) => {
prevState[index] = !prevState[index];
return prevState;
});
};
return (
<>
{{custom_json_array}.map((record, index) => {
return (
<div>{teacher_render[index] ? 'true' : 'false'}</div>
<button onClick={() => display_data(index)}>change state</button>
{render[index] ? <OtherComponent /> : ''}
</div>)
})}
</>
);
}
But strange thing is if I return {...prevState} from hook updater function, everything is normal and re-render is triggerd!
I am totally confused, why react behaves like this?!
I assume the problem is that you are mutating render?
<button
onClick={() => setRender({ ...render, [index]: !render[index] })}
>
change state
</button>
In this example, click on the names to see the custom component and click again to hide
https://codesandbox.io/s/dark-wind-3310q
const CustomComponent = () => (
<div style={{ marginLeft: 10, background: "red" }}>I'm Selected!</div>
);
function App() {
const [people] = useState([
{ id: 0, name: "Mario" },
{ id: 1, name: "Luigi" },
{ id: 2, name: "Peach" }
]);
const [selected, setSelected] = useState({});
return (
<div>
{people.map(({ id, name }) => (
<div
style={{ display: "flex", cursor: "pointer" }}
key={id}
onClick={() => setSelected({ ...selected, [id]: !selected[id] })}
>
{name}
{selected[id] && <CustomComponent />}
</div>
))}
</div>
);
}
Here is a simplified example of the erroneous code you posted, to show no update occurring:
https://codesandbox.io/s/goofy-williamson-22fgs
function App() {
const [obj, setObj] = useState({ name: "Mario" });
const change = () => {
// The following commented code will display no change
// obj.name = "Peach";
// setObj(obj);
setObj({ ...obj, name: "Peach" });
};
return (
<div className="App">
<div>{obj.name}</div>
<button onClick={change}>Change!</button>
</div>
);
}
Your onclick handler is called display_teacher_data in the markup but display_data in the component. Set them to be the same so the state changes.

Categories