I need to save my data from array to cookies, but I have some issues. :)
At the begining here bellow my code:
import { LineChartProps } from "./LineChart.props";
import { Input } from "../Input/Input";
import { Button } from "../Button/Button";
import cookie from "js-cookie";
import React, { useState, useEffect } from "react";
export const LineChart = ({ }: LineChartProps): JSX.Element => {
const [inputValue, setInputValue] = useState("");
const [citiesList, setCitiesList] = useState<string[]>(
(cookie.get("citiesList")?.split(",")) || [
"London",
"Kyiv",
"Los Angeles"
]
);//array in state
const onChange = ((event) => {
setInputValue(event.target.value);
});
const onClick = ((event) => {
setCitiesList((currentArray) => [...currentArray, inputValue])
console.log(citiesList)
});
useEffect(() => {
cookie.set("citiesList", JSON.stringify(citiesList), { path: '' });
}, [citiesList]);
return (
<div>
<Button appearance="primary" onClick={onClick}>click me</Button>
<Input type="text" name="search" onChange={onChange} />
<div>
<p>{inputValue}</p>
</div>
</div>
)
};
To simplify it I'll also add here screenshot of my code:
As you see I have an array with cities. I want to save my array data to cookies, because I use next.js, but not a react.
I have button and input field:
So when I start my app, my array looks like this:
but for example if I reaload the page, I'll see this:
Then if I reload one more time, this:
So, what is better? Prevent cookies.set if all of elements are already in cookies, or there is other (better) solution for this?
And the second problem is: When I add some cities to my cookies Array, at the begining it looks like this:
But if I refresh the page twice, I'll see this:
Do you have any idea?
And if so, thanks in advance! Will be waiting for any solution:)
Try to use JSON.parse instead:
const [citiesList, setCitiesList] = useState<string[]>([]);
useEffect(() => {
const defaultCities = ["London","Kyiv","Los Angeles"]
const cities = cookie.get("citiesList");
if (!cities) {
setCitiesList(defaultCities)
} else {
try {
setCitiesList(JSON.parse(cities))
} catch (err) {
setCitiesList(defaultCities)
}
}
}, [])
Related
I have the following code and it is working fine. <AddContact /> is a simple component that presents a input form which collects name + email from user - I have attached its code at the end for completeness. The collected contacts array is stored in localStorage, and when I refresh the page, they simply get reloaded. all good
import './App.css'
import { useState, useEffect } from 'react'
function App() {
const LOCAL_STORAGE_KEY = 'contacts'
const [contacts, setContacts] = useState(JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY)) || [])
const addNewContact = (newContact) => {
setContacts([...contacts, newContact])
}
useEffect(() => {
console.table(contacts)
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts))
}, [contacts])
return (
<AddContact newContact={addNewContact} />
)
}
export default App
my question is that the following revision does not work - every time the page is refreshed, local storage is wiped out. But it really look like it should work - I was following an online tutorial and it was working when the instructor did it.
import './App.css'
import { useState, useEffect } from 'react'
function App() {
const LOCAL_STORAGE_KEY = 'contacts'
const [contacts, setContacts] = useState([]) // changed line
const addNewContact = (newContact) => {
setContacts([...contacts, newContact])
}
useEffect(() => {
console.table(contacts)
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts))
}, [contacts])
// added
useEffect(() => {
const savedContacts = JSON.parse(
localStorage.getItem(LOCAL_STORAGE_KEY)
)
if (savedContacts) {
setContacts(savedContacts)
}
}, [])
return (
<AddContact newContact={addNewContact} />
)
}
export default App
for completeness, here's the code for <AppContact />
import React, { Component } from 'react'
export class AddContact extends Component {
state = {
name: '',
email: '',
}
updateState = (e) => {
this.setState({ [e.target.name]: e.target.value })
}
addContact = (e) => {
e.preventDefault()
if (this.state.name === '' || this.state.email === '') {
return
}
this.props.newContact(this.state)
}
render() {
return (
<div className='ui main'>
<h2>Add Contact</h2>
<form className='ui form' onSubmit={this.addContact}>
<div className='field'>
<label>Name</label>
<input
type='text'
name='name'
value={this.state.name}
placeholder='Name'
onChange={this.updateState}
/>
</div>
<div className='field'>
<label>Email</label>
<input
type='text'
name='email'
value={this.state.email}
placeholder='Email'
onChange={this.updateState}
/>
</div>
<button className='ui button blue' type='submit'>
Add
</button>
</form>
</div>
)
}
}
export default AddContact
I would like to understand why the second method does not work.
The value you set when calling addNewContact does get stored in localStorage when the first useEffect runs (as expected). The problem is that, when you reload the page, that same useEffect is overwriting what's in localStorage because the state is reset to an empty array ([]). This triggers the useEffect with contacts equals to [], and stores that in localStorage.
There are a few ways to handle it, but one way is to check if contacts is an empty array before storing its value to localStorage.
useEffect(() => {
console.table(contacts)
if (contacts.length > 0) {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts))
}
}, [contacts])
This prevents the initial state of contacts to be stored in localStorage when the page gets first loaded.
In a given render, effect callbacks will run in the order that they're declared. With this:
useEffect(() => {
console.table(contacts)
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts))
}, [contacts])
// added
useEffect(() => {
const savedContacts = JSON.parse(
localStorage.getItem(LOCAL_STORAGE_KEY)
)
if (savedContacts) {
setContacts(savedContacts)
}
}, [])
On mount, the first one runs before the second - you call localStorage.setItem before the second one runs localStorage.getItem - so by the time the second one runs, storage has been set to the initial value of the contacts state, which is the empty array.
To fix it, reverse their order, so that the one that calls .getItem runs first.
useEffect(() => {
const savedContacts = JSON.parse(
localStorage.getItem(LOCAL_STORAGE_KEY)
)
if (savedContacts) {
setContacts(savedContacts)
}
}, []);
useEffect(() => {
console.table(contacts)
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(contacts))
}, [contacts]);
That said, your first approach of
const [contacts, setContacts] = useState(JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY)) || [])
looks a lot nicer than an effect hook with an empty dependency array, IMO.
Beginner question. I know this is a simple question but I haven't been able to get this to work. I'm passing an object which holds an array of k:v pairs to a component. Eventually this props will contain multiple k:v pairs, but for now I'm just passing the one.
[{goal: 20000}]
In the component I'm trying to grab the value, 20000, so I can display it on screen. I can't seem to get just the number. If I look at props.goal I get the entire k:v.
[{goal: 20000}]
If I try props[0].goal I get 'TypeError: undefined is not an object (evaluating 'props[0].goal')'
What am I missing? Thanks for any help.
Update:
Here is the entire code for the component in question.
import { React, useState } from "react";
import Form from "react-bootstrap/Form";
import { Row, Col, Button } from "react-bootstrap";
import "./../css/Goal.css";
const Goal = (props) => {
// const [goal, setGoal] = useState("");
const [record, setRecord] = useState("");
const monthlyGoal = 2;
console.log("props[0]");
console.log(props[0]); //undefined
console.log("props");
console.log({ props }); //See below
props: Object
goal: Object
goals: [{goal: 20000}] (1)
const handleInput = (event) => {
console.log(event);
event.preventDefault();
setRecord(event.target.value);
console.log(record);
};
const defaultOptions = {
significantDigits: 2,
thousandsSeparator: ",",
decimalSeparator: ".",
symbol: "$",
};
const formattedMonthlyGoal = (value, options) => {
if (typeof value !== "number") value = 0.0;
options = { ...defaultOptions, ...options };
value = value.toFixed(options.significantDigits);
const [currency, decimal] = value.split(".");
return `${options.symbol} ${currency.replace(
/\B(?=(\d{3})+(?!\d))/g,
options.thousandsSeparator
)}${options.decimalSeparator}${decimal}`;
};
return (
<Form>
<Row className="align-items-center flex">
<Col sm={3} className="goal sm={3}">
<Form.Control
id="inlineFormInputGoal"
placeholder="Goal"
// onChange={(e) => setGoal(e.target.value)}
/>
<Button type="submit" className="submit btn-3" onSubmit={handleInput}>
Submit
</Button>
</Col>
<Col>
<h1 className="text-box">
Goal: {formattedMonthlyGoal(monthlyGoal)}
</h1>
</Col>
</Row>
</Form>
);
};
export default Goal;
Update 2:Here is the parent component:
import React, { useEffect, useState } from "react";
import Goal from "./Goal";
import axios from "axios";
const Dashboard = () => {
const [dashboardinfo, setdashboardinfo] = useState([]);
useEffect(() => {
async function fetchData() {
try {
const data = (await axios.get("/api/goals/getgoals")).data;
setdashboardinfo(data);
} catch (error) {
console.log(error);
}
}
fetchData();
}, []);
return (
<React.Fragment>
<Goal dashboardinfo={dashboardinfo} />
</React.Fragment>
);
};
export default Dashboard;
If you get an object like the following from console logging destructured props:
{
dashboardinfo: {goals: [{goal: 20000}]}
}
You need to use props.dashboardinfo.goals[0].goal to get the value.
Your props contains the object "dashboardinfo" so you need to do
props.dashboardinfo.goals[0].goal
or a better way is to destructure your props object like this
const Goal = ({dashboardinfo: { goals }}) => {
...
goals[0].goal
...
}
I believe I've resolved my issue. It wasn't so much a problem with accessing the key:value as I thought, because when the page was initialized I was able to grab the value and display it fine. However, when I refreshed the page I lost all of the props data and that resulted in an error. I tracked it down to the useState didn't seem to be updating the value before I was trying to read it. So I added a useEffect in the child component.
const Goal = (props) => {
const [goal, setgoal] = useState([]);
useEffect(() => {
setgoal(props.goal);
console.log("the goal", goal);
}, [props.goal, goal]);
...
This seems to have worked as I'm getting the information I want and not getting any errors when I refresh. This may not be the ideal way to go about this but it is working.
I am working on a component where the user searches a term and it is returned to them through a filter. I am using useContext hook to pass data from db via axios. I would like to use the button in the CompSearch component to render the results rather than having them show up on a keystroke. My question is how do I render the results via button click?
Here is the code
Follow these steps to achieve that.
Change the input element into an uncontrolled component.
Get the value using reference in React.
import React, { useContext, useRef, useState } from "react";
import CompanyInfoList from "./CompanyInfoList";
import { CompListContext } from "./CompListContext";
const CompSerach = () => {
const [company, setCompany] = useContext(CompListContext);
const [searchField, setSearchField] = useState("");
const [searchShow, setSearchShow] = useState(false);
const filtered = company.filter((res) => {
return res.company.toLowerCase().includes(searchField.toLowerCase());
});
const inputRef = useRef(null); // 1. Create the ref
const handleClick = () => {
const val = inputRef.current.value; // 3. Get the value
setSearchField(val);
if (val === "") {
setSearchShow(false);
} else {
setSearchShow(true);
}
};
const searchList = () => {
if (searchShow) {
return <CompanyInfoList filtered={filtered} />;
}
};
return (
<>
<div>
<em>
NOTE: if you search "ABC" or "EFGH" you will see results - my goal is
to see those results after clicking button
</em>
<br />
<input
type="search"
placeholder="search Company Title"
ref={inputRef} {/* 2. Assign the ref to the Input */}
/>
<button onClick={handleClick}>Enter</button>
</div>
{searchList()}
</>
);
};
export default CompSerach;
https://codesandbox.io/s/show-on-button-click-68157003-rot6o?file=/src/TestTwo/CompSearch.js
Let me know if you need further support.
const handleChange = (e) => {
setSearchField(e.target.value);
if (e.target.value === "") {
setSearchShow(false);
} else {
setSearchShow(true);
}
**setCompany(e.target.value);**
};
i think your question is similar with autocomplete.
Here is the where I am having the problem,
const handleCLick = () => {
const parsedId = getYouTubeID(videoLink);
console.log(parsedId);
setVideoId(parsedId);
console.log(videoId);
}
Here when I am trying to log the 'parsedId' it logs the data correctly
ioNng23DkIM
And after using the setVideoId() function when I try to log the value it returns undefined
undefined
Here is a snap shot of the log output.
Home.js code:
import React, { useRef, useState } from "react";
import { Link } from "react-router-dom";
import getYouTubeID from 'get-youtube-id';
function Home(props) {
const [videoLink, setVideoLink] = useState();
const [isBool, setBool] = useState(false);
const [videoId, setVideoId] = useState();
const urlRef = useRef();
const handleChange = (event) => {
setVideoLink(event.target.value);
if (urlRef.current.value === '') {
alert('Please enter a URL');
setBool(true);
} else {
setBool(false);
}
}
const handleCLick = () => {
const parsedId = getYouTubeID(videoLink);
console.log(parsedId);
setVideoId(parsedId);
console.log(videoId);
}
return (
<section className="homeLayout">
<div className="logo-display">
<img className="logo-img" alt="logo" src="./logo.png" />
<h1>WatchIt</h1>
</div>
<div className="searchlayer">
<form>
<input ref={urlRef} id="videoLink" placeholder="Enter the youtube video URL:" onBlur={handleChange} required />
<Link style={{ pointerEvents: isBool ? 'none' : 'initial' }} to={`/play?=${videoId}`} onClick={handleCLick}>Play</Link>
</form>
</div>
</section>
);
}
export default Home;
You can use useEffect to solve your problem.
Use effect will listen to you state change n then you can perform logic in there.
The problem you're facing is because setState will set the value eventually, not immediately (Usually this means the update will be visible when the component is rendered again). If you want to do something after the value is set, you need to use useEffect.
Splitting your handleClick we get,
const handleCLick = () => {
const parsedId = getYouTubeID(videoLink);
console.log(parsedId);
setVideoId(parsedId); // Queue the change for `videoId`
}
useEffect(() => {
console.log(videoId);
}, [videoId]); // Call this function when the value of `videoId` changes
I'm working on building out a small application. Currently I'm stuck at trying to display the response given from my back-end.
I've been using this guide to implement a data fetching hook, and everything works except for trying to display the returned entries in a list.
Here is what my code in the component looks like:
import React, { Fragment, useState, useEffect } from "react";
import axios from 'axios';
function Search() {
const [zone, setZone] = useState('');
const [results, setResults] = useState({entries: []});
const [url, setUrl] = useState(
`http://localhost:4000/entries/view/`
);
useEffect(() => {
const fetchEntries = async () => {
const result = await axios.get(url);
setResults({entries: result.entries});
};
fetchEntries();
}, [url]);
return (
<Fragment>
<input type="text" value={zone} onChange={e => setZone(e.target.value)} />
<button type="btn-primary" onClick={() =>
setUrl(`http://localhost:4000/entries/view/${zone}`)
}>
Search
</button>
<ul>
{results.entries.map(item => (
<li key={item._id}>
<div>{item.entry_description}</div>
</li>
))}
</ul>
</Fragment>
);
}
export default Search;
And here is a sample of what the API response looks like:
{
"entries": [
{
"_id": "5d365d9af8b0625f345a8cea",
"entry_description": "Test ui",
"entry_time": "Now",
"entry_author": "Me",
"entry_zone": "12345",
"__v": 0
},
{
"_id": "5d367e3bbd8b13188c0d638b",
"entry_description": "Test still?",
"entry_time": "Now",
"entry_author": "Me",
"entry_zone": "12345",
"__v": 0
}
]
}
Current error message I'm receiving is:
The above error occurred in the <Search> component:
in Search (created by Context.Consumer)
in Route (at App.js:21)
in div (at App.js:14)
in Router (created by BrowserRouter)
in BrowserRouter (at App.js:13)
in App (at src/index.js:7)
Consider adding an error boundary to your tree to customize error handling behavior.
TypeError: results.entries is undefined
home.component.js:30
TypeError: results.entries is undefined
home.component.js:30
I really appreciate any help figuring out what is causing this.
You seem to be wrapping the api result in an array, whereas you just need to assign it to enteries
useEffect(() => {
const fetchEntries = async () => {
const result = await axios.get(url);
setResults({entries: result.entries});
};
fetchEntries();
}, [url]);
So I've got it working now, although I don't quite understand it. I used this code and changed some of the names to work with my variable names. I think the fix has something to do with the useEffect() dependency in this code including the query variable. In my original code, I combined them into a url, and then didn't also include the zone variable in the dependency.
Here's my final code:
import React, { useState, useEffect } from "react";
import axios from 'axios';
function Search() {
const [data, setData] = useState({ entries: [] });
const [query, setQuery] = useState('Enter zone');
useEffect(() => {
let ignore = false;
async function fetchData() {
const result = await axios('http://localhost:4000/entries/view/' + query);
if (!ignore) setData(result.data);
}
fetchData();
return () => { ignore = true; }
}, [query]);
return (
<>
<input value={query} onChange={e => setQuery(e.target.value)} />
<ul>
{data.entries.map(item => (
<li key={item._id}>
<p>{item.entry_description}</p>
</li>
))}
</ul>
</>
);
}
export default Search;