How to rerender component based on new data - reactjs

I have below component which gets data expenses as props,
Initial reader will show table with expenses values, but when row is deleted new data are stored in newExpenses which need to use in DataGrid
const DataTable = ({ expenses }) => {
const handleDeleteCompleted = async () => {
// Function for delete
const newExpenses = await deleteExpense(fetchExpenseId)
setOpenConfirmDelete(false);
enqueueSnackbar(t('The user account has been removed'), {
variant: 'success',
anchorOrigin: {
vertical: 'top',
horizontal: 'right'
},
TransitionComponent: Zoom
});
};
return (
<>
<div style={{ height: 800, width: "100%" }}>
<DataGrid
rows={expenses}
columns={columns}
/>
</div>
</>
);
};
export default DataTable;
Now I want to update DataGrid rows with newExpenses which are fetch with deleteExpense function.
How can Irerender DataGrid with new data?
Thank youm

You're duplicating state and confusing where your system of record is. The first thing you need to do is decide:
Should the expenses state be managed in this component, or should it be managed in a parent and passed to this component?
Don't mix the two.
If the answer is that state should be managed in this component, remove the prop. Within this component you would fetch the data (perhaps once initially in a useEffect for example), store it in useState, and update that state accordingly (which will trigger a re-render).
If the answer is that state should be managed in a parent component, then that component needs to perform the updates/deletes/etc. It can pass functions to do that as additional props for this component to use. As that parent component updates its state, that would trigger a re-render of this child component.

When you need to rerender a component based on data, you need to put that data into state. Whenever React state changes, the component gets rerendered.
In your case you'd need to put the prop expenses inside state.
import { useState } from 'react';
const DataTable = ({ expenses: initialExpenses }) => {
const [expenses, setExpenses] = useState(initialExpenses);
...
// On handler
const newExpenses = await deleteExpense(fetchExpenseId);
setExpenses(newExpenses);
}
That will make sure that your component gets rerendered.
This is the case in which the prop is the initial data, because you can't update the components props, you can only use them. In this example, your component is managing the state.
In the case where the parent component needs to manage the state, you'd need to not only pass expenses as a prop, but a callback to update it. Whenever a React prop changes, the component also rerenders. In this case you'd pass in both expenses and onDeleteExpense (for example). The parent component is the one who defines onDeleteExpense and handles the state (using useState).
const Parent = () => {
const [expenses, setExpenses] = useState(someInitialValue);
const onDeleteExpense = (expenseId) => {
const newExpenses = await deleteExpense(expenseId);
setExpenses(newExpenses);
}
return <DataTable expenses={expenses} onDeleteExpenses={onDeleteExpenses} />;
}

Related

Toast Editor UI - doesn't re-render initialValue when Parent state changes

So I'm making a write component which can be used for both create, and update the notice.
the write component has a Toast-UI editor to write/update the notice.decs
I create [form, setForm] = useState({desc="defaul"}
after first render, useEffect call API with params of noticeID to get data, then setForm(data).
Form is taken into Editor as props.
After useEffect API call, the Form is updated, Toast-UI editor props updated, but the Initial value doesn't change.
Please does anybody have any thoughts on why this happens and how to fix?
Write (parent component)
const [form, setForm] = useState({
ttl: "",
desc: "DEFAULT",
});
UseEffect( Fetch API then setForm(res)
return (
<ToastEditor
onChangeForm={onChangeForm}
form={form}
setForm={setForm}
/>
);
Toast-UI editor component
const { form, setForm, onChangeForm } = props;
return (
<Editor
initialValue={form?.desc ?? ""}
/>
);
Grand children component
export class Editor extends Component<EditorProps> {
getInstance(): ToastuiEditor;
getRootElement(): HTMLElement;
}
I expect the Toast Editor to render the data instead of just the first time initital State Form

Card Component wont re-render

For some reason, my card won't re-render when I make a call to the API.
when the information updates in the backend it doesn't display in the front unless I refresh the app.
I'm passing down info as a prop and grabbing the company name, address, and phone.
const CpoCard = ({ info, navigation }) => {
const [companyName, setCompanyName] = useState(info.companies[0].companyName);
const [address, setAddress] = useState(info.companies[0].city);
const [phoneNumber, setPhoneNumber] = useState(info.companies[0].phoneNumber);
const [employeeID, setEmployeeID] = useState(info.userId.employeeId);
const { setSelectedEmployee } = useCompany();
const handleEditPress = () => {
setSelectedEmployee(info);
navigation.navigate('DeleteEmployee');
//deleteEmployee
};
const handlePress = () => {
console.log(info);
navigation.navigate('CpoInfo', { info: info });
};
return (
<View style={styles.outerContainer}>
{
<View style={styles.innerContainer}>
<View style={styles.mainContent}>
<Image
style={styles.employeeImg}
source={require('~assets/pngs/avatar.png')}
/>
<View style={styles.companyInfoContainer}>
<TouchableOpacity onPress={handlePress}>
<Text style={{ fontSize: 18, fontWeight: 'bold' }}>
{companyName !== null ? `${companyName} ` : 'info'}
</Text>
<Text>{`Phone Number: ${phoneNumber}`}</Text>
<Text>{`address: ${address}`}</Text>
</TouchableOpacity>
</View>
</View>
</View>
}
</View>
);
};
Whenever the prop info is being updated by the parent component, the CpoCard will re-render. You could simply test it by including console.log('rerendered) in the component.
Based on your wording in your question it seems the issue isint the re-render but more likely your structure of a CpoCard component.
In React, whenever the component first loads, it creates its states to whatever initial value you set in useState. Later down the line the state will not mutate nor charge unless the setter function is called. That triggers the component to re-render. You can NOT set the state value otherwise.
In your case, in the CpoCard component, the prop info is ONLY used to set the initial state value via useState(info.companies[0].companyName). So the value is being set on the first load of a component. But later on, regardless how info prop changes - the state will NOT change regardless of how many times the component re-renders.
Simple example:
At CpoCard load <CpoCard info={'initial value'} /> -> component CpoCard creates a state via useState and stores its value as passed const [info, setinfo] = useState(props.info) -> component renders.
Later down the line info prop changes <CpoCard info={'second value'} /> -> component CpoCard re-renders info from const [info, setinfo] = useState(props.info) remains the initial value 'initial value' since you can not change the state value like so. The initial value will remain for the entire time, regardless of how many re-renders, unless called the setter setInfo and changes.
So in your case your structure is the issue.
I am unsure why you even store each individual prop into its own state - it seems like a flaw in a structure by itself. If you are getting passed values - use those. If for some reason you actually require to use a state for each value then there is also a work around - useEffect
useEffect hook allows you to listen to changes and react based on that.
So you should call each setter in a useEffect that is listening to your info prop change:
useEffect(() => {
setCompanyName(info.companies[0].companyName)
setAddress(info.companies[0].city)
setPhoneNumber(info.companies[0].phoneNumber)
setEmployeeID(info.userId.employeeId)
}, [info])
With this useEffect what happens is the component loads the initial value, stores it in state and renders. Whenever the prop.info changes the component re-renders, the useEffect dependency array (the [] in the end) checks that prop.info changes hence it executes the function (the code inside the useEffect hook) that sets the states based on new info prop and that causes a component to re-render once again displaying the new data.

Do the same fetch logic in different situation in one component

I have a page where there are some components: Filters, Pagination and List (with some data from the server)
Filter component includes some selects elements, inputs, etc. and button "Find".
Pagination component includes page switchers buttons and a selector for a direct page and a select to choose how many elements we should show on a page
List component shows data from the server API
Also, I have a Parent component that has all of them and a common state. (I will simplify fetch logic, but the main point that it is sent a request with 2 objects as data)
const reducer = (options, newOptions) => ({
...options,
...newOptions
})
const ParentComponent = () => {
const [filterOptions, setFilterOptions] = useReducer(
reducer,
{
filterOption1: "",
filterOption2: ""
....
}
)
const [pagingOptions, setPagingOptions] = useReducer(
reducer,
{
pageNumber: 1,
elementsPerPage: 10,
totalElement: 100
}
)
const doFetch = () => {
fetch('https://example.com', {
body: {filters: filterOptions, paging: pagingOptions}
})
}
return (
<>
<Filters
filterOptions={filterOptions}
onFilterOptionsChange={setFilterOptions}
onFindClick={doFetch}
/>
<Pagination
pagingOptions={pagingOptions}
onPagingOptionsChange={setPagingOptions}
/>
<List data={data} />
</>
)
}
When we change some filters, data in filterOptions changes because we put setFilterOptions dispatch in to Filters component (I did not show what is inside Filters component, the main point is that we use setFilterOptions({filterOption1: 'new_value'}) to change filterOptions when some filter select or input are changed) and when we click on the Find button inside Filters we use the method doFetch and do fetch with new values of filterOptions and default values of pagingOptions
After that, when data comes, we put it into List component and show it on a screen. Again I simplified it and write just <List data={data} />
My question is: How should I implement the logic for page changing. For Filters I have just one Find button to use fetch, but for paging, there are a lot of selectors and buttons that can change some of pagingOptions. And after we change a pagingOptions (a page or an elementsPerPage) I should immediately fire doFetch() method
I used useEffect
useEffect(() => {
doFetch()
}, [pagingOptions])
and see when pagingOptions changes then fire doFetch(), but eslint hooks warning told me that I should add doFetch in deps (I agree) but then it was a message in the console that doFetch should be in useCallback(), but if I wrapped doFetch() in useCallback() console told me that I should add filterOptions and pagingOptions in useCallback deps (because I use them in fetch) and after that, I got infinity loop of fetching api in network

reactjs forfeit current component render

Scenario
Depending on the data coming into props I may want my component to render but I also may NOT want my component to rerender.
Code
Currently I'm using if(!props.data) return null but this merely sends back a blank component.
Question
How can I forfeit the render so that my current dom element stays unchanged, leaving the last rendered content intact? I'm wanting my component only update when props.data has a truthy value.
You can store the data received in a local state and only update it if props.data changes and has a truthy value like this:
import React, {useEffect, useState} from 'react';
const MyComponent = ({data}) => {
const [localData, setLocalData] = useState(data);
useEffect(() => {
if (data) setLocalData(data);
}, [data]);
return (
<div>{localData}</div>
);
};
But note that this has a little bit of a design smell. Why does your component receive new props in the first place if it shouldn't update. The correct place to hold that state may be in a component further up the tree.
The simple way is to try using conditional rendering of the component if the data is present in the parent instead of checking the props within the component.
function ParentComponent(props) {
const data = <your custom data>;
if(data) {
return <ChildComponent data={data} />;
}
}

React 16.8 hooks - child component won't re-render after updating parent state

I have a functional parent Map component with a few states, one of which is layers, the value of which is set initially from a config.
I then have a child LayerControl component which takes the layers state of the map as its props. The LayerControl component makes some changes and passes them back to the Map with onLayerChange which then subsequently calls the setState method of the layers (setLayers).
From what I understand, this should trigger a re-render of my LayerControl component, but it doesn't. What have I done wrong?
export default function Map(){
const [viewport, setViewport] = useState(mapConfig.viewport);
const [style, setStyle] = useState(mapConfig.mapStyle);
const [layers, setLayers] = useState(mapConfig.layers);
function updateLayers(layers){
setLayers(layers); //why won't LayerControl re-render?
}
return(
<MapGL
zoom={[viewport.zoom]}
containerStyle={{
height: "100%",
width: "100%"
}}
style={style}>
<LayerControl
onLayerChange={layers => updateLayers(layers)}
layers={layers}
/>
<ZoomControl/>
<ScaleControl/>
</MapGL>
);
}
Are you creating a new layers Array in the LayerController or are you just adding/removing an item to the layers array? I assume setLayers only does a shallow reference check to see if layers changed, which will return false if you modified the array instead of re-creating it. Thus your component won't re-render.
For example
// In LayerController
// this mutates the array, but won't trigger a render
layers.push(/* some new layer */);
onLayerChange(layers);
// creates a new array, which should trigger a render
const newLayer = {/* your new layer */};
const newLayers = [...layers, newLayer];
onLayerChange(newLayers)

Resources