How to specify the type - reactjs

I am learning typescript.
I used axios and stored the api return value in useEffect and displayed it on the screen, but I get an error saying that there is no name in the interface todoList.
What is the cause?
thank you.
import React, {useEffect, useState} from 'react';
import axios from 'axios';
interface todoList {
name: string
age: number
}
function App() {
const defaultProps: todoList[] = [];
const [todoList, setTodoList]: [todoList[], (todoList: todoList[]) => void] = useState(defaultProps);
useEffect(() => {
async function getTodo() {
const response = await axios.get('http://localhost/laravel/public/api/todo');
setTodoList(response.data);
}
getTodo();
}, []);
return (
<div className="App">
{todoList.name} // Error here.
</div>
);
}
export default App;

Point1: This is because UI gets rendered before useEffect and hence the moment UI gets rendered it looks for todolist.name where todolist is empty and fetching name throws error. If specific name of any items is to be displayed check the array length and name is not null before mapping to UI
Point2: If all the names is to placed in UI then map the array as below
import React, {useEffect, useState} from 'react';
import axios from 'axios';
function App() {
const [todoList, setTodoList] = useState([]);
useEffect(() => {
async function getTodo() {
const response = await axios.get('http://localhost/laravel/public/api/todo');
setTodoList(response.data);
 }
getTodo();
}, []);
return (
<div>
{todoList.map((todo) =>
<p> {todo.name} </p>
)}
</div>
);
}
export default App;

Your todoList variable is an array of todoLists, e.g. todoList[]. You're trying to read .name of an array, which isn't defined.
You probably want to display a list of items:
return <div className="App">
{todoList.map(todo => <div key={todo.name}>
{todo.name}
</div>)}
</div>;
Your typing otherwise seems fine. One suggestion for readability that I have is simplifying your useState statement:
const [todoList, setTodoList] = useState<todoList>(defaultProps);
With how React declared the type of useState, you can use useState<T> and it'll automatically know that it's returning a [T, (value: T) => void].
Another small note: Usually interfaces/types are PascalCase, e.g. TodoList, to make sure it doesn't clash with variables and function parameters which usually have camelCase, e.g. your todoList variable.

Related

Why is my state value undefined when I pass it and try use it?

I've fetched movies from an API and stored the movie object in a state value called 'movies'.
I then pass this state as a prop to my slideshow component where I want to access the poster_path property of this object to display.
When I receive the object, I destructure it and take the 'poster_path' and store it as a variable called 'posters' eg: let posters = movies.poster_path.
When I console log 'movies' it gives me all of the movies, but when I log this variable it is giving me 'undefined'. I'm sure I'm missing something small but I can't figure it out. (I don't show the full route that my state travels but it starts in App.js and it's received in the Slideshow component).
Basically, how do I get this value so I can then use it within my slideshow?
Apologies in advance for my code as I'm still pretty new to React.
import React, { useState, useEffect } from 'react';
// import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
//Components
import Header from './components/Header'
import MusicPlayer from './components/MusicPlayer';
//Pages
import Home from './pages/Home';
import Kids from './pages/Kids';
import Movies from './pages/Movies';
import Music from './pages/Music'
//CSS
import './css/Header.css'
import './App.css';
import './css/Movies-Kids.css'
import './css/Music.css'
import './css/Home.css'
import './css/HeroSlider.css'
import './css/FilterButton.css'
import './css/MusicPlayer.css'
import { Delete } from '#mui/icons-material';
function App() {
// * MOVIE API *
//State
const [movies, setMovies] = useState([])
//API URL
const url = 'https://api.themoviedb.org/3/discover/movie?api_key=APIKEY&with_genres=28/';
//Async function to fetch API
async function getMoviesData (url) {
await fetch(url).then(res => res.json()).then(data => setMovies(data.results))
}
//Use Effect
useEffect(() => {
getMoviesData(url);
}, [])
return (
<div className='app'>
<div className="header">
<Header Home={Home} Movies={Movies} Kids={Kids} Music={Music} movies={movies} />
</div>
<div className="music-player">
{/* <MusicPlayer /> */}
</div>
</div>
)
}
export default App
import React, { useState, useEffect } from 'react'
//Images Posters
import BackDrop from '../images/sandwich.jpg'
import FoodBanner from '../images/foodbanner.jpg'
import Batman from '../images/batman.jpg'
import Pulpfiction from '../images/pulp-fiction.jpg'
// const posters = [Pulpfiction, Batman, BackDrop, FoodBanner, FoodBanner];
const delay = 50000;
function Slideshow({movies}) {
let posters = movies.poster_path
console.log(posters)
//State
const [index, setIndex] = useState(0)
//UseEffect
useEffect(() => {
setTimeout(
() =>
setIndex((prevIndex) =>
prevIndex === posters.length - 1 ? 0 : prevIndex + 1
),
delay
);
return () => {};
}, [index]);
return (
<div className="slideshow">
<div className="slideshowSlider" style={{ transform: `translate3d(${-index * 100}%, 0, 0)` }}>
{posters.map((poster, index) => (
<img src={poster} className="slide" key={index} style={{ poster }}/>
))}
</div>
{/* <div className="slideshowDots">
{posters.map((_, index) => (
<div key={index} className="slideshowDot"></div>
))}
</div> */}
</div>
);
}
export default Slideshow
From comments on the question...
When the 'movies' state is passed to the header through to the slideshow it is not undefined. When I log 'movies' it works.
Indeed. It's an array. That array is initially empty here:
const [movies, setMovies] = useState([])
And presumably your AJAX operation sets it to a potentially non-empty array.
The problem is when I try to log a property of that movie it gives me undefined. eg: movies.poster_path returns 'undefined'
What is "that movie"? movies is an array. It's a collection of zero or more "movies". If nothing else, look at the semantics of your variable. It's called movies, not movie.
The code has no way of knowing which specific element from the collection you are referring to unless you tell it. An array has no property called poster_path, but an object in the array could:
movies[0].poster_path
(This of course assumes there is at least one object in the array. There may be none.)
In fact, you're even expecting poster_path to also be an array here:
let posters = movies.poster_path
// and later...
posters.map((poster, index) => (
So... Is your data an array or isn't it? Sometimes you think it is, sometimes you think it isn't. It has to be one or the other, it can't simultaneously be both.
If movies is indeed an array (and the code indicates that it is), and if objects within that array have a property called poster_path, and if you expect posters to be an array of that property (and the code indicates that you do), then make it an array of that property:
let posters = movies.map(m => m.poster_path)
Overall you just need to be aware of what data is in your variables. Be aware of what is a collection of objects and what is a single instance of an object.
movies is initially an array and movies.poster_path is definitely undefined. you may want to use a useEffect hook to monitor when the data is updated.
so change posters to useState and set the state when movie is updated with a useEffect.
const [posters, setPosters] = useState("")
useEffect(() => {setPosters(movies.poster_path)},[movies])
Hope this helps.

Why is my component failing to run when I call it?

I am struggling to find why my component is not responding to being called by its parent. I am trying to integrate Cloud Firestore with code that previously ran using Redux. My first goal is to populate my List with data from Firestore.
Here are my (simplified) components in question:
// List.js
import React, { useEffect, useState } from "react";
import db from "../../db";
import { onSnapshot, query, collection, orderBy } from "firebase/firestore";
import TaskItem from "./TaskItem";
const List = () => {
const [taskList, setTaskList] = useState([]); // Currently assumes DB never empty, populates on initial render
const [isInitialRender, setIsInitialRender] = useState(true);
// Firestore
const ref = collection(db, "Tasks");
const q = query(ref, orderBy("listIndex"));
useEffect(() => {
// Execute only on initial render
if (isInitialRender) {
// Populate task list
onSnapshot(q, (querySnapshot) => {
setTaskList(() => querySnapshot.docs)
}, (error) => {
console.log(error)
})
};
setIsInitialRender(() => false);
}, []);
return (
<>
<h2>List</h2>
{taskList.forEach((task) => ( // console-logging `task` here will output correct data
<ul key={task.data().key}>
<TaskItem
id={task.data().key}
// docRef={taskDoc}
/>
</ul>
))
}
</>
);
};
export default List;
// TaskItem.js
import React from "react";
const TaskItem = (props) => {
console.log('This will not print')
const submitHandler = () => console.log('Submitted');
return (
<form onSubmit={submitHandler}>
<input
autoFocus
type="text"
/>
</form>
);
};
export default TaskItem;
I have tried:
Populating the state with the data from each document (rather than assigning it directly), then passing the contents as props. This led to (I believe) an infinite loop, and ideally I would like to pass the actual DocumentReference to the TaskItem anyways. So this was a bust for me.
Returning [...querySnapshot.docs], or even (prev) => prev = [...querySnapshot.docs] in the state setter. No response from TaskItem().
Decomposing the taskList state into a new dummy array, and using that array to populate the props for TaskItem.
I know that the task data is being fetched successfully because I can satisfactorily log taskList's contents from the map function in List's return statement. But it seems like TaskItem() never runs.
Does anyone see my error here?
edit: sorry I assumed you were using map. I'm not sure why your forEach isn't working but map would work, from my example
edit 2: you probably are looking to use map because you want to transform every element in the array: JavaScript: Difference between .forEach() and .map()
you forgot to return something from the map, and maybe need {} instead.
try
{taskList.forEach((task) => {
return (
<ul key={task.data().key}>
<TaskItem
id={task.data().key}
// docRef={taskDoc}
/>
</ul>
)
})

React How to pass arguments to function

Hi I found a question asking the same thing but they coded completely different using 'class name extends', I am just using 'function name'. I was wondering how I would I solve this problem or do I have to rewrite my program.
I have styles at the bottom I left off.
Window.js
import React from 'react';
import "../css/Start.css";
export default function Window(nuts) {
let ulList=[]
for (let i=0;i<nuts.file.length;i++) {
ulList.push(<li>
{
nuts.file[i]
}
</li>)
}
let imageList=[]
for (let i=0;i<nuts.image.length;i++) {
imageList.push(<img src={nuts.image[i]} alt={nuts.image[i]}/>)
}
return (
<div style={main}>
<p>{nuts.name}</p>
<p>{nuts.date}</p>
<p>{nuts.note}</p>
{ulList}
{imageList}
<button> Demo </button>
</div>
);
}
Project.js
import React from 'react';
import Background from '../images/projectbackground.jpg';
import "../css/Start.css";
import Window from './Window'
export default function Project() {
const files = ['f1','f2','f3']
const images = ['p1','p2','p3']
const nuts = {name:'phil',date:'2/2/16',note:'this is a note',file:files,image:images}
return (
<div style={main}>
<Window nuts={nuts}/>
<div style={footer}>
</div>
</div>
);
}
Your function component will get passed all the properties together in an object.
There are three changes you could make to have this work:
render <Window {...{nuts}} /> instead (not recommended but is an option)
change parameters to function Window(props) and all your other code to say props.nuts instead of just nuts
change parameters to function Window({nuts}) to destructure the (no longer named) "props" object
nuts is being passed to Window via the props object.
You either need to destructure nuts in-line or in your function body.
function Window({ nuts })
or
function Window(props) {
const { nuts } = props;
}

Make localStorage retain its content on page refresh in React

I am trying to add a favorite page to my application, which basically will list some of the previously inserted data. I want the data to be fetched from localStorage. It essentially works, but when I navigate to another page and come back, the localStorage is empty again. I want the data in localStorage to persist when the application is refreshed.
The data is set to localStorage from here
import React, { useState, createContext, useEffect } from 'react'
export const CombinationContext = createContext();
const CombinationContextProvider = (props) => {
let [combination, setCombination] = useState({
baseLayer: '',
condiment: '',
mixing: '',
seasoning: '',
shell: ''
});
const saveCombination = (baseLayer, condiment, mixing, seasoning, shell) => {
setCombination(combination = { baseLayer: baseLayer, condiment: condiment, mixing: mixing, seasoning: seasoning, shell: shell });
}
let [combinationArray, setCombinationArray] = useState([]);
useEffect(() => {
combinationArray.push(combination);
localStorage.setItem('combinations', JSON.stringify(combinationArray));
}, [combination]);
return (
<CombinationContext.Provider value={{combination, saveCombination}}>
{ props.children }
</CombinationContext.Provider>
);
}
export default CombinationContextProvider;
And fetched from here
import React, { useContext, useState } from 'react'
import { NavContext } from '../contexts/NavContext';
const Favorites = () => {
let { toggleNav } = useContext(NavContext);
let [favorites, setFavorites] = useState(localStorage.getItem('combinations'));
console.log(favorites);
return (
<div className="favorites" >
<img className="menu" src={require("../img/tacomenu.png")} onClick={toggleNav} />
<div className="favorites-title">YOUR FAVORITES</div>
<div>{ favorites }</div>
</div>
);
}
export default Favorites;
There are a few issues with your code. This code block:
useEffect(() => {
combinationArray.push(combination);
localStorage.setItem('combinations', JSON.stringify(combinationArray));
}, [combination]);
Will run any time the dependency array [combination] changes, which includes the first render. The problem with this is combination has all empty values on the first render so it is overwriting your local storage item.
Also, combinationArray.push(combination); is not going to cause a rerender because you are just changing a javascript value so react doesn't know state changed. You should use the updater function react gives you, like this:
setCombinationArray(prevArr => [...prevArr, combination])
You should push to your combinationArray and set the result as the new state value and be careful not to overwrite your old local storage values

ReactJS hooks useContext issue

I'm kind of to ReactJS and I'm trying to use useContext with hooks but I'm having some trouble. I've been reading through several articles but I could not understand it.
I understand its purpose, but I can't figure out how to make it work properly. If I'm correct, the purpose is to be able to avoid passing props down to every children and be able to access values from a common provider at any depth of the component tree. This includes functions and state values. Please correct me if I'm wrong.
I've been testing with the following files. This is the ManagerContext.js file:
import { createContext } from 'react';
const fn = (t) => {
console.log(t);
}
const ctx = createContext({
title: 'This is a title',
editing: false,
fn: fn,
})
let ManagerContext = ctx;
export default ManagerContext;
Then I have the LessonManager.js file which is used in my main application:
import React from 'react';
import LessonMenu from './LessonMenu.js';
export default function LessonManager() {
return (
<LessonMenu />
)
}
And finally the LessonMenu.js:
import React from 'react';
import 'rsuite/dist/styles/rsuite.min.css';
import ManagerContext from './ManagerContext.js';
export default function LessonMenu() {
const value = React.useContext(ManagerContext);
return (
<div>
<span>{value.title}</span>
<button
onClick={()=>value.fn('ciao')}
>click</button>
<button
onClick={()=>value.title = 'new title'}
>click</button>
</div>
)
}
In the LessonMenu.js file the onClick={()=>value.fn('ciao')} works but the onClick={()=>value.title = 'new title'} doesn't re render the component.
I know something is wrong, but can someone make it a bit clearer for me?
In order for rerendering to occur, some component somewhere must call setState. Your code doesn't do that, so no rendering happens.
The setup you've done for the ManagerContext creates a default value, but that's only going to get used if you don't render any ManagerContext.Provider in your component tree. That's what you're doing now, but it's almost certainly not what you want to. You'll want to have some component near the top of your tree render a ManagerContext.Provider. This component can will be where the state lives, and among the data it sends down will be a function or functions which set state, thus triggering rerendering:
export default function LessonManager() {
const [title, setTitle] = useState('SomeOtherTitle');
const [editing, setEditing] = useState(false);
const value = useMemo(() => {
return {
title,
setTitle,
editing,
setEditing,
log: (t) => console.log(t)
}
}, [title, editing]);
return (
<ManagerContext.Provider value={value} >
<LessonMenu />
</ManagerContext.Provider/>
)
}
// used like:
export default function LessonMenu() {
const value = React.useContext(ManagerContext);
return (
<div>
<span>{value.title}</span>
<button onClick={() => value.log('ciao')}>
click
</button>
<button onClick={() => value.setTitle('new title')}>
click
</button>
</div>
)
}

Resources