Use state from props with Hooks when top Component uses ComponentDidMount - javascript

So, this is my first time using Hooks and also be aware that Im learning please. Im currently working on a group project were only I was allowed to use React Hooks, we are working on a front-end project. I'll try my best to detail everything. We have an App class component were we hold default state currentProduct: ''. We use componentDidMount() to get data from an API and then update the state to be by default, the first product from an Array of Objects.
I created a separate component called RelatedItems, where Im using hooks.
This is how my component looks like:
import React, { useState, useEffect } from 'react';
const RelatedItems = (props) => {
const [items, setItems] = useState(props.currentProduct);
useEffect(() => {
setItems(props.currentProduct);
}, [items])
return (
<div className="Related-Items">
<h1>Related Items</h1>
<h3>{items}</h3>
</div>
)
}
export default RelatedItems;
I tried using useEffect looking at the Docs and some other articles in StackOverflow, but I still keep my items state as an empty string. I figured it's happening because essentially componentDidMount is an async function.
This is how the App component looks like:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
currentProduct: ''
}
this.handleClick = this.handleClick.bind(this);
}
handleClick(e, id) {
e.preventDefault();
this.setState({ currentProduct: Number(id) })
}
componentDidMount() {
axios.get('/sedna/products')
.then((response) => {
this.setState({
currentProduct: response.data[0].id
})
})
.catch((error) => {
console.error(error);
})
}
render() {
return (
<div>
<h1>Hello Sedna</h1>
<SearchBar handleClick={this.handleClick} />
<RelatedItems currentProduct={this.state.currentProduct} />
</div>
)
}
}
export default App;
I'm rendering items in an H3 just to test that I get the proper state, any feedback is welcome! Sorry any grammar mistakes

Your current code will result in an infinite loop if the useEffect ever fires because the dependency is changed by the useEffect.
Instead you want the dependency to be the passed prop, so that when the prop changes the useEffect is triggered and the state is updated appropriately.
const RelatedItems = (props) => {
const [items, setItems] = useState(props.currentProduct);
useEffect(() => {
setItems(props.currentProduct);
}, [props.currentProduct])
return (
<div className="Related-Items">
<h1>Related Items</h1>
<h3>{items}</h3>
</div>
)
}
Keep in mind though that setting state from props is a bit of an anti-pattern because it fragments the state so there is no longer a single source of truth. (If you change items in RelatedItems the parent will hold a different value)
For this reason you are better off handling state management in the parent and passing props to the child for rendering.
const RelatedItems = (props) => {
return (
<div className="Related-Items">
<h1>Related Items</h1>
<h3>{props.currentProduct}</h3>
</div>
)
}

If you want to duplicate data inside the component state, you should specify props.currentProduct as a hook dependency.
...
useEffect(() => {
setItems(props.currentProduct);
}, [props.currentProduct])
...
But in the code that you have given, the RelatedItems component does not need an own state at all. In any case, it will be re-render when the prop changes
const RelatedItems = (props) => {
return (
<div className="Related-Items">
<h1>Related Items</h1>
<h3>{props.currentProduct}</h3>
</div>
)
}

Related

React Warning: Cannot update a component from inside the function body of a different component

I am using Redux with Class Components in React. Having the below two states in Redux store.
{ spinner: false, refresh: false }
In Parent Components, I have a dispatch function to change this states.
class App extends React.Component {
reloadHandler = () => {
console.log("[App] reloadComponent");
this.props.onShowSpinner();
this.props.onRefresh();
};
render() {
return <Child reloadApp={this.reloadHandler} />;
}
}
In Child Component, I am trying to reload the parent component like below.
class Child extends React.Component {
static getDerivedStateFromProps(props, state) {
if (somecondition) {
// doing some redux store update
props.reloadApp();
}
}
render() {
return <button />;
}
}
I am getting error as below.
Warning: Cannot update a component from inside the function body of a
different component.
How to remove this warning? What I am doing wrong here?
For me I was dispatching to my redux store in a React Hook. I had to dispatch in a useEffect to properly sync with the React render cycle:
export const useOrderbookSubscription = marketId => {
const { data, error, loading } = useSubscription(ORDERBOOK_SUBSCRIPTION, {
variables: {
marketId,
},
})
const formattedData = useMemo(() => {
// DISPATCHING HERE CAUSED THE WARNING
}, [data])
// DISPATCHING HERE CAUSED THE WARNING TOO
// Note: Dispatching to the store has to be done in a useEffect so that React
// can sync the update with the render cycle otherwise it causes the message:
// `Warning: Cannot update a component from inside the function body of a different component.`
useEffect(() => {
orderbookStore.dispatch(setOrderbookData(formattedData))
}, [formattedData])
return { data: formattedData, error, loading }
}
If your code calls a function in a parent component upon a condition being met like this:
const ListOfUsersComponent = ({ handleNoUsersLoaded }) => {
const { data, loading, error } = useQuery(QUERY);
if (data && data.users.length === 0) {
return handleNoUsersLoaded();
}
return (
<div>
<p>Users are loaded.</p>
</div>
);
};
Try wrapping the condition in a useEffect:
const ListOfUsersComponent = ({ handleNoUsersLoaded }) => {
const { data, loading, error } = useQuery(QUERY);
useEffect(() => {
if (data && data.users.length === 0) {
return handleNoUsersLoaded();
}
}, [data, handleNoUsersLoaded]);
return (
<div>
<p>Users are loaded.</p>
</div>
);
};
It seems that you have latest build of React#16.13.x. You can find more details about it here. It is specified that you should not setState of another component from other component.
from the docs:
It is supported to call setState during render, but only for the same component. If you call setState during a render on a different component, you will now see a warning:
Warning: Cannot update a component from inside the function body of a different component.
This warning will help you find application bugs caused by unintentional state changes. In the rare case that you intentionally want to change the state of another component as a result of rendering, you can wrap the setState call into useEffect.
Coming to the actual question.
I think there is no need of getDerivedStateFromProps in the child component body. If you want to trigger the bound event. Then you can call it via the onClick of the Child component as i can see it is a <button/>.
class Child extends React.Component {
constructor(props){
super(props);
this.updateState = this.updateState.bind(this);
}
updateState() { // call this onClick to trigger the update
if (somecondition) {
// doing some redux store update
this.props.reloadApp();
}
}
render() {
return <button onClick={this.updateState} />;
}
}
Same error but different scenario
tl;dr wrapping state update in setTimeout fixes it.
This scenarios was causing the issue which IMO is a valid use case.
const [someState, setSomeState] = useState(someValue);
const doUpdate = useRef((someNewValue) => {
setSomeState(someNewValue);
}).current;
return (
<SomeComponent onSomeUpdate={doUpdate} />
);
fix
const [someState, setSomeState] = useState(someValue);
const doUpdate = useRef((someNewValue) => {
setTimeout(() => {
setSomeState(someNewValue);
}, 0);
}).current;
return (
<SomeComponent onSomeUpdate={doUpdate} />
);
In my case I had missed the arrow function ()=>{}
Instead of onDismiss={()=>{/*do something*/}}
I had it as onDismiss={/*do something*/}
I had same issue after upgrading react and react native, i just solved that issue by putting my props.navigation.setOptions to in useEffect. If someone is facing same problen that i had i just want to suggest him put your state changing or whatever inside useEffect
Commented some lines of code, but this issue is solvable :) This warnings occur because you are synchronously calling reloadApp inside other class, defer the call to componentDidMount().
import React from "react";
export default class App extends React.Component {
reloadHandler = () => {
console.log("[App] reloadComponent");
// this.props.onShowSpinner();
// this.props.onRefresh();
};
render() {
return <Child reloadApp={this.reloadHandler} />;
}
}
class Child extends React.Component {
static getDerivedStateFromProps(props, state) {
// if (somecondition) {
// doing some redux store update
props.reloadApp();
// }
}
componentDidMount(props) {
if (props) {
props.reloadApp();
}
}
render() {
return <h1>This is a child.</h1>;
}
}
I got this error using redux to hold swiperIndex with react-native-swiper
Fixed it by putting changeSwiperIndex into a timeout
I got the following for a react native project while calling navigation between screens.
Warning: Cannot update a component from inside the function body of a different component.
I thought it was because I was using TouchableOpacity. This is not an issue of using Pressable, Button, or TouchableOpacity. When I got the error message my code for calling the ChatRoom screen from the home screen was the following:
const HomeScreen = ({navigation}) => {
return (<View> <Button title = {'Chats'} onPress = { navigation.navigate('ChatRoom')} <View>) }
The resulting behavior was that the code gave out that warning and I couldn't go back to the previous HomeScreen and reuse the button to navigate to the ChatRoom. The solution to that was doing the onPress in an inline anonymous function.
onPress{ () => navigation.navigate('ChatRoom')}
instead of the previous
onPress{ navigation.navigate('ChatRoom')}
so now as expected behavior, I can go from Home to ChatRoom and back again with a reusable button.
PS: 1st answer ever in StackOverflow. Still learning community etiquette. Let me know what I can improve in answering better. Thanx
If you want to invoke some function passed as props automatically from child component then best place is componentDidMount lifecycle methods in case of class components or useEffect hooks in case of functional components as at this point component is fully created and also mounted.
I was running into this problem writing a filter component with a few text boxes that allows the user to limit the items in a list within another component. I was tracking my filtered items in Redux state. This solution is essentially that of #Rajnikant; with some sample code.
I received the warning because of following. Note the props.setFilteredItems in the render function.
import {setFilteredItems} from './myActions';
const myFilters = props => {
const [nameFilter, setNameFilter] = useState('');
const [cityFilter, setCityFilter] = useState('');
const filterName = record => record.name.startsWith(nameFilter);
const filterCity = record => record.city.startsWith(cityFilter);
const selectedRecords = props.records.filter(rec => filterName(rec) && filterCity(rec));
props.setFilteredItems(selectedRecords); // <-- Danger! Updates Redux during a render!
return <div>
<input type="text" value={nameFilter} onChange={e => setNameFilter(e.target.value)} />
<input type="text" value={cityFilter} onChange={e => setCityFilter(e.target.value)} />
</div>
};
const mapStateToProps = state => ({
records: state.stuff.items,
filteredItems: state.stuff.filteredItems
});
const mapDispatchToProps = { setFilteredItems };
export default connect(mapStateToProps, mapDispatchToProps)(myFilters);
When I ran this code with React 16.12.0, I received the warning listed in the topic of this thread in my browser console. Based on the stack trace, the offending line was my props.setFilteredItems invocation within the render function. So I simply enclosed the filter invocations and state change in a useEffect as below.
import {setFilteredItems} from './myActions';
const myFilters = props => {
const [nameFilter, setNameFilter] = useState('');
const [cityFilter, setCityFilter] = useState('');
useEffect(() => {
const filterName = record => record.name.startsWith(nameFilter);
const filterCity = record => record.city.startsWith(cityFilter);
const selectedRecords = props.records.filter(rec => filterName(rec) && filterCity(rec));
props.setFilteredItems(selectedRecords); // <-- OK now; effect runs outside of render.
}, [nameFilter, cityFilter]);
return <div>
<input type="text" value={nameFilter} onChange={e => setNameFilter(e.target.value)} />
<input type="text" value={cityFilter} onChange={e => setCityFilter(e.target.value)} />
</div>
};
const mapStateToProps = state => ({
records: state.stuff.items,
filteredItems: state.stuff.filteredItems
});
const mapDispatchToProps = { setFilteredItems };
export default connect(mapStateToProps, mapDispatchToProps)(myFilters);
When I first added the useEffect I blew the top off the stack since every invocation of useEffect caused state change. I had to add an array of skipping effects so that the effect only ran when the filter fields themselves changed.
I suggest looking at video below. As the warning in the OP's question suggests, there's a change detection issue with the parent (Parent) attempting to update one child's (Child 2) attribute prematurely as the result of another sibling child's (Child 1) callback to the parent. For me, Child 2 was prematurely/incorrectly calling the passed in Parent callback thus throwing the warning.
Note, this commuincation workflow is only an option. I personally prefer exchange and update of data between components via a shared Redux store. However, sometimes it's overkill. The video suggests a clean alternative where the children are 'dumb' and only converse via props mand callbacks.
Also note, If the callback is invoked on an Child 1 'event' like a button click it'll work since, by then, the children have been updated. No need for timeouts, useEffects, etc. UseState will suffice for this narrow scenario.
Here's the link (thanks Masoud):
https://www.youtube.com/watch?v=Qf68sssXPtM
In react native, if you change the state yourself in the code using a hot-reload I found out I get this error, but using a button to change the state made the error go away.
However wrapping my useEffect content in a :
setTimeout(() => {
//....
}, 0);
Worked even for hot-reloading but I don't want a stupid setTimeout for no reason so I removed it and found out changing it via code works just fine!
I was updating state in multiple child components simultaneously which was causing unexpected behavior. replacing useState with useRef hook worked for me.
Try to use setTimeout,when I call props.showNotification without setTimeout, this error appear, maybe everything run inTime in life circle, UI cannot update.
const showNotifyTimeout = setTimeout(() => {
this.props.showNotification();
clearTimeout(showNotifyTimeout);
}, 100);

Why isn't `useContext` re-rendering my component?

As per the docs:
When the nearest <MyContext.Provider> above the component updates, this Hook will trigger a rerender with the latest context value passed to that MyContext provider. Even if an ancestor uses React.memo or shouldComponentUpdate, a rerender will still happen starting at the component itself using useContext.
...
A component calling useContext will always re-render when the context value changes.
In my Gatsby JS project I define my Context as such:
Context.js
import React from "react"
const defaultContextValue = {
data: {
filterBy: 'year',
isOptionClicked: false,
filterValue: ''
},
set: () => {},
}
const Context = React.createContext(defaultContextValue)
class ContextProviderComponent extends React.Component {
constructor() {
super()
this.setData = this.setData.bind(this)
this.state = {
...defaultContextValue,
set: this.setData,
}
}
setData(newData) {
this.setState(state => ({
data: {
...state.data,
...newData,
},
}))
}
render() {
return <Context.Provider value={this.state}>{this.props.children}</Context.Provider>
}
}
export { Context as default, ContextProviderComponent }
In a layout.js file that wraps around several components I place the context provider:
Layout.js:
import React from 'react'
import { ContextProviderComponent } from '../../context'
const Layout = ({children}) => {
return(
<React.Fragment>
<ContextProviderComponent>
{children}
</ContextProviderComponent>
</React.Fragment>
)
}
And in the component that I wish to consume the context in:
import React, { useContext } from 'react'
import Context from '../../../context'
const Visuals = () => {
const filterByYear = 'year'
const filterByTheme = 'theme'
const value = useContext(Context)
const { filterBy, isOptionClicked, filterValue } = value.data
const data = <<returns some data from backend>>
const works = filterBy === filterByYear ?
data.nodes.filter(node => node.year === filterValue)
:
data.nodes.filter(node => node.category === filterValue)
return (
<Layout noFooter="true">
<Context.Consumer>
{({ data, set }) => (
<div onClick={() => set( { filterBy: 'theme' })}>
{ data.filterBy === filterByYear ? <h1>Year</h1> : <h1>Theme</h1> }
</div>
)
</Context.Consumer>
</Layout>
)
Context.Consumer works properly in that it successfully updates and reflects changes to the context. However as seen in the code, I would like to have access to updated context values in other parts of the component i.e outside the return function where Context.Consumer is used exclusively. I assumed using the useContext hook would help with this as my component would be re-rendered with new values from context every time the div is clicked - however this is not the case. Any help figuring out why this is would be appreciated.
TL;DR: <Context.Consumer> updates and reflects changes to the context from child component, useContext does not although the component needs it to.
UPDATE:
I have now figured out that useContext will read from the default context value passed to createContext and will essentially operate independently of Context.Provider. That is what is happening here, Context.Provider includes a method that modifies state whereas the default context value does not. My challenge now is figuring out a way to include a function in the default context value that can modify other properties of that value. As it stands:
const defaultContextValue = {
data: {
filterBy: 'year',
isOptionClicked: false,
filterValue: ''
},
set: () => {}
}
set is an empty function which is defined in the ContextProviderComponent (see above). How can I (if possible) define it directly in the context value so that:
const defaultContextValue = {
data: {
filterBy: 'year',
isOptionClicked: false,
filterValue: ''
},
test: 'hi',
set: (newData) => {
//directly modify defaultContextValue.data with newData
}
}
There is no need for you to use both <Context.Consumer> and the useContext hook.
By using the useContext hook you are getting access to the value stored in Context.
Regarding your specific example, a better way to consume the Context within your Visuals component would be as follows:
import React, { useContext } from "react";
import Context from "./context";
const Visuals = () => {
const filterByYear = "year";
const filterByTheme = "theme";
const { data, set } = useContext(Context);
const { filterBy, isOptionClicked, filterValue } = data;
const works =
filterBy === filterByYear
? "filter nodes by year"
: "filter nodes by theme";
return (
<div noFooter="true">
<div>
{data.filterBy === filterByYear ? <h1>Year</h1> : <h1>Theme</h1>}
the value for the 'works' variable is: {works}
<button onClick={() => set({ filterBy: "theme" })}>
Filter by theme
</button>
<button onClick={() => set({ filterBy: "year" })}>
Filter by year
</button>
</div>
</div>
);
};
export default Visuals;
Also, it seems that you are not using the works variable in your component which could be another reason for you not getting the desired results.
You can view a working example with the above implementation of useContext that is somewhat similar to your example in this sandbox
hope this helps.
Problem was embarrassingly simple - <Visuals> was higher up in the component tree than <Layout was for some reason I'm still trying to work out. Marking Itai's answer as correct because it came closest to figuring things out giving the circumstances
In addition to the solution cited by Itai, I believe my problem can help other people here
In my case I found something that had already happened to me, but that now presented itself with this other symptom, of not re-rendering the views that depend on a state stored in a context.
This is because there is a difference in dates between the host and the device. Explained here: https://github.com/facebook/react-native/issues/27008#issuecomment-592048282
And that has to do with the other symptom that I found earlier: https://stackoverflow.com/a/63800388/10947848
To solve this problem, just follow the steps in the first link, or if you find it necessary to just disable the debug mode

Child component doesn't rerender but parent component does rerender. How to make child component rerender?

Parent component does rerender upon receiving new props but its child component doesn't rerender. Child components only render for the first time and never rerender nor receive props from the redux store
I'm getting updated data from redux store in Parent component but not in the child components. Child components only receive data from redux store when they render for the first time
My Parent Component Home.js
Object seaFCLJSON look like this
const seaFCLJSON ={"rates": {"sort":"faster", "someOther": "someOtherValues"}};
when the redux store gets updated, seaFCLJSON looks like this
const seaFCLJSON ={"rates": {"sort":"cheaper","someOther": "someOtherValues"}};
class Home extends Component {
state = {
seaFCLJSON: {}
};
componentDidMount = () => {
this.setState({ seaFCLJSON: this.props.seaFCLJSON });
};
componentWillReceiveProps = nextProps => {
if (this.state.seaFCLJSON !== nextProps.seaFCLJSON) {
this.setState({ seaFCLJSON: nextProps.seaFCLJSON });
}
};
render() {
const { seaFCLJSON } = this.props;
return (
<>
{!isEmpty(seaFCLJSON) && seaFCLJSON.rates && seaFCLJSON.rates.fcl ? (
<FCLContainer fclJSON={seaFCLJSON} />
) : null} //it never rerenders upon getting updated data from redux store
<h5>{JSON.stringify(seaFCLJSON.rates && seaFCLJSON.rates.sort)}</h5> //it rerenders everytime upon getting updated data from redux store
</>
);
}
}
const mapStateToProps = state => {
return {
seaFCLJSON: state.route.seaFCLJSON
};
};
export default connect(
mapStateToProps,
actions
)(Home);
isEmpty.js
export const isEmpty = obj => {
return Object.entries(obj).length === 0 && obj.constructor === Object;
};
My Child Component FCLContainer.js
class FCLContainer extends Component {
state = {
seaFCLJSON: {}
};
componentDidMount = () => {
this.setState({ seaFCLJSON: this.props.seaFCLJSON });
};
componentWillReceiveProps = nextProps => {
console.log("outside state value: ", this.state.seaFCLJSON);
if (this.state.seaFCLJSON !== nextProps.seaFCLJSON) {
this.setState({ seaFCLJSON: nextProps.seaFCLJSON });
console.log("inside state value: ", this.state.seaFCLJSON);
}
};
render() {
const { seaFCLJSON } = this.state;
console.log("rendering .. parent props: ", this.props.fclJSON);
console.log("rendering .. redux store props: ", this.props.seaFCLJSON);
return (
<>
<div className="home-result-container">
<div>
<h5>This child component never rerenders :(</h5>
</div>
</div>
</>
);
}
}
const mapStateToProps = state => {
return {
seaFCLJSON: state.route.seaFCLJSON
};
};
export default connect(
mapStateToProps,
actions
)(FCLContainer);
I don't know whether there are problems in Parent component or problems in the child component. componentWillReceiveProps gets invoked in the parent component but not in the child component. Please ignore any missing semi-colon or braces because I have omitted some unnecessary codes.
Edit 1: I just duplicated value from props to state just for debugging purposes.
I will appreciate your help. Thank you.
Edit 2: I was directly changing an object in redux actions. That's the reason CWRP was not getting fired. It was the problem. For more check out my answer below.
componentWillReceiveProps will be deprecated in react 17, use componentDidUpdate instead, which is invoked immediately after updating occurs
Try something like this:
componentDidUpdate(prevProps, prevState) {
if (this.prevProps.seaFCLJSON !== this.props.seaFCLJSON) {
this.setState({ seaFCLJSON: this.props.seaFCLJSON });
}
};
At the first place it is absolutely meaningless to duplicate value from props to state, what is the meaning of it? Totally pointless, just keep it in props
About your issue - most probably this condition doesnt match, thats why child component doesnt trigger
!isEmpty(seaFCLJSON) && seaFCLJSON.rates && seaFCLJSON.rates.fcl
check it in debugger
As far as I can see, your problem is that you pass the following to your child component:
<FCLContainer fclJSON={seaFCLJSON} />
But you assume that you receive a prop called 'seaFCLJSON':
componentDidMount = () => {
this.setState({ seaFCLJSON: this.props.seaFCLJSON });
};
You should change your code to:
<FCLContainer seaFCLJSON={seaFCLJSON} />
Apart from that, as #Paul McLoughlin already mentioned, you should use the prop directly instead of adding it to your state.
I found the issue I was directly mutating the object in actions. I only knew state should not be directly mutated in class or inside reducer. I changed the actions where I was directly changing an object and then saving it in redux store via dispatch and, then I received the updated props in CWRP. This really took me a lot of times to figure out. This kind of issue is hard to find out at least for me. I guess I get this from https://github.com/uberVU/react-guide/issues/17
A lesson I learned: Never directly mutate an Object
I changed this
//FCL sort by faster
export const sortByFasterFCLJSON = () => async (dispatch, getState) => {
let seaFCLJSON = getState().route.seaFCLJSON;
if (!seaFCLJSON.rates) return;
seaFCLJSON.rates.fcl = _.orderBy(
seaFCLJSON.rates.fcl,
["transit_time"],
["asc"]
);
seaFCLJSON.rates.sort = "Faster"; //this is the main culprit
dispatch({ type: SET_SEA_FCL_JSON, payload: seaFCLJSON });
};
to this
//FCL sort by faster
export const sortByFasterFCLJSON = () => async (dispatch, getState) => {
let seaFCLJSON = getState().route.seaFCLJSON;
if (!seaFCLJSON.rates) return;
seaFCLJSON.rates.fcl = _.orderBy(
seaFCLJSON.rates.fcl,
["transit_time"],
["asc"]
);
// seaFCLJSON.rates.sort = "Faster"; //this was the main culprit, got lost
seaFCLJSON = {
...seaFCLJSON,
rates: { ...seaFCLJSON.rates, sort: "Faster" }
};
dispatch({ type: SET_SEA_FCL_JSON, payload: seaFCLJSON });
};
the power of not mutating data
Side note: Redux Troubleshooting

Render functional component on variable change

I can not get my component to re-render when an internal variable changes. The system I working on uses functional components. The case is like this:
export const myComponent = (props: compPropsType) => {
const myClassNames ....
let objectList= getObjectList(window.location.hash, props.pageTree);
window.addEventListener('hashchange', () => {
console.log('hello');
objectList = getObjectList(window.location.hash, props.pageTree);
});
return (
<>
<header className={headerClassNames}>
<Block className={...}>
...
<myChildComp objList={objectList}>
...
)
};
The problem is to render <myCildComp> when the hash updates. (objectListis an array of strings, used to create navigation toolbar.)
When I click a link on the page, hello is written to console, so the listener is working, but it does not re-render the child component.
a function component in react is equivalent to the render() function in a class component.
so in your case you're adding an eventListener every time the component is re-rendered which creates a memory leak, you should use useEffect hook instead to add it once and remove it when the component/hook is destroyed.
your component doesn't re-render because nothing is telling it to.. components in react only re-render when either state or props changes.. so in your case I think you'd need to add objectList to state combined with useEffect hook like so
export const myComponent = (props: compPropsType) => {
const myClassNames ....
const [objectList, setObjectList] = useState([])
const onHashChanged = () => {
console.log('hello')
let newObjectList = getObjectList(window.location.hash, props.pageTree)
setObjectList(newObjectList)
}
useEffect(() => {
window.addEventListener('hashchange', onHashChanged)
return () => window.removeEventListener('hashchange', onHashChanged)
}, [])
return (
<>
<header className={headerClassNames}>
<Block className={...}>
...
<myChildComp objList={objectList}>
...
)
};
when using an empty array as a second argument in useEffect it will only be called once equivalent to componentDidMount and I'm returning a callback function to unsubscribe/remove the listener which works similar to componentWillUnmount
As suggested in a comment, I rewrote the function to be a class:
export class myComponent extends React.PureComponent<PropsTypes> {
constructor(props) {
super(props)
this.state = {objList = getObjectList(window.location.hash, this.props.pageTree)};
}
componentDidMount(): void {
window.addEventListener('hashchange', () => {
this.setState({
breadcrumbList: getObjList(window.location.hash, this.props.pageTree),
});
});
}
render() {
return (....
<myChildComp objList={this.state.objList}>
...
This works, but it is the best way?

How to update redux store for many components at the same time on click of a single button?

I have four stateful react components in a single page, they get updated simultaneously on the click of a single button.
I now want to use redux store to save the state of all the components.
I wrote react-redux connect for the first component and it works fine.
Then followed the same logic for the second component, but the store is not getting updated along with the first one.
How can I save the states of all the components at same time to store?
I think you can use this kind of structure.
The main page in which you are using the four stateful components should look like this.
class App extends React.Component {
render() {
const { commonState, updateCommonStateHandler } = this.props;
return (
<div>
<Component1 commonState={commonState} updateCommonStateHandler={updateCommonStateHandler} />
<Component2 commonState={commonState} updateCommonStateHandler={updateCommonStateHandler} />
<Component3 commonState={commonState} updateCommonStateHandler={updateCommonStateHandler} />
<Component4 commonState={commonState} updateCommonStateHandler={updateCommonStateHandler} />
</div>
);
}
}
const mapStateToProps = state => {
return {
commonState: state.commonState
};
};
const mapDispatchToProps = dispatch => {
return {
updateCommonStateHandler: change => {
dispatch(() => ({
type: 'UPDATE_COMMON_STATE',
change
}));
}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
With the reducer
const updateCommonStateReducer = (state = {}, action) => {
const newState = extend({}, state);
if(action.type === 'UPDATE_COMMON_STATE') {
newState.commonState = newState.commonState || {};
extend(newState.commonState, action.change || {});
}
return newState;
};
So if you want to update the state from any child component, you should call the updateCommonStateHandler, which will dispatch an action to update the state.
On state update react will re-render all the components with new commonState.
PS: This is just a sample code explaining the situation, not the solution and it's
written in ES6

Categories

Resources