React State vs. Props - Why are state changes not reflected in components? - javascript

I'm trying to create a div that sits underneath the main app (lexically) but is styled to only show up after a timed delay. I feel like this is most likely a very simple failure on my part to grasp some of the react concepts I'm working with.
Here's my code: (The CSS is pseudo code)
import React, {Component} from 'React'; //eslint-disable-line
import styled from 'styled-components';
import ReactTimeout from 'react-timeout';
const Icon = styled.div.attrs({
dataRight: props => props.dataRight || '1em',
dataLeft: props => props.dataLeft || '1em',
displayIcons: props => props.displayIcons|| 'none'
})`
display: ${props => props.displayIcons};
font-size: 1.5rem;
background-color: rebeccapurple;
position: absolute;
top: 1rem;
right: ${props => props.dataRight};
left: ${props => props.dataLeft};
`;
class Iconset extends Component {
constructor(props) {
super(props);
this.state = {
displayIcons: 'none'
};
}
componentDidMount () {
this.props.setTimeout(this.showIcons, 4000);
alert('Display Icons = ' + this.state.displayIcons);
}
showIcons() {
this.setState({displayIcons: 'Block'});
alert('Display Icons = ' + this.state.displayIcons);
}
render () {
return (
<div id='iconset'>
<Icon dataLeft="auto" dataRight="1em" display={this.props.displayIcons}>First Icon</Icon> {/*eslint-disable-line */}
<Icon dataLeft="1em" dataRight="auto" display={this.props.displayIcons}>Second Icon</Icon> {/*eslint-disable-line */}
</div>
);
}
}
export default ReactTimeout(Iconset);
So, my current understanding is that when the timeout fires the container state change should populate down to the children and override the display: none with display: block. That change never seems to happen although the state-change itself does happen.
What concept am I missing here?

When you use setState, you're setting the displayIcons variable in component's internal state which would be accessed by this.state.displayIcons.
If you look at your render, in the display prop, you're targeting this.props.displayIcons
You would only use props here if you were changing the displayIcons property in a parent component.
Change that to this.state.displayIcons and it should work as you expect.

I'm not sure what makes sense or not to your code.
this.props.setTimeout(this.showIcons, 4000);
Is setTimeout really a props function? It looks like that what you really want was to just call setTimeOut:
setTimeout(this.showIcons, 4000);
Why are you rendering props.displayIcons ?
<Icon dataLeft="auto" dataRight="1em" display={this.props.displayIcons}>First Icon</Icon> {/*eslint-disable-line */}
<Icon dataLeft="1em" dataRight="auto" display={this.props.displayIcons}>Second Icon</Icon> {/*eslint-disable-line */}
I believe what you really want is to render the state that you changed on timeOut:
<Icon dataLeft="auto" dataRight="1em" display={this.state.displayIcons}>First Icon</Icon> {/*eslint-disable-line */}
<Icon dataLeft="1em" dataRight="auto" display={this.state.displayIcons}>Second Icon</Icon> {/*eslint-disable-line */}
Hope that helps!

Related

React Toggle Body Class with button

I'm still learning React but I'm having an issue toggling a body class with a button in the menu.
const toggleSideMenu = event => {
// toggle class on click
//Below is not correct
event.getElementsByTagName('body').classList.toggle('sb-sidenav-toggled');
};`
<button onClick={toggleSideMenu} id="sidebarToggle" href="#!"><i className="fas fa-bars"></i></button>
I'm used to doing this easily in jQuery but it's not recommended to use jQuery in React because of the dom. I would appreciate any suggestions.
Thanks so much!
In this example, we are using the useState hook to keep track of the toggle state. The initial state is set to false. We are using the isToggled state in the JSX to determine what to render on the screen, and to update the text of the button.
We have an onClick event on the button, which calls the setIsToggled function and pass the negation of the current state (!isToggled), this is the way to toggle the state, every time the button is clicked.
import React, { useState } from 'react';
const MyComponent = () => {
// useState hook to keep track of the toggle state
const [isToggled, setIsToggled] = useState(false);
return (
<div>
{/* render some content or change className based on the toggle state */}
<p className={isToggled? "class1" : "classB">Toggled on</p>
<button onClick={() => setIsToggled(!isToggled)}>
{isToggled ? 'Turn off' : 'Turn on'}
</button>
</div>
);
}
export default MyComponent;
But if you need to do something more advanced, maybe you can learn more about React Context.
import React, { useState } from 'react';
// Create a context to share the toggle state
const ToggleContext = React.createContext();
const MyApp = () => {
// useState hook to keep track of the toggle state
const [isToggled, setIsToggled] = useState(false);
return (
<ToggleContext.Provider value={{ isToggled, setIsToggled }}>
<MyComponent1 />
<MyComponent2 />
{/* any other components that need access to the toggle state */}
</ToggleContext.Provider>
);
}
const MyComponent1 = () => {
// use the toggle state and toggle function from the context
const { isToggled, setIsToggled } = useContext(ToggleContext);
return (
<div>
<p className={isToggled? "class1" : "classB">Toggled on</p>
<button onClick={() => setIsToggled(!isToggled)}>
{isToggled ? 'Turn off' : 'Turn on'}
</button>
</div>
);
}
const MyComponent2 = () => {
// use the toggle state from the context
const { isToggled } = useContext(ToggleContext);
return (
<div>
{isToggled ? <p>Toggled on</p> : <p>Toggled off</p>}
</div>
);
}
export default MyApp;
A very basic example to show you how to use state to maintain whether a menu should be open or not.
It has one button that when clicked calls a function that updates the state.
It has one Menu component that accepts that state, and uses CSS to determine whether it should be "open" (ie on/off screen).
Like I said, as simple as I could make it.
const { useState } = React;
function Example() {
// The state set to either true or false
// Initially it's false / menu closed
const [ menuOpen, setMenuOpen ] = useState(false);
// When the button is clicked we take the
// previous state and toggle it - either from true
// to false, or vice versa
function handleClick() {
setMenuOpen(prev => !prev);
}
// One Menu component that accepts that state
// and one button that updates the state
return (
<div>
<Menu open={menuOpen} />
<button onClick={handleClick}>
Toggle Sidebar Menu
</button>
</div>
);
}
// Small menu (an aside element) which uses CSS
// to work out its position on the screen
// It does this by creating a classList using the default
// "menu" which it ties together with "open" but it only
// adds that if the state is true
// And then just use that joined array as the className on
// the element
// You can see in the CSS what both those classes do
function Menu({ open }) {
const menuStyle = [
'menu',
open && 'open'
].join(' ');
return (
<aside className={menuStyle}>
I am a sidebar
</aside>
);
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
.menu { width: 100px; top: 0px; left: -120px; background-color: salmon; position: fixed; height: 100vh; padding: 10px; transition-property: left; transition-duration: 0.25s;}
.open { left: 0px; }
button { position: fixed; left: 150px; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
getElementsByTagName() is method of Document or Element, not react event.
What you need to do, is to look for body inside document.
Also getElementsByTagName(), returns HTMLCollection (many elements), so you need to grab first one (usually there is only one body element on page)
document.getElementsByTagName('body')[0].classList.toggle('sb-sidenav-toggled');
There is also shortcut for body element document.body, so it can be also written as:
document.body.classList.toggle('sb-sidenav-toggled');

How to use CSS withinin the file - ReactJS

I'm very new to react. And I'm trying to learn some new stuff. So what I want to do is to add CSS within my Header.js file, And I don't know how to do that. Because I don't want to import external or inline CSS. But rather use it like in Html with tag on the header. But not just that, I want to use that CSS specifically for the file, in this case, Header.js.
This might help
Header.js
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
// alignSelf: 'center',
},
textStyle: {
marginTop: Metrics.ratio(0),
marginHorizontal: Metrics.ratio(70),
fontFamily: Fonts.type.base,
fontSize: Fonts.size.normal,
color: Colors.black,
},
});
class Header extends React.Component {
render() {
return (
<div style={ styles.color } />
);
}
}
You have several possibilites.
The simplest is to use css code directly in the element like
<div style={{color:'#000',backgroundColor:'#fff'}}>
...
</div>
Or you can use the libraries for that, like styled-components (https://styled-components.com/) for this.
You need to import this:
import styled from 'styled-components';
Then you can define your element css on the top of the page, i.e. SomeCSSStyling
const SomeCSSStyling = styled.div`
color:#000;
background-color:#fff;
`
Then you can use this constant in the class code of the react component:
class MyReactComponent extends Component {
constructor(props) {
...
}
render() {
return (
<SomeCSSStyling>
...
</SomeCSSStyling>
);
}
}
export default MyReactComponent;
UPDATE:
You can also define :hover or ::before etc. with style-components:
const SomeCSSStyling = styled.div`
color:#000;
background-color:#fff;
&:hover{
font-weight:bold;
}
`
You can create style object inside react component like this:
const myDivStyles = {
color: "red",
fontSize: "32px"
}
All propertis are the same like in CSS but this one with "-" sign change in to camelCase nams, e.g. font-size change to fontSize, background-color change to backgroundColor.
Then you can add this style to elements in your components by style attribute.
render() {
return (
<div style={ myDivStles } />
)
}
You can also describe style without creating style object like this:
<div style={{ color: "red", backgroundColor: "#fff" }} />
Be sure you are using double closure {{ }}.
EDIT
With :hover selector
You have two prosibilites. First you can use component state to determinate if component is hovered and then prepare correct style, e.g:
class Component extends React.Component {
constructor(props) {
super(props)
this.state = {
hovered: false
}
this.toggleHover = this.toggleHover.bind(this)
}
toggleHover(state) {
this.setState({
hovered: state
})
}
render() {
const styles = {
color: this.state.hovered ? "red" : "blue"
}
return (
<div style={styles} onMouseEnter={ () => this.toggleHover( true ) } onMouseLeave={ () => this.toggleHover( false ) }>
Text
</div>
);
}
}
Second you can use js styled components syntax and refer to other component, you can read more about this here: https://styled-components.com/docs/advanced#referring-to-other-components
But to be honest when I dealing with :hover or other selectors I prefer using default css files or much more often scss files prepared for components. So when I have e.g Button component in same location I have Button.css ( or Button.scss ) file where I can work with standard css. After this I have css files connected with components which should handled them.

Is it safe to useMemo for JSX?

I'm trying to do a memoize Modal and I have a problem here.
When I change input I dont need to re-render the Modal component.
For example:
Modal.tsx looks like this:
import React from "react";
import { StyledModalContent, StyledModalWrapper, AbsoluteCenter } from "../../css";
interface ModalProps {
open: boolean;
onClose: () => void;
children: React.ReactNode
};
const ModalView: React.FC<ModalProps> = ({ open, onClose, children }) => {
console.log("modal rendered");
return (
<StyledModalWrapper style={{ textAlign: "center", display: open ? "block" : "none" }}>
<AbsoluteCenter>
<StyledModalContent>
<button
style={{
position: "absolute",
cursor: "pointer",
top: -10,
right: -10,
width: 40,
height: 40,
border: 'none',
boxShadow: '0 10px 10px 0 rgba(0, 0, 0, 0.07)',
backgroundColor: '#ffffff',
borderRadius: 20,
color: '#ba3c4d',
fontSize: 18
}}
onClick={onClose}
>
X
</button>
{open && children}
</StyledModalContent>
</AbsoluteCenter>
</StyledModalWrapper>
);
};
export default React.memo(ModalView);
Here is an example of how I wrap it.
import React from 'react'
import Modal from './modal';
const App: React.FC<any> = (props: any) => {
const [test, setTest] = React.useState("");
const [openCreateChannelDialog, setOpenCreateChannelDialog] = React.useState(false);
const hideCreateModalDialog = React.useCallback(() => {
setOpenCreateChannelDialog(false);
}, []);
return (
<>
<input type="text" value={test} onChange={(e) => setTest(e.target.value)} />
<button onClick={() => setOpenCreateChannelDialog(true)}>Create channel</button>
<Modal
open={openCreateChannelDialog}
onClose={hideCreateModalDialog}
children={<CreateChannel onClose={hideCreateModalDialog} />}
/>
</>
};
I know, Modal re-rendered because children reference created every time when App component re-renders (when I change an input text).
Know I'm interested, if I wrap <CreateChannel onClose={hideCreateModalDialog} /> inside React.useMemo() hook
For example:
const MemoizedCreateChannel = React.useMemo(() => {
return <CreateChannel onClose={hideCreateModalDialog} />
}, [hideCreateModalDialog]);
And change children props inside Modal
from:
children={<CreateChannel onClose={hideCreateModalDialog} />}
to
children={MemoizedCreateChannel}
It works fine, but is it safe? And it is only one solution that tried to memoize a Modal?
Memoizing JSX expressions is part of the official useMemo API:
const Parent = ({ a }) => useMemo(() => <Child1 a={a} />, [a]);
// This is perfectly fine; Child re-renders only, if `a` changes
useMemo memoizes individual children and computed values, given any dependencies. You can think of memo as a shortcut of useMemo for the whole component, that compares all props.
But memo has one flaw - it doesn't work with children:
const Modal = React.memo(ModalView);
// React.memo won't prevent any re-renders here
<Modal>
<CreateChannel />
</Modal>
children are part of the props. And React.createElement always creates a new immutable object reference (REPL). So each time memo compares props, it will determine that children reference has changed, if not a primitive.
To prevent this, you can either use useMemo in parent App to memoize children (which you already did). Or define a custom comparison function for memo, so Modal component now becomes responsible for performance optimization itself. react-fast-compare is a handy library to avoid boiler plate for areEqual.
Is it safe? Yes. At the end of the day the JSX is just converted into a JSON object, which is totally fine to memoize.
That said, I think it is stylistically a bit weird to do this, and I could foresee it leading to unexpected bugs in the future if things need to change and you don't think it through fully.

New react native element is not accessible

I'm working on react-native project (main target is iPhone 6) and got some problems with including new elements in accessibility chain. For some reasons Voice Over does not update when new element appears after re-rendering. Hidden button does not appear in accessibility chain after running showButton() method. It becomes visible, but iOS Voice Over does not see it. The problem occurs only when app does something asynchronously. Here is my code:
export default class SmartView extends Component {
constructor(props) {
super(props)
this.state = {
showButton: false,
}
}
showButton = () => {
setTimeout(() => {
this.setState({ showButton: true })
}, 500)
}
render() {
const { showButton } = this.state
return (
<View style={style.root}>
<Button
onPress={this.showButton}
accessibilityRole="button"
accessibilityTraits="button"
accessibilityLabel="appeared"
accessible
simple
>
<Text>Appeared</Text>
</Button>
{showButton && (
<Button
accessibilityRole="button"
accessibilityTraits="button"
accessibilityLabel="appeared"
accessible
simple
>
<Text>Hidden</Text>
</Button>
)}
</View>
)
}
}
So, if I remove setTimeout and do state updating in current js stream, everything work fine. Is there any possibility to make something like VoiceOverReload()?
I use: react-native v0.59.9 and iPhone 6, software version 12.4
Thanks.
Below demo works fine, probably your custom Button component has issues
import React, { useState } from 'react'
import { View, Text, TouchableOpacity } from 'react-native'
export default function Screen () {
const [showButton, setShowButton] = useState(false)
function handleShow () {
setTimeout(() => {
setShowButton(true)
}, 1000)
}
return (
<View style={{ padding: 40 }}>
<TouchableOpacity
onPress={handleShow}
accessibilityRole='button'
accessibilityTraits='button'
accessibilityLabel='This button label is long for demo'
accessible
>
<Text>Appeared</Text>
</TouchableOpacity>
{showButton && (
<TouchableOpacity
accessibilityRole='button'
accessibilityTraits='button'
accessibilityLabel='hidden'
accessible
>
<Text>Hidden</Text>
</TouchableOpacity>
)}
</View>
)
}
If your view is going to update and you need voice over to detect the change faster, the you can add the following trait to the parent view: frequentUpdates. This will be the equivalent of setting "Updates Frequently" on the accessibility properties in XCode, as explained in the following answer: Making dynamically updating content in a UITableView accessible for VoiceOver
This works for ReactNative 0.59, though its deprecated and I don't know how to do it in newer versions of RN.

How to make functional components in CxJS?

It would be really helpful if we could compose multiple Cx components into one functional component that accepts multiple parameters as JSX attributes and can have its own nested (child) components.
<LoadingOverlay loading={bind('$page.loading')} >
<Grid />
</LoadingOverlay/>
When creating a custom Cx component, normally we need to create a class that extends some of the base components and implements certain predefined methods, which adds complexity to the process.
Here is one possible implementation of LoadingOverlay:
class LoadingOverlay extends PureContainer {
declareData() {
super.declareData(...arguments, {
loading: undefined
});
}
render (context, instance, key) {
let {data} = instance;
return <div key={key} className="cxb-loading-overlay-container">
{this.renderChildren(context, instance)}
{data.loading && <div className="cxe-loading-overlay">
<div className="cxe-loading-indicator">
{Icon.render('loading', {
style: {
width: '24px',
height: '24px',
position: 'relative',
top: '6px'
}
})
}
Loading...
</div>
</div>}
</div>;
}
}
For the example above, LoadingOverlay had to inherit from PureContainer and implement declareData and render methods.
And I would like to be able to use something simmilar to React's stateless functional components, like this:
const LoadingOverlay = (props) => {
return <div className="cxb-loading-overlay-container">
{props.children}
{data.loading && <div className="cxe-loading-overlay">
<div className="cxe-loading-indicator">
{Icon.render('loading', {
style: {
width: '24px',
height: '24px',
position: 'relative',
top: '6px'
}
})
}
Loading...
</div>
</div>}
</div>;
}
Is this possible in CxJS?
CxJS allows mixing React components with CxJS components, so your second example should work, except that you should use props.loading instead of data.loading.
React components can be defined as functional stateless components, or as ES6 classes extending the base React.Component class (or VDOM.Component in CxJS).
CxJS recently got support for CxJS functional components, and that would probably be the best choice for this example:
const LoadingOverlay = ({ loading, children }) => (
<cx>
<div className="cxb-loading-overlay-container">
{children}
<div className="cxe-loading-overlay" visible={loading}>
<div className="cxe-loading-indicator" ws>
<Icon
name="loading"
style="width:24px;height:24px;position:relative;top:6px"
/>
Loading...
</div>
</div>
</div>;
</cx>
);

Categories