REACT: Unable to update children props - javascript

I'm having troubles updating the header class so it updates it's className whenever displaySection() is called. I know that the parent state changes, because the console log done in displaySection() registers the this.state.headerVisible changes but nothing in my children component changes, i don't know what I'm missing, I've been trying different solutions for some hours and I just can't figure it out what i'm doing wrong, the header headerVisible value stays as TRUE instead of changing when the state changes.
I don't get any error code in the console, it's just that the prop headerVisible from the children Header doesn't get updated on it's parent state changes.
Thank you!
class IndexPage extends React.Component {
constructor(props) {
super(props)
this.state = {
section: "",
headerVisible: true,
}
this.displaySection = this.displaySection.bind(this)
}
displaySection(sectionSelected) {
this.setState({ section: sectionSelected }, () => {
this.sectionRef.current.changeSection(this.state.section)
})
setTimeout(() => {
this.setState({
headerVisible: !this.state.headerVisible,
})
}, 325)
setTimeout(()=>{
console.log('this.state', this.state)
},500)
}
render() {
return (
<Layout>
<Header selectSection={this.displaySection} headerVisible={this.state.headerVisible} />
</Layout>
)
}
}
const Header = props => (
<header className={props.headerVisible ? 'visible' : 'invisible'}>
<div className="navbar-item column is-size-7-mobile is-size-5-tablet is-uppercase has-text-weight-semibold">
<span onClick={() => { this.props.selectSection("projects")}}>
{" "}
Projects
</span>
</header>
)

There seemed to be a couple of issues with your example code:
Missing closing div in Header
Using this.props instead of props in onclick in span in Header
The below minimal example seems to work. I had to remove your call to this.sectionRef.current.changeSection(this.state.section) as I didn't know what sectionRef was supposed to be because it's not in your example.
class IndexPage extends React.Component {
constructor(props) {
super(props)
this.state = {
section: "",
headerVisible: true,
}
this.displaySection = this.displaySection.bind(this)
}
displaySection(sectionSelected) {
this.setState({ section: sectionSelected })
setTimeout(() => {
this.setState({
headerVisible: !this.state.headerVisible,
})
}, 325)
setTimeout(()=>{
console.log('this.state', this.state)
},500)
}
render() {
return (
<div>
<Header selectSection={this.displaySection} headerVisible={this.state.headerVisible} />
</div>
)
}
}
const Header = props => (
<header className={props.headerVisible ? 'visible' : 'invisible'}>
<div className="navbar-item column is-size-7-mobile is-size-5-tablet is-uppercase has-text-weight-semibold">
<span onClick={() => { props.selectSection("projects")}}>
{" "}
Projects
</span>
</div>
</header>
)
ReactDOM.render(
<IndexPage />,
document.getElementsByTagName('body')[0]
);
.visible {
opacity: 1
}
.invisible {
opacity: 0
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

There is a markup error in your code in Header component - div tag is not closed.
Also, I suppose, you remove some code to make example easy, and there is artifact of this.sectionRef.current.changeSection(this.state.section) cause this.sectionRef is not defined.
As #Felix Kling said, when you change the state of the component depending on the previous state use function prevState => ({key: !prevState.key})
Any way here is a working example of what you trying to achieve:
// #flow
import * as React from "react";
import Header from "./Header";
type
Properties = {};
type
State = {
section: string,
headerVisible: boolean,
};
class IndexPage extends React.Component<Properties, State> {
static defaultProps = {};
state = {};
constructor(props) {
super(props);
this.state = {
section: "",
headerVisible: true,
};
this.displaySection = this.displaySection.bind(this)
}
displaySection(sectionSelected) {
setTimeout(
() => this.setState(
prevState => ({
section: sectionSelected,
headerVisible: !prevState.headerVisible
}),
() => console.log("Debug log: \n", this.state)
),
325
);
}
render(): React.Node {
const {section, headerVisible} = this.state;
return (
<React.Fragment>
<Header selectSection={this.displaySection} headerVisible={headerVisible} />
<br/>
<div>{`IndexPage state: headerVisible - ${headerVisible} / section - ${section}`}</div>
</React.Fragment>
)
}
}
export default IndexPage;
and Header component
// #flow
import * as React from "react";
type Properties = {
headerVisible: boolean,
selectSection: (section: string) => void
};
const ComponentName = ({headerVisible, selectSection}: Properties): React.Node => {
const headerRef = React.useRef(null);
return (
<React.Fragment>
<header ref={headerRef} className={headerVisible ? 'visible' : 'invisible'}>
<div className="navbar-item column is-size-7-mobile is-size-5-tablet is-uppercase has-text-weight-semibold">
<span onClick={() => selectSection("projects")}>Projects</span>
</div>
</header>
<br/>
<div>Header class name: {headerRef.current && headerRef.current.className}</div>
</React.Fragment>
);
};
export default ComponentName;

Related

not passing state correctly in react typescript

I am trying to toggle a modal from separate components. the first most common component is my app.tsx so i set the state in that file.
type TokenUpdateType = {
sessionToken: string | undefined | null,
createActive: boolean
}
export default class App extends Component<{}, TokenUpdateType> {
constructor(props: TokenUpdateType) {
super(props)
this.state = {
sessionToken: undefined,
createActive: false
}
...
toggleModal = () => {
this.setState({createActive: !this.state.createActive})
}
return
<Home isOpen={this.state.createActive} toggleModal={this.toggleModal} />
my home component takes these props and passes again to another component
type AuthProps = {
isOpen: boolean
toggleModal: () => void
...
}
const Home = (props: AuthProps) => {
return(
<>
<Sidebar sessionToken={props.sessionToken} toggleModal={props.toggleModal}
<ChannelEntryModalDisplay sessionToken={props.sessionToken} isOpen={props.isOpen} toggleModal={props.toggleModal}/>
</>
)
}
isOpen gets passes to my modal component and is used in this component
type AuthProps = {
isOpen: boolean
toggleModal: () => void
...
}
const ChannelEntryModalDisplay = (props: AuthProps) => {
return(
<div>
<Modal show={props.isOpen}>
<ChannelEntry sessionToken={props.sessionToken}/>
<Button className='button' type='button' outline onClick={props.toggleModal}>close</Button>
</Modal>
</div>
)
}
my modal is not showing even when i set createactive to true. i believe i may be passing props incorrectly but im not sure what i am doing incorrectly. i appreciate any feedback.
try to create a new state from the props:
const [createActive, setCreateActive] = useState<boolean>()
constructor(props: TokenUpdateType)
{
super(props)
setCreateActive(props.createActive)
}
useEffect(() => {
setCreateActive(props.createActive) // update the state when props changes
}, [props])
...
toggleModal = () => {
this.setCreateActive(!createActive)
}
<Home isOpen={createActive} toggleModal={this.toggleModal} />

Using useEffect (or the equivalent) for class component (making a loading screen)

I am fairly new to React. I have currently made a loading screen in React with useEffect, but I'm not sure how to make it with class Components. This is my functional component which works.
const [sourceLoading, setSourceLoading] = React.useState(true);
// control when to stop loading
useEffect(() => {
setTimeout(() => {
setSourceLoading(false);
}, 1000);
}, [])
return (
<div>
{sourceLoading ? (<LoadingScreen />): (
<>
</>
)}
</div>
);
I'm currently converting the function like so, however it isn't working, and my loading screen never appears. Where am I going wrong? is componentDidMount not the correct substitution for useEffect here?
this.state = {
sourceLoading: true,
};
this.componentDidMount = this.componentDidMount.bind(this);
componentDidMount() {
setTimeout(() => {
this.setState({ sourceLoading: false});
}, 1000);
}
render() {
return (
<div>
{this.sourceLoading ? (<LoadingScreen />) : (<>
</>
)}
</div>
);
}
It works for me, if you change this line:
{this.sourceLoading ? (content) : ""}
// should be
{this.state.sourceLoading ? (content) : ""}
class App extends React.Component {
constructor() {
super();
this.state = {
sourceLoading: true,
};
}
componentDidMount() {
setTimeout(() => {
this.setState({
sourceLoading: false
});
}, 1000);
}
render() {
return (
<div>
{this.state.sourceLoading ? "loading" : "not"}
</div>
);
}
}
ReactDOM.render( <App /> , document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
You need to access the state in render function like
{this.state.sourceLoading ? (<LoadingScreen />) : null}

React Link To not updating the component

I am using React with Redux to list number of items and inside the item I have a list of similar items
In Home Page (there is a list of items when you click on any of them , it goes to the item path ) which is working well , but inside the item page , when you click on any items from similar items list (the view not updating )
the codeSandobx is here
App.js
const store = createStore(ItemsReducer, applyMiddleware(...middlewares));
class App extends React.Component {
render() {
return (
<Provider store={store}>
<Main />
</Provider>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
main.js
const Main = () => {
return (
<Router>
<div>
<Header />
<div className="container-fluid">
<Switch>
<Route exact path="/" component={Home} />
<Route path="/item/:id" component={Item} />
</Switch>
</div>
</div>
</Router>
);
};
export default Main;
Home.js
class Home extends React.Component {
render() {
const itemsList = this.props.items.map(item => {
return <ItemList item={item} key={item.id} />;
});
return <div className="items-list"> {itemsList}</div>;
}
}
const mapStateToProps = state => ({
items: state.items,
user: state.user
});
export default connect(mapStateToProps, null, null, {
pure: false
})(Home);
Item.js
class Item extends React.Component {
constructor(props) {
super();
this.state = {
item_id: props.match.params.id,
};
}
render() {
const itemsList = this.props.items.map(item => {
return <ItemList item={item} key={item.id} />;
});
return (
<div id="item-container">
<div className="item-list fav-items"> {itemsList} </div>;
</div>
);
}
}
const mapStateToProps = state => ({
items: state.items,
user: state.user
});
export default connect(mapStateToProps, null, null, {
pure: false
})(Item);
and finally the ItemList.js
class ItemList extends React.Component {
render() {
const item = this.props.item;
const item_link = "/item/" + item.id;
return (
<Link to={item_link}>
<div className="item-li">
{item.title}
</div>
</Link>
);
}
}
export default ItemList;
I've tired to use this solution from react-redux docs , but it didn't work
What do you expect to update on link click?
Any path /item/:id (with any id: 2423, 2435, 5465) will show the same result, because you don't use params.id inside the Item component
UPDATED
When id changes the component doesn't remount, only updates component (It's correct behavior)
If you want to fetchData on each changes of id, the next solution has to work for you
on hooks:
const Item = () => {
const params = useParams();
useEffect(() => {
axios.get(`/item/${params.id}`).then(...)
}, [params.id]);
return (
...
)
}
useEffect will call fetch each time when id is changing
and in class component you have to use componentDidUpdate:
class Item extends Component {
componentDidMount() {
this.fetchData();
}
componentDidUpdate(prevProps) {
if (prevProps.match.params.id !== this.props.match.params.id) {
this.fetchData();
}
}
fetchData = () => {
...
}
...
}

Reactjs, update all child components from sibling

I do sorting on reactjs, I can’t understand how to redraw all child components so that only one selected remains active, I can update the current one, but the others do not change. Here is the code for an example. Can anyone help / explain how to do it right?
nodejs, webpack, last reactjs
App.js
import React, { Component } from "react";
import Parent from "./Parent";
class App extends Component {
render() {
return(
<Parent />
)
}
}
export default App;
Parent.js
import React, { Component } from "react";
import Child from "./Child";
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
popularity: {"sorting": "desc", "active": true},
rating: {"sorting": "desc", "active": false},
reviews_count: {"sorting": "desc", "active": false},
};
}
updateFilters = () => {
// ??
};
render() {
return (
<div>
<Child type="popularity" sorting={this.state.popularity.sorting} active={this.state.popularity.active} updateFilters={this.updateFilters} />
<Child type="rating" sorting={this.state.rating.sorting} active={this.state.rating.active} updateFilters={this.updateFilters} />
<Child type="reviews_count" sorting={this.state.reviews_count.sorting} active={this.state.reviews_count.active} updateFilters={this.updateFilters} />
</div>
)
}
}
export default Parent;
Child.js
import React, { Component } from "react";
class Child extends Component {
handleClick = () => {
this.props.updateFilters();
};
render() {
let activeStr = "";
if (this.props.active) {
activeStr = "active"
} else {
activeStr = "inactive";
}
return(
<div onClick={() => this.handleClick}>
{this.props.type} {activeStr} {this.props.sorting}
</div>
);
}
}
export default Child;
Assuming you are trying to set the active flag for a clicked Type to true and also set all the other types to false.
<div onClick={() => this.handleClick}> this isn't correct, as you aren't invoking the function. This could be corrected to:
<div onClick={() => this.handleClick()}>
Then you can update handleClick to pass the Type:
handleClick = () => {
this.props.updateFilters(this.props.type);
};
OR
You could ignore that handleClick and call the prop function:
<div onClick={() => this.props.updateFilters(this.props.type)}>
Once you have passed the Type back into the updateFilters, we can simply iterate over the previous State Properties, setting all Types' Active Flag to false. However, if the Key matches the Type which was clicked, we set it to true.
updateFilters = type => {
this.setState(prevState => {
return Object.keys(prevState).reduce(
(result, key) => ({
...result,
[key]: { ...prevState[key], active: key === type }
}),
{}
);
});
};
Your Child component could be heavily refactored into a Pure Functional Component, making it a lot simpler:
const Child = ({ type, active, updateFilters, sorting }) => (
<div onClick={() => updateFilters(type)}>
{type} {active ? "active" : "inactive"} {sorting}
</div>
);
Work solution:
https://codesandbox.io/s/4j83nry569

Unmount component on click in child component button // React

I am struggling with successfully removing component on clicking in button. I found similar topics on the internet however, most of them describe how to do it if everything is rendered in the same component. In my case I fire the function to delete in the child component and pass this information to parent so the state can be changed. However I have no idea how to lift up the index of particular component and this is causing a problem - I believe.
There is a code
PARENT COMPONENT
export class BroadcastForm extends React.Component {
constructor (props) {
super(props)
this.state = {
numberOfComponents: [],
textMessage: ''
}
this.UnmountComponent = this.UnmountComponent.bind(this)
this.MountComponent = this.MountComponent.bind(this)
this.handleTextChange = this.handleTextChange.bind(this)
}
MountComponent () {
const numberOfComponents = this.state.numberOfComponents
this.setState({
numberOfComponents: numberOfComponents.concat(
<BroadcastTextMessageForm key={numberOfComponents.length} selectedFanpage={this.props.selectedFanpage}
components={this.state.numberOfComponents}
onTextChange={this.handleTextChange} dismissComponent={this.UnmountComponent} />)
})
}
UnmountComponent (index) {
this.setState({
numberOfComponents: this.state.numberOfComponents.filter(function (e, i) {
return i !== index
})
})
}
handleTextChange (textMessage) {
this.setState({textMessage})
}
render () {
console.log(this.state)
let components = this.state.numberOfComponents
for (let i = 0; i < components; i++) {
components.push(<BroadcastTextMessageForm key={i} />)
}
return (
<div>
<BroadcastPreferencesForm selectedFanpage={this.props.selectedFanpage}
addComponent={this.MountComponent}
textMessage={this.state.textMessage} />
{this.state.numberOfComponents.map(function (component) {
return component
})}
</div>
)
}
}
export default withRouter(createContainer(props => ({
...props
}), BroadcastForm))
CHILD COMPONENT
import React from 'react'
import { createContainer } from 'react-meteor-data'
import { withRouter } from 'react-router'
import { BroadcastFormSceleton } from './BroadcastForm'
import './BroadcastTextMessageForm.scss'
export class BroadcastTextMessageForm extends React.Component {
constructor (props) {
super(props)
this.handleChange = this.handleChange.bind(this)
this.unmountComponent = this.unmountComponent.bind(this)
}
handleChange (e) {
this.props.onTextChange(e.target.value)
}
unmountComponent (id) {
this.props.dismissComponent(id)
}
render () {
console.log(this.props, this.state)
const textMessage = this.props.textMessage
return (
<BroadcastFormSceleton>
<div className='textarea-container p-3'>
<textarea id='broadcast-message' className='form-control' value={textMessage}
onChange={this.handleChange} />
</div>
<div className='float-right'>
<button type='button'
onClick={this.unmountComponent}
className='btn btn-danger btn-outline-danger button-danger btn-small mr-3 mt-3'>
DELETE
</button>
</div>
</BroadcastFormSceleton>
)
}
}
export default withRouter(createContainer(props => ({
...props
}), BroadcastTextMessageForm))
I am having problem with access correct component and delete it by changing state. Any thoughts how to achieve it?
Please fix the following issues in your code.
Do not mutate the state of the component. Use setState to immutably change the state.
Do not use array index as the key for your component. Try to use an id field which is unique for the component. This will also help with identifying the component that you would need to unmount.
Try something like this. As mentioned before, you don't want to use array index as the key.
class ParentComponent extends React.Component {
constructor() {
this.state = {
// keep your data in state, as a plain object
textMessages: [
{
message: 'hello',
id: '2342334',
},
{
message: 'goodbye!',
id: '1254534',
},
]
};
this.handleDeleteMessage = this.handleDeleteMessage.bind(this);
}
handleDeleteMessage(messageId) {
// filter by Id, not index
this.setState({
textMessages: this.state.textMessages.filter(message => message.id !== messageId)
})
}
render() {
return (
<div>
{this.state.textMessages.map(message => (
// Use id for key. If your data doesn't come with unique ids, generate them.
<ChildComponent
key={message.id}
message={message}
handleDeleteMessage={this.handleDeleteMessage}
/>
))}
</div>
)
}
}
function ChildComponent({message, handleDeleteMessage}) {
function handleClick() {
handleDeleteMessage(message.id)
}
return (
<div>
{message.message}
<button
onClick={handleClick}
>
Delete
</button>
</div>
);
}

Categories