Is it possible to pass dynamic props, from one page to another with next.js? - javascript

I'm new to Next and have been trying to make a page(index.js) that fetches data(countries) and then displays that data, where each returned element(country) has a button to go to a page(info.js) where that specific countries data will be displayed, was wondering if its possible to pass the props(all country data) to the info.js page? I've tried reading the documentation and watching YT videos but can't seem understand what i'm reading/watching.
index.js:
import Link from 'next/link'
Welcome.getInitialProps = async function (props) {
const res = await fetch('https://restcountries.eu/rest/v2/all')
const data = await res.json()
return {
data: data
}
}
const MyLink = props => {
return (
<p>
<Link href={`/info?name=${props.name}`} >
<a>Learn More</a>
</Link>
</p>
)
}
function Welcome(props) {
return (
<div>
<div className="main-content">
<style jsx>{`
.main-content {
width: 80%;
margin: 0 auto;
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-gap: 5px;
}
.item {
border: 1px solid black;
text-align: center;
}
.item ul{
padding: 0;
}
.item ul li {
list-style-type: none;
}
`}</style>
{props.data.map(country => (
<div key={country.numericCode} className="item">
<h4>{country.name}</h4>
<p>Region: {country.region}</p>
<p>Population: {country.population}</p>
<MyLink name={country.name} borders={country.borders} currencies={country.currencies}/>
</div>
))}
</div>
</div>
)
}
export default Welcome
info.js:
import { withRouter } from 'next/router'
import Link from 'next/link'
const Info = (props) => {
return (
<div>
<h1>{props.router.query.name}</h1>
<Link href="/">
<a>Home</a>
</Link>
</div>
)
}
export default withRouter(Info)

In MyLink component instead of using Link you can create a normal div (style it like a link) and onClick of that div push it to different page using nextjs router:
//import useRouter
import { useRouter } from 'next/router'
//then call it
const router = useRouter()
const MyLink = props => {
return (
<p onClick={() => {
router.push({
pathname: `/info?name=${props.name}`,
query: { data: //data to pass },
})
}}>
<a>Learn More</a>
</p>
)
}
You can access that data in the location object in the query key
import {useLocation} from ""
const location = useLocation()
const data = location.query

Related

How to call a function with arguments onclick of a button in a react component

Here is my function with arguments that i added in index.html in publics folder in a script tag
function displayContent(event, contentNameID) {
let content = document.getElementsByClassName("contentClass");
let totalCount = content.length;
for (let count = 0; count < totalCount; count++) {
content[count].style.display = "none";
}
let links = document.getElementsByClassName("linkClass");
totalLinks = links.length;
for (let count = 0; count < totalLinks; count++) {
links[count].classList.remove("active");
}
document.getElementById(contentNameID).style.display = "block";
event.currentTarget.classList.add("active");
}
Trying to call this function from click of buttons on my react component that looks like below
<button class="linkClass" onclick="displayContent(event, 'project2')">Meet at Campus
</button>
Please guide me with the syntax
Here's the correct syntax
<button className="linkClass" onClick={(event)=>displayContent(event,'project2')}>Meet at Campus</button>
Edit: please note that React components return JSX
It looks like you're trying to make some sort accordion but you shouldn't really be mixing vanilla JS with React as React needs control of the DOM.
So here's a brief example of how you might approach this using 1) state, and 2) a Panel component which comprises a button, and some content.
const { useState } = React;
function Example() {
// Initialise state with an array of false values
const [ state, setState ] = useState([
false, false, false
]);
// When a button in a panel is clicked get
// its id from the dataset, create a new array using `map`
// and then set the new state (at which point the component
// will render again
function handleClick(e) {
const { id } = e.target.dataset;
const updated = state.map((el, i) => {
if (i === id - 1) return true;
return false;
});
setState(updated);
}
// Pass in some props to each Panel component
return (
<div>
<Panel
name="Panel 1"
active={state[0]}
id="1"
handleClick={handleClick}
>
<span className="text1">Content 1</span>
</Panel>
<Panel
name="Panel 2"
active={state[1]}
id="2"
handleClick={handleClick}
>
<span className="text2">Content 2</span>
</Panel>
<Panel
name="Panel 3"
active={state[2]}
id="3"
handleClick={handleClick}
>
<span className="text3">Content 3</span>
</Panel>
</div>
);
}
function Panel(props) {
// Destructure those props
const {
name,
id,
active,
handleClick,
children
} = props;
// Return a div with a button, and
// content found in the children prop
// When the button is clicked the handler is
// called from the parent component, the state
// is updated, a new render is done. If the active prop
// is true show the content otherwise hide it
return (
<div className="panel">
<button data-id={id} onClick={handleClick}>
{name}
</button>
<div className={active && 'show'}>
{children}
</div>
</div>
);
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
.panel button:hover { cursor: pointer; }
.panel { margin: 1em 0; }
.panel div { display: none; }
.panel div.show { display: block; margin: 1em 0; }
.add { margin-top: 1em; background-color: #44aa77; }
.text1 { color: darkblue; font-weight: 600; }
.text2 { color: darkgreen; font-weight: 700; }
.text3 { color: darkred; font-weight: 300; }
<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>
Can't you use
document.getElementById("linkClass").onclick = () =>{
displayContent();
}
by giving the element an id with same of the class?

Is it appropriate to use Document in the react framework?

I have this React component. Which renders a simple HTML. I have an event handler attached to an element. On clicking that particular element I want some CSS styles to change. For that I used the code below-
import React from 'react';
import './start.css';
class Start extends React.Component {
handleEvent() {
const login = document.querySelector('.login');
const start = document.querySelector('.start')
login.style.right = '0';
start.style.left = '-100vw';
}
render() {
return (
<section className = 'page start'>
<h1>Welcome To Our App</h1>
<button onClick = {this.handleEvent}>Next</button>
</section>
)
}
}
export default Start;
My question is in the handleEvent() is it appropriate to select the elements using Document and style the elements using .style. Is there any other "react-specific" way to do this?
class Test extends React.Component {
constructor(){
super();
this.state = {
black: true
}
}
changeColor(){
this.setState({black: !this.state.black})
}
render(){
let btn_class = this.state.black ? "blackButton" : "whiteButton";
return (
<div>
<button className={btn_class}
onClick={this.changeColor.bind(this)}>
Button
</button>
</div>
)
}
}
ReactDOM.render(<Test />, document.querySelector("#app"))
button{
width: 80px;
height: 40px;
margin: 15px;
}
.blackButton{
background-color: black;
color: white;
}
.whiteButton{
background-color: white;
color: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
first of all yes you can use document in react for that. But "react specific" style you be something like this:
<div id="app"></div>
In css file :
button{
width: 80px;
height: 40px;
margin: 15px;
}
.blackButton{
background-color: black;
color: white;
}
.whiteButton{
background-color: white;
color: black;
}
and finally a component :
class Test extends React.Component {
constructor(){
super();
this.state = {
black: true
}
}
changeColor(){
this.setState({black: !this.state.black})
}
render(){
let btn_class = this.state.black ? "blackButton" : "whiteButton";
return (
<div>
<button className={btn_class}
onClick={this.changeColor.bind(this)}>
Button
</button>
</div>
)
}
}
ReactDOM.render(<Test />, document.querySelector("#app"))
You can set a state to check whether the button has been clicked and change the class name
Similar approach can be used.
This is the React Specific way!
You can refer to React doc
https://reactjs.org/docs/faq-styling.html
import React from 'react';
import './start.css';
class Start extends React.Component {
constructor(props) {
super(props);
this.state = {hasButtonClicked : false};
}
handleEvent() {
this.setState({hasButtonClicked : true});
}
render() {
let clicked = this.state.hasButtonClicked;
return (
<section className = { clicked ? someCssClass :'page start'} >
<h1>Welcome To Our App</h1>
<button onClick = {this.handleEvent}>Next</button>
</section>
)
}
}

Styling react component in styled components

I have a question with styled components, I would like to know how to position elements from their parents, I saw that there are the following options but I do not like any.
Through props, I don't like this method because I consider that the maintainability of this is horrible since in complex cases we will have many props.
Through className, generally in styled components we don't have class since we create styled.div for example, I like to have consistency in my structure and I don't want to have class names in some and not in others.
In this case CurrentFinderLocationButton is a react component, how would you position them? Is there a way to select it and apply styles from StyledHome without className or props?
import React from "react";
import styled from "styled-components";
import CurrentLocationFinderButton from "../buttons/CurrentLocationFinderButton";
import fullLogotype from "../../assets/images/full-logotype.svg";
const Home = () => {
return (
<StyledHome>
<StyledLogotype src={fullLogotype} />
<CurrentLocationFinderButton />
</StyledHome>
);
};
const StyledHome = styled.div`
`;
const StyledLogotype = styled.img`
width: 150px;
display: block;
margin: auto;
`;
export default Home;
you can just add some styles to wrapper
const StyledCurrentLocationFinderButton = styled(CurrentLocationFinderButton)`
{any styles}
`
Finally i solved this problem binding the component class and styled components class through the props.
import React from "react";
import styled from "styled-components";
import fullLogotype from "../../assets/images/full-logotype.svg";
import CurrentLocationFinderButton from "../buttons/CurrentLocationFinderButton";
import AddressFinder from "../finders/AddressFinder";
const Logotype = ({ className }) => {
return <img className={className} alt="" src={fullLogotype} />;
};
const EntryText = ({ className }) => {
return (
<p className={className}>
Atisbalo es una app donde podrás encontrar información sobre tus locales
favoritos y enterarte de todas las promociones que oferta tu cuidad al
instante
</p>
);
};
const Home = ({ className }) => {
return (
<StyledHome className={className}>
<StyledLogotype />
<StyleEntryText />
<StyledCurrentLocationFinderButton />
<StyledAddressFinder/>
</StyledHome>
);
};
const StyledHome = styled.div``;
const StyledLogotype = styled(Logotype)`
width: 150px;
display: block;
margin: auto auto 30px auto;
`;
const StyleEntryText = styled(EntryText)`
display: block;
width: 90%;
text-align: center;
margin: auto auto 30px auto;
`;
const StyledCurrentLocationFinderButton = styled(CurrentLocationFinderButton)`
display: block;
margin: auto auto 30px auto;
`;
const StyledAddressFinder = styled(AddressFinder)`
width: 80%;
margin: auto;
`
export default Home;

React/CSS - Aligning Images, Button, and Text in a column

Currently, in my app, when I click on the button, it generates a random image but it bunches up everything like so. I'd like the image to show up, which are all in different sizes, have the dog's name right below it, centered and then the button below that.
Code
import * as React from "react";
import { render } from "react-dom";
import axios from "axios";
import "./styles.css";
function App() {
const [images, setImage] = React.useState("");
const [text, setText] = React.useState("");
function btnClick() {
axios
.all([axios.get("https://dog.ceo/api/breeds/image/random"),
axios.get("https://dog.ceo/api/breeds/list/all")
])
.then(axios.spread((response) => {
setImage(response.data.message);
setText(response.data.message.split('/')[4]);
}))
.catch(err => {
console.log("Error happened during fetching!", err);
});
}
return (
<div className = "App">
<img className = "Img" src={images} alt="broken"/>
<button className = "Button" onClick = {btnClick}>Doggie!</button>
<strong>{text}</strong>
</div>
);
}
const rootElement = document.getElementById("root");
render(<App />, rootElement);
CSS
.App {
font-family: sans-serif;
text-align: center;
}
.Button {
display: flex;
}
.Img {
max-width:100%;
height:auto;
max-height:100%;
}
Re-arranging the element should solve the issue, wrap the name with <p></p> to display it on a new paragraph.
import React from "react";
import axios from "axios";
import "./styles.css";
export default function App() {
const [images, setImage] = React.useState("");
const [text, setText] = React.useState("");
function btnClick() {
axios
.all([
axios.get("https://dog.ceo/api/breeds/image/random"),
axios.get("https://dog.ceo/api/breeds/list/all")
])
.then(
axios.spread(response => {
setImage(response.data.message);
setText(response.data.message.split("/")[4]);
})
)
.catch(err => {
console.log("Error happened during fetching!", err);
});
}
return (
<div className="App">
<img src={images} alt="broken" />
<p><strong>{text}</strong></p>
<button className="button" onClick={btnClick}>
Doggie!
</button>
</div>
);
}
You should rearrange your rendered elements, so it goes <img/> <strong/> <button />.
Then in your CSS, make sure the elements that are inline natively become block.
See snippet below.
Note: left out React as it's not related to your issue so ignore class usage.
.App {
font-family: sans-serif;
text-align: center;
}
.App strong {
display: block;
}
.Button {
width: 100%;
text-align: center;
}
.Img {
max-width:100%;
height:auto;
max-height:100%;
display: block;
}
<div class="App">
<img class="Img" src="https://i.stack.imgur.com/0WaeI.png" alt="broken"/>
<strong>{text}</strong>
<button class="Button" onClick={btnClick}>Doggie!</button>
</div>
Flexbox
If you would prefer to use flexbox styling see this thorough post about it.

React close all child modal from parent

I've three components with the following tree:
<Update>
<ExpenseItem>
<ExpenseItemModal>
Update takes an array of expenses and render a ExpenseItem component for each expense.
I'm using an hook to handle modal visibility. As you can expect, i'm using this modal to edit the expense attributes.
A toggle method is imported from useModal hook on ExpenseItem to open and close the modal. What I expect is to click outside of the modal and close it. But if I've another ExpenseItem with the modal set to true, it will close the current, but it will still show the other one. I want to click outside of the modal (maybe on Update component) and close all modals at once, to avoid multiple modals opened. Actually I want only on modal open at once.
These are the following components:
Upload
import { useState, useEffect } from 'react';
import useModal from '../hooks/useModal';
import ExpenseItem from './expenseItem';
import axios from 'axios';
function Update({ data }) {
useEffect(() => console.log('update component', expenses));
const saveToDatabase = () => {
axios.post('http://localhost:3001/expenses', expenses).then((res) => {
console.log('data is saved to database');
});
};
const { setIsShowing } = useModal();
const closeModals = () => setIsShowing(false);
const [ expenses, setExpenses ] = useState(data);
return (
<div>
{expenses.map((expense, index) => {
return <ExpenseItem key={index} index={index} expenses={expenses} setExpenses={setExpenses} />;
})}
<button onClick={() => saveToDatabase()}>Save</button>
</div>
);
}
export default Update;
ExpenseItem
import useModal from '../hooks/useModal';
import EditExpenseModal from './editExpenseModal';
function ExpenseItem(props) {
const { isShowing, toggle, setIsShowing } = useModal();
let { description, date, credit, debit } = props.expenses[props.index];
const updateValue = (expense, setExpenses, success) => {
const expenses = [ ...props.expenses ];
expenses.splice(props.index, 1, {
...expense
});
setExpenses(expenses);
success();
};
return (
<div>
<div className="expense-box" onClick={toggle}>
<p>{date}</p>
<div className="expense-info">
<p className="expense-info--description">{description}</p>
<p className="expense-info--debit">{debit}</p>
<p className="expense-info--credit">{credit}</p>
</div>
</div>
<EditExpenseModal
isShowing={isShowing}
hide={toggle}
expense={props.expenses[props.index]}
updateExpense={updateValue}
setExpenses={props.setExpenses}
/>
<style jsx>{`
.expense-box {
width: 800px;
border: 1px solid black;
border-radius: 2px;
margin: 25px auto;
padding: 0 10px;
}
.expense-info {
display: flex;
}
.expense-info--description {
margin: 0 auto 0 0;
}
.expense-info--debit {
color: red;
}
.expense-info--credit {
color: green;
}
`}</style>
</div>
);
}
export default ExpenseItem;
EditExpenseModal
import { useState, useEffect, Fragment } from 'react';
import { createPortal } from 'react-dom';
const EditExpenseModal = ({ expense, isShowing, hide, updateExpense, setExpenses }) => {
const { description, date, credit, debit } = expense;
useEffect(() => {
document.body.style.overflow = 'hidden';
return () => (document.body.style.overflow = 'unset');
}, []);
const [ expenseItem, setExpenseItem ] = useState({
date,
description,
category: null,
subcategory: null,
credit,
debit
});
const handleInputChange = (e) => {
const { name, value } = e.target;
setExpenseItem({ ...expenseItem, [name]: value });
};
return isShowing
? createPortal(
<Fragment>
<div>
<div className="form">
<form>
<ul>
<li className="form-inputs">
<label>Date</label>
<input type="text" name="date" defaultValue={date} onChange={handleInputChange} />
</li>
<li className="form-inputs">
<label>Description</label>
<input
type="text"
name="description"
defaultValue={description}
onChange={handleInputChange}
/>
</li>
<li className="form-inputs">
<label>Category</label>
<input type="text" name="category" onChange={handleInputChange} />
</li>
<li className="form-inputs">
<label>Subcategory</label>
<input type="text" name="subcategory" onChange={handleInputChange} />
</li>
<li className="form-inputs">
<label>Credit</label>
<input
type="text"
name="credit"
defaultValue={credit}
onChange={handleInputChange}
/>
</li>
<li className="form-inputs">
<label>Debit</label>
<input
type="text"
name="debit"
defaultValue={debit}
onChange={handleInputChange}
/>
</li>
</ul>
</form>
<button onClick={() => updateExpense(expenseItem, setExpenses, hide)}>save</button>
<button onClick={hide}>close</button>
</div>
<style jsx>{`
.form {
background: grey;
display: flex;
flex-direction: column;
position: absolute;
height: 100vh;
top: 0;
right: 0;
width: 40%;
}
.form-inputs {
display: flex;
flex-direction: column;
list-style-type: none;
padding: 1rem 2rem;
}
`}</style>
</div>
</Fragment>,
document.body
)
: null;
};
export default EditExpenseModal;
useModal Hook
import { useState } from 'react';
const useModal = () => {
const [ isShowing, setIsShowing ] = useState(false);
function toggle() {
setIsShowing(!isShowing);
}
return {
isShowing,
setIsShowing,
toggle
};
};
export default useModal;
I don't mind to change these modal structure to make it work.
In this case, to avoid these scenarios you can write a separate method to close modal,
inside ExpenseItem.js
<EditExpenseModal
isShowing={isShowing}
hide={hideModal} //instead of toggle
...
>
and write hideModal method to close modal by passing directly 'false' value instead of using! operator.
like this in useModal Hook :
function hideModal() {
setIsShowing(false);
}

Categories