react async select add new options in select not getting updated - javascript

how to add option to react-select/async component explicitly on click of add option , I'm not able to update options , but state is getting updated !!
is there any other way to achive this ?
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import AsyncSelect from "react-select/async";
const MyComponent = () => {
const optionsData = [
{ value: "Spring", label: "Spring" },
{ value: "Summer", label: "Summer" },
{ value: "Autumn", label: "Autumn" },
{ value: "Winter", label: "Winter" }
];
const [options, setOptions] = useState(optionsData);
const loadOptions = (inputValue) => {
console.log(inputValue, "pppp");
return new Promise((resolve, reject) => {
// using setTimeout to emulate a call to server
setTimeout(() => {
resolve(filter(inputValue));
}, 2000);
});
};
const filter = (inputValue) =>
options.filter((option) =>
option.label.toLowerCase().includes(inputValue.toLowerCase())
);
const handleAddOption = () => {
const newOptions = [
{ value: "Apple", label: "Apple" },
{ value: "Ball", label: "Ball" }
];
setOptions((prevState) => [...prevState, ...newOptions]);
};
return (
<div style={{ display: "flex", justifyContent: "space-around" }}>
<div style={{ width: "400px" }}>
<AsyncSelect defaultOptions cacheOptions loadOptions={loadOptions} />
</div>
<button onClick={handleAddOption}>ADD OPTIONS</button>
</div>
);
};
ReactDOM.render(<MyComponent />, document.getElementById("app"));
CODESANDBOX LINK HERE

You should use options state as the value of defaultOptions prop of AsyncSelect component. From the docs about defaultOptions:
Providing an option array to this prop will populate the initial set of options used when opening the select, at which point the remote load only occurs when filtering the options (typing in the control)
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import AsyncSelect from "react-select/async";
const MyComponent = () => {
const optionsData = [
{ value: "Spring", label: "Spring" },
{ value: "Summer", label: "Summer" },
{ value: "Autumn", label: "Autumn" },
{ value: "Winter", label: "Winter" }
];
const [options, setOptions] = useState(optionsData);
const loadOptions = (inputValue) => {
return new Promise((resolve, reject) => {
// using setTimeout to emulate a call to server
setTimeout(() => {
resolve(filter(inputValue));
}, 2000);
});
};
const filter = (inputValue) =>
options.filter((option) =>
option.label.toLowerCase().includes(inputValue.toLowerCase())
);
const handleAddOption = () => {
const newOptions = [
{ value: "Apple", label: "Apple" },
{ value: "Ball", label: "Ball" }
];
setOptions((prevState) => [...prevState, ...newOptions]);
};
return (
<div style={{ display: "flex", justifyContent: "space-around" }}>
<div style={{ width: "400px" }}>
<AsyncSelect
defaultOptions={options}
cacheOptions
loadOptions={loadOptions}
/>
</div>
<button onClick={handleAddOption}>ADD OPTIONS</button>
</div>
);
};
ReactDOM.render(<MyComponent />, document.getElementById("app"));
CodeSandbox

Related

Material-UI tab indicator does not change when previous button is pressed

as far as now my code is working fine : The tab indicator is moving accordingly to the url of my tab.
But there's a strange behaviour happening when the back button of the browser is getting pressed, the url is changing but the indicator stay on the same tab as before.
Here's the code :
import * as React from 'react';
import { Tabs, Tab, Box } from '#mui/material';
import { useNavigate } from 'react-router-dom';
import { HOME_PAGE } from '../../../router/customRouter';
const navigationsListTabs = [
{
id: 0,
path: '/dev',
label: 'Dev',
isDisable: false,
},
{
id: 1,
path: '/images',
label: 'Images',
isDisable: false,
},
{
id: 2,
path: '/services',
label: 'Services',
isDisable: false,
},
{
id: 3,
path: '/users',
label: 'users',
isDisable: true,
},
];
export const Header = () => {
const [value, setValue] = React.useState(0);
const navigate = useNavigate();
React.useEffect(() => {
navigate('/dev');
}, []);
function handleChange(event, newValue) {
const selectedTab = navigationsListTabs.find((tab) => tab.id === newValue);
navigate(HOME_PAGE + selectedTab.path);
setValue(newValue);
}
return (
<Box sx={{ width: '100%' }}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs
value={value}
onChange={handleChange}
data-testid="tabs-menu-component"
>
{navigationsListTabs.map((item) => (
<Tab
key={item.id}
value={item.id}
label={item.label}
aria-label={item.label}
disabled={item.isDisable}
/>
))}
</Tabs>
</Box>
</Box>
);
};
Tried so far :
Using multiple conditions comparing url (not working) :
let url = window.location.pathname;
console.log(url);
const valueGetter = () => {
if (url.includes('dev')) {
return 0;
} else if (url.includes('images')) {
return 1;
} else if (url.includes('services')) {
return 2;
} else {
return 3;
}
};
console.log(valueGetter());
const [value, setValue] = React.useState(valueGetter);
Thanks to anyone who can help :)
If value is fully dependent on path, perhaps consider to always get value from pathname, instead of saving it as a state and handle both.
This example handles pathname with useLocation, so it gets updated by the hook when path changes. value is generated based on pathname.
Example:
import * as React from "react";
import { Tabs, Tab, Box } from "#mui/material";
import { useNavigate, useLocation } from "react-router-dom";
import { HOME_PAGE } from "../../../router/customRouter";
const navigationsListTabs = [
{
id: 0,
path: "/dev",
label: "Dev",
isDisable: false,
},
{
id: 1,
path: "/images",
label: "Images",
isDisable: false,
},
{
id: 2,
path: "/services",
label: "Services",
isDisable: false,
},
{
id: 3,
path: "/users",
label: "users",
isDisable: true,
},
];
export const Header = () => {
const navigate = useNavigate();
// 👇 Get value from pathname instead of saving it as state
const { pathname } = useLocation();
const currentTab = navigationsListTabs.find(
(tab) => tab.path === `/${pathname.split("/")?.pop()}`
);
const value = currentTab ? currentTab?.id : 0;
React.useEffect(() => {
navigate("/dev");
}, []);
function handleChange(event, newValue) {
const selectedTab = navigationsListTabs.find((tab) => tab.id === newValue);
navigate(HOME_PAGE + selectedTab.path);
}
return (
<Box sx={{ width: "100%" }}>
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<Tabs
value={value}
onChange={handleChange}
data-testid="tabs-menu-component"
>
{navigationsListTabs.map((item) => (
<Tab
key={item.id}
value={item.id}
label={item.label}
aria-label={item.label}
disabled={item.isDisable}
/>
))}
</Tabs>
</Box>
</Box>
);
};

Update Nested Objects in React Native using setState

I am trying to change the properties of objects inside of an object and
trying to add new properties to these objects but keeping the old values.
I can't find out how to get the right nested object by index, not id because
the id can be different from the .map index.
This is what I got so far, the Object names are for testing purposes
only and maybe the "updateNestedObject" can be run in the parent?
Thank you in advance and sorry if this is a noob question.
Neval
import React, { useState } from 'react';
import { View, TextInput, Text, StyleSheet, Button, Alert } from 'react-native';
function ObjectScreen() {
const [state, setState] = useState({
id: 1,
name: 'Test Object',
nested: [
{
id: 1,
title: 'Object 1',
},
{
id: 2,
title: 'Object 1',
}
]
});
function editNested({nestedObject, index, setState}) {
const updateNestedObject = () => {
setState(prevState => ({
nested: [
...prevState.nested,
[prevState.nested[index].comment]: 'Test Comment',
},
}));
}
return (
<View>
<Text>{object.title}</Text>
<TextInput
style={styles.input}
name="comment"
onChangeText={updateNestedObject}
/>
</View>
);
}
return (
<>
<Text>{state.name}</Text>
{ state.nested.map((nestedObject, key)=>{
return (
<editNested key={key} index={key} object={object} nestedObject={nestedObject}/>
)
})}
</>
)
}
const styles = StyleSheet.create({
container: {},
input: {
height: 40,
margin: 12,
borderWidth: 1,
padding: 10,
},
});
export default ObjectScreen;
There were few issues:
JSX component name editNested should start with a capital letter.
And editNested component should be on its own function, should not define inside another component which caused your TextInput to lose focus after each render cycle.
The setState call should be changed like below:
const updateNestedObject = (text) => {
setState((prevState) => ({
...prevState,
nested: prevState.nested.map((item) =>
item.id === nestedObject.id ? { ...item, value: text } : item
)
}));
};
Try the code below:
import React, { useState } from "react";
import { View, TextInput, Text, StyleSheet, Button, Alert } from "react-native";
function EditNested({ nestedObject, setState }) {
const updateNestedObject = (text) => {
setState((prevState) => ({
...prevState,
nested: prevState.nested.map((item) =>
item.id === nestedObject.id ? { ...item, value: text } : item
)
}));
};
return (
<View>
<Text>{nestedObject.title}</Text>
<TextInput
style={styles.input}
onChangeText={updateNestedObject}
value={nestedObject.value}
/>
</View>
);
}
function ObjectScreen() {
const [state, setState] = useState({
id: 1,
name: "Test Object",
nested: [
{
id: 1,
title: "Object 1",
value: ""
},
{
id: 2,
title: "Object 1",
value: ""
}
]
});
console.log(state);
return (
<>
<Text>{state.name}</Text>
{state.nested.map((nestedObject, key) => {
return (
<EditNested
key={key}
nestedObject={nestedObject}
setState={setState}
/>
);
})}
</>
);
}
const styles = StyleSheet.create({
container: {},
input: {
height: 40,
margin: 12,
borderWidth: 1,
padding: 10
}
});
export default ObjectScreen;
Working Demo
As per que you can update nested array with below method
const updateNestedObject = (values, item, index) => {
console.log('values', values);
const tempMainObj = state;
const tempArr = state.nested;
tempArr[index].value = values;
const updatedObj = { ...tempMainObj, nested: tempArr };
setState(updatedObj);
};
Full Example
import React, { useState } from 'react';
import { View, TextInput, Text, StyleSheet, Button, Alert } from 'react-native';
function ObjectScreen() {
const [state, setState] = useState({
id: 1,
name: 'Test Object',
nested: [
{
id: 1,
title: 'Object 1',
value: '',
},
{
id: 2,
title: 'Object 1',
value: '',
},
],
});
const updateNestedObject = (values, item, index) => {
console.log('values', values);
const tempMainObj = state;
const tempArr = state.nested;
tempArr[index].value = values;
const updatedObj = { ...tempMainObj, nested: tempArr };
setState(updatedObj);
};
return (
<>
<Text style={{ marginTop: 50 }}>{state.name}</Text>
{state.nested.map((item, index) => {
return (
<>
<Text>{item.title}</Text>
<TextInput
value={item?.value}
style={styles.input}
name="comment"
onChangeText={(values) => updateNestedObject(values, item, index)}
/>
</>
);
})}
</>
);
}
const styles = StyleSheet.create({
container: {
marginTop: 50,
},
input: {
height: 40,
margin: 12,
borderWidth: 1,
padding: 10,
},
});
export default ObjectScreen;
Snack expo: Live Example

React Native dropdown api value not selected

I am using react-native-element-dropdown in react native app. It works fine with default value if set in useState but it's not work with set api response value and not selected in dropdown
import { Dropdown } from "react-native-element-dropdown";
const Profile = ({ navigation, route }) => {
const [country, setCountry] = useState("");
useEffect(() => {
getUserProfile();
}, []);
const getUserProfile = async () => {
return api
.getuserprofile(locale, authValues.user.id, authValues.token)
.then((response) => {
if (response.data.status) {
setCountry(response.data.body.user.country_id);
}
})
.catch((error) => {
//console.log(error);
});
};
return (
<SafeAreaView style={globalStyles.appContainer}>
<View style={globalStyles.inputBox}>
<Text style={globalStyles.inputLabel}>Country of Residence</Text>
<Dropdown
data={CountryData}
search
maxHeight={300}
labelField="value"
valueField="key"
placeholder="Country of Residence"
searchPlaceholder={"Search..."}
value={country}
onChange={(item) => {
setCountry(item.key);
}}
/>
</View>
</SafeAreaView>
);
};
export default Profile;
I've create an example of how to archive it on React native:
import * as React from 'react';
import {useState, useEffect} from 'react';
import { Text, View, StyleSheet } from 'react-native';
import Constants from 'expo-constants';
import { Dropdown } from 'react-native-element-dropdown';
import AntDesign from 'react-native-vector-icons/AntDesign';
export default function App() {
const [data, setData] = useState([{
key: 1,
value: 'Australia'
}, {
key: 2,
value: 'New Zeland'
}, {
key: 3,
value: 'The United State'
}]);
const [selectedValue, setSelectedValue] = useState(null);
const [isFocus, setIsFocus] = useState(false);
const getSelected = () => {
fetch('https://api.agify.io/?name=michael').then(res => {
setSelectedValue(3);
}).catch((err) => {
console.log(err);
})
}
useEffect(() => {
getSelected();
}, []);
return (
<View style={styles.container}>
<Dropdown
style={[styles.dropdown, isFocus && { borderColor: 'blue' }]}
data={data}
search
maxHeight={300}
labelField="value"
valueField="key"
placeholder={!isFocus ? 'Select item' : '...'}
searchPlaceholder="Search..."
value={selectedValue}
onFocus={() => setIsFocus(true)}
onBlur={() => setIsFocus(false)}
onChange={item => {
setSelectedValue(item.key);
setIsFocus(false);
}}
renderLeftIcon={() => (
<AntDesign
style={styles.icon}
color={isFocus ? 'blue' : 'black'}
name="Safety"
size={20}
/>
)}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
});
and you can check the working example from here
you used item.value insted of item.key
onChange={item => {
setValue(item.value);
setIsFocus(false);
}}

ReactNative deep cloning state for Flatlist

My FlatList renderItem was re-rendering every item when one of them was changed.
After doing some debugging i deepcloned the state variable which holds the items (+ React.memo), it's working fine now but not sure if it's the optimal solution.
Snack: https://snack.expo.io/-419PhiUl
App.js
import * as React from 'react';
import { View, StyleSheet, FlatList } from 'react-native';
import Constants from 'expo-constants';
import _ from 'lodash';
import Item from './components/Item';
const keyExtractor = item => item.id.toString();
export default function App() {
const [data, setData] = React.useState([
{id: 1, title: 'Post 1', liked: false, user: {name: 'A'}},
{id: 2, title: 'Post 2', liked: false, user: {name: 'B'}},
{id: 3, title: 'Post 3', liked: false, user: {name: 'C'}},
]);
/**
* Like / Unlike the item.
*/
const like = React.useCallback((id) => {
setData(state => {
let clonedState = [...state];
let index = clonedState.findIndex(item => item.id === id);
clonedState[index].liked = ! clonedState[index].liked;
return clonedState;
});
}, []);
/**
* Render items.
*/
const renderItem = React.useCallback(({item}) => (
<Item item={item} onLike={like} />
), []);
const deepClonedData = React.useMemo(() => _.cloneDeep(data), [data]);
return (
<View style={styles.container}>
<FlatList
data={deepClonedData}
renderItem={renderItem}
keyExtractor={keyExtractor}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
}
});
Item.js
import React from 'react';
import {
Text, TouchableOpacity, StyleSheet
} from 'react-native';
function Item({item, onLike}) {
const _onLike = React.useCallback(() => {
onLike(item.id);
}, []);
console.log('rendering', item.title);
return (
<TouchableOpacity onPress={_onLike} style={styles.item}>
<Text>{item.title} : {item.liked ? 'liked' : 'not liked'}</Text>
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
item: {
marginVertical: 10,
backgroundColor: 'white',
padding: 15,
borderWidth: 1
}
});
const areEqual = (prevProps, nextProps) => {
return prevProps.item.liked === nextProps.item.liked;
}
export default React.memo(Item, areEqual);

Warning: setState(...): Can only update a mounted or mounting component on a new reactjs app

I have the following component:
import React, { Component } from 'react';
import { Row, Col } from 'antd';
import PageHeader from '../../components/utility/pageHeader';
import Box from '../../components/utility/box';
import LayoutWrapper from '../../components/utility/layoutWrapper.js';
import ContentHolder from '../../components/utility/contentHolder';
import basicStyle from '../../settings/basicStyle';
import IntlMessages from '../../components/utility/intlMessages';
import { adalApiFetch } from '../../adalConfig';
export default class extends Component {
constructor(props) {
super(props);
this.state = {
data: []
};
this.fetchData();
}
getValues() {
adalApiFetch(fetch, '/values', {})
.then((response) => {
// This is where you deal with your API response. In this case, we
// interpret the response as JSON, and then call `setState` with the
// pretty-printed JSON-stringified object.
response.json()
.then((responseJson) => {
this.setState({ data: JSON.stringify(responseJson, null, 2) })
});
})
.catch((error) => {
// Don't forget to handle errors!
console.error(error);
})
}
fetchData() {
try {
const data = this.getValues();
!this.isCancelled && this.setState({ data });
} catch(error) {
console.log(error);
}
}
render() {
const { data } = this.state;
const { rowStyle, colStyle, gutter } = basicStyle;
const radioStyle = {
display: 'block',
height: '30px',
lineHeight: '30px'
};
const plainOptions = ['Apple', 'Pear', 'Orange'];
const options = [
{ label: 'Apple', value: 'Apple' },
{ label: 'Pear', value: 'Pear' },
{ label: 'Orange', value: 'Orange' }
];
const optionsWithDisabled = [
{ label: 'Apple', value: 'Apple' },
{ label: 'Pear', value: 'Pear' },
{ label: 'Orange', value: 'Orange', disabled: false }
];
return (
<div>
<LayoutWrapper>
<PageHeader>{<IntlMessages id="pageTitles.TenantAdministration" />}</PageHeader>
<Row style={rowStyle} gutter={gutter} justify="start">
<Col md={12} sm={12} xs={24} style={colStyle}>
<Box
title={<IntlMessages id="pageTitles.TenantAdministration" />}
subtitle={<IntlMessages id="pageTitles.TenantAdministration" />}
>
<ContentHolder>
<ul>
{data && data.map(item => (
<li>{item.name}</li>
))}
</ul>
</ContentHolder>
</Box>
</Col>
</Row>
</LayoutWrapper>
</div>
);
}
}
and my adalconfig
import { AuthenticationContext, adalFetch, withAdalLogin } from 'react-adal';
export const adalConfig = {
tenant: 'aa-c220-48a2-a73f-1177fa2c098e',
clientId: 'aa-bd54-456d-8aa7-f8cab3147fd2',
endpoints: {
api:'aa-abaa-4519-82cf-e9d022b87536'
},
'apiUrl': 'https://webapi-app.azurewebsites.net/api',
cacheLocation: 'localStorage'
};
export const authContext = new AuthenticationContext(adalConfig);
export const adalApiFetch = (fetch, url, options) =>
adalFetch(authContext, adalConfig.endpoints.api, fetch, adalConfig.apiUrl+url, options);
export const withAdalLoginApi = withAdalLogin(authContext, adalConfig.endpoints.api);
and the error in the console is:
Warning: setState(...): Can only update a mounted or mounting
component. This usually means you called setState() on an unmountedA
component. This is a no-op.
This happens because you are calling
this.fetchData()( which in turn calls this.getValues() ) in the constructor. But setState() should only be called after the component has been rendered.
Better call this.fetchData() in componentDidMount().
In your render() function add:
<ContentHolder>
<ul>
{data.length && data.map(item => ( <li>{item.name}</li> ))}
</ul>
</ContentHolder>
**data.length and not data as an empty array always evaluates to true.

Categories

Resources