i'm sorry for the disturbance,
i'm a trying React Native for the first time ( I'm a Full Stack Engineer React NodeJS ),
i tried by differents tips to put AsyncStorage.getItem inside my state, then display in the map,
but everytime, "Error map undefined", but if i put the value inside my State Array, it's working,
i tried with JSON Stringify, JSON Parse... Like in WEB,
but not working...
Here is my code :
import { useEffect, useState } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import RadioForm from 'react-native-simple-radio-button';
import AsyncStorage from '#react-native-async-storage/async-storage';
const SelectOption = () => {
const [value, setValue] = useState([]);
const saveOption = (item) => {
try {
setValue([...value, {name: item, id: Math.random()}]);
} catch (e) {
console.log(e);
}
};
useEffect(() => {
AsyncStorage.setItem('option', JSON.stringify(value));
}, [value]);
// Put GetItem in the state
useEffect(() => {
const getOption = async () => {
try {
const jsonValue = await AsyncStorage.getItem('option');
if (jsonValue !== null) {
setValue(JSON.parse(jsonValue));
}
} catch (e) {
console.log(e);
}
};
getOption();
}, []);
AsyncStorage.getItem('option').then((value) => {
console.log(value);
});
const radioProps = [
{label: 'Option 1', value: 'option1'},
{label: 'Option 2', value: 'option2'},
{label: 'Option 3', value: 'option3'}
];
return (
<View style={styles.sectionContainer}>
<RadioForm
radio_props={radioProps}
initial={0}
onPress={(value) => {
saveOption(value);
}}
/>
{value.map((item) => {
return <Text key={item.id}>{item.name}</Text>;
})
}
</View>
);
};
const styles = StyleSheet.create({
sectionContainer: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
export default SelectOption;
Thanks :pray:
i tried with JSON Stringify, JSON Parse... Like in WEB,
You wants to set value in local storage so, don't need to set the value in useEffect hook. Just add in your saveOption function like
const saveOption = (item) => {
try {
let newValue = [...value, {name: item, id: Math.random()}]
AsyncStorage.setItem('option', newValue);
setValue(newValue);
} catch (e) {
console.log(e);
}
};
And make a getOption async function like
const getOption = async () => {
try {
const jsonValue = await AsyncStorage.getItem('option');
if (jsonValue) {
await setValue(jsonValue);
}
} catch (e) {
console.log(e);
}
};
Then render in JSX like
{value.map((item) => {
return <Text key={item.id}>{item.name}</Text>;
}
Related
I am trying to use React Scheduler with my shifts database. The current state after trying to use hooks instead of class is that I cannot edit any field in the form. I have deleted some of the code to make it cleaner, for now I am trying only to add a shift.
React Scheduler original code:
import * as React from 'react';
import Paper from '#material-ui/core/Paper';
import { ViewState, EditingState } from '#devexpress/dx-react-scheduler';
import {
Scheduler,
Appointments,
AppointmentForm,
AppointmentTooltip,
WeekView,
} from '#devexpress/dx-react-scheduler-material-ui';
import { appointments } from '../../../demo-data/appointments';
export default class Demo extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
data: appointments,
currentDate: '2018-06-27',
addedAppointment: {},
appointmentChanges: {},
editingAppointment: undefined,
};
this.commitChanges = this.commitChanges.bind(this);
this.changeAddedAppointment = this.changeAddedAppointment.bind(this);
this.changeAppointmentChanges = this.changeAppointmentChanges.bind(this);
this.changeEditingAppointment = this.changeEditingAppointment.bind(this);
}
changeAddedAppointment(addedAppointment) {
this.setState({ addedAppointment });
}
changeAppointmentChanges(appointmentChanges) {
this.setState({ appointmentChanges });
}
changeEditingAppointment(editingAppointment) {
this.setState({ editingAppointment });
}
commitChanges({ added, changed, deleted }) {
this.setState((state) => {
let { data } = state;
if (added) {
const startingAddedId = data.length > 0 ? data[data.length - 1].id + 1 : 0;
data = [...data, { id: startingAddedId, ...added }];
}
return { data };
});
}
render() {
const {
currentDate, data, addedAppointment, appointmentChanges, editingAppointment,
} = this.state;
return (
<Paper>
<Scheduler
data={data}
height={660}
>
<ViewState
currentDate={currentDate}
/>
<EditingState
onCommitChanges={this.commitChanges}
addedAppointment={addedAppointment}
onAddedAppointmentChange={this.changeAddedAppointment}
appointmentChanges={appointmentChanges}
onAppointmentChangesChange={this.changeAppointmentChanges}
editingAppointment={editingAppointment}
onEditingAppointmentChange={this.changeEditingAppointment}
/>
<WeekView
startDayHour={9}
endDayHour={17}
/>
<Appointments />
<AppointmentTooltip
showOpenButton
showDeleteButton
/>
<AppointmentForm />
</Scheduler>
</Paper>
);
}
}
My function component code:
import React, { useState } from 'react';
import Paper from '#material-ui/core/Paper';
import { ViewState, EditingState } from '#devexpress/dx-react-scheduler';
import {
Scheduler,
Appointments,
AppointmentForm,
AppointmentTooltip,
WeekView,
ConfirmationDialog,
} from '#devexpress/dx-react-scheduler-material-ui';
const DataSheet = ( { addShift, shifts, deleteShift } ) => {
const [data, setData] = useState(shifts)
const [currentDate, setCurrentDate] = useState('2018-06-27')
const [addedAppointment, setAddedAppointment] = useState({})
const [appointmentChanges, setAppointmentChanges] = useState({})
const [editingAppointment, setEditingAppointment] = useState(undefined)
const changeAddedAppointment = (addedAppointment) => {
setAddedAppointment({ addedAppointment });
}
const changeAppointmentChanges = (appointmentChanges) => {
setAppointmentChanges({ appointmentChanges });
}
const changeEditingAppointment = (editingAppointment) => {
setEditingAppointment({ editingAppointment });
}
const commitChanges = ({ added, changed, deleted }) => {
setData ((????) => {
let { data } = data;
console.log(data); //returns undefined
if (added) {
const startingAddedId = data > 0 ? data[data.length - 1].id + 1 : 0;
data = [...data, { id: startingAddedId, ...added }];
addShift(added);
}
return { data };
});
}
return (
<Paper>
<Scheduler
data={data}
height={660}
>
<ViewState
currentDate={currentDate}
/>
<EditingState
onCommitChanges={commitChanges}
addedAppointment={addedAppointment}
onAddedAppointmentChange={changeAddedAppointment}
appointmentChanges={appointmentChanges}
onAppointmentChangesChange={changeAppointmentChanges}
editingAppointment={editingAppointment}
onEditingAppointmentChange={changeEditingAppointment}
/>
<WeekView
startDayHour={9}
endDayHour={17}
/>
<Appointments />
<AppointmentTooltip
showOpenButton
showDeleteButton
/>
<AppointmentForm />
</Scheduler>
</Paper>
);
}
export default DataSheet
App.js:
import React from 'react';
import backgroundImage from './Resources/BennyBackground.jpeg'
import Header from "./components/Header";
import { useState, useEffect } from "react"
import DataSheet from './components/DataSheet';
const containerStyle= {
width: '100vw',
height: '100vh',
backgroundImage: `url(${backgroundImage})`,
backgroundPosition: 'center',
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
}
const App = () => {
const [shifts, setShifts] = useState([])
useEffect(() => {
const getShifts = async () => {
const shiftsFromServer = await fetchShifts()
setShifts(shiftsFromServer)
}
getShifts()
}, [])
const fetchShifts = async () => {
const res = await fetch(`http://localhost:5000/shifts/`)
const data = await res.json()
return data
}
const addShift = async (shift) => {
const startingAddedId = shifts.length > 0 ? shifts[shifts.length - 1].id + 1 : 0;
shift.id = startingAddedId;
const res = await fetch(`http://localhost:5000/shifts/`,{
method: 'POST',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify(shift)
})
const data = await res.json()
setShifts([...shifts, data])
}
return (
<div className="container"
style={containerStyle} >
<div className='secondary_container'>
<Header />
<DataSheet shifts={shifts} addShift={addShift}/>
</div>
</div>
);
}
export default App;
I know it is a lot of code and a lot to ask and I would highly appreciate help with this.
I believe the issue is that you are using setXxx as you would use this.setState. In class components, you have one function that modifies all the state (this.setState), while in function components you have a setter function for each field.
So change this:
const changeAddedAppointment = (addedAppointment) => {
setAddedAppointment({ addedAppointment });
}
to this:
const changeAddedAppointment = (addedAppointment) => {
setAddedAppointment(addedAppointment);
}
As far as the commitChanges function goes, you can do the data manipulation before using setData. Also I'm not sure that this let { data } = data would work since there is already a data field. You can try this:
const commitChanges = ({ added, changed, deleted }) => {
let newData = [...data.data];
if (added) {
const startingAddedId = newData > 0 ? newData [data.length - 1].id + 1 : 0;
newData = [...newData , { id: startingAddedId, ...added }];
addShift(added);
}
setData(newData);
};
I managed to build a simple music player using react-native-track-player following a tutorial as part of my learning curve in react native. Now, instead of just playing/streaming songs from provided url tracks in my array of songs, I want to stream the songs from API (though it can beb any API - but I have registered for Napster API) which I feel is not limited by just few songs. But, I can't put together how to implement or call the Napster API to fetch songs.
Please any help/guide I will appreciate so much.
Below is my code:
I have data.js, my array of songs:
const songs = [
{
title: "death bed",
artist: "Powfu",
artwork: require("../assets/album-arts/death-bed.jpg"),
url: "https://github.com/ShivamJoker/sample-songs/raw/master/death%20bed.mp3",
id: "1",
},
{
title: "bad liar",
artist: "Imagine Dragons",
artwork: require("../assets/album-arts/bad-liar.jpg"),
url: "https://github.com/ShivamJoker/sample-songs/raw/master/Bad%20Liar.mp3",
id: "2",
},
{
title: "faded",
artist: "Alan Walker",
artwork: require("../assets/album-arts/faded.jpg"),
url: "https://github.com/ShivamJoker/sample-songs/raw/master/Faded.mp3",
id: "3",
},
];
export default songs;
And here's my playerScreen.js :
import React, {useRef, useEffect, useState} from 'react';
import {
View,
SafeAreaView,
Text,
Image,
FlatList,
Dimensions,
Animated,
StyleSheet,
} from 'react-native';
import TrackPlayer, {
Capability,
useTrackPlayerEvents,
usePlaybackState,
TrackPlayerEvents,
STATE_PLAYING,
Event,
} from 'react-native-track-player';
import songs from './data';
import Controller from './Controller';
import SliderComp from './SliderComp';
const {width, height} = Dimensions.get('window');
// const events = [
// TrackPlayerEvents.PLAYBACK_STATE,
// TrackPlayerEvents.PLAYBACK_ERROR
// ];
export default function PlayerScreen() {
const scrollX = useRef(new Animated.Value(0)).current;
const slider = useRef(null);
const isPlayerReady = useRef(false);
const index = useRef(0);
const [songIndex, setSongIndex] = useState(0);
const isItFromUser = useRef(true);
// for tranlating the album art
const position = useRef(Animated.divide(scrollX, width)).current;
const playbackState = usePlaybackState();
useEffect(() => {
// position.addListener(({ value }) => {
// console.log(value);
// });
scrollX.addListener(({value}) => {
const val = Math.round(value / width);
setSongIndex(val);
});
TrackPlayer.setupPlayer().then(async () => {
// The player is ready to be used
console.log('Player ready');
// add the array of songs in the playlist
await TrackPlayer.reset();
await TrackPlayer.add(songs);
TrackPlayer.play();
isPlayerReady.current = true;
await TrackPlayer.updateOptions({
stopWithApp: false,
alwaysPauseOnInterruption: true,
capabilities: [
Capability.Play,
Capability.Pause,
Capability.SkipToNext,
Capability.SkipToPrevious,
],
});
//add listener on track change
TrackPlayer.addEventListener(Event.PlaybackTrackChanged, async (e) => {
console.log('song ended', e);
const trackId = (await TrackPlayer.getCurrentTrack()) - 1; //get the current id
console.log('track id', trackId, 'index', index.current);
if (trackId !== index.current) {
setSongIndex(trackId);
isItFromUser.current = false;
if (trackId > index.current) {
goNext();
} else {
goPrv();
}
setTimeout(() => {
isItFromUser.current = true;
}, 200);
}
// isPlayerReady.current = true;
});
//monitor intterupt when other apps start playing music
TrackPlayer.addEventListener(Event.RemoteDuck, (e) => {
// console.log(e);
if (e.paused) {
// if pause true we need to pause the music
TrackPlayer.pause();
} else {
TrackPlayer.play();
}
});
});
return () => {
scrollX.removeAllListeners();
TrackPlayer.destroy();
// exitPlayer();
};
}, []);
// change the song when index changes
useEffect(() => {
if (isPlayerReady.current && isItFromUser.current) {
TrackPlayer.skip(songs[songIndex].id)
.then((_) => {
console.log('changed track');
})
.catch((e) => console.log('error in changing track ', e));
}
index.current = songIndex;
}, [songIndex]);
const exitPlayer = async () => {
try {
await TrackPlayer.stop();
} catch (error) {
console.error('exitPlayer', error);
}
};
const goNext = async () => {
slider.current.scrollToOffset({
offset: (index.current + 1) * width,
});
await TrackPlayer.play();
};
const goPrv = async () => {
slider.current.scrollToOffset({
offset: (index.current - 1) * width,
});
await TrackPlayer.play();
};
const renderItem = ({index, item}) => {
return (
<Animated.View
style={{
alignItems: 'center',
width: width,
transform: [
{
translateX: Animated.multiply(
Animated.add(position, -index),
-100,
),
},
],
}}>
<Animated.Image
source={item.artwork}
style={{width: 320, height: 320, borderRadius: 5}}
/>
</Animated.View>
);
};
return (
<SafeAreaView style={styles.container}>
<SafeAreaView style={{height: 320}}>
<Animated.FlatList
ref={slider}
horizontal
pagingEnabled
showsHorizontalScrollIndicator={false}
scrollEventThrottle={16}
data={songs}
renderItem={renderItem}
keyExtractor={(item) => item.id}
onScroll={Animated.event(
[{nativeEvent: {contentOffset: {x: scrollX}}}],
{useNativeDriver: true},
)}
/>
</SafeAreaView>
<View>
<Text style={styles.title}>{songs[songIndex].title}</Text>
<Text style={styles.artist}>{songs[songIndex].artist}</Text>
</View>
<SliderComp />
<Controller onNext={goNext} onPrv={goPrv} />
</SafeAreaView>
);
}
I would pass the songs into your current PlayerScreen through props. You can use a separate component to load tracks from the Napster API and then render a PlayerScreen with those props.
The only part I'm not sure about is what path to pass to the player as the url. The Napster data contains a property previewURL which is an mp3 but it's not the whole song. I believe that the href is the streamable URL. It requires authentication to load the full track though.
The API path that I'm using here is for the most popular tracks.
export default function TopTracks() {
const [songs, setSongs] = useState([]);
useEffect(() => {
const loadData = async () => {
try {
const url = `http://api.napster.com/v2.2/tracks/top?apikey=${API_KEY}&limit=5`;
const res = await axios.get(url);
setSongs(
res.data.tracks.map((track) => ({
duration: track.playbackSeconds,
title: track.name,
artist: track.artistName,
album: track.albumName,
id: track.id,
url: track.href // or track.previewURL?
}))
);
} catch (error) {
console.error(error);
}
};
loadData();
}, []);
return <PlayerScreen songs={songs} />;
}
I wrote a hook that calls apollo useQuery. It's pretty simple:
useDecider:
import { useState } from 'react';
import { useQuery, gql } from '#apollo/client';
export const GET_DECIDER = gql`
query GetDecider($name: [String]!) {
deciders(names: $name) {
decision
name
value
}
}
`;
export const useDecider = name => {
const [enabled, setEnabled] = useState(false);
useQuery(GET_DECIDER, {
variables: {
name
},
onCompleted: data => {
const decision = data?.deciders[0]?.decision;
setEnabled(decision);
},
onError: error => {
return error;
}
});
return {
enabled
};
};
I'm trying to test it now and the MockedProvider is not returning the expected data:
import React from 'react';
import { render, screen } from '#testing-library/react';
import '#testing-library/jest-dom';
import { MockedProvider } from '#apollo/client/testing';
import { useDecider, GET_DECIDER } from './useDecider';
const getMock = (value = false, decider = '') => [
{
request: {
query: GET_DECIDER,
variables: {
name: decider
}
},
result: () => {
console.log('APOLLO RESULT');
return {
data: {
deciders: [
{
decision: value,
name: decider,
value: 10
}
]
}
};
}
}
];
const FakeComponent = ({ decider }) => {
const { enabled } = useDecider(decider);
return <div>{enabled ? 'isEnabled' : 'isDisabled'}</div>;
};
const WrappedComponent = ({ decider, value }) => (
<MockedProvider mocks={getMock(value, decider)} addTypename={false}>
<FakeComponent decider={decider} />
</MockedProvider>
);
describe('useDecider', () => {
it('when decider returns true', () => {
// should return true
render(<WrappedComponent decider="fake_decider" value={true} />);
screen.debug();
const result = screen.getByText('isEnabled');
expect(result).toBeInTheDocument();
});
});
I simplified your hook implementation and put together a working example:
import { useQuery, gql } from "#apollo/client";
export const GET_DECIDER = gql`
query GetDecider($name: [String]!) {
deciders(names: $name) {
decision
name
value
}
}
`;
export const useDecider = (name) => {
const { data } = useQuery(GET_DECIDER, { variables: { name } });
return { enabled: data?.deciders[0]?.decision || false };
};
Note that in the test I also updated your getBy to an await findBy:
describe("useDecider", () => {
it("when decider returns true", async () => {
// should return true
render(<WrappedComponent decider="fake_decider" value={true} />);
screen.debug();
const result = await screen.findByText("isEnabled");
expect(result).toBeInTheDocument();
});
});
This is because you need to wait for your API call to complete before the data will be on the page, hence you would not expect the data to be there on the first render.
From https://www.apollographql.com/docs/react/development-testing/testing/#testing-the-success-state
To test how your component is rendered after its query completes, you
can await a zero-millisecond timeout before performing your checks.
This delays the checks until the next "tick" of the event loop, which
gives MockedProvider an opportunity to populate the mocked result
try adding before your expect call
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 0));
});
I'm trying to build a treeview component in react where data for the tree is fetched based on the nodes expanded by the user.
Problem
I want to replace the code inside handleChange with data from my server, so that I append the data i fetch to the tree state. How can I achieve this with react?
The data i get can look like this:
{
"children": [
{
"id": "2212",
"parentId": "3321",
"name": "R&D",
"address": "homestreet"
},
{
"id": "4212",
"parentId": "3321",
"name": "Testing",
"address": "homestreet"
}
]
}
My Code
import React, { useState } from "react";
import { makeStyles } from "#material-ui/core/styles";
import TreeView from "#material-ui/lab/TreeView";
import ExpandMoreIcon from "#material-ui/icons/ExpandMore";
import ChevronRightIcon from "#material-ui/icons/ChevronRight";
import TreeItem from "#material-ui/lab/TreeItem";
const useStyles = makeStyles({
root: {
height: 216,
flexGrow: 1,
maxWidth: 400
}
});
export default function FileSystemNavigator() {
const classes = useStyles();
const initialData = {
root: [
{
id: "1",
label: "Applications"
}
],
};
const [tree, setTree] = useState(initialData);
const handleChange = (event, nodeId) => {
setTimeout(() => {
const newTree = {
...tree,
[nodeId]: [
{
id: "2",
label: "Calendar"
},
{
id: "3",
label: "Settings"
},
{
id: "4",
label: "Music"
}
]
};
setTree(newTree);
}, 1000); // simulate xhr
};
const renderTree = children => {
return children.map(child => {
const childrenNodes =
tree[child.id] && tree[child.id].length > 0
? renderTree(tree[child.id])
: [<div />];
return (
<TreeItem key={child.id} nodeId={child.id} label={child.label}>
{childrenNodes}
</TreeItem>
);
});
};
return (
<TreeView
className={classes.root}
defaultCollapseIcon={<ExpandMoreIcon />}
defaultExpandIcon={<ChevronRightIcon />}
onNodeToggle={handleChange}
>
{renderTree(tree.root)}
</TreeView>
);
}
If I am understanding correctly, you want to replace your "fake" setTimeout implementation of an API call with a real call using fetch.
In this case, it's as simple as calling fetch inside of the handleChange handler and updating your state with new items that you get back as a result.
function FileSystemNavigator() {
const initialData = {...}
const [tree, setTree] = React.useState(initialData)
const handleChange = (event, nodeId) => {
const handleResult = (data) => {
const items = data.children.map(item => {
return { id: item.id, label: item.name }
})
setTree({
root: [...tree.root, ...items]
})
}
const handleError = (error) => {
// handle errors appropriately
console.error(error.message)
}
fetch("https://api.myjson.com/bins/1aqhsc")
.then(res => res.json())
.then(handleResult)
.catch(handleError)
}
// ...
return (...)
}
This should do the trick.
Note that I've used your sample API endpoint that you've provided in the comments, so you will have to change the handleResult callback inside of the handleChange handler to make sure you're parsing out your new data appropriately.
If you'd like to see a quick example, I created a CodeSandbox with a button that can be clicked to fetch more data and display it in a list:
Demo
Let me know if you have any questions.
I am making a App in Reactjs. In it I have an API that gets data from a AWS database table and populates a state array which then populates a tables.
I cant figure out how to let a user in turn update the values of the table and then save those changes and let the API upload it back into the data base.
I have a update method but it gives an error saying that I cant update the state Array or would complain that the column array is undefined.
I have the following code:
export default function Complaints(props) {
const [complaint, setComplaint] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [isInEditMode, setIsEditMode] = useState(false);
const [defaultValue, setDefaultValue] = useState("");
var columsArr = [
{ title: 'Customer ID', field: 'id' },
{ title: 'Issue', field: 'complaintName' },
{ title: 'Description', field: 'complaintDescription'},
{ title: 'Order ID', field: 'complaintOrderId'},
{ title: 'Submitted', field: 'createdAt'},
{ title: 'Updated', field: 'updatedAt'},
{ title: 'Admin Comment', field: 'adminComment'},
];
useEffect(() => {
async function onLoad() {
if (!props.isAuthenticated) {
return;
}
try {
const complaint = await loadComplaint();
setComplaint(complaint);
setState({
columns: [state.columns, ...columsArr],
complaint: [...state.complaint, ...complaint]
});
console.log(complaint)
} catch (e) {
alert(e);
}
setIsLoading(false);
}
onLoad();
}, [props.isAuthenticated]);
function loadComplaint() {
return API.get("kleen", "/Complaint");
}
// function edit(adminComment) {
// setIsEditMode(true);
// setDefaultValue(adminComment);
// console.log("value is"+ adminComment);
// }
// function updateComplaint() {
// return API.put("kleen", `/Complaint/${props.}`);
// }
const [state, setState] = React.useState({
columns: [],
complaint: []
});
return (
<MaterialTable style={{
marginTop: "8rem",
marginLeft: "auto",
marginRight: "auto",
position: "sticky",
}}
title="Complaints"
columns={state.columns}
data={state.complaint}
editable={{
onRowUpdate: (newData, oldData) =>
new Promise(resolve => {
setTimeout(() => {
resolve();
if (oldData) {
let key = 1;
setState(prevState => {
complaint: prevState.complaint.map(el => el.key === key ? {...el, adminComment: "testing"}: el)
// const data = [...prevState.data];
// data[data.indexOf(oldData)] = newData;
// return { ...prevState, data };
});
}
}, 600);
}),
}}
/>
);
}
I am very new to Reactjs, any help would be greatly appreciated.
One way that you can achieve is by:
First store the state in a variable
Update the array variable
Restore the variable to the state.
If you want to use state then better to use component class
export default class Complaints extends Component {
//assign initial value to state
state={
propsObj : this.props
}
...
}
If you want to use component you have to import it
import React, { Component } from "react";
inside component class you have to use this.props instead of props
later if you want to change state just use this.setState({ propsObj: 'some thing new'});