Is it possible to place useEffect inside a if statement? - javascript

So guys, I've been working on my Landing and Dashboard page.
So workflow of the page is this:
User gets on Landing page where he can choose to insert into form location, or press a button to recive all locations. Now on the backend I've made two APIs one to get all locations, and second where I've added :location as req.body.param and then filter locations based on that param. And everything works fine in postman.
Now because I've got two ways of user getting locations(all of them or some that he wants) I've thinked that I place two useEffects inside if statement like this:
const filter = props.location.data;
if (filter) {
useEffect(() => {
const fetchFiltered = async () => {
const res = await ArticleService.filterByName(filter);
setContent(res.data);
};
fetchFiltered();
}, []);
} else {
useEffect(() => {
const fetchPosts = async () => {
const res = await ArticleService.articles();
setContent(res.data);
};
fetchPosts();
}, []);
}
So my logic behind this was if there is filter inside props.location execute me useEffect which gets data from ArticleService who then send filter inside of a api url. If there is no filter just retrieve me all data, and setContent to res.data.
But when I compiled the code error is this: React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render
Is there some way of doing this with my logic or I need to create two components: one normal dashboard and second for filtered result?
Landing.js where user sends location
<Form>
<div className='form-group'>
<Input
type='text'
className='form-control text-center'
name='name'
placeholder='Enter desired location'
value={location}
onChange={onChangeLocation}
/>
<Link to={{ pathname: '/dashboard', data: location }}>
<i className='fas fa-check'></i>
</Link>
</div>
<p className='text-center'>or</p>
<Link className='btn btn-primary btn-block' to='/dashboard'>
Show all locations
</Link>
</Form>
Dashboard.js
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import Pagination from 'react-js-pagination';
import ArticleService from '../../services/article.service';
const Dashboard = (props) => {
const [content, setContent] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [postsPerPage] = useState(10);
const filter = props.location.data;
if (filter) {
useEffect(() => {
const fetchFiltered = async () => {
const res = await ArticleService.filterByName(filter);
setContent(res.data);
};
fetchFiltered();
}, []);
} else {
useEffect(() => {
const fetchPosts = async () => {
const res = await ArticleService.articles();
setContent(res.data);
};
fetchPosts();
}, []);
}
let counter = content.length;
// Get current posts
const indexOfLastPost = currentPage * postsPerPage;
const indexOfFirstPost = indexOfLastPost - postsPerPage;
const currentPosts = content.slice(indexOfFirstPost, indexOfLastPost);
// Change page
const handlePageChange = (pageNumber) => {
setCurrentPage(pageNumber);
};
const render = (item, index) => {
return (
<tr key={index}>
<td className='text-center'>
<div key={item.id}>
<img
src={`${item.pictures}`}
alt='slika artikla'
className='rounded'
></img>
</div>
</td>
<td className='text-center'>
<div key={item.id}>
<h4>{item.descr}</h4>
<br></br>
<h6 className='text-left'>Number of m2: {item.sqm}m2</h6>
<div className='text-left'>
<small className='text-left'>
{' '}
<a href={item.link} target='_blank' rel='noopener noreferrer'>
Show on website
</a>
</small>
</div>
</div>
</td>
<td className='text-center'>
<div key={item.id}>
<h4>{item.price}</h4>
<small className='text-left'>Price per m2: {item.ppm2}</small>
</div>
</td>
<td className='text-center'>
<div key={item.id}>
<Link to={`/article/${item.id}`}>
<h4>Show</h4>
</Link>
</div>
</td>
</tr>
);
};
return (
<div>
<div className='container'>
<h4 className='text-center'>
Number {counter}
</h4>
<div className='table-responsive'>
<table className='table'>
<thead className='thead-dark'>
<tr>
<th className='text-center' scope='col'>
Pic
</th>
<th className='text-center' scope='col'>
Description
</th>
<th className='text-center w-25' scope='col'>
Price
</th>
<th className='text-center' scope='col'>
Show offer
</th>
</tr>
</thead>
<tbody>{currentPosts.map(render)}</tbody>
</table>
</div>
</div>
<nav>
<div className='w3-bar w3-xlarge'>
<ul className='pagination justify-content-center'>
<li className='page-item'>
<Pagination
hideDisabled
hideNavigation
hideFirstLastPages
currentPage={currentPage}
itemsCountPerPage={10}
totalItemsCount={content.length}
pageRangeDisplayed={indexOfLastPost}
onChange={handlePageChange}
/>
</li>
</ul>
</div>
</nav>
</div>
);
};
export default Dashboard;
Thanks! :D

Basic answer, no, you cannot conditionally call useEffect. You must put the conditional logic inside the useEffect callback.
const filter = props.location.data
useEffect(() => {
if (filter) {
const fetchFiltered = async () => {
const res = await ArticleService.filterByName(filter)
setContent(res.data)
}
fetchFiltered()
} else {
const fetchPosts = async () => {
const res = await ArticleService.articles()
setContent(res.data)
}
fetchPosts()
}
}, [filter, setContent, ArticleService.filterByName, ArticleService.articles])
Hooks in React do not really follow the standard rules of javascript. There are performance reasons around the way they have to be implemented, often some caching is done to stop excess code being executed every time a render pass is done.
The useEffect hook will only run its callback function during a render where one of the values in the dependency array (the second arg of useEffect) has changed. It's standard to put in all external values that could change. Thus when the value of filter changes, the app will rerender, the useEffect will do a comparision, realise that something has changed and run it's call back again which will then perform the if statement.
You can read more about this in the performance part of the docs

In addition to the above answer,from the official documentatation
Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls.

Related

Cannot render list created in one component in another

Ok. I have the app.js (which will render all components on my screen) and inside this file i embeded two other js files (components). The first one is basically a button that adds one more word to an array. It goes something like this:
import { useState } from "react";
function DescriptionSector() {
const [title, setTitle] = useState([]);
return (
<button onClick={() => setTitle([...title, "New title defined"])}>add word</button>
)
This first component is working just fine as I used console.log to test it.
THe problem is with the second part.
The second part consists basically of a list that renders the array create on the first part and here's where i having trouble.
function FinancialResume({ title }) {
return (
<ul>
{title.map(e => {
return (
<li>{e}</li>
)
})}
</ul>
)
}
I tried using the props object to send the updated array like this:
import { useState } from "react";
function DescriptionSector() {
const [title, setTitle] = useState([]);
return (
<button
onClick={() => {
setTitle([...title, "New title defined"]);
FinancialResume(title);
}}
>
add word
</button>
)
BUT IT DIDNT WORKED
EDIT: here's my app.js
import DescriptionSector from "./Components/descriptionSector/description";
import FinancialResume from "./Components/financialresume/financialresume";
function App() {
return (
<div className="App">
<div className="user-body__leftSector">
<DescriptionSector />
</div>
<div className="user-body__rightSector">
<FinancialResume />
</div>
</div>
)}
export default App;
Assuming you want the changes made in DescriptionSector to be rendered by FinancialResume, one way you can do that with React is by passing props from a shared parent.
Let App control the title state. It can pass the setter down to DescriptionSector and the value down to FinancialResume.
React states are reactive to changes. App and FinancialResume will re-render when title changes without you having to call any functions.
function App() {
const [title, setTitle] = useState([]);
return (
<div className="App">
<div className="user-body__leftSector">
<DescriptionSector setTitle={setTitle} />
</div>
<div className="user-body__rightSector">
<FinancialResume title={title} />
</div>
</div>
);
}
function DescriptionSector({ setTitle }) {
return (
<button
onClick={() => {
setTitle((title) => [...title, "New title defined"]);
}}
>
add word
</button>
);
}
function FinancialResume({ title }) {
return (
<ul>
{title.map((e, i) => {
return <li key={i}>{e}</li>;
})}
</ul>
);
}
There are of course other ways to manage shared state such as Context and state stores like Redux Toolkit but those are more advanced topics.

Location/city value is undefined after passing through other component and then render page gone blank..any solution?

I have two components "search" and "Maindata". I am passing the input value from the search component to maindata component where I want to replace the city attribute with the input value(location) in API. but the browser display went blank and the console give an undefined 'city' error, etc. I got stuck in this problem if anyone has a solution?
Here "search" component;
import React , {useState} from "react";
import Maindata from "./Maindata";
import "../Componentstyle/search.css";
export default function Search() {
const [location, setLocation] = useState();
<Maindata city={location}/>
return (
<div className="main">
<nav className="istclass">
<form className="form">
<div className="search">
<input
value={location}
placeholder="search city"
className="searchbox"
onChange={(e)=>setLocation(e.target.value)}
/>
<button className="nd" onClick={(e)=>setLocation(e.target.value)}>
Submit
</button>
</div>
</form>
</nav>
</div>
);
}
Here "Maindata" component;
import React, { useState, useEffect } from "react";
import "../Componentstyle/Main.css";
export default function Maindata(props) {
const [data, setData] = useState(null);
let city = console.log(props.city);
let weather = async () => {
const key = "1ab6ef20384db1d7d9d205d609f7eef0";
await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${key}&units=metric&formatted=0`
)
.then((response) => response.json())
.then((actualData) => setData(actualData));
};
useEffect(() => {
weather();
}, []);
if (!data) {
return <div>Loading...</div>;
}
const link = `http://openweathermap.org/img/w/${data.weather[0].icon}.png`;
return (
<div className="maindata">
<div className="city">{data.name}</div>
<div className="temp">{data.main.temp} C</div>
<div className="icon">
<img src={link} alt="not found" />{" "}
</div>
<div className="feel">feels Like {data.main.feels_like} C</div>
<div className="wind">Wind {data.wind.speed} Km/hr</div>
<div className="cloudy">{data.weather[0].main}</div>
<div className="humidity">humidity {data.main.humidity}%</div>
<div className="sunrise">
sunrise :- {new Date(data.sys.sunrise * 1000).toUTCString()}{" "}
</div>
<div className="sunset">
sunset :- {new Date(data.sys.sunset * 1000).toUTCString()}
</div>
</div>
);
}
<Maindata city={location}/>
keep this line of code inside the return
In your example, there is no meaningful connection between the Search and Maindata components. Meaning Maindata component will not get rendered on the page because it is not in the return statement of the Search component.
The Maindata component as below, is in JSX format, when you use JSX in your code in React, under the hood, React.createElement() method is being called.
Each call to React.createElement returns an object describing what to render to that part of the page. So it makes sense to put the Maindata component in the return statement. That is responsible for rendering the HTML elements from that component when you're loading a page containing that component.
<Maindata city={location}/> // is JSX and should be in the return statement to get rendered on the page and showing the right location

How to trigger a function of sibling component in React?

I am new to front end world and could not figure out how to trigger a function from a sibling component. Lets say I have 2 component in App.js. The page is:
function App() {
return (
<div className="App">
<h1>Customer List</h1>
<MainPanel/>
<TableFooterPanel/>
</div>
);
}
MainPanel code is:
function MainPanel() {
const [customers, setCustomers] = useState([]);
useEffect(() => {
const fetchPostList = async () => {
const response = await service.getCustomerList();
setCustomers(response.data);
console.log(response.data)
};
fetchPostList()
}, []);
const deleteCustomer = (id) => {
service.deleteCustomerById(id);
}
return (
<ReactBootStrap.Table striped bordered hover>
<thead>
<tr>
<th>ID</th>
<th>First Name</th>
<th>Last Name</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
{customers &&
customers.map((item) => (
<tr key={item.id}>
<td>{item.id}</td>
<td>{item.firstName}</td>
<td>{item.lastName}</td>
<td><Button onClick={() => deleteCustomer(item.id)} ><FontAwesomeIcon icon={faTrashRestore} /></Button></td>
</tr>
))}
</tbody>
</ReactBootStrap.Table>
);
}
export default MainPanel;
And TableFooterPanel code is:
function TableFooterPanel() {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const addNewCustomer = (name, surname) => {
service.addCustomer(name, surname);
}
return (
<>
<Card className='buttonFooter'>
<Form className='buttonFooter'>
<input type="text" placeholder="First Name" defaultValue={firstName} onChange={e => setFirstName(e.target.value)}></input>
<input type="text" placeholder="Last Name" defaultValue={lastName} onChange={e => setLastName(e.target.value)}></input>
<Button onClick={() => addNewCustomer(firstName, lastName)}>Add</Button>
</Form>
</Card>
</>
);
}
export default TableFooterPanel;
What I want to do is, whenever I click button and send the customer info to backend to save then immediately call a function in MainPanel to set it's table data and update the table immediately after adding. Something like, I want to click button and add it and if response is success then refresh the table (without refreshing whole page).
What is the best way to do that ?
In React we have one data flow. From parent to children, and there is no way to pass some information, from children to parent/sibling. What you want is called Lifting State Up
Just put necessary logic to parent component, here it will be the App component. In App component you will have a function which triggers table data loading, and when data will be loaded you should save it, also inside the App component. You will pass table data to MainPanel as prop. Then pass a that function from App component (which triggers table data loading) to your TableFooterPanel component and call it when onClick event happens.
I would introduce a component to hold both the table and the footer, and put the state in this component. Then you pass a function as a prop down to the footer component, which will update the table data. Then you pass the table data as props to the table component.
function Customers({initialData = []}) {
const [customers, setCustomers] = useState(initialData);
return (
<div>
<h1>Customers</h1>
<CustomerTable customers={customers} />
<TableFooter addCustomer={(customer) => setCustomers([...customers, customer])} />
</div>
)
}
By setting initialData to default to an empty array, you can rely on array methods like .map in the table component.

Why My ReactJs Loop Dropdown List call many times automatically

import React, { useState, useEffect } from 'react';
import axios from 'axios';
const App = () => {
const [num, setNum] = useState();
const [name, setName] = useState();
const [moves, setMoves] = useState();
useEffect(() => {
async function pokemonAPI() {
const getPokemon = await axios.get(`https://pokeapi.co/api/v2/pokemon/${num}`);
console.log(getPokemon.data.name);
setName(getPokemon.data.name);
setMoves(getPokemon.data.moves.length);
}
pokemonAPI();
// this function call many times automatically how can I fix this.
**function dropDown() { // this function call many times itself
for (var y = 0; y <101; y++) {
document.getElementById("100dropdown").innerHTML += ("<option value =" + y + " >" + y + "</option>");
}
}
dropDown();**
});
// its works fine on first reload of page but when i choose any number this function call it many times
return (
<>
<div className="main_div">
<h1 className="choose_value">
Please Choose your <strong> Pokemon </strong> Value <br />
<select name="100dropdown" id="100dropdown" value={num} onChange={(event) => {
setNum(event.target.value);
}}>
</select>
</h1>
<div className="pokemonList">
<h1>You've choose <span style={{ color: 'green' }}> {num} </span> value</h1>
<h1>Your Pokemon is <span style={{ color: 'red' }}> {name}</span></h1>
<h1>Your Pokemon has <span style={{ color: 'blue' }}> {moves}</span> moves </h1>
</div>
</div>
</>
);
}
export default App;
Issue
The useEffect is missing dependencies, so it triggers the effect callback every time the component renders. pokemonAPI updates state so a new render is triggered each time. This creates a render loop, so dropDown will also be called each render.
Additionally, I don't see any reason why dropDown should be declared in the useEffect at all. It's rendering static data and mutates the DOM, which is an anti-pattern in React.
Solution
Fix the dependencies, add num that is used in the GET request, and remove dropDown function:
useEffect(() => {
async function pokemonAPI() {
const getPokemon = await axios.get(`https://pokeapi.co/api/v2/pokemon/${num}`);
console.log(getPokemon.data.name);
setName(getPokemon.data.name);
setMoves(getPokemon.data.moves.length);
}
pokemonAPI();
}, [num]); // <-- add dependency array!
If you need to run the effect more than this then add any missing dependencies your linter may also complain about.
Compute and render the select options in your JSX inside the element with id="100dropdown".
<select
name="100dropdown"
id="100dropdown"
value={num}
onChange={(event) => setNum(event.target.value)}
>
{Array.from({ length: 100 }, (v, i) => i)).map(i => (
<option key={i} value={i}>{i}</option>
))}
</select>

How to update user info with react redux?

I am learning react-redux, I am creating a simple CRUD app using JSON placeholder, now I am able to display data and delete data using post and delete method's but I can't figure out how to update data with put method in redux, I need help.
**
Here is a live demo in the sandbox: redux live demo
**
Here is what I have so far, user component (just part of codes)
return(
<div>
<table id="users">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Action</th>
</tr>
</thead>
{userData &&
userData.users &&
userData.users.map(user =>
<tbody>
{user.editing ? <UserForm user={user} key={user.id} />:
<tr key={user.id}>
<td>{user.id}</td>
<td>{user.name}</td>
<td>
<button key={user.id} type="button" className="btn btn-danger btn-link" onClick={() => deleteUser(user.id)}>
<i className="material-icons">delete</i>
</button>
<button key={user.id} type="button" className="btn btn-success btn-link" onClick={() =>editUser(user.id)}>
<i className="material-icons">edit</i>
</button>
</td>
</tr>
}
</tbody>
)}
</table>
</div>
)
And here is userfom component
import React from 'react'
import { useForm } from "react-hook-form";
function UserForm() {
const { edit, handleSubmit} = useForm();
return (
<div>
<form onSubmit={handleSubmit}>
<input name="name" defaultValue="test" ref={edit} />
<input type="submit" />
</form>
</div>
)
}
export default UserForm
And here is Edit user in reducer
case ActionTypes.EDIT_USER:
return{
...state,
users:state.users.map((user)=>user.id === action.payload ? {
...user,editing:!user.editing
}:user)
}
Now when I click edit and submit the data, it refreshes the page and nothing is updated in user info (check it here live
What is wrong with my code?
Well I don't have a lot of knowledge about react-hook-form but I'll try to help you, first you need to pass a function to your 'handleSubmit' because otherwise I think that you don't prevent the default behaviour of a submit, I mean the handleSubmit function doesn't do a 'event.preventDefault()', so you can put the following below your useForm hook:
const onSubmit = data => {
console.log(data);
}
Then in your jsx you will have
<form onSubmit={handleSubmit(onSubmit)}>
I'm not sure because as I told you I don't use react-hook-form, but I think another bug that I saw is that you are trying to get a 'edit' property from the useForm hook, well that won't work, you are not declaring a variable there, you are trying to access a property from the useForm hook, so in order of tracking the changes of your inputs you should use 'register', I mean you should have the following code:
const { register, handleSubmit } = useForm();
And you should update your jsx with the following:
<input name="name" defaultValue="test" ref={register} />
Now with every submit you will have your form with the changes in the console.log that we add in our onSubmit function.
You already connected redux with your Users component, I mean you are using mapDispatchToProps and because of that inside your Users components you will be able to access the edit prop to dispatch an editUser action. So in order to continue your work with redux you can pass that prop via this.props.editUser to your UserForm component and continue. Another option is connect the UserForm component with redux and access the editUser prop.
I've modified your code, update user can work now.
revised version demo
redux/user/Users
// need to pass whole user data as parameter
export const updateUser = data => {
return dispatch => {
axios
// add data into axios second arg
.put(`https://jsonplaceholder.typicode.com/users/${data.id}`, data)
.then(response => {
dispatch(editUser(data.id));
// refresh user list after successfully update
dispatch(fetchUsers());
})
.catch(error => {
const errorMsg = error.message;
dispatch(fetchUsersFailure(errorMsg));
});
};
};
components/UserForm:
You don't need react-hook-form, just use the useDispatch hook that provided by react-redux.
import React from "react";
import { useDispatch } from "react-redux";
import { editUser, updateUser } from "../redux/acitons/users/Users";
function UserForm({ user }) {
const dispatch = useDispatch();
const [name, setName] = React.useState(user.name);
const handleSubmit = () => {
dispatch(updateUser({ ...user, name }));
};
const handleCancel = () => {
dispatch(editUser(user.id));
};
return (
{/* I modify it to inline edit row */}
<tr>
<td>{user.id}</td>
<td>
<input
defaultValue={user.name}
onChange={e => setName(e.target.value)}
/>
</td>
<td>
<button type="button" className="btn" onClick={handleCancel}>
<i className="material-icons">Cancel</i>
</button>
<button
type="button"
className="btn btn-success btn-link"
onClick={handleSubmit}
>
<i className="material-icons">save</i>
</button>
</td>
</tr>
);
}
export default UserForm;
But you will notice that new user list is still old after update successfully, this is normal
https://github.com/typicode/jsonplaceholder/issues/42#issuecomment-284507310
and I suggest you can group your actions/reducer/constants into one file by feature, this is useful when your app grow, You can easily find the file you want to modify when your code base becomes very large.
for example:
|-- reducers
|---- auth.js
|---- counter.js
|---- index.js
|---- store.js
|---- users.js
This approach actually has a name called duck pattern

Categories

Resources