ReactDom.render is not rendering modal component to target 2nd time - reactjs

i am trying to display modal component when user click on a link.
handleShow fn is called when anchor tag is clicked which calls ReactDom.render to display modal.
handleShow() {
ReactDOM.render(<WaiverModal />, document.getElementById("modal"));
}
i could see modal working for 1st time but 2nd time it doesn't pop up.

This is a case of conditional rendering in react. Instead of making an onClick function to call ReactDOM.render, simply declare a boolean isClicked variable in props, and pass it to that onclick function. Then use the JSX syntax to display that component based on your click.
e.g
isClicked = false;
<a onClick={!isClicked}></a>
<div>
{ this.props.isClicked && <WaiverModal /> }
</div>

I think it's happend because you try to render already the same component second time. How you "unrender" your popup? - this is the question.
So, how you can fix this - create an "modal container" and always render it in some place of page at application start.
import React from "react";
import ReactDOM from "react-dom";
import App from "./component/App";
import ModalContainer from "./component/ModalContainer";
import "./index.css";
ReactDOM.render(<App />, document.getElementById("root"));
ReactDOM.render(<ModalContainer />, document.getElementById("modal"));
Then when you want to render some modal - call function and pass the modal component to it.
showModal(component) {
dispatch({ type: APPEND_MODAL, component }),
}
Then this function will send this component to some storage (redux for example)
import { APPEND_MODAL, HIDE_MODAL } from '../constants/actionTypes';
export default (state = [], action) => {
switch (action.type) {
case APPEND_MODAL:
return [
...state,
action.component
];
case HIDE_MODAL:
return state.slice(0, -1); // Get all exept last
default:
return state;
}
};
and then your "modal container" will pick up this component and render it inside.
class ModalConainer extends React.Component {
render() {
// modals comes from redux - connect ModalContainer to redux store and pass the modals pr
return this.props.modals.map((component, idx) => (
<div key={idx}>{component}</div>
);
}
}
But, sure, you cand find more easy way to do this. It's up to you. Good luck.

Related

How to render a component with props derivative from NextJS router

I'm trying to render a component that uses a dynamic router path prop. I want mysite.com/something to load the component with the something prop. If the route is mysite.com/somethingelse, I want to load the component with the somethingelse prop. Here's my code:
page.js:
import { useRouter } from "next/router";
import List from "./List";
function DefaultPage() {
const router = useRouter();
console.log(router.query.category); // Works correctly
return (
<div>
<List category={router.query.category} />
</div>
);
}
export default DefaultPage;
The component, list.js:
import React, { Component } from "react";
class List extends Component {
constructor(props) {
super(props);
console.log(this.props.category); // This is where I'm confused
}
static defaultProps = { category: "default" };
render() {
return <p>Hello</p>;
}
}
export default List;
The problem is, this.props.category always returns as default (my default prop), unless I recompile. It works perfectly after a fresh compile, but then breaks after every subsequent refresh in the browser.
I can visually see the router query returning the correct value in the log, but the component is rendering before everything else, thus returning a default value. Is there a way I can stop the List component from rendering before its own props are specified? Or is there a better way of doing this all together? Thanks.
I would do something like this in the DefaultPage component:
if(router.query.category === 'something') {
return <ListComponent/>
}
if(router.query.category === 'somethingElse') {
return <SomethingElseComponent/>
}
If you don't want to use two separate components, you could pass the prop to useEffect so it can re-render the component when that prop changes https://reactjs.org/docs/hooks-effect.html

React-toastify notification won't return in conditional

The notification works quite easily with a button. However, I'm trying to have it activate when a props passes through (true/false).
Basically, the user clicks on this tab, if they're not signed in, it'll pop up with the notification telling them to sign in.
However, I cannot make it work without being a button. The props passess through just fine, and I can console.log it. And the conditional returns... something, but it's like an odd bunch of letters, and each refresh it changes. And does not pop up like a notification. It's just obscure letters in the middle of the screen (because of {notify} placed above the form).
import React, {Component} from 'react';
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
class Alerts extends Component {
constructor(props){
super(props)
this.state = {
alertLogin: ''
}
}
render() {
const notify = () => toast("Please login before adding a recipe!");
// tried to make a conditional to check if prop alertLogin is true or false
// then calls notify function if false
if (!this.props.alertLogin) {
console.log('alert props received', this.props.alertLogin)
return notify()
}
return (
<div>
{/* <button onClick={notify}>Notify !</button> */}
{notify}
<ToastContainer />
</div>
);
}
}
export default Alerts;
import React, { Component } from "react";
import "./styles.css";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
class Alerts extends Component {
constructor(props){
super(props)
this.state = {
alertLogin: ''
}
}
componentDidMount() {
// tried to make a conditional to check if prop alertLogin is true or false
// then calls notify function if false
if (!this.props.alertLogin) {
console.log("alert props received", this.props.alertLogin);
this.notify();
}
}
notify = () => toast("Please login before adding a recipe!");
render() {
return (
<div>
<button onClick={(e) => this.notify()}>Notify !</button>
<ToastContainer />
</div>
);
}
}
export default Alerts;
Codepen for the solution
First you take the if statement with the function and put it in componentDidMount
cause i'm guessing is stopping the rendered elements themselves from rendering
second make the toast function accessible by component did mount and the button by declaring it before the render function hope i was clear enough

Reactjs redux store changes but in component's props doesn't change

I'm tryin to show navigation depends on changes of categoryURL from
redux store and changing the state in other components. Redux changes the store and it works fine. But in my
component "this.props.categoryUrl" doesn't reflect on value. Can't
find out why?
import React, {Component} from 'react';
import NavigationItems from './NavigationItems/NavigationItems';
import classes from './Navigation.module.css';
import {connect} from 'react-redux';
const mapStateToProps = state => {
console.log(state)
return {
categoryURL: state.categoryUrl
};
};
class navigation extends Component {
componentDidMount() {
console.log(this.props.categoryUrl);
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('NAVIGATION!', this.props.categoryUrl);
}
render() {
let nav = null;
if (this.props.categoryUrl) {
nav = (
<div className={classes.Navigation}>
<NavigationItems/>
</div>
)
}
return (
<>
{nav}
</>
)
}
}
export default connect(mapStateToProps, null)(navigation);
In "normal" React it is needed to use <Navigation/> (Capital letter at the beginning) instead of <navigation/>. Also, If <Navigation/> is being used by another React component then it might be needed to add code that will be executed inside <Navigation/> to refresh the component where you are using <Navigation/> (some kind of callback passed to <Navigation/>). It is this the way or move all the <Navigation/>'s code to the component where you are using <Navigation/>. This is how I solved this kind of problem.

How to prevent parent component from re-rendering with React (next.js) SSR two-pass rendering?

So I have a SSR app using Next.js. I am using a 3rd party component that utilizes WEB API so it needs to be loaded on the client and not the server. I am doing this with 'two-pass' rendering which I read about here: https://itnext.io/tips-for-server-side-rendering-with-react-e42b1b7acd57
I'm trying to figure out why when 'ssrDone' state changes in the next.js page state the entire <Layout> component unnecessarily re-renders which includes the page's Header, Footer, etc.
I've read about React.memo() as well as leveraging shouldComponentUpdate() but I can't seem to prevent it from re-rendering the <Layout> component.
My console.log message for the <Layout> fires twice but the <ThirdPartyComponent> console message fires once as expected. Is this an issue or is React smart enough to not actually update the DOM so I shouldn't even worry about this. It seems silly to have it re-render my page header and footer for no reason.
In the console, the output is:
Layout rendered
Layout rendered
3rd party component rendered
index.js (next.js page)
import React from "react";
import Layout from "../components/Layout";
import ThirdPartyComponent from "../components/ThirdPartyComponent";
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
ssrDone: false
};
}
componentDidMount() {
this.setState({ ssrDone: true });
}
render() {
return (
<Layout>
{this.state.ssrDone ? <ThirdPartyComponent /> : <div> ...loading</div>}
</Layout>
);
}
}
export default Home;
ThirdPartyComponent.jsx
import React from "react";
export default function ThirdPartyComponent() {
console.log("3rd party component rendered");
return <div>3rd Party Component</div>;
}
Layout.jsx
import React from "react";
export default function Layout({ children }) {
return (
<div>
{console.log("Layout rendered")}
NavBar here
<div>Header</div>
{children}
<div>Footer</div>
</div>
);
}
What you could do, is define a new <ClientSideOnlyRenderer /> component, that would look like this:
const ClientSideOnlyRenderer = memo(function ClientSideOnlyRenderer({
initialSsrDone = false,
renderDone,
renderLoading,
}) {
const [ssrDone, setSsrDone] = useState(initialSsrDone);
useEffect(
function afterMount() {
setSsrDone(true);
},
[],
);
if (!ssrDone) {
return renderLoading();
}
return renderDone();
});
And you could use it like this:
class Home extends React.Component {
static async getInitialProps({ req }) {
return {
isServer: !!req,
};
};
renderDone() {
return (
<ThirdPartyComponent />
);
}
renderLoading() {
return (<div>Loading...</div>);
}
render() {
const { isServer } = this.props;
return (
<Layout>
<ClientSideOnlyRenderer
initialSsrDone={!isServer}
renderDone={this.renderDone}
renderLoading={this.renderLoading}
/>
</Layout>
);
}
}
This way, only the ClientSideOnlyRenderer component gets re-rendered after initial mount. 👍
The Layout component re-renders because its children prop changed. First it was <div> ...loading</div> (when ssrDone = false) then <ThirdPartyComponent /> (when ssrDone = true)
I had a similar issue recently, what you can do is to use redux to store the state that is causing the re-render of the component.
Then with useSelector and shallowEqual you can use it and change its value without having to re-render the component.
Here is an example
import styles from "./HamburgerButton.module.css";
import { useSelector, shallowEqual } from "react-redux";
const selectLayouts = (state) => state.allLayouts.layouts[1];
export default function HamburgerButton({ toggleNav }) {
let state = useSelector(selectLayouts, shallowEqual);
let navIsActive = state.active;
console.log("navIsActive", navIsActive); // true or false
const getBtnStyle = () => {
if (navIsActive) return styles["hamBtn-active"];
else return styles["hamBtn"];
};
return (
<div
id={styles["hamBtn"]}
className={getBtnStyle()}
onClick={toggleNav}
>
<div className={styles["stick"]}></div>
</div>
);
}
This is an animated button component that toggles a sidebar, all wrapped inside a header component (parent)
Before i was storing the sidebar state in the header, and on its change all the header has to re-render causing problems in the button animation.
Instead i needed all my header, the button state and the sidebar to stay persistent during the navigation, and to be able to interact with them without any re-render.
I guess now the state is not in the component anymore but "above" it, so next doesn't start a re-render. (i can be wrong about this part but it looks like it)
Note that toggleNav is defined in header and passed as prop because i needed to use it in other components as well. Here is what it looks like:
const toggleNav = () => {
dispatch(toggleLayout({ id: "nav", fn: "toggle" }));
}; //toggleLayout is my redux action
I'm using an id and fn because all my layouts are stored inside an array in redux, but you can use any logic or solution for this part.

How to change state in a handler

I have a React project and it's using Recompose. Let's say I have a Form, and I supply a 'withHandler' to be used for ..
How can I also change the state of the React component when the Form is submitted?
So let's say the form is submitted with a button, and we have a onClick attribute on the button.
It's a very simple example but hopefully shows you how you would update state with the onClick. Remember, this an attribute that can be applied on HTML elements. You can read about it the onClick attribute here.
import React, { Component } from 'react';
import React from "react";
import { render } from "react-dom";
import Component from "react-component-component";
class Button extends Component {
state = {
counter: 0
};
handleButtonClick = () => {
this.setState({
counter: this.state.counter + 1
});
};
getButton = () => {
const { text } = this.props;
return (
<button
onClick={this.handleButtonClick}
>
{text}
{this.state.counter}
</button>
);
};
render() {
return <div>{this.getButton()}</div>;
}
}
render(
<Button text="press me to increase counter: " />,
document.getElementById("root")
);
The following can be seen here: https://codesandbox.io/s/ly11qv0vr7
There is also a very good example of react documentation regarding handling events. You can read about handling events in react here. I believe the above link will provide you all the information needed to be able to handle a form being submitted.

Resources