In component A I have a function that adds a object to my localforage:
localforage.setItem('trackedMovies', value)
In component B I map through the trackedMovies array that I grab from localforage:
getData = async () => {
const trackedMovies = await localforage.getItem<[]>('trackedMovies');
this.setState({
data: trackedMovies
})
}
componentDidMount = () => {
this.getData()
}
render() {
return (
<React.Fragment>
<h1>Dashboard</h1>
{ this.state.data.map(movie => {
return (
<span key={movie.id}>{movie.original_title}</span>
)
})}
</React.Fragment>
)
}
Initially this works fine. But if I add new objects in the trackedMovies array component B does not update so I have to manually refresh the page to see the newly added object.
How do I rerender component B to show the results in the updated trackedMovies array?
Try maintaining the elements of the localStorage in a state. So whenever you add an item in component A, add it to the state as well. Then you can pass it to B.
Related
I was trying to create a delete operation on the array of objects (videoData).videoData is getting mapped in the child component along with the DELETE button. At the click of the DELETE button in the child component (childComp).
I want to set the current id to the "childData" state in the child component but it's not getting updated with the current id.
When I am consoling log the childData, in the child component, it still says null which means it was not updated.Why is it not updating?
My own explanation -
When the delete button is getting clicked, the testFunc() is getting fired in the parent component which is removing the item with that particular id from videoData array, and as a result, the id is not able to pass to the child component due to which child component is getting rendered with the original state (null). I don't know if the explanation is correct or not, can someone help me in clearing this up?
function ParentComp() {
const [videoData, setvideoData] = useState([{ id: 2 }, { id: 3 }]);
function testFunc(id) {
let hasMatch = false
if (!hasMatch) {
let arr = videoData.filter(item => {
return item.id !== id
})
setvideoData(arr)
}
}
return (
<childComp testFunc={testFunc} videoData={videoData}/>
)
}
function childComp({testFunc, videoData}) {
const [childData, setchildData] = useState(null)
function ChildFunc(itemId) {
testFunc(itemId)
setchildData(itemId)
}
console.log(childData) //null (state not getting updated)
return (
<>
{videoData.map((item) => {
return (
<button onClick={() => ChildFunc(item.id)}>Delete</button>
);
})}
</>
)
}
I've tried to find a solution to this, but nothing seems to be working. What I'm trying to do is create a TreeView with a checkbox. When you select an item in the checkbox it appends a list, when you uncheck it, remove it from the list. This all works, but the problem I have when I collapse and expand a TreeItem, I lose the checked state. I tried solving this by checking my selected list but whenever the useEffect function runs, the child component doesn't have the correct parent state list.
I have the following parent component. This is for a form similar to this (https://www.youtube.com/watch?v=HuJDKp-9HHc)
export const Parent = () => {
const [data,setData] = useState({
name: "",
dataList : [],
// some other states
})
const handleListChange = (newObj) => {
//newObj : { field1 :"somestring",field2:"someotherString" }
setDataList(data => ({
...data,
dataList: data.actionData.concat(newObj)
}));
return (
{steps.current === 0 && <FirstPage //setting props}
....
{step.current == 3 && <TreeForm dataList={data.dataList} updateList={handleListChange}/>
)
}
The Tree component is a Material UI TreeView but customized to include a checkbox
Each Node is dynamically loaded from an API call due to the size of the data that is being passed back and forth. (The roots are loaded, then depending on which node you select, the child nodes are loaded at that time) .
My Tree class is
export default function Tree(props) {
useEffect(() => {
// call backend server to get roots
setRoots(resp)
})
return (
<TreeView >
Object.keys(root).map(key => (
<CustomTreeNode key={root.key} dataList={props.dataList} updateList={props.updateList}
)))}
</TreeView>
)
CustomTreeNode is defined as
export const CustomTreeNode = (props) => {
const [checked,setChecked] = useState(false)
const [childNodes,setChildNodes] = useState([])
async function handleExpand() {
//get children of current node from backend server
childList = []
for( var item in resp) {
childList.push(<CustomTreeNode dataList={props.dataList} updateList={props.updateList} />)
}
setChildNodes(childList)
}
const handleCheckboxClick () => {
if(!checked){
props.updateList(obj)
}
else{
//remove from list
}
setChecked(!checked)
}
// THIS IS THE ISSUE, props.dataList is NOT the updated list. This will work fine
// if I go to the next page/previous page and return here, because then it has the correct dataList.
useEffect(() => {
console.log("Tree Node Updating")
var isInList = props.dataList.find(function (el) {
return el.field === label
}) !== undefined;
if (isInList) {
setChecked(true);
} else {
setChecked(false)
}
}, [props.dataList])
return ( <TreeItem > {label} </TreeItem> )
}
You put props.data in the useEffect dependency array and not props.dataList so it does not update when props.dataList changes.
Edit: Your checked state is a state variable of the CustomTreeNode class. When a Tree is destroyed, that state variable is destroyed. You need to store your checked state in a higher component that is not destroyed, perhaps as a list of checked booleans.
I have an array that I am rendering on the render() function. Each element in the array is a HTML element that has state variables that I need to display, the HTML are displaying correctly, but the internal state variables do not update even when the rendering is happening
state = {
array: [],
id: 2
}
updateState() {
this.setState({id: 4})
}
componentDidMount(){
array = [<div> {this.state.id} </div>, <div> {this.state.id} </div>]
}
render() {
{this.state.array.map(el => return el)}
//assume something happens here that triggers updateState() multiple times: buttons presses, etc
}
I never see 4, it re renders but keeps the old value 2
You are creating the array in the componentDidMount function which is only being called once when the component first renders.
You should do something like this
//create function
createArray = () => [<div> {this.state.id} </div>, <div> {this.state.id} </div>]
then use it in your code like this
{this.createArray().map(el => el)}
Hope this helps.
You need to save the data and render again:
state = {
id: 2
}
updateState() {
this.setState({id: 4})
}
componentDidMount(){
this.getElements(this.state.id)
}
getElements = (id) => {
return [<div> {id} </div>, <div> {id} </div>]
}
render() {
{this.getElements(this.state.id).map(el => el)}
//assume something happens here that triggers updateState() multiple times: buttons presses, etc
}
In my componentDidMount(), I am calling an actionCreator in my redux file to do an API call to get a list of items. This list of items is then added into the redux store which I can access from my component via mapStateToProps.
const mapStateToProps = state => {
return {
list: state.list
};
};
So in my render(), I have:
render() {
const { list } = this.props;
}
Now, when the page loads, I need to run a function that needs to map over this list.
Let's say I have this method:
someFunction(list) {
// A function that makes use of list
}
But where do I call it? I must call it when the list is already available to me as my function will give me an error the list is undefined (if it's not yet available).
I also cannot invoke it in render (before the return statement) as it gives me an error that render() must be pure.
Is there another lifecycle method that I can use?
Just do this, and in redux store please make sure that initial state of list should be []
const mapStateToProps = state => {
return {
list: someFunction(state.list)
};
};
These are two ways you can play with received props from Redux
Do it in render
render() {
const { list } = this.props;
const items = list && list.map((item, index) => {
return <li key={item.id}>{item.value}</li>
});
return(
<div>
{items}
</div>
);
}
Or Do it in componentWillReceiveProps method if you are not using react 16.3 or greater
this.state = {
items: []
}
componentWillReceiveProps(nextProps){
if(nextProps.list != this.props.list){
const items = nextProps.list && nextProps.list.map((item, index) => {
return <li key={item.id}>{item.value}</li>
});
this.setState({items: items});
}
}
render() {
const {items} = this.state;
return(
<div>
{items}
</div>
);
}
You can also do it in componentDidMount if your Api call is placed in componentWillMount or receiving props from parent.
Let's say we got two different React components. One contains reports with dates, the other should show employees that worked that particular month.
So depending on what reports month was clicked, I need to be able to show those employees, but in a second component.
I'm able to get the date that was clicked in the first one but in order to know which employees to show I need to compare that data (from the 1st component) with employees data (second component).
The big question here is - HOW CAN I TRANSFER THAT NEWLY CONSTRUCTED (onClick - Custom function)EVENTS DATA TO THAT SECOND COMPONENT SO I CAN COMPARE THEM ??
You can create a "Parent" component which will render your two components.
The Parent component will have the selected date in the state.
class Parent extends Component {
constructor() {
this.handleDateChange = this.handleDateChange.bind(this);
this.state = { date: null };
}
handleDataChange(date) {
this.setState({ date });
}
render() {
return (
<div>
<Component1 onDateChange={this.handeDataChange} />
<Component2 date={this.state.date} />
</div>
);
}
}
You have to update your Component1 to receive onDateChange, and you have to call that function when the date is updated:
// where the date is updated
this.props.onDateChange(newDate);
Also you have to update your Component2 to receive date (the selected date) which you can use to filter your employees:
// maybe in the render function... you will know the selected date with this.props.date. For example you could do something like this:
const filtered = this.employees.filter(employee => employee.date === this.props.date);
How does this work?
when you select your date in your first component, it will call handleDateChange
... It will update Parent's state
... then Parent's render function will be called (because the state changed)
... then it will pass the new date (stored in the state) to the second component.
Contain the component within a common parent component that's then able to act as a broker for the relevant data.
Often, several components need to reflect the same changing data. We recommend lifting the shared state up to their closest common ancestor.
[From: lifting state up]
This is simplified but something like:
class Employees extends Component {
state = {
employees: []
}
async componentDidMount() {
const { clickedDate } = this.props
const employees = await fetchEmployees(clickedDate) // or whatever
this.setState({ employees })
}
render() {
const { employees } = this.state
if (employees.length === 0) {
return
<p>Loading...</p>
}
return (
<div className='employees'>
{
employees.map(employee => (
<p>{employee}</p>
))
}
</div>
)
}
}
const Reports = ({ dates, setClickedDate }) => (
<div className='reports'>
{
dates.map(date => (
<p onClick={() => setClickedDate(date)}>{date}</p>
))
}
</div>
)
class Parent extends Component {
state = {
clickedDate: undefined,
dates: ['dates', 'from', 'somewhere']
}
setClickedDate = clickedDate => this.setState({ clickedDate })
render() {
const { clickedDate } = this.state
return [
<Reports dates={dates} setClickedDate={this.setClickedDate} />,
<Employees clickedDate={clickedDate} />
]
}
}