Getting Cannot read properties of undefined (reading 'map')? - reactjs

Im having trouble with this little test component what am I missing here? This is just a simple little teaser question, seems like the mapping is where is breaking. I don't know if Im passing the value down incorrectly or what
import React from "react";
export default function App() {
const [state, setState] = React.useState({
input: "",
items: [],
error: false
});
const { input, items, error } = state;
const changeState = (state) => {
return setState(state);
};
if (input.length > 10) {
React.useEffect(() => {
changeState({ error: false });
});
}
const handleUpdateInput = (e) => {
if (e.key === "Enter") {
changeState({ items: [e.target.value, ...items] });
changeState({ input: "" });
}
};
const removeItem = (item) => {
changeState({ items: items.filter((i) => i !== item) });
};
return (
<div>
<input
onChange={(e) => {
changeState({ input: e.target.value });
}}
value={input}
onKeyPress={handleUpdateInput}
/>
{items.map((item, index) => {
return (
<div key={index}>
{item} <span onClick={removeItem(item)}>X</span>
</div>
);
})}
{error && <div>Input is too long!</div>}
</div>
);
}

When you set state using hooks, the value passed in will be the exact new state - it won't be merged with the old state if it happens to be an object, as with class components.
So, for example, you need to change
changeState({ error: false });
to
changeState({ ...state, error: false });
and do the same for everywhere else you're setting state.
But a better approach (that I'd recommend, and that React recommends) would be to use separate state variables from the beginning:
const [items, setItems] = useState([]);
Set it by changing
changeState({ items: [e.target.value, ...items] });
to
setItems([e.target.value, ...items]);
and follow that same pattern for input and error too.

Related

React TypeError is not a function with Onboarding implementation

I want to expand a demo provided by some tutorial about React Design Patterns, subject: Controlled Onboarding Flows, to implement multiple forms on several steps via Onboarding. But unfortunately the tutor did stop at the exciting part when it comes to having two-directional flows.
So I'm stuck and don't understand how to select the resp. function (marked with "// HOW TO DECIDE?!" in the 2nd code segment here).
So, every time I hit the prev. button, I receive the "Uncaught TypeError: goToPrevious is not a function" message, because both are defined.
Any suggestions on how to handle this?
This is what I got so far.
The idea behind this is to get the data from each form within the respo. Step Component and manage it witihin the parent component - which atm happens to be the App.js file.
Any help, tips, additional sources to learn this would be highly appreciated.
This is my template for the resp. controlled form components I want to use:
export const ControlledGenericForm = ({ formData, onChange }) => {
return (
<form>
{Object.keys(formData).map((formElementKey) => (
<input
key={formElementKey}
value={formData[formElementKey]}
type="text"
id={formElementKey}
onInput={(event) => onChange(event.target.id, event.target.value)}
/>
))}
</form>
);
};
That's my controlled Onboarding component, I want to use:
import React from "react";
export const ControlledOnboardingFlow = ({
children,
currentIndex,
onPrevious,
onNext,
onFinish,
}) => {
const goToNext = (stepData) => {
onNext(stepData);
};
const goToPrevious = (stepData) => {
onPrevious(stepData);
};
const goToFinish = (stepData) => {
onFinish(stepData);
};
const currentChild = React.Children.toArray(children)[currentIndex];
if (currentChild === undefined) goToFinish();
// HOW TO DECIDE?!
if (currentChild && onNext)
return React.cloneElement(currentChild, { goToNext });
if (currentChild && onPrevious)
return React.cloneElement(currentChild, { goToPrevious });
return currentChild;
};
And that's the actual use of this two components within my App:
import { useState } from "react";
import { ControlledOnboardingFlow } from "./ControlledComponents/ControlledOnboardingFlow";
import { ControlledGenericForm } from "./ControlledComponents/ControlledGenericForm";
function App() {
const [onboardingData, setOnboardingData] = useState({
name: "Juh",
age: 22,
hair: "green",
street: "Main Street",
streetNo: 42,
city: "NYC",
});
const [currentIndex, setCurrentIndex] = useState(0);
const formDataPartOne = (({ name, age, hair }) => ({ name, age, hair }))(
onboardingData
);
const formDataPartTwo = (({ street, streetNo, city }) => ({
street,
streetNo,
city,
}))(onboardingData);
const onNext = (stepData) => {
setOnboardingData({ ...onboardingData, ...stepData });
setCurrentIndex(currentIndex + 1);
};
const onPrevious = (stepData) => {
setOnboardingData({ ...onboardingData, ...stepData });
setCurrentIndex(currentIndex - 1);
};
const onFinish = () => {
console.log("Finished");
console.log(onboardingData);
};
const handleFormUpdate = (id, value) => {
setOnboardingData({ ...onboardingData, [id]: value });
};
const StepOne = ({ goToPrevious, goToNext }) => (
<>
<h1>Step 1</h1>
<ControlledGenericForm
formData={formDataPartOne}
onChange={handleFormUpdate}
/>
<button onClick={() => goToPrevious(onboardingData)} >
Prev
</button>
<button onClick={() => goToNext(onboardingData)}>Next</button>
</>
);
const StepTwo = ({ goToPrevious, goToNext }) => (
<>
<h1>Step 2</h1>
<ControlledGenericForm
formData={formDataPartTwo}
onChange={handleFormUpdate}
/>
<button onClick={() => goToPrevious(onboardingData)}>Prev</button>
<button onClick={() => goToNext(onboardingData)}>Next</button>
</>
);
const StepThree = ({ goToPrevious, goToNext }) => (
<>
<h1>Step 3</h1>
<h3>
Congrats {onboardingData.name} for being from, {onboardingData.city}
</h3>
<button onClick={() => goToNext(onboardingData)}>Next</button>
</>
);
return (
<ControlledOnboardingFlow
currentIndex={currentIndex}
onPrevious={onPrevious}
onNext={onNext}
onFinish={onFinish}
>
<StepOne />
<StepTwo />
{onboardingData.city === "NYC" && <StepThree />}
</ControlledOnboardingFlow>
);
}
export default App;
if (currentChild && onNext)
return React.cloneElement(currentChild, { goToNext });
Since onNext exists, this is the code that will run. It clones the element and gives it a goToNext prop, but it does not give it a goToPrevious prop. So when you press the previous button and run code like onClick={() => goToPrevious(onboardingData)}, the exception is thrown.
It looks like you want to pass both functions into the child, which can be done like:
const currentChild = React.Children.toArray(children)[currentIndex];
if (currentChild === undefined) goToFinish();
if (currentChild) {
return React.cloneElement(currentChild, { goToNext, goToPrevious });
}
return currentChild;
If one or both of them happens to be undefined, then the child will get undefined, but that's what you would do anyway with the if/else.

How to create delayed effect with setTimeout and useEffect?

Ref: https://stackblitz.com/edit/react-ts-8ykcuf?file=index.tsx
I created a small example to replicate the issue I am facing.
I am trying to create a delayed effect with setTimeout inside useEffect. I can see from console.log that setTimeout has already triggered and I expect the DOM to be updated, but actually the DOM is not rendered until the next human interaction.
The side effect in the sample example is to simulate a bot appending new message after user has entered a new message.
import React, { useEffect, useState } from 'react';
import { render } from 'react-dom';
interface Chat {
messages: string[];
poster: string;
}
const App = () => {
const [activeChat, setActiveChat] = useState<Chat>({
poster: 'Adam',
messages: ['one', 'two', 'three'],
});
const [message, setMessage] = useState('');
const [isBotChat, setIsBotChat] = useState(false);
useEffect(() => {
if (isBotChat) {
setIsBotChat(false);
setTimeout(() => {
activeChat.messages.push('dsadsadsada');
console.log('setTimeout completed');
}, 500);
}
}, [isBotChat]);
const handleSubmit = (e) => {
e.preventDefault();
if (message !== '') {
activeChat.messages.push(message);
setMessage('');
setIsBotChat(true);
}
};
return (
<div>
<h1>Active Chat</h1>
<div>
{activeChat?.messages.map((m, index) => (
<div key={index}>{m}</div>
))}
</div>
<form onSubmit={handleSubmit}>
<input
type="text"
value={message}
onChange={(e) => setMessage(e.currentTarget.value)}
/>
<button type="submit">Send Message</button>
</form>
</div>
);
};
render(<App />, document.getElementById('root'));
To set your state you need to use setActiveChat, in this case something like:
setActiveChat(previous => ({
...previous,
messages: [...previous.messages, 'dsadsadsada']
}))
The set state function React provides can accept a function, which we'll use in this case to avoid race conditions. previous is the previous value of activeChat (We can't rely on activeChat itself being up to date yet since the current render may be out of sync with the state) Then we expand the existing state and add the new property.
In your comments you mention only changing properties, and I'm afraid it's really not recommended to change anything in state directly, there are several in depth explanations of that here (StackOverflow question), and here (Documentation).
Full example (StackBlitz):
import React, { useEffect, useState } from 'react';
import { render } from 'react-dom';
import './style.css';
interface Chat {
messages: string[];
poster: string;
}
const App = () => {
const [activeChat, setActiveChat] = useState<Chat>({
poster: 'Adam',
messages: ['one', 'two', 'three'],
});
const [message, setMessage] = useState('');
const [isBotChat, setIsBotChat] = useState(false);
useEffect(() => {
if (isBotChat) {
setIsBotChat(false);
setTimeout(() => {
setActiveChat(previous => ({
...previous,
messages: [...previous.messages, 'dsadsadsada']
}))
console.log('setTimeout completed');
}, 500);
}
}, [isBotChat]);
const handleSubmit = (e) => {
e.preventDefault();
if (message !== '') {
setActiveChat(previous => ({
...previous,
messages: [...previous.messages, message]
}))
setMessage('');
setTimeout(() => {
setActiveChat(previous => ({
...previous,
messages: [...previous.messages, 'dsadsadsada']
}))
console.log('setTimeout completed');
}, 500);
}
};
return (
<div>
<h1>Active Chat</h1>
<div>
{activeChat?.messages.map((m, index) => (
<div key={index}>{m}</div>
))}
</div>
<form onSubmit={handleSubmit}>
<input
type="text"
value={message}
onChange={(e) => setMessage(e.currentTarget.value)}
/>
<button type="submit">Send Message</button>
</form>
</div>
);
};
render(<App />, document.getElementById('root'));

React Functional Component - Binding handler to an array

I am trying to bind a handler to an array position that prints the state of the object. The function is being called but the state is always in the initial state.
import React from "react";
export default function MyForm() {
const [state, setState] = React.useState({
buttons: [],
object: null
});
const stateRef = React.useRef(null);
const onExecButtonHandler = (index) => {
console.log("[MyForm][execButton]: " + index);
alert(JSON.stringify(state.object));
alert(JSON.stringify(stateRef.current.object));
};
const onChangeHandler = (event)=>{
let obj = state.object
obj['description'] = event.target.value
console.log(obj)
setState((state) => ({ ...state, object: obj }));
stateRef.current = state;
}
const generateButtons = () => {
let buttons = [];
//buttons.push([name,(event)=>onExecButtonHandler(event,i),obj[i].icon,obj[i].jsFunction])
buttons.push([
"Show Hi",
onExecButtonHandler.bind(this, 1),
"icon",
"custom function"
]);
return buttons;
};
React.useEffect(() => {
console.log("[MyForm][useEffect]");
let buttons = generateButtons();
setState({ buttons: buttons, object: { description: "hi" } });
stateRef.current = state;
}, []);
return (
<>
{state.object && (
<form>
<input text="text" onChange={onChangeHandler} value={state.object.description} />
<input
type="button"
value="Click me"
onClick={() => {
state.buttons[0][1]();
}}
/>
</form>
)}
</>
);
}
You can test here: https://codesandbox.io/s/charming-robinson-g1yuo?fontsize=14&hidenavigation=1&theme=dark
I was able to access the latest state of the object using the "useRef" hook. I'd like to access using the state though. Is there some way to do so?

React: how to replicate a specific component class setstate callback example with functional usestate/useEffect?

I am trying to convert a simple React component with a this.setstate callback to a functional component with useState/useEffect but i cant reproduce same functionality with latter.
I am using an example of a simple alert/notice system to append and remove alerts after a timeout. Sandbox here:
https://codesandbox.io/s/class-and-function-callback-comparison-54tus?file=/src/index.js
relevant code for comparison:
const NoticesWithFunctionCallback = () => {
const [state, setState] = useState({
alerts: [],
show: false
});
const addNotice = (e) => {
setState({
alerts: [...state.alerts, ""]
});
};
useEffect(() => {
(async () => {
await timeout(3000);
console.log("timeout occurred");
const newAlerts = tail([...state.alerts]);
setState({
alerts: newAlerts
});
})();
}, [state.alerts]);
return (
<div className="App">
<h3>
Notices using function component with UseState and UseEffect callback
</h3>
<Generator addNotice={addNotice} />
<Container>
{state.alerts.map((item, index) => (
<Alert>some alert here</Alert>
))}
</Container>
</div>
);
};
class NoticesWithClassCallback extends React.Component {
state = {
alerts: [],
show: false
};
addNotice = (e) => {
this.setState(
{
alerts: [...this.state.alerts, ""]
},
async () => {
await timeout(3000);
console.log("timeout occurred");
const newAlerts = tail([...this.state.alerts]);
this.setState({
alerts: newAlerts
});
}
);
};
render() {
return (
<div className="App">
<h3>Notices using class component and setState callback</h3>
<Generator addNotice={this.addNotice} />
<Container>
{this.state.alerts.map((item, index) => (
<Alert>some alert here</Alert>
))}
</Container>
</div>
);
}
}
I am hoping for suggestions on how to replace the correctly functioning class component setstate callback component with the function component using usestate/useeffect.
Any suggestions most welcome.
There is no need in useEffect here. useEffect triggers every time alerts array is changed: by adding items to it as well as removing. It is also triggered right after initialization, so things get messed up. Instead you should modify addNotice function and update state using the previous one like so:
const NoticesWithFunctionCallback = () => {
const [state, setState] = useState({
alerts: [],
show: false
});
const addNotice = (e) => {
setState({
...state,
alerts: [...state.alerts, '']
});
(async () => {
await timeout(3000);
setState(prevState => {
return {
...prevState,
alerts: [...tail([...prevState.alerts])]
}
});
})()
};
return (
<div className="App">
<h3>
Notices using function component with UseState and UseEffect callback
</h3>
<p>I want this to replicate the class component but its broken...</p>
<Generator addNotice={addNotice} />
<Container>
{state.alerts.map((item, index) => (
<Alert key={index}>Alert</Alert>
))}
</Container>
</div>
);
};
There are some steps to go from a class to a functional component :
Step 1 :
class NameOfComponent extends Component
becomes
function NameOfComponent(props){
Step 2 : remove the constructor
Step 3 : remove the render() method, keep the return
Step 4. add const before all methods
Step 5 : remove this.state throughout the component
Step 6. remove all references to ‘this’ throughout the component
Step 7 : Set initial state with useState() (and import it from "react")
Example for a number :
const [count, setCount] = useState(0) // the useState() param is the initial value
Example for an object :
const [form, setValues] = useState({
id: 0,
first: ‘’,
last: ‘’,
password: ‘’,
subscribe: false
})
Step 8 : - change this.setState() by the the setter you defined ( for example setValues, or setCount in Step 7 )
this.setState({ count: this.state.count + 1 )} would become setCount(count+1)
Step 9 : replace componentDidMount with useEffect
useEffect(() => {
fetch(‘url’)
.then(res => res.json())
.then(items => setSomething(items)
.catch(console.log(err))
}, [])
Step 10 : replace componentDidUpdate, or componentWillReceiveProps with useEffect
useEffect(() => {
console.log(myPropsToCheck+ " has changed ! ")
}, [myPropsToCheck])
})

How to get select option value React

I need a little help with my project. I think it is almost done, but I don't know how to finish...
So, I want to build app with input, select and button. Into input u can write for example, mettalica and after click on button app renders list with all songs, titles and tabTypes(guitar tabs). The problem is that i want to get info from select and render only that songs which includes for example player tabs.
Sandbox Code: https://codesandbox.io/s/react-example-ys6py?fontsize=14&hidenavigation=1&theme=dark
class Search extends React.Component {
state = {
searchValue: "",
songs: [],
musicTabs: [
'Dowolne',
'Bass',
'Player',
'Chords',
'Guitar'
],
result: ''
};
handleOnChange = event => {
this.setState({ searchValue: event.target.value });
};
handleSelectChange = (event) => {
this.setState({
result: event.target.value
})
console.log(this.state.result)
}
handleSearch = () => {
this.makeApiCall(this.state.searchValue);
};
makeApiCall = async searchInput => {
let api_url = `https://www.songsterr.com/a/ra/songs/byartists.json?artists=${searchInput}`;
const response = await fetch(api_url);
const songs = await response.json();
this.setState({ songs });
};
render() {
return (
<div>
<Header />
<input
name="text"
type="search"
placeholder="Search..."
onChange={event => this.handleOnChange(event)}
value={this.state.SearchValue}
/>
<Select optionValue={ this.state.musicTabs } change={ this.handleSelectChange } value={ this.state.result } />
<br />
<button onClick={this.handleSearch}>Search</button>
{this.state.songs ? (
<div>
{
this.state.songs.map((song, index) => (
<div key={index} className="lists">
<h1>Artist: <span>{song.artist.name}</span></h1>
<h2>Song title: <span>{song.title}</span></h2>
<ol>
<b>Available tabs:</b>
{song.tabTypes.map((tab, index) =>
<li key={index}> {song.tabTypes[index]} </li>
)}
</ol>
</div>
))
}
</div>
) : (
<p>Something</p>
)}
</div>
);
}
}
const Select = (props) => {
const { optionValue, change } = props;
const valueMusicTabs = optionValue.map((musicTab, index) => {
return <option name={ optionValue[index] } key={ index }> { optionValue[index] } </option>
})
return (
<>
<select onChange={ change }>
{ valueMusicTabs }
</select>
</>
)
};
Thanks for help guys!
I think you did everything right, just used the wrong prop
<Select optionValue={ this.state.musicTabs } onChange={ this.handleSelectChange } value={ this.state.result } />
the change prop on the Select component should just be changed to onChange since it's a default event it will be passed with the event to your handleChange method
I checked the codesandbox, everything was working right. this.setState is an asynchronous function. So, if you will console.log after this.setState chances are your will not log updated value. You can do it like this with a callback function.
handleSelectChange = (event) => {
this.setState({
result: event.target.value
}, () => console.log(this.state.result))
}
If you want to filter you can do that by making a function like:
filterSongs = selected => {
return songs.filter(song => song.tabTypes === selected);
}
and After that modify your handleSelectChange as:
handleSelectChange = (event) => {
let songs = filterSongs(event.target.value);
this.setState({
result: event.target.value,
toDisplay: songs
}, () => console.log(this.state.result))
}
and finally in your JSX:
return (
<>
{toDisplay.map((song, index) => {
return <p key={index}>{song.toString()}</p>
})}
</>
);
If I understand correctly. you want to get the results from API based on selected values of input and select.
as I can see you are only passing the param from input nothing from select.
handleSearch = () => {
this.makeApiCall(this.state.searchValue);
};
makeApiCall = async searchInput => {
let api_url = `https://www.songsterr.com/a/ra/songs/byartists.json?artists=${searchInput}`;
const response = await fetch(api_url);
const songs = await response.json();
this.setState({ songs });
};
The new call will be
let api_url = `https://www.songsterr.com/a/ra/songs/byartists.json?artists=${searchInput}&tabTypes=${selectValue}`;
I do not know how what are the parameters this API accepts.
Thanks for answers! I have last question, where I have to use method to filter that rendered list. If I select "Player" I want to render only that songs, which inlcudes "Player" tab in tabTypes. I still can't get it. I can't do it by changing API link.

Resources