I am a newbie in react-native. I have a folder structure like below:
-screens
-page1.js
-page2.js
-page3.js
-page4.js
-App.js
In page1.js, I have a function to store data to localStorage
let obj = {
name: 'John Doe',
email: 'test#email.com',
city: 'Singapore'
}
AsyncStorage.setItem('user', JSON.stringify(obj));
Now I have to display these data in few of my other pages. This is my code.
class Page2 extends Component {
state = {
username: false
};
async componentDidMount() {
const usernameGet = await AsyncStorage.getItem('user');
let parsed = JSON.parse(usernameGet);
if (parsed) {
this.setState({
username: parsed.name,
email: parsed.email
});
} else {
this.setState({
username: false,
email: false
});
}
}
render() {
return (
<View style={styles.container}>
<Text style={styles.saved}>
{this.state.username}
</Text>
</View>
);
}
}
export default Page2;
This is how I display data in page2. I may need to show these in other page too.
I dont want to repeat these codes in each page.
Any suggestions how to do it in react-native?
You can extract the data you need to display into it's own component and re-use it in any page that you need to display it in.
Another option is to use a higher-order component, that way you can wrap it around any components that need the data and it'll be passed down as a prop.
You can make your Constant.js where you can put all your common required utils and constants, reusable anywhere n your app.
In your Constant.js:
export const USER_DATA = {
set: ({ user}) => {
localStorage.setItem('user', JSON.stringify(obj));
},
remove: () => {
localStorage.removeItem('user');
localStorage.removeItem('refresh_token');
},
get: () => ({
user: localStorage.getItem('user'),
}),
}
in your any component, you can import it and use like this :
import { USER_DATA } from './Constants';
let user = {
name: 'John Doe',
email: 'test#email.com',
city: 'Singapore'
}
// set LocalStorage
USER_DATA.set(user);
// get LocalStorage
USER_DATA.get().user
That's you can make Constant common file and reuse them anywhere to avoid writing redundant code.
Simplified Reusable approach of localStorage
export const localData = {
add(key, value) {
localStorage.setItem(key, JSON.stringify(value));
},
remove(key, value) {
localStorage.removeItem(key);
},
load(key) {
const stored = localStorage.getItem(key);
return stored == null ? undefined : JSON.parse(stored);
},
};
localData.add("user_name", "serialCoder")
console.log( "After set 👉", localData.load("user_name") )
localData.remove("user_name")
console.log( "After remove 👉", localData.load("user_name") )
Related
I'm using a hook that fills in my inputs automatically, according to the zip code the user enters. Then the user's address, street, etc are filled in automatically.
However, for the input to be filled in automatically, the component is re-rendering.
As my form is a modal it opens and closes again because of rendering. I need to make the user fill in the zip code, the inputs are filled in real time.
Can you help me with this?
useCEP Hook:
import { useState } from 'react'
import { api } from 'services/apiClient'
interface Cep {
bairro: string
logradouro: string
localidade: string
uf: string
}
export function useCep() {
const [checkCep, setCheckCep] = useState<Cep>()
const getCEP = async (e) => {
const cep = e.target.value.replace(/\D/g, '')
try {
const { data } = await api.get(`https://viacep.com.br/ws/${cep}/json/`)
setCheckCep(data)
} catch (err) {
console.log(err)
}
}
return { checkCep, getCEP }
}
Component:
const { control, formState, register, reset } = useFormContext()
const { checkCep, getCEP } = useCep()
useEffect(() => {
reset({
responsible: [
{
address: checkCep?.logradouro,
district: checkCep?.bairro,
city: checkCep?.localidade,
state: checkCep?.uf,
name: '',
email: '',
student_name: [],
cep: '',
residence: '',
telephone: '',
sex: ''
}
]
})
}, [checkCep])
<Input
name="cep"
type="number"
label="Cep"
{...register(`responsible.${index}.cep`)}
error={errors?.responsible?.[index]?.cep}
onBlur={(e) => getCEP(e)}
/>
{...}
Maybe you can try to use the setValue method instead of reset as it will reset the form.
https://react-hook-form.com/api/useform/setvalue
I have two stores: formStore and profileStore
FormStore
export class ProfileFormStore {
#observable editing = false;
profileStore = new ProfileStore(this.roleId);
originalValue?: ApiModel | null;
#action.bound
startEdit() {
// this.originalValue = this.profileStore.toJson();
/* if uncomment above, next error thrown
RangeError: Maximum call stack size exceeded
at initializeInstance (mobx.module.js:391)
at ProfileStore.get (mobx.module.js:381)
at ProfileStore.get
*/
this.editing = true;
}
}
ProfileStore
export class ProfileStore {
#observable userProfile: ApiModel = {
userProfile: {
newsAndUpdates: false,
email: "",
phone: "",
lastName: "",
firstName: "",
},
};
#observable email = "";
#action.bound
fetch() {
// this.fromJson(this.actions.fetch());
console.log("start");
this.email = "qwe";
console.log("end");
}
#computed
toJson(): ApiModel {
return {
userProfile: {
firstName: this.userProfile.userProfile.firstName,
lastName: this.userProfile.userProfile.lastName,
phone: this.userProfile.userProfile.phone,
email: this.userProfile.userProfile.email,
newsAndUpdates: this.userProfile.userProfile.newsAndUpdates,
},
};
}
}
And I want to use contexts
const formStore = new ProfileFormStore();
export const profileFormContext = React.createContext({
formStore,
profileStore: formStore.profileStore,
});
export const useProfileContext = () => React.useContext(profileFormContext);
And there are two components: form and formControl
const controls = {
admin: (<><ProfileName /><Email /></>),
user: (<><ProfileName /></>)
};
export const Form = () => {
const { formStore, profileStore } = useProfileContext();
// this.fromJson(this.actions.fetch()); // if uncomment throws 'Missing option for computed get'
return <form>(controls.admin)</form>
}
export const ProfileName = () => {
const { formStore, profileStore } = useProfileContext();
formStore.startEdit(); // check form store, when assigning from profileStore get overflow error
return formStore.editing ? <input value='test' /> : <label>Test</label>
}
So there are two kinds of errors:
When accessing observables from ProfileStore that is part of FormStore
When updating observables in ProfileStore that is part of FormStore
the FormStore working well
both stores injecting via React.useContext have followed these example https://mobx-react.js.org/recipes-context , however their stores are not nested. I made them nested, beacuse I wanted to get access to profileStore from formStore
What do these errors mean? How to fix them?
Actually it is not the answer :) But the solution I have used
export class ProfileStore {
#observable editing;
#observablt userProfile: UserProfile;
...
}
That's all - instead of using two stores, now there is one store, I happy that solution is working. I assume that error was that I forgot to write get at toJson. If in future I encounter same error and understand why it happened. I will try not to forget to update this answer.
I'm trying to pass the table row object to Redux-Form to Edit the object values.
here some contents of ViewStudents.js file
handleEdit(e, student) {
e.preventDefault();
const { dispatch } = this.props;
dispatch(allActions.editStudents(this.state));
this.setState({
firstName: student.firstName,
lastName: student.lastName
});
this.props.history.push("/EditStudents");
}
<button onClick={e => this.handleEdit(e, student)}>Edit</button>
function mapStateToProps(state) {
const { students } = state.viewStudents;
return {
students
};
}
export default connect(mapStateToProps)(ViewStudents);
here some contents of EditStudents.js
constructor(student) {
super(student);
this.state = {
firstName: student.firstName,
lastName: student.lastName
};
}
handleSubmit(e) {
e.preventDefault();
const { dispatch } = this.props;
dispatch(allActions.editStudents(this.state));
this.setState({
firstName: "",
lastName: ""
});
this.props.history.push("/ViewStudents");
}
function mapStateToProps(state) {
const { student } = state.addStudents;
return {
initialValues: {
firstName: state.student.firstName,
lastName: state.student.lastName
}
};
}
export default reduxForm({
form: "EditForm",
validate,
mapStateToProps
})(EditStudents);
Problem is, this object values not passing to edit form, though I bind in mapStateToProps, initialValues and passed this object in constructor
how to bind this and pass properly the clicking object in a table row and edit/save that object
Few small issues I can see with this.
You shouldn't use state to select what item you wish to edit. Always think that a user will refresh the page at any item. Therefore you should use React Router Splat to pass a unique value/ID.
http://localhost:3000/EditStudents/1
To do this you need to add a unique id for each student when adding and then use the ID with the route.
<Route path="/EditStudents/:studentId" component={EditStudents} />
You can then read the ID and load on componentDidMount
componentDidMount() {
const { dispatch } = this.props;
var {studentId } = props.match.params;
dispatch( { type: "LOAD_STUDENT", studentId });
}
The use of refreshing the state (request, success) has the effect of clearing the data. Therefore any initial state is lost.
Note, you are also using a Service to load in data.
Really you should be using an async thunk to load data into redux.
You should only get data from and to redux and then use middleware to persist the data.
https://github.com/reduxjs/redux-thunk
https://codesandbox.io/s/MQnD536Km
I have a data set much larger than the example below. I'm trying to add notes to the profiles, however, there is a relatively large lag between typing and the text appearing in the textarea. I assume this happening because it has to go through a lot of data. Is there a more efficient way to do this.
Data
const profiles = [{
firstName: 'Brady',
lastName: 'Smith'
}, {
firstName: 'Jason',
lastName: 'Brady'
}, {
firstName: 'Michael',
lastName: 'Bolten'
}];
Component
class Notes extends React.Component {
constructor(props) {
super(props);
this.state = {
profiles: []
};
this.handleAddingUserNotes = this.handleAddingUserNotes.bind(this);
}
handleAddingUserNotes(e, userId) {
const { profiles } = this.state;
const addNotesToProfiles = profiles.map(profile => {
if (profile.userId === userId) {
profile.notes = e.target.value;
}
return profile;
});
this.setState({ profiles: addNotesToProfiles });
}
render() {
const { profiles } = this.state;
return (
<div>
{profiles.map(profile => {
const { userId, notes } = profile;
return (
<textarea
className="form-control"
value={notes}
onChange={e => this.handleAddingUserNotes(e, userId)}
/>
);
})}
</div>
);
}
}
You can pass the index of the profile to update in the change handler and then directly update that profile.
// in your render method
{profiles.map((profile, index) => {
const { userId, notes } = profile;
return (
<textarea
className="form-control"
value={notes}
onChange={e => this.handleAddingUserNotes(e, index, userId)}
/>
);
})}
// in your handle change
handleAddingUserNotes(e, index, userId) {
const { profiles } = this.state;
const newProfiles = [...profiles.slice(0, index), {...profiles[index], notes: e.target.value}, ...profiles.slice(index+1)]
this.setState({ profiles: newProfiles });
}
Or, if you anyways have unique userIds for each user, you can convert the whole array into an object where each user is indexed by the userId and then directly update that user in the userProfiles object. O(1) operation.
Edit:
And yes, as the other answer mentions, please use key with each of your textarea so that react does not rerender every text area on every render.
I've build a component which basically list entries in a table, on top of that, i have another component to which filters can be applied. It all works really great with Apollo.
I'm trying to add deep linking into the filters, which on paper seems incredible simple, and i almost had i working.
Let me share some code.
const mapStateToProps = ({ activeObject }) => ({ activeObject });
#withRouter
#connect(mapStateToProps, null)
#graphql(FILTER_REPORT_TASKS_QUERY, {
name: 'filteredTasks',
options: (ownProps) => {
const filters = queryString.parse(location.search, { arrayFormat: 'string' });
return {
variables: {
...filters,
id: ownProps.match.params.reportId,
},
};
},
})
export default class TasksPage extends Component {
static propTypes = {
filteredTasks: PropTypes.object.isRequired,
activeObject: PropTypes.object.isRequired,
match: PropTypes.object.isRequired,
};
constructor(props) {
super(props);
const filters = queryString.parse(location.search, { arrayFormat: 'string' });
this.state = { didSearch: false, initialFilters: filters };
this.applyFilter = this.applyFilter.bind(this);
}
applyFilter(values) {
const variables = { id: this.props.match.params.reportId };
variables.searchQuery = values.searchQuery === '' ? null : values.searchQuery;
variables.categoryId = values.categoryId === '0' ? null : values.categoryId;
variables.cardId = values.cardId === '0' ? null : values.cardId;
/*
this.props.history.push({
pathname: `${ this.props.history.location.pathname }`,
search: '',
});
return null;
*/
this.props.filteredTasks.refetch(variables);
this.setState({ didSearch: true });
}
..... Render functions.
Basically it calls the apply filter method, when a filter is chosen.
Which all works great, my problem is that when the activeObject is updated (By selecting a entry in the list). It seems to run my HOC graphql, which will apply the filters from the URL again, ignoring the filters chosen by the user.
I tried to remove the query strings from the url, once filters are applied, but i get some unexpected behavior, basically it's like it doesn't fetch again.
How can i prevent Apollo from fetching, just because the redux pushes new state?
I actually solved this by changing the order of the HOC's.
#graphql(FILTER_REPORT_TASKS_QUERY, {
name: 'filteredTasks',
options: (ownProps) => {
const filters = queryString.parse(location.search, { arrayFormat: 'string' });
return {
variables: {
...filters,
id: ownProps.match.params.reportId,
},
};
},
})
#withRouter
#connect(mapStateToProps, null)