Hi I have a little meme editor using the imgflip public api. I usually develop using Angular but I'm trying to learn react so I'm a little lost right now.
On my project when I load the page I get a list of all the meme templates available, then when you select one template you have the template and one text field for each meme text line. The number of input texts changes on each template this is where I'm stuck.
The idea is to get all the input text values, send it to the api and show the generated meme to the user.
This is my code right now:
App.js
import { useEffect, useState } from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from "react-router-dom";
import memeService from './services/memeService';
import Meme from './components/Meme';
import './App.css';
import Editor from './components/Editor';
function App() {
const [memes, setMemes] = useState([]);
useEffect(() => {
(async function getData() {
await getMemes();
})();
}, []);
const getMemes = async () => {
const results = await memeService.getMemes();
setMemes(results.data.data.memes);
}
return (
<>
<Router>
<Switch>
<Route path="/:id/edit" children={<Editor />} />
<Route path="/">
<div className='container'>
{memes.map(meme => <Meme key={meme.id} meme={meme} />)}
</div>
</Route>
</Switch>
</Router>
</>
)
}
export default App;
Editor.js
import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import memeService from './../services/memeService';
const Editor = () => {
const [meme, setMeme] = useState({});
const {id } = useParams()
const getMeme = async () => {
setMeme(await memeService.getMeme(id));
}
useEffect(getMeme, [id]);
const TextBox = () => {
const inputs = [];
for(let i = 0; i < meme.box_count; i++){
inputs.push(<input key={i} type='text' />);
}
return (
<>
{inputs.map(input => {return input})}
</>
)
}
const generateMeme = () => {
console.log('generating meme');
}
return (
<>
<div className='container'>
<div className='meme'>
<img alt={meme.name} src={meme.url} />
</div>
<div className='text'>
<TextBox />
</div>
</div>
<button onClick={generateMeme}>Save</button>
</>
)
}
export default Editor;
I'm not proud at all of the TextBox function that renders the input text fields but for now I'm mostly concerned about making this work.
THe point where I'm stuck is on the Editor.js I need to get all the text on the input text field that I have on the editor to send it to the API. On other tutorials that I followed I didi it using the app's state using the onChange event so when the user types on the text submit the states gets updated and when clicking on the submit button I just use the current state but on this scenario I don't see it possible as there's multiple and different inputs.
By the way this is the API I'm using: https://imgflip.com/api
First, you need to keep track of values in the inputs, by add an array to the TextBox and making inputs controlled.
Second, you need to pass the values to the parent. For that you can add a handler method, which will remember the values into a ref, like
const values = useRef()
handleChange(newValues){
values.current(newValues)
}
Then you pass handleChange as a prop and call it after setValues. And on submit you'll have your values in values.current
The complete TextBox:
const TextBox = (props) => {
const [values, setValues] = useState([])
const inputs = [];
useEffect(()=>{
props.onChange && props.onChange(values)
}, [values])
function handleInput(e, i)
{
setValues(v =>{
const temp=[...v];
temp[i]=e.target.value;
return temp})
}
for(let i = 0; i < meme.box_count; i++){
inputs.push(<input key={i} type='text' value={values[i]} onChange={(e) => handleInput(e,i) } />);
}
return (inputs)
}
Related
Hello I am making an application to practice React, my notes app has a pagination which works perfectly, the problem is in the search engine, which only looks for notes from the page I'm on, for example, if I'm on page 2 and I look for a note on page 2, it shows it, however if the note is on a different page, it doesn't show it, it doesn't find it.
I know where the problem is but I'm not sure how to solve it, I'm a bit new to React and I was asking for your help.
I was able to do my pagination with the package react-paginate here is the documentation https://www.npmjs.com/package/react-paginate
My code:
Component App.js
import { useState, useEffect } from "react";
import { nanoid } from 'nanoid';
import './App.css';
import Search from "./components/Search";
import Header from "./components/Header";
import Pagination from "./components/Pagination";
const App = () => {
const [notes, setNotes] = useState([]);
const [searchText, setSearchText] = useState('');
const [darkMode, setDarkMode] = useState(false);
const [showNote, setShowNote] = useState(true); //eslint-disable-line
useEffect(() => {
const saveNotes = JSON.parse(localStorage.getItem('notes-data'));
if (saveNotes){
setNotes(saveNotes);
}
}, []);
useEffect(() => {
localStorage.setItem('notes-data', JSON.stringify(notes))
},[notes])
const addNote = (inputText, text) => {
const date = new Date();
const newNote = {
id: nanoid(),
title: inputText,
text: text,
date: date.toLocaleString()
}
const newNotes = [newNote, ...notes];
setNotes(newNotes)
}
const deleteNote = (id) => {
var response = window.confirm("Are you sure?");
if (response){
const notesUpdated = notes.filter((note) => note.id !== id)
setNotes(notesUpdated);
}
}
return (
<div className={darkMode ? 'dark-mode' : ''}>
<div className="container">
<Header
handleToggleTheme={setDarkMode}
/>
<Search
handleSearchNote={setSearchText}
setShowNote={setShowNote}
/>
<Pagination
data={notes}
handleAddNote={addNote}
handleDeleteNote={deleteNote}
searchText={searchText}
/>
</div>
</div>
)
}
export default App;
Component Pagination.js
import React, { useEffect, useState } from 'react'
import ReactPaginate from 'react-paginate';
import '../styles/Pagination.css';
import NoteList from './NoteList';
import { MdSkipPrevious, MdSkipNext } from 'react-icons/md';
const Pagination = (props) => {
const { data, searchText, handleAddNote, handleDeleteNote } = props;
// We start with an empty list of items.
const [currentItems, setCurrentItems] = useState([]);
const [pageCount, setPageCount] = useState(0);
// Here we use item offsets; we could also use page offsets
// following the API or data you're working with.
const [itemOffset, setItemOffset] = useState(0);
const itemsPerPage = 9;
useEffect(() => {
// Fetch items from another resources.
const endOffset = itemOffset + itemsPerPage;
console.log(`Loading items from ${itemOffset} to ${endOffset}`);
setCurrentItems(data.slice(itemOffset, endOffset));
setPageCount(Math.ceil(data.length / itemsPerPage));
}, [itemOffset, itemsPerPage, data]);
// Invoke when user click to request another page.
const handlePageClick = (event) => {
const newOffset = (event.selected * itemsPerPage) % data.length;
console.log(
`User requested page number ${event.selected}, which is offset ${newOffset}`
);
setItemOffset(newOffset);
};
return (
<>
<NoteList
notes={currentItems.filter((noteText) =>
noteText.title.toLowerCase().includes(searchText)
)}
handleAddNote={handleAddNote}
handleDeleteNote={handleDeleteNote}
/>
<div className="pagination-wrapper">
<ReactPaginate
breakLabel="..."
nextLabel={<MdSkipNext
className='icons'
/>}
onPageChange={handlePageClick}
pageRangeDisplayed={3}
pageCount={pageCount}
previousLabel={<MdSkipPrevious
className='icons'
/>}
renderOnZeroPageCount={null}
containerClassName="pagination"
pageLinkClassName="page-num"
previousLinkClassName="page-num"
nextLinkClassName="page-num"
activeLinkClassName="activee boxx"
/>
</div>
</>
);
}
export default Pagination;
Component NoteList.js
import React from 'react'
import Note from './Note'
import '../styles/NoteList.css'
import AddNote from './AddNote'
const NoteList = ({ notes, handleAddNote, handleDeleteNote }) => {
return (
<>
<div className="add-notes-wrapper">
<AddNote
handleAddNote={handleAddNote}
/>
</div>
<div className='notes-list'>
{notes.map((note =>
<Note
key={note.id}
id={note.id}
title={note.title}
text={note.text}
date={note.date}
handleDeleteNote={handleDeleteNote}
/>
))}
</div>
</>
)
}
export default NoteList;
Component Search.js
//import React, { useState } from 'react'
import {MdSearch, MdAdd} from 'react-icons/md'
import '../styles/Search.css'
const Search = ({ handleSearchNote, setShowNote }) => {
const handleShowAddNote = () => {
if (setShowNote){
let addNote = document.querySelector('.new');
addNote.classList.add('wobble-horizontal-top')
addNote.style.display='flex';
document.querySelector('.notes-list').style.display='none';
document.querySelector('.pagination').style.display='none';
}
}
return (
<div className='search'>
<div className="input-wrapper">
<MdSearch
className='icon search-icon'
/>
<input
type="text"
placeholder='What note are you looking for?'
onChange={(event) => handleSearchNote(event.target.value) }
/>
</div>
<div className="btn-wrapper-search">
<button
className='btn-addNote'
onClick={handleShowAddNote}>
Nueva Nota
</button>
<MdAdd
className='icon add-icon'
/>
</div>
</div>
)
}
export default Search
The problem is in the component Pagination.js because I'm filtering the notes on each page with the currentItems variable, if I did it with the data variable it would work, but then it would show all the notes, and I don't want that, I currently want to show 9 notes per page.
greetings and thanks in advance.
Edit:
#Newbie I'm doing what you said, but I don't know if you mean this, in my Pagination.js component I did:
useEffect(() => {
const filterNotes=data.filter((noteText) =>
noteText.title.toLowerCase().includes(searchText)
)
setItemOffset(0);
}, [data, searchText])
It doesn't work, do I have to pass a prop to my components additionally?
greetings.
As I suggested to you, search all the notes with searchText in your App.js and pass the results into the Pagination component and it will solve your problem.
Codesandbox: https://codesandbox.io/s/youthful-thompson-xugs0c
Edit
All changes are as per what we talked about in the email.
Codesandbox: https://codesandbox.io/s/green-fast-3k76wx
Search and pagination do not play well together, one of the common solutions is to jump to page 1 each time the filter term changes.
So use an useEffect on searchText to filter data and reset itemOffset to 0, then redo pagination as if the data changed.
The user will jump to page 1 at each keystroke of the search, then he can navigate pages (if there are more than one). This will lead to a less confusing UX.
I'm getting a placeholder value through props in my input component and I need to send the input value back to the main class. I'm using React but I'm not getting it. Follow my code.... The value I need to send is the value of 'usuario'
import React, { useState } from 'react';
import { EntradaDados } from './styled';
const PesqDados = ({placeholder, usuario}) => {
const [usuario, SetUsuario] = useState('')
const setValor =(e)=>{
SetUsuario(e.target.value);
}
console.log(usuario);
return(
<EntradaDados
onChange={setValor}
placeholder={placeholder}
>
</EntradaDados>
);
}
export default PesqDados;
You need to add a callback prop (onUsuarioChange) to your PesqDados component and call it with the new usuario. You have two options:
Call it from a useEffect with usuario as dependency (assuming usuario could get updated from somewhere other than setValor.
Call it from setValor, assuming that's the only place where usuario is going to get updated from.
This is how this should look:
import React, { useState } from 'react';
import { EntradaDados } from './styled';
const PesqDados = ({
placeholder,
usuario,
onUsuarioChange
}) => {
const [usuario, setUsuario] = useState('');
// Option 1:
useEffect(() => {
onUsuarioChange(usuario);
}, [usuario]);
const setValor = (e) => {
const nextUsuario = e.target.value;
setUsuario(nextUsuario);
// Option 2:
onUsuarioChange(nextUsuario);
};
return (
<EntradaDados
onChange={ setValor }
placeholder={ placeholder } />
);
}
export default PesqDados;
After studying properly, I found that I don't need to implement the function in the component page. I just needed to create a hook that calls the component's OnChange property on the component's page and then create a function just in the place where the component is installed. In this case, App.js.
Page Component....
const PesqDados = ({placeholder, Dados}) => {
return(
<EntradaDados
onChange={Dados}
placeholder={placeholder}
>
</EntradaDados>
);
}
export default PesqDados;
Page App.js
function App() {
const [usuario, SetUsuario] = useState('Aguardando Dados...')
const setValor =(e)=>{
SetUsuario(e.target.value);
}
const teste = ()=>{
alert("O usuário digitado foi : "+usuario)
};
return (
<>
<div className='divRepos'>
<div className='bloco'>
<div className='pesquisar'>
<p>{usuario}</p>
<PesqDados
placeholder={"Digite um username válido"}
Dados={setValor}
/>
<Button nomeBotao={"Pesquisar Perfil"}
onClick={teste}/>
</div>
...
I need React App level access via refs to my functions.
Specifically, Square > updateVal & updateColor. The hierarcy being App > Grid > Row x6 > Square x5. For whatever reason my useRef([]) array remains empty when it should contain 30 references (one to each Square). Hopefully the codebase will clarify my ask!
A secondary question of lesser importance: Sometimes my grid fails to display upon refreshing the page... if I change any of the text that's expected to render, the app finishes loading completely. I assume now that I've started to work with a useEffect() my issue might be related. Let me know if you notice anything that might relate to that issue as well.
// index.js
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
// App.js
import React from 'react'
import Row from './Components/Row/Row';
import Square from './Components/Square/Square';
import { useEffect, useState, useRef } from 'react';
import Grid from './Components/Grid/Grid';
let hunch = ''
function App() {
const [hurdle, setHurdle] = useState('')
const [flag, setFlag] = useState('')
const MAX_GUESSES = 6
const MAX_WORD_LENGTH = 5
let guess = 0
let g = [...new Array(MAX_GUESSES)].map(a => new Array(MAX_WORD_LENGTH))
let hunchCharOccurrences = {}
const refs = useRef([])
let rows = new Array(MAX_GUESSES)
function handleEvent(event){
// ...
// OMITTED FOR BREVITY
// ...
let id = (MAX_GUESSES * guess) + hunch.length
if( refs && "current" in refs){
refs?.current[id].updateVal(k.toUpperCase())
refs?.current[id].setColor('palevioletred')
}
event.preventDefault()
}
function listen(){
document.addEventListener('keydown', handleEvent)
}
useEffect(() => {
refs.current = refs.current.slice(0, MAX_GUESSES * MAX_WORD_LENGTH)
let idIndex = 0
for(let i = 0; i < MAX_GUESSES; i++){
let squares = new Array(5);
for(let j = 0; j < MAX_WORD_LENGTH; j++){
squares[j] = <Square key={idIndex} ref={el => refs.current[idIndex] = el} />
idIndex++
}
rows[i] = squares
}
return listen()
}, [])
return (
<div className="App">
<main>
<div className='rendered-grid-container'>
<Grid rows={rows} />
</div>
<br/>
</main>
</div>
);
}
export default App;
// src>Components>Grid.js
import React, { useState } from 'react'
import Row from '../Row/Row'
function Grid({rows}, ref){
const gridItems = rows.map((row, index) =>
<tbody key={index}>
<Row squares={row} />
</tbody>
)
return (
<table className='grid-container'>
{ gridItems }
</table>
)
}
Grid.displayName = `Grid`
export default React.forwardRef(Grid)
// src>Components>Grid.js
import React, { useState } from 'react'
import Square from '../Square/Square'
function Row({squares}, ref){
const rowItems = squares.map((square, index) =>
<td key={index}>
{square}
</td>
)
return (
<tr className='row-container'>
{ rowItems }
</tr>
)
}
Row.displayName = `Row`
export default React.forwardRef(Row)
// src>Components>Square.js
import React, { useState, useImperativeHandle } from 'react'
function Square(props, ref) { // anonymouus -> named function
const [val, setVal] = useState('')
const [color, setColor] = useState('aqua')
function updateVal(v){
setVal(v)
}
function updateColor(c){
setColor(c)
}
useImperativeHandle(
ref,
() => {
return {
updateVal: updateVal,
updateColor: updateColor
}
},
)
return (
<div className='square-container'>
<div className='square' ref={ref} style={{backgroundColor: color}}>
{ val }
</div>
</div>
)
}
Square.displayName = `Square`
export default React.forwardRef(Square) //forwardRef HOC
I'm referencing the following posts as implementation guides:
How can I use multiple refs for an array of elements with hooks?
How to create dynamic refs in functional component- Using useRef Hook
React - Forwarding multiple refs
I'm aware that common React convention is passing data from children to their parent components. In addition to fixing my error, I'd appreciate some clarification on using functions for forwarded ref HOC's like mine. After all, my app itself is a F(x).
I'm trying to make an option in jsx to be populated by the values in an array (currencyOptions). I used this approach but it is not working as the options still remain to be blank. The array is passed down to the component as a prop. I set the array using usestate and the data is gotten from an API. Please help.
import React from "react";
function Currencyrow(props) {
const {
currencyOptions,
selectedCurrency,
onChangeCurrency,
amount,
onChangeAmount,
} = props;
// console.log(currencyOptions);
return (
<>
<input
type="number"
className="input"
value={amount}
onChange={onChangeAmount}
></input>
<select value={selectedCurrency} onChange={onChangeCurrency}>
{currencyOptions.map((option) => {
<option key={option} value={option}>
{option}
</option>;
})}
</select>
</>
);
}
export default Currencyrow;
That is the component where I pass down currencyOptions as a prop from my main app.js
import "./App.css";
import React from "react";
import Currencyrow from "./Components/Currencyrow";
import { useEffect, useState } from "react";
const BASE_URL =
"http://api.exchangeratesapi.io/v1/latest?access_key=1fe1e64c5a8434974e17b04a023e9348";
function App() {
const [currencyOptions, setCurrencyOptions] = useState([]);
const [fromCurrency, setFromCurrency] = useState();
const [toCurrency, setToCurrency] = useState();
const [exchangeRate, setExchangeRate] = useState();
const [amount, setAmount] = useState(1);
const [amountInFromCurrency, setAmountInFromCurrency] = useState(true);
let toAmount, fromAmount;
if (amountInFromCurrency) {
fromAmount = amount;
toAmount = fromAmount * exchangeRate;
} else {
toAmount = amount;
fromAmount = amount / exchangeRate;
}
useEffect(() => {
fetch(BASE_URL)
.then((res) => res.json())
.then((data) => {
const firstCurrency = Object.keys(data.rates)[0];
setCurrencyOptions([Object.keys(data.rates)]);
setFromCurrency(data.base);
// console.log(currencyOptions);
setToCurrency(firstCurrency);
setExchangeRate(data.rates[firstCurrency]);
});
}, []);
function handleFromAmountChange() {
// setAmount(e.target.value);
setAmountInFromCurrency(true);
}
function handleToAmountChange() {
// setAmount(e.target.value);
setAmountInFromCurrency(false);
}
return (
<>
<h1>Convert</h1>
<Currencyrow
currencyOptions={currencyOptions}
selectedCurrency={fromCurrency}
onChangeCurrency={(e) => {
setFromCurrency(e.target.value);
}}
amount={fromAmount}
onChangeAmount={handleFromAmountChange}
/>
<div className="equals">=</div>
<Currencyrow
currencyOptions={currencyOptions}
selectedCurrency={toCurrency}
onChangeCurrency={(e) => {
setToCurrency(e.target.value);
}}
amount={toAmount}
onChangeAmount={handleToAmountChange}
/>
</>
);
}
export default App;
When I run the app the option element is still blank.
Is there a way to populate option html tag with an array in react?
This is possible. Just as a tip, you can always try hardcoding currencyOptions in your CurrencyRow and test it out.
Looking through your code, firstly it may be not what you want wrapping Object.keys() in an additional array in setCurrencyOptions([Object.keys(data.rates)]). Object.keys() already returns an array. You probably are not accessing the actual options in your currencyOptions.map((option) => ..). Try setting the keys array directly like this setCurrencyOptions(Object.keys(data.rates)).
Secondly, you should return the desired value inside map by either using it as an arrow function or adding the return keyword in front of the option JSX.
Other than that, is there any error displayed in the browser console? And it would certainly help you to log the mapped option to the console and see what you are actually getting from it.
Your map function should return a value.
<select>{numbers.map((m)=>{return(<option>{m}</option>)})}</select>
I couldn't find how to make the field (input) work to filter the searches into cards:
can someone help me to operate the input field ?
**Notes.js**
import React, { useEffect, useState } from 'react'
import NoteCard from '../components/NoteCard'
import Masonry from 'react-masonry-css'
import Notess from '../components/CardList'
export default function Notes({details }) {
const [searchField, setSearchField] = useState("");
const [notes, setNotes] = useState([]);
const filteredNote = notes.filter(
note => {
return (
note.title.toLowerCase().includes(searchField.toLowerCase())
);});
const handleChange = e => {
setSearchField(e.target.value);
};
return (
<Container>
<input type='search' onChange = {handleChange} />
<Notess filter={filteredNote} />
</Container>
) }
someone can help me to operate the input field ?