I am very beginner in react and i got stacked with a warning, I can not resolve them even i read a lot about it in the internet.
The warning is:
The App.tsx relevant code parts:
const [selectedMoment, setSelectedMoment] = useState<IMoment | null>(null);
const [editMode, setEditMode] = useState(false);
const handleOpenCreateForm = () => {
setSelectedMoment(null);
setEditMode(true);
}
return (
<Fragment>
<NavBar openCreateForm={handleOpenCreateForm} />
</Fragment>);
The menu is in the NavBar.tsx:
interface IProps {
openCreateForm: () => void;
}
export const NavBar: React.FC<IProps> = ({ openCreateForm }) => {
return (
<Menu fixed='top' inverted>
<Container>
<Menu.Item>
<Button positive content="Moment upload" onClick={openCreateForm} />
</Menu.Item>
</Container>
</Menu>
)
}
They are semantic-ui-react elements.
Anybody idea why do i get this warning?
This method is considered legacy, the alternative API is getDerivedStateFromProps.
Here’s a sample of what the old method would look like:
class List extends React.Component {
componentWillReceiveProps(nextProps) {
if (nextProps.selected !== this.props.selected) {
this.setState({ selected: nextProps.selected });
this.selectNew();
}
}
// ...
}
The new method works a bit differently:
class List extends React.Component {
static getDerivedStateFromProps(props, state) {
if (props.selected !== state.selected) {
return {
selected: props.selected,
};
}
// Return null if the state hasn't changed
return null;
}
// ...
}
Related
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} />
I'm trying to write a test to check if the component Header is rendering the company logo (context.company?.logoUrl, via context) and if it receives the companyId, via props.
import { Container, Image } from "react-bootstrap";
type Props = {
context: AppContextProps;
companyId: string | undefined;
};
const Header = ({ context, companyId }: Props) => {
if (!context.company) {
return <label>Loading...</label>;
}
return (
<Container className="header">
<a href={`/${companyId}`}>
<Image
src={context.company?.logoUrl}
className="header-logo"
/>
</a>
</Container>
);
};
export default WithContext(Header);
The component Header is wrapped by a high order component, WithContext.
const WithContext = Component => {
return props => (
<AppContext.Consumer>
{({state}) => <Component context={ state } {...props} />}
</AppContext.Consumer>
);
}
export default WithContext;
And here it is the AppContext structure.
export const AppContext = React.createContext();
class AppContextProvider extends Component {
state = {
company: null,
};
getCompanyData() {
try {
const response = await companyService.getPublicProfile();
this.setState({ company: response });
} catch (error) {
console.log({ error });
}
}
componentDidMount() {
this.setState({
company: this.getCompanyData,
});
}
render() {
return (
<AppContext.Provider value={{ state: this.state }}>
{this.props.children}
</AppContext.Provider>
);
}
}
export default AppContextProvider;
This code was written 4 years ago, that's the reason why we are using class components and context this way (I know it is not the best, but we need to keep it that way). 😊
Here it is the base of the test (Jest one).
it("should load company logo URL", async () => {
const propsMock = {
companyId: "abcd9876",
};
const contextMock = {
company: {
logoUrl: "https://picsum.photos/200/300",
},
};
render(
<AppContext.Consumer>
{({ state }) => <Header context={state} {...propsMock} />}
</AppContext.Consumer>
);
// Don't mind this expect :P
expect(1 + 2).toBe(3);
});
Every time I run the test, I get an error in Context.Consumer.
TypeError: Cannot destructure property 'state' of 'undefined' as it is
undefined.
render(
28 | <AppContext.Consumer>
> 29 | {({ state }) => <Header context={state} {...propsMock} />}
| ^
30 | </AppContext.Consumer>
31 | );
Basically I'm struggling to mock a Consumer and pass the context (state, which is supposed to be contextMock) and props (propsMock) so I can pass them to the Header component.
Do you guys have any idea how to make this test work properly?
Thank you!
I have a registration view where in my table i have command to show modal with confirmation:
(...)
render: (rowData) => (
<button
onClick={() => RenderModals(rowData, 'DELETE_USER_MODAL')}
>
Remove
</button>
),
(...)
My RenderModals function looks like this:
type RenderModalProps = {
data: any;
modalCommand: string;
};
export const RenderModals = (data, modalCommand) => {
console.log(data);
switch (modalCommand) {
case 'DELETE_USER_MODAL':
return <DeleteUserModal data={data} />;
case 'SOME_MODAL':
return console.log('some modal');
default:
undefined;
}
};
and I can see console.log(data) in the example above. But... I cant see any console.log from DeleteUserModal component.
DeleteUserModal:
type DeleteUserModalProps = {
data: any;
};
export const DeleteUserModal = ({ data }: DeleteUserModalProps) => {
console.log(`show data ${data}`);
return <div>some text...</div>;
};
I can't figure out what I'm doing wrong ?
Why console.log from DeleteUserModal doesn't trigger?
The way you currently have things set up, this would work:
class RegistrationExampleOne extends React.Component {
constructor(props) {
super(props);
this.state = {component: null};
}
render() {
return (
<div>
<button onClick={() => this.setState({component: RenderModals(rowData, 'DELETE_USER_MODAL')})}>Remove</button>
{this.state.component}
</div>
);
}
}
Option one is not necessarily the better way of doing things, but it is more dynamic.
Option two (as mentioned in the comments by #Brian Thompson) would be similar to this:
import DeleteModal from './path';
class RegistrationExampleTwo extends React.Component {
constructor(props) {
super(props);
this.state = {showDeleteModal: null};
}
render() {
return (
<div>
<button onClick={() => this.setState({showDeleteModal: true})}>Remove</button>
{this.state.showDeleteModal && <DeleteModal data={rowData} />}
</div>
);
}
}
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;
I have a simple compound component with a bunch of static subcomponents:
// #flow
import React, { Component, Children } from 'react';
type Props = {
children: React.ChildrenArray<React.Node> | React.Node,
}
class Toggle extends Component<Props> {
static On = props => (props.on ? props.children : null);
static Off = props => (props.on ? null : props.children);
static Button = props => (
<button
onClick={props.toggle}
type="button"
style={{ display: 'inline-block' }}
>
<pre>{JSON.stringify(props.on, null, 2)}</pre>
</button>
);
state = { on: false }
toggle = () => {
this.setState(
({ on }) => ({ on: !on }),
// maybe this.props.someCallback
() => console.log(this.state.on),
);
}
render() {
return Children.map(
this.props.children,
childElem => React.cloneElement(childElem, {
on: this.state.on,
toggle: this.toggle,
}),
);
}
}
export default Toggle;
The warning happens when I try to put some other elements into Toggle children scope.
For example:
<Toggle>
<Toggle.On>On</Toggle.On>
<span /> <-- this is savage
<Toggle.Button />
<Toggle.Off>Off</Toggle.Off>
</Toggle>
Everything is working, but my flowtype warn me about this span like so:
Warning: Received `false` for a non-boolean attribute `on`.....
Warning: Invalid value for prop `toggle` on <span> tag....
How can I to pacify this nasty girl?
Thank you guys, I think, right solution is just check if type of mounted node is correct one, otherwise - just clone node with regular node props:
// #flow
import React, { Component, Children } from 'react';
type Props = {
children: React.ChildrenArray<React.Node> | React.Node,
}
class Toggle extends Component<Props> {
static On = props => (props.on ? props.children : null);
static Off = props => (props.on ? null : props.children);
static Button = props => (
<button
onClick={props.toggle}
type="button"
style={{ display: 'inline-block' }}
>
<pre>{JSON.stringify(props.on, null, 2)}</pre>
</button>
);
state = { on: false }
toggle = () => {
this.setState(
({ on }) => ({ on: !on }),
// maybe this.props.someCallback
() => console.log(this.state.on),
);
}
// Checking here
allowedTypes = ({ type }) => {
return [
(<Toggle.On />).type,
(<Toggle.Off />).type,
(<Toggle.Button />).type,
].includes(type);
}
render() {
return Children.map(
this.props.children,
(childElem) => {
const elemProps = this.allowedTypes(childElem) ? {
on: this.state.on,
toggle: this.toggle,
} : childElem.props;
return React.cloneElement(childElem, elemProps);
},
);
}
}
export default Toggle;
You can also do this, just having the components in a list and checking their type inside .map, putting on the custom props or otherwise just returning the original child.
const allowedTypes = [ToggleOn, ToggleOff, ToggleButton]
return React.Children.map(props.children, child => {
if (allowedTypes.includes(child.type)) {
return React.cloneElement(child, {on, toggle})
}
return child
})
}