Why is my component not receiving data from another? - javascript

I have React Components:
My account.jsx
import "./myAccount.sass";
import { auth } from "../../firebaseConfig";
import {
signOut,
onAuthStateChanged,
} from "https://www.gstatic.com/firebasejs/9.6.0/firebase-auth.js";
import { useEffect, useState } from "react";
import { Hotel } from "../Hotel/Hotel";
function MyAccount(props) {
const { isauth } = props;
const { hotels = [] } = props;
const [userEmail, setUserEmail] = useState([]);
function logout() {
signOut(auth);
isauth(false);
console.log("Successful logout");
}
function userInfo() {
onAuthStateChanged(auth, (user) => {
console.log(`user info: ${user.email}"`);
setUserEmail(user.email);
});
}
useEffect(() => {
userInfo();
}, []);
return (
<div>
<h3>Hi, {userEmail}</h3>
<button onClick={logout}>Logout...</button>
<h4>Your reviews:</h4>
<hr />
<div className="Hotels">
{hotels.map((hotel, id) => (
<Hotel key={id} {...hotel} filter={true} user={userEmail} />
))}
</div>
</div>
);
}
export default MyAccount;
Hotels.jsx
import starsMap from "../../starsMap";
import { useEffect, useState } from "react";
import "./hotel.sass";
function Hotel(props, filter = false, user = null) {
const { name, img, localization, stars, review, author } = props;
const [userEmail, setUserEmail] = useState(user);
if (filter) {
console.log(author, userEmail);
if (author === user) {
return (
<div className="Hotel_card">
<h2 className="Hotel_card-name">{name}</h2>
<div className="Hotel_card_wrapper">
<img className="Hotel_card-img" src={img} alt="hotel_img" />
<h3 className="Hotel_card-localization">
{/* Lat:{localization._lat}
Long:{localization._lat} */}
<button>Show on map</button>
</h3>
{starsMap.get(stars)}
</div>
<p className="Hotel_card-review">{review}</p>
<h5 className="Hotel_card-author">Wroten by {author}</h5>
</div>
);
}
} else {
return (
<div className="Hotel_card">
<h2 className="Hotel_card-name">{name}</h2>
<div className="Hotel_card_wrapper">
<img className="Hotel_card-img" src={img} alt="hotel_img" />
<h3 className="Hotel_card-localization">
{/* Lat:{localization._lat}
Long:{localization._lat} */}
<button>Show on map</button>
</h3>
{starsMap.get(stars)}
</div>
<p className="Hotel_card-review">{review}</p>
<h5 className="Hotel_card-author">Wroten by {author}</h5>
</div>
);
}
}
export { Hotel };
The MyAccount.jsx component passes two attributes to the Hotels.jsx component - filter={true} user={userEmail}.
Hotels.jsx only sees filter={true}, user remains null for it.
Question - why doesn't Hotels.jsx see the user={userEmail} passed to it?
If you look at the state userEmail in MyAccount.jsx, then the desired value is found this way, but it is lost at the moment it is transferred to Hotels.jsx

It's because you are using an intermediate useState in the Hotel.jsx file and you are not receiving correctly the props
const [userEmail, setUserEmail] = useState(user);
remove that line (it seems that you are not using it anyway) and change in the Hotels.jsx component the prop user to userEmail:
function Hotel({filter = false, userEmail = null, ...props})

A React Functional Component, is a function that returns some JSX code. When you write it like:
<Hotel key={id} {...hotel} filter={true} user={userEmail} />
This is JSX, and this code gets converted to React first class API:
React.createElement(
Hotel,
[props],
[...children]
)
props are filter, user and whatever comes out of {...hotel}.
The React.createElement just takes props and children ( if at all ), and passes an object to your Hotel function with them all inside, remember that props is an object:
Hotel(props)
Being an object, when you want to recall your props from within your Component, you have two possible ways:
function Hotel (props) {
console.log(props.filter, props.user)
}
or through destructuring them directly when declaring them:
function Hotel ({filter, user}) {
console.log(filter, user)
}
If you are passing other props that you don't want to name directly:
function Hotel ({filter, user, ...props}) {
console.log(filter, user, props)
}

Related

How can I reload a react js component after filtering data

I will appreciate your help with my project.
I created button filters for a component that displays list of courses in a nextjs project. When I click on a button it filters the courses but when I refresh the page it gives me the error below:
./pages/index.js
Module parse failed: Identifier 'courses1' has already been declared (15:15)
File was processed with these loaders:
./node_modules/next/dist/build/webpack/loaders/next-swc-loader.js
You may need an additional loader to handle the result of these loaders.
Please see code below.
import { useState, useEffect } from "react"
import axios from "axios"
import CourseCard from "../components/cards/CourseCard";
import { Button } from 'antd'
const Index = ({ courses }) => {
const allTopics = ['All', ...new Set(courses.map(courses => courses.topic))]
const [courses, setCourses] = useState(courses)
const [buttons, setButtons] = useState(allTopics)
const filter = (button) => {
if (button === 'All') {
setCourses(courses)
}
const filteredData = courses.filter(courses => courses.topic === button)
setCourses(filteredData)
}
return (
<>
<h1 className="jumbotron p-5 text-center bg-primary text-white square">OEP</h1>
<div className="container-fluid">
<div>
{
buttons.map((topic, i) => {
return <Button onClick={() => filter(topic)} className="btn ms-2">{topic}</Button>
})
}
</div>
<div className="row">
{courses.map((course) => <div key={course._id} className="col-md-4">
<CourseCard course={course} />
</div>)}
</div>
</div>
</>
)
};
export async function getServerSideProps() {
const { data } = await axios.get(`${process.env.API}/courses`);
return {
props: {
courses: data,
},
}
}
export default Index;
The constant courses you declared (in line 15) already existed. It was destructured from the parameters of Index. Try to change the name of one of these 2 variables.

React #reach/Router issues How to make the switchcase work

I am trying to make my App.js route to my People.jsx etc.. but it is not working correctly. I hope I could fix the issue from there if I could make this work. I have been trying to do this for about 2 hours with the 20 min rule but this one I need help with. I have tried other variations but my goal is to get the,theID over to Person as well. I am thinking about using {useContext } to do that but I can't even get it to route. I wish I knew what I was doing wrong so I could correct it but other people are using different types of routers and I was confused with them even more.
I updated it with links still a no go for me any other suggestions?
App.js
import './App.css';
import { useState } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import People from './components/People'
import Planet from './components/Planets'
import Starship from './components/Starships'
import { Router, Link } from '#reach/router';
function App() {
const [starwarsState, setStarwarsState] = useState('')
const [theID, setTheID] = useState('')
const selectedState = (e) => {
setStarwarsState(e.target.value)
}
const switchItem = () => {
switch (starwarsState) {
case 'people':
<Link path='/people/' />;
break;
case 'planets':
<Link path="/planets/" />;
break;
case 'starships':
<Link path='/starships/' />;
break;
default:
return null;
}
}
const addId = e => {
setTheID(e.target.value)
console.log(theID)
}
return (
<div className='App'>
<header className='App-header' >
Search For:
<select onChange={selectedState} className='form-control-lg bg-dark text-white'>
<option value='people' active >People</option>
<option value='planets' >Planets</option>
<option value='starships' >Starships</option>
</select>
ID:
<input type='text' onChange={addId} className='form-control-lg col-sm-1 bg-dark text-white' />
<button className='btn-lg btn-warning' onClick={switchItem} >Search Item</button>
<Router>
<People path='/people/' />
<Planet path="/planets/" />
<Starship path='/starships/' />
</Router>
</header>
{starwarsState}
</div>
)
}
export default App;
People.jsx
import React, { useState, useEffect } from 'react'
import axios from 'axios'
import { Link } from '#reach/router';
const People = props => {
const [peopleData, setpeopleData] = useState([]);
useEffect(() => {
axios.get(`https://swapi.dev/api/people/${props.theID}`)
.then(response => { setpeopleData(response.data) })
console.log(peopleData)
}, []);
return (
<div>
<span> the People have spoken</span>
<Link to='/people' />
</div>
)
}
export default People;
Issues
You aren't actually rendering the routes/links from switchItem since onClick callbacks can't return renderable UI directly to the render method.
Solution
Unconditionally render your routes all at the same time within a single Router and imperatively navigate to them in the switchItem handler.
App
...
import { Router, navigate } from "#reach/router";
...
function App() {
const [starwarsState, setStarwarsState] = useState("");
const [theID, setTheID] = useState("");
...
const switchItem = () => {
switch (starwarsState) {
case "people":
navigate("/people"); // <-- imperative navigation
break;
case "planets":
navigate("/planets");
break;
case "starships":
navigate("/starships");
break;
default:
return null;
}
};
return (
<div className="App">
<header className="App-header">
Search For:
<select
onChange={selectedState}
value={starwarsState}
className="form-control-lg bg-dark text-white"
>
<option disabled value="">
Choose Path
</option>
<option value="people">
People
</option>
<option value="planets">Planets</option>
<option value="starships">Starships</option>
</select>
ID:
<input
type="text"
onChange={addId}
className="form-control-lg col-sm-1 bg-dark text-white"
/>
<button className="btn-lg btn-warning" onClick={switchItem}>
Search Item
</button>
</header>
<Router>
<People path="/people" theID={theID} /> // <-- pass `theID` state as prop
<Planet path="/planets" />
<Starship path='/starships' />
</Router>
</div>
);
}
People
const People = ({ theID }) => {
const [peopleData, setpeopleData] = useState([]);
useEffect(() => {
axios.get(`https://swapi.dev/api/people/${theID}`)
.then(response => { setpeopleData(response.data) });
}, [theID]);
return (
<div>
<div>The ID: {theID}</div>
<span>the People have spoken</span>
</div>
);
};
Use Imperative Routing (not switch statement) with Event Handlers
Your code is using a switch statement in combination with the switchItem() function. This is not how to redirect the user imperatively (meaning, through something other than a link clicked directly by the user).
To imperatively route your users, use the navigate method.
Via Reach Router docs (link):
Sometimes you need to navigate in response to something other than the user clicking on a link. For this we have navigate. Let’s import navigate.
import {
Router,
Link,
navigate
} from "#reach/router"
In your case, the entire switch statement can be rewritten as follows:
useEffect(() => navigate(`/${starwarsState}`), [starwarsState])
useEffect will watch for changes to the starwarsState, which is either going to be 'people', 'planets', or 'starships'. Once a change occurs, it will navigate the user to the corresponding path.
Solution: Routing Only
The following solution doesn't implement axios, it focuses solely on the client-side routing logic.
I found some other issues with your code when I was working through a solution. Here is a version that I wrote that implements parameter-level routing while also making some other cleanup (refactoring the swapi categories into a config object, etc).
App.js
import React, { useState, useEffect } from 'react'
import { useWhatChanged } from "#simbathesailor/use-what-changed";
import { Router, Link, navigate } from "#reach/router";
import 'bootstrap/dist/css/bootstrap.min.css';
import { People, Person } from './components/People'
import { Planets, Planet } from './components/Planets'
import { Starships, Starship } from './components/Starships'
import './App.css';
function App() {
// destructure categories from config
const { people, planets, starships } = config.categories
// initialize state
const [starwarsState, setStarwarsState] = useState(people);
const [theID, setTheID] = useState('');
// log updates to ID and starwarsState
useWhatChanged([starwarsState, theID], 'starwarsState, theID')
// change state on input from user
const addId = e => setTheID(e.target.value)
const selectCategory = (e) => setStarwarsState(e.target.value)
// route the user based on starwarsState
useEffect(() => navigate(`/${starwarsState}`), [starwarsState])
// search swapi based on category and id
const searchSwapi = e => {
e.preventDefault()
navigate(`/${starwarsState}/${theID}`)
}
return (
<div className="App">
<header className='App-header' >
Search For:
<form onSubmit={searchSwapi}>
<select onChange={selectCategory} className='form-control-lg bg-dark text-white'>
<option value={people} >People</option>
<option value={planets} >Planets</option>
<option value={starships} >Starships</option>
</select>
ID:
<input type='text' onChange={addId} className='form-control-lg col-sm-1 bg-dark text-white' />
<button className='btn-lg btn-warning' >Search Item</button>
</form>
</header>
<Router>
<People path='/people/'>
<Person path=':personId' />
</People>
<Planets path="/planets/">
<Planet path=':planetId' />
</Planets>
<Starships path='/starships/'>
<Starship path=':starshipId' />
</Starships>
</Router>
</div>
)
}
const config = {
categories: {
people: 'people',
planets: 'planets',
starships: 'starships'
}
}
export default App;
Planets.js
import React from 'react'
export const Planets = props => {
return (
<div>
<span> the Planets have spoken</span>
{props.children}
</div>
)
}
export const Planet = props => {
return (
<div>
Planet Data
</div>
)
}
People.js
import React, { useState, useEffect } from 'react'
export const People = props => {
return (
<div>
<span> the People have spoken</span>
{props.children}
</div>
)
}
export const Person = props => {
return (
<div>
Person Data
</div>
)
}
Starships.js
import React from 'react'
export const Starships = props => {
return (
<div>
<span> the Starships have spoken</span>
{props.children}
</div>
)
}
export const Starship = props => {
return (
<div>
Starship Data
</div>
)
}
[UPDATE] Solution: Routing with API Calls
The following solution takes the code from above and refactors it using the state management pattern proposed by Leigh Halliday. The solution adds three things:
useContext for managing global state
React.memo() for memoizing AppContent component
react-query for managing remote API calls to SWAPI.
View code on GitHub
App.js
// App.js
import React, {
useState,
useEffect,
createContext,
useContext,
memo
} from 'react'
import { ReactQueryDevtools } from "react-query-devtools";
import { useWhatChanged } from "#simbathesailor/use-what-changed";
import { Router, navigate } from "#reach/router";
import 'bootstrap/dist/css/bootstrap.min.css';
import { People, Person } from './components/People'
import { Planets, Planet } from './components/Planets'
import { Starships, Starship } from './components/Starships'
import './App.css';
import Axios from 'axios';
// APP w/ CONTEXT PROVIDER
export default function App() {
return (
<>
<StarwarsProvider>
<AppContent />
</StarwarsProvider>
<ReactQueryDevtools initialIsOpen={false} />
</>
)
}
// CREATE CONTEXT
export const StarwarsContext = createContext()
// CONTEXT PROVIDER
function StarwarsProvider({ children }) {
// import categories
const categories = config.categories
// destructure default category of search selection
const { people } = categories
// initialize state
const [category, setCategory] = useState(people);
const [theID, setTheID] = useState('');
return (
<StarwarsContext.Provider value={{
category,
setCategory,
theID,
setTheID,
categories,
fetchStarwarsData
}}>
<AppContent />
</StarwarsContext.Provider>
)
}
async function fetchStarwarsData(category, id) {
if (!id) {
return
}
const response = await Axios.get(
`https://swapi.dev/api/${category}/${id}`
).then(res => res.data)
// const data = await response.json()
const data = response
// console.log(data)
return data
}
// APP CONTENT
const AppContent = memo(() => {
// import global state into component
const { category, setCategory } = useContext(StarwarsContext)
const { theID, setTheID } = useContext(StarwarsContext)
// destructure categories
const { categories: { people, planets, starships } } = useContext(StarwarsContext)
// log updates to ID and category
useWhatChanged([category, theID], 'category, theID')
// change state on input from user
const addId = e => setTheID(e.target.value)
const selectCategory = (e) => setCategory(e.target.value)
// route the user based on category
useEffect(() => navigate(`/${category}`), [category])
// search swapi based on category and id
const searchSwapi = e => {
e.preventDefault()
navigate(`/${category}/${theID}`)
}
return (
<div className="App">
<header className='App-header' >
Search For:
<form onSubmit={searchSwapi}>
<select onChange={selectCategory} className='form-control-lg bg-dark text-white'>
<option value={people} >People</option>
<option value={planets} >Planets</option>
<option value={starships} >Starships</option>
</select>
ID:
<input type='text' onChange={addId} className='form-control-lg col-sm-1 bg-dark text-white' />
<button className='btn-lg btn-warning' >Search Item</button>
</form>
</header>
<Router>
<People path='/people/'>
<Person path=':personId' fetchStarwarsData />
</People>
<Planets path="/planets/">
<Planet path=':planetId' fetchStarwarsData />
</Planets>
<Starships path='/starships/'>
<Starship path=':starshipId' fetchStarwarsData />
</Starships>
</Router>
</div>
)
})
const config = {
categories: {
people: 'people',
planets: 'planets',
starships: 'starships'
}
}
People.js
// People.js
import React from 'react'
import { useQuery } from "react-query";
import { StarwarsContext, StarwarsProvider } from "../App"
export const People = props => {
return (
<div>
<span> the People have spoken</span>
{props.children}
</div>
)
}
export const Person = () => {
const { category, theID, fetchStarwarsData } = React.useContext(StarwarsContext)
const { data, isLoading, error } = useQuery([category, theID], fetchStarwarsData)
if (isLoading) return <div>loading...</div>
if (error) return <div>oop!! error ocurred</div>
return (
<div>
<h1>/{category}/{theID}</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
)
}
Planets.js
// Planets.js
import React from 'react'
import { useQuery } from "react-query";
import { StarwarsContext, StarwarsProvider } from "../App"
export const Planets = props => {
return (
<div>
<span> the Planets have spoken</span>
{props.children}
</div>
)
}
export const Planet = props => {
const { category, theID, fetchStarwarsData } = React.useContext(StarwarsContext)
const { data, isLoading, error } = useQuery([category, theID], fetchStarwarsData)
if (isLoading) return <div>loading...</div>
if (error) return <div>oop!! error ocurred</div>
return (
<div>
<h1>/{category}/{theID}</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
)
}
Starships.js
// Starships.js
import React from 'react'
import { useQuery } from "react-query";
import { StarwarsContext, StarwarsProvider } from "../App"
export const Starships = props => {
return (
<div>
<span> the Starships have spoken</span>
{props.children}
</div>
)
}
export const Starship = () => {
const { category, theID, fetchStarwarsData } = React.useContext(StarwarsContext)
const { data, isLoading, error } = useQuery([category, theID], fetchStarwarsData)
if (isLoading) return <div>loading...</div>
if (error) return <div>oop!! error ocurred</div>
return (
<div>
<h1>/{category}/{theID}</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
)
}

TypeError: Cannot assign to read only property '0' of object '[object Array]'

Im really rellied about this issue, the case im trying to use a Material UI slider with redux.
here the slider component:
import { Slider } from '#material-ui/core'
const RangeSlider = ({handleRange, range}) => {
const handleChange = (event, newValues) => {
handleRange(newValues)
}
return (
<Slider
value={range}
onChange={handleChange}
valueLabelDisplay="auto"
aria-labelledby="range-slider"
min={0}
max={100}
/>
)
}
export default RangeSlider
here the filter component with redux logic:
import {
Button,
Typography as Tp,
Select,
MenuItem,
Accordion,
AccordionDetails,
AccordionSummary,
} from "#material-ui/core";
import { ExpandMore } from "#material-ui/icons";
import { RangeSlider } from "../components";
import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { filterUpdate } from "../redux/actions/filter";
const Filter = ({ models }) => {
const [range, setRange] = useState([0,60]);
const [modelSelected, setModelSelected] = useState("None");
const handleRange = (newValue) => setRange(newValue);
const handleModel = (event) => setModelSelected(event.target.value);
const dispatch = useDispatch();
const handleSubmit = () => {
let filterValues = {
range,
modelSelected,
};
dispatch(filterUpdate(filterValues));
};
return (
<div className="container mt-3 ">
<Accordion>
<AccordionSummary
expandIcon={<ExpandMore />}
aria-controls="panel-filter-content"
id="panel-filter-header"
>
<Tp>Sort your search</Tp>
</AccordionSummary>
<AccordionDetails className='d-flex align-items-center bg-light pb-3'>
<div className='col-md-6'>
<Tp className="mt-3">Pick up a price range</Tp>
<RangeSlider handleRange={handleRange} range={range} />
</div>
<div className="col">
<div className="col text-center pb-3">
<Tp className="mt-3">Sort by model</Tp>
<Select value={modelSelected} onChange={handleModel}>
{models.map((model) => {
return (
<MenuItem key={model} value={model}>
{model}
</MenuItem>
);
})}
</Select>
</div>
</div>
<div className=" col mt-5 text-center">
<Button onClick={handleSubmit} variant="outlined" color="primary">
Submit
</Button>
</div>
</AccordionDetails>
</Accordion>
</div>
);
};
export default Filter;
Redux action logic:
import { createAction } from '#reduxjs/toolkit'
import types from '../types'
export const filterUpdate = createAction(types.FILTER_UPDATE)
Here Reducer:
import { createReducer } from '#reduxjs/toolkit'
import { filterUpdate } from '../actions/filter'
const reducer = createReducer({
range: [0,60],
model: 'None'
}, {
[filterUpdate]: (state, action) => {
state.range = action.payload.range
state.model = action.payload.modelSelected
}
})
export default reducer
The error appear when i include the redux logic, when i tested with a console.log was fine, but with the dispatch, appear this error, also, its a random error because i can test to dispatch 2-3 times without error, but the next this error appear
What can i do to deal with that?
Thanks in advance.
In my case, it fails to sort function inside the Slider.
That because the value that I passed into the component was a read-only (immutable).
So to avoid this error I should to pass in the value prop the copy of range array. Like this:
<Slider
value={[...range]}
// ....other props
/>
I am not sure what is the problem, but your reducer is definitely wrong. You do:
const reducer = createReducer({
range: [0,60],
model: 'None'
}, {
[filterUpdate]: (state, action) => {
state.range = action.payload.range
state.model = action.payload.modelSelected
}
})
You probably meant to do this:
const reducer = createReducer({
range: [0,60],
model: 'None'
}, {
[filterUpdate]: (state, action) => ({
range: action.payload.range,
model: action.payload.modelSelected,
})
})
Also you are not actually pulling state from redux store. You are importing useSelector and not calling it anywhere. Whatever useState and useDispatch return are independent from each other in your code.

Delay in storing the content of an event in array by one click in React

I am new to React. I am working on a piece of code. I am trying to make a ToDoList kind of thing. So i have created 3 different component , one for taking input and one for displaying the entered code along with App.jsx.
Here are the code for each.
App.jsx
import React, { useState } from "react";
import Header from "./Header";
import Footer from "./Footer";
import Note from "./Note";
import CreateArea from "./CreateArea";
function App() {
const [arr, setArr] = useState([]);
function getData(note) {
// setArr(previous => [...previous, { title: newTitle, content: newContent }]);
setArr(previous => {return [...previous, note];});
console.log(arr);
}
return (
<div>
<Header />
<CreateArea reply={getData} />
{arr.map((arry, index) => (
<Note
key={index}
id={index}
title={arry.title}
content={arry.content}
/>
))}
<Footer />
</div>
);
}
export default App;
CreateArea.jsx
import React, { useState } from "react";
function CreateArea(props) {
const [note, setNote] = useState({
title: "",
content: ""
});
function handleChange(event) {
const { name, value } = event.target;
setNote(prevNote => {
return {
...prevNote,
[name]: value
};
});
}
function submitNote(event) {
// const newTitle = note.title;
// const newContent = note.content;
// props.reply(newTitle, newContent);
props.reply(note);
setNote({
title: "",
content: ""
});
event.preventDefault();
}
return (
<div>
<form>
<input
name="title"
onChange={handleChange}
value={note.title}
placeholder="Title"
/>
<textarea
name="content"
onChange={handleChange}
value={note.content}
placeholder="Take a note..."
rows="3"
/>
<button onClick={submitNote}>Add</button>
</form>
</div>
);
}
export default CreateArea;
Note.jsx
import React from "react";
function Note(props) {
return (
<div className="note">
<h1>{props.title}</h1>
<p>{props.content}</p>
<button>DELETE</button>
</div>
);
}
export default Note;
Now somehow i am getting the result right and the new post are being added as required but when I do a console.log for the array which stores inside getData() in App.jsx ,I observe there is a delayy of one click before the array is being added.
Sample problem
In the images attached.I have added new post with random data but when i see the console log the array is still empty. When i click the Add button for the second time , only then after second click on adding new post is the data being shown. I can't seem to figure out the reasoning behind it. Am I doing something wrong or missing something ?
Your setArr() from useState() is an asynchronous function. Hence, the console.log(arr) is called before the state is actually updated.
However, you can log arr in the useEffect() hook which is called on every state change, like this:
useEffect(() => {console.log(arr)})
You can find more information about this hook at https://reactjs.org/docs/hooks-effect.html.
This is delay is because useState mutation is asynchronous, you cannot get updated result in one tick.
try console.log result in useEffect hook like below:
function App() {
const [arr, setArr] = useState([]);
function getData(note) {
// setArr(previous => [...previous, { title: newTitle, content: newContent }]);
setArr(previous => {
return [...previous, note];
});
}
useEffect(() => {
console.log(arr);
});
return (
<div>
<CreateArea reply={getData} />
{arr.map((arry, index) => (
<Note
key={index}
id={index}
title={arry.title}
content={arry.content}
/>
))}
</div>
);
}
more info: hooks-reference.html#useeffect

Changing specific index of an array in state instead of all of my state object

I'm doing a React refresher. I set state in App.js and created an event called handleUserNameChange() to change usernames in the state object. Each input from UserInput.js should change it's relative UserOutput component's username in state that's set in App.js. How can I make so that when I type text into one input it only changes one index in my users array in state? Do I need to change my handleUserNameChange event?
App.js
import React, { Component } from 'react';
//Components
import UserInput from './UserInput/UserInput';
import UserOutput from './UserOutput/UserOutput';
class App extends Component {
state = {
users: [
{user: 'Debbie'},
{user: 'Ronald'}
]
};
handleUserNameChange = (event) => {
this.setState({
users: [
{user: event.target.value},
{user: event.target.value}
]
});
};
render() {
return (
<div className="App">
<UserOutput
username = {this.state.users[0].user}
/>
<UserInput
nameChange={this.handleUserNameChange} />
<UserOutput
username={this.state.users[1].user}
/>
<UserInput
nameChange={this.handleUserNameChange}
/>
</div>
);
}
}
export default App;
UserOuput.js
import React from 'react';
const UserOutput =(props) => {
return (
<div>
<h3>{props.username}</h3>
</div>
);
}
export default UserOutput;
UserInput.js
import React from 'react';
const UserInput = (props) => {
return (
<div>
<input type="text"
onChange={props.nameChange}
/>
</div>
);
}
export default UserInput;
In App.js:
<UserInput
nameChange={this.handleUserNameChange(0)}//0 for first, 1 for second
/>
handleUserNameChange = (index) => (event) => {
this.setState({
users: this.state.users.map(
(user,i)=>
(i===index)
? event.target.value
: user
)
});
};
It would probably be better to not hardcode user 0 and user 1 but just map the state to react modules.
render() {
const userInput = index =>
<UserInput
nameChange={this.handleUserNameChange(index)} />;
const UserOutput = user =>
<UserOutput
username = {user}/>;
return (
<div className="App">
this.state.users.map(
(user,index)=>
<div>{userInput(index)}{UserOutput(user)}</div>
)
</div>
);
}
pass the index value in handleUserNameChange function from render function and use double arrow in handleUserNameChange to get the index value.
handleUserNameChange = index => event => {
this.setState(prevState => {
const users = [...prevState.users];
users[index].user = event.target.value;
return { users };
});
};
render() {
return (
<div className="App">
{this.state.users.map((user, index) => (
<React.Fragment>
<UserOutput username={user} />
<UserInput nameChange={this.handleUserNameChange(index)} />
</React.Fragment>
))}
</div>
);
}

Categories

Resources