I want to use hook to get and set the value of my component WITHOUT PROPS. I import the component and it lists the data returned from the request, ok, but when I use the hook to see the data it returns empty, as if it were another instance.
Initially I used the state of the parent component, but when I needed to change some value of my component, everything would re-render as it affected the state of the parent component, so I want to isolate the state in the child component and use it freely as a hook elsewhere .
How I would like to use:
import MyComp, { useMyHook } from '../../../components/MyComp';
const OtherComp = () => {
const { data } = useMyHook();
return(
<div>
<button type="button" onClick={() => console.log(data)}>
click test
</button>
<MyComp />
</div>
);
};
export default OtherComp;
Component render
Example1
Example2
But the click button log: [ ]
Without using external components/libs like redux and etc.
My custom hook:
src/useMyHook.ts
import { useState } from 'react';
export const useMyHook = () => {
const [data, setData] = useState<any[]>([]);
const addItem = (item: unknown) => {
setData([...date, item]);
};
return { data, setData, addItem};
};
export default useMyHook;
My main component:
src/MyComp.tsx
import {useEffect} from 'react';
import useMyHook from './useMyHook';
const MyComp = () => {
const { data, setData } = useMyHook();
const req = async() => {
const {values} = await anyRequest(); // to do any request
setData(values);
};
useEffect(() => { req() },[]);
return(
<div>
{data.map((item) => <p>{item.name}</p>)}
</div>
);
};
export { useMyHook };
export default MyComp;
src/index.tsx
import MyComp, { useMyHook } from './MyComp';
export default MyComp;
export { useMyHook };
To demonstrate my comment:
import React,{useState, createContext} from 'react';
import { Text, View, StyleSheet, Button } from 'react-native';
import Constants from 'expo-constants';
import useMyHook from './useMyHook'
import Example1 from './Example1'
import Example2 from './Example2'
import OtherComp from './OtherComp'
import {Data, DataSetter} from './types'
type DContext = {
data:Data,
setData:DataSetter
}
export const DataContext = createContext({
data:[],
setData:()=>{}
} as DContext)
export default function App() {
const [data, setData] = useState<Data>([])
return (
<View style={styles.container}>
<DataContext.Provider value={{data,setData}}>
<Example1/>
<Example2/>
<OtherComp/>
</DataContext.Provider>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
paragraph: {
margin: 24,
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
},
});
import { useContext, useCallback } from 'react';
import { DataContext } from './App';
export const useMyHook = () => {
const { data, setData } = useContext(DataContext);
// wrapped in useCallback to prevent function from
// being recreated
const addItem = useCallback((item: unknown) => {
setData(prev=>[...prev, item]);
},[]);
return { data, setData, addItem };
};
export default useMyHook;
useMyHook usage:
import React, { useEffect } from 'react';
import { View, Button } from 'react-native';
import useMyHook from './useMyHook';
export default function Example1() {
const { data, addItem } = useMyHook();
return (
<View style={{ width: '100%' }}>
<Button
title="Add Item From Example 1"
onPress={() => {
addItem(Math.floor(Math.random() * 25) + ' From Example1');
}}
/>
</View>
);
}
A demo
Related
I am getting this error /!\ You are using legacy implementaion. Please update your code: use createWrapper() and wrapper.useWrappedStore(). when I compile my application. The application works fine, but I do not know why I get the error. I tried configuring similar to other projects on google and nothing seems to work.
I tried using this solution, but had no luck. Not sure how to fix this.
Here is my _app.tsx
import type { AppProps } from 'next/app'
import { Toaster } from 'react-hot-toast';
import { useRouter } from 'next/router';
import { wrapper } from '../features/store';
import useAuth from '../hooks/useAuth';
import useDarkMode from '../hooks/useDarkMode';
import '../styles/global.css';
const App = ({ Component, pageProps }: AppProps) => {
useAuth();
useDarkMode();
const router = useRouter();
return (
<main>
<Component {...pageProps} key={router.asPath} />
<Toaster
position="bottom-center"
toastOptions={{
duration: 3000,
}}
/>
</main>
)
};
export default wrapper.withRedux(App);
here is index.tsx
import { useEffect } from "react";
import { useRouter } from "next/router";
import { useSelector } from "react-redux";
import { User } from "../domain/accounts/user";
import Loading from "../icons/Loading";
import useUser from "../hooks/useUser";
const Home = () => {
const user = useUser();
const router = useRouter();
useEffect(() => {
if (user.isAuthenticated !== null) {
if (user.isAuthenticated) router.push('/home');
if (!user.isAuthenticated) router.push('/explore')
}
}, [user.isAuthenticated]);
return (
<div style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
minHeight: '95vh',
}}>
<Loading height={80} width={80} />
</div>
);
}
export default Home;
here is store.js
import { configureStore } from "#reduxjs/toolkit";
import { userSlice } from "./user";
import { userPreferenceSlice } from "./userPreference";
import { createWrapper } from "next-redux-wrapper";
const makeStore = () => {
return configureStore({
reducer: {
user: userSlice.reducer,
userPreference: userPreferenceSlice.reducer,
},
});
}
export const wrapper = createWrapper(makeStore);
Did you try npm install next-redux-wrapper
Then, in _app.tsx try replaceing the wrapper.withRedux import with import { useWrappedStore } from "next-redux-wrapper";
And instead of using the wrapper.withRedux function, try using the useWrappedStore method:
const MyAppWithRedux = useWrappedStore(MyApp);
export default MyAppWithRedux;
I am trying to get React-Redux to work in my new React-Native app and am not succeeding. In my attempts to find online help, all of the examples are using Classes for each Component/Screen, but the beginning React Native template app is no longer using Classes. Everything is defined using "const" and the main App function is defined as:
`const App: () => Node = () => {...`
which are all new to me and I'm not sure if it has anything to do with my failures.
I have several Components/Screens all working nicely and do not have errors until I try to implement Redux.
Reducer:
const initState = {
syncedDate: '01/02/2022'
}
const projectsReducer = (state = initState, action) => {
switch (action.type) {
case 'PJS_SET_SYNCEDDATE':
return {
...state,
syncedDate: action.syncedDate
}
break;
default:
}
return state
}
export default projectsReducer;
Action:
export const pjsSetSyncedDate = (syncedDate) => {
return {
type: 'PJS_SET_SYNCEDDATE',
syncedDate
}
}
Store:
import { createStore, combineReducers } from 'redux';
import projectsReducer from '../reducers/projects';
const rootReducer = combineReducers({
projects: projectsReducer
});
const configureStore = () => {
return createStore(rootReducer);
};
export default configureStore;
App:
...
const store = configureStore();
...
const App: () => Node = () => {
const isDarkMode = useColorScheme() === 'dark';
const backgroundStyle = {
backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
};
return (
<Provider store={store}>
<NavigationContainer>
<Tab.Navigator
...
</Tab.Navigator>
</NavigationContainer>
</Provider>
);
};
...
Component:
import React from 'react';
import type { Node } from 'react';
import {
Text,
View,
} from 'react-native';
import { connect } from 'react-redux';
export const DetailsScreen = ({ route, navigation }) => {
const { name } = route.params;
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Details List</Text>
<Text>Incoming param: {JSON.stringify(name)}</Text>
<Text>SyncedDate: {this.props.syncedDate}</Text>
</View>
);
};
const mapStateToProps = (state) => {
return {
syncedDate: state.projects.syncedDate
}
};
export default ConnectedDetailsScreen = connect(mapStateToProps)(DetailsScreen);
The error occurs in the Text block
"this.props.syncedDate" - undefined is not an object
You are mixing some old implementation with the new one. try to change their step by step:
As a best practice, use payload property to pass your data through your action, so add it in your action.js:
export const pjsSetSyncedDate = (syncedDate) => {
return {
type: 'PJS_SET_SYNCEDDATE',
payload: syncedDate // -----> added here
}
}
So, change your reducer to get new syncedData with payload:
const initState = {
syncedDate: '01/02/2022'
}
const projectsReducer = (state = initState, action) => {
switch (action.type) {
case 'PJS_SET_SYNCEDDATE':
return {
...state,
syncedDate: action.payload.syncedDate // -------> add payload here
}
// break;
default:
}
return state
}
export default projectsReducer;
Note: you don't need the break expression in the switch/case since you returning the result in the case. I commented out this line in the above reducer code.
Now, back to the component DetailsScreen:
import React from 'react';
import { Text,View } from 'react-native';
import { useSelector } from 'react-redux'; // -----> import useSelector instead of connect method
export const DetailsScreen = ({ route, navigation }) => {
const { name } = route.params;
const syncedData = useSelector(state => state.projects.syncedData)
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Details List</Text>
<Text>Incoming param: {JSON.stringify(name)}</Text>
<Text>SyncedDate: {syncedDate}</Text>
</View>
);
};
export default DetailsScreen;
Note: useSelector hook will get the state with a selector function. you define your reducer as projects in the combineReducers so your syncedData is in your state.projects
Note: with the above procedure, you don't need to connect your DetailsScreen to the store to get the state. useSelector hook will do that.
TypeError: undefined is not an object (evaluating 'this.props.all_subjects.current').
Index.js
import { registerRootComponent } from 'expo';
import App from './App';
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in the Expo client or in a native build,
// the environment is set up appropriately
registerRootComponent(App);
App.js
import 'react-native-gesture-handler';
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { StyleSheet } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import subjectsReducer from './reducers/subjectsReducer';
import Home from "./screens/home";
import Subjects from "./screens/subjects";
const store = createStore(subjectsReducer);
const Stack = createStackNavigator();
class App extends Component {
render() {
return (
<Provider store={store}>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={Home}
/>
<Stack.Screen
name="Subjects"
component={Subjects}
/>
</Stack.Navigator>
</NavigationContainer>
</Provider>
);
}
}
export default App;
subjectsReducers.js
import { combineReducers } from 'redux';
const INITIAL_STATE = {
current: [],
all_subjects: [
'Literature',
'Speech',
'Writing',
'Algebra',
'Geometry',
'Statistics',
'Chemisrty',
'Biology',
'Physics',
'Economics',
'Geography',
'History',
],
};
const subjectsReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'SELECT_SUBJECT':
// copy the state
const { current, all_subjects,} = state;
//remove a subject from the all_subjects array
const addedSubject = all_subjects.splice(action.payload, 1);
// put subject in current array
current.push(addedSubject);
// update the redux state to reflect the change
const newState = { current, all_subjects };
//return new state
return newState;
default:
return state
}
};
export default combineReducers({
subjects: subjectsReducer
});
subjectsActions.js
export const addSubject = subjectsIndex => (
{
type: 'SELECT_SUBJECT',
payload: subjectsIndex,
}
);
Subjects.js
import React from 'react';
import { connect } from 'react-redux';
import { StyleSheet, Text, View, Button } from 'react-native';
class Subjects extends React.Component {
render() {
return (
<View style={styles.container}>
<Text>Select Subjects Below!</Text>
{this.props.subjects.all_subjects.map((subject, index) => (
<Button
key={ subject }
title={ `Add ${ subject }` }
onPress={() =>
this.props.addSubject(index)
}
/>
))}
<Button
title="Back to home"
onPress={() =>
this.props.navigation.navigate('Home')
}
/>
</View>
);
}
}
// const mapStateToProps = (state) => {
// const { subjects } = state
// return { subjects }
// };
const mapStateToProps = state => ({
subjects: state.subjects
});
export default connect(mapStateToProps)(Subjects);
Home.js
import React from 'react';
import { connect } from 'react-redux';
import { StyleSheet, Text, View, Button } from 'react-native';
class Home extends React.Component {
render() {
return (
<View style={styles.container}>
<Text>You have { this.props.all_subjects.current.length } subjects.</Text>
<Button
title="Select more subjects"
onPress={() =>
this.props.navigation.navigate('Subjects')
}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
text:{
color: '#888',
fontSize: 18
},
input: {
margin: 15,
height: 40,
borderColor: '#7a42f4',
borderWidth: 1
},
submitButton: {
backgroundColor: '#7a42f4',
padding: 10,
margin: 15,
height: 40,
},
submitButtonText:{
color: 'white'
}
});
// const mapStateToProps = (state) => {
// const { subjects } = state
// return { subjects }
// };
const mapStateToProps = state => ({
subjects: state.subjects
});
export default connect(mapStateToProps, null)(Home);
I am new to react native. I have searched everywhere for a solution for this issue, but I can’t seem to get anywhere. I have used redux in standard react, but I don’t know why this isn’t working. Any thoughts or suggestions would be greatly appreciated.
I'm trying to access useTheme() directly from the styles
But so far my solution doesn't seem to work.
I'm not getting in error back.
Is there a way to do what I'm trying to do?
import { StyleSheet } from "react-native";
import { useTheme } from '#react-navigation/native'
export default function () {
const { colors } = useTheme();
const styles = GlobalStyles({ colors: colors })
return styles
}
const GlobalStyles = (props) => StyleSheet.create({
container: {
flex: 1,
backgroundColor: props.colors.backgroundColor,
},
})
Accessing style in component
import React from "react";
import GlobalStyles from "../globalStyles.js"
class Inventory extends React.Component {
render() {
return (
<View style={globalStyles.container}>
)
}
You have a couple of issues: you can only use a hook within a hook or a function component. So you could convert your global stylesheet into a hook:
import { StyleSheet } from "react-native";
import { useTheme } from '#react-navigation/native'
const getGlobalStyles = (props) => StyleSheet.create({
container: {
flex: 1,
backgroundColor: props.colors.backgroundColor,
},
});
function useGlobalStyles() {
const { colors } = useTheme();
// We only want to recompute the stylesheet on changes in color.
const styles = React.useMemo(() => getGlobalStyles({ colors }), [colors]);
return styles;
}
export default useGlobalStyles;
Then you can use the hook by converting your Inventory class component into a function component and using the new useGlobalStyles hook.
import React from "react";
import useGlobalStyles from "../globalStyles.js"
const Inventory = () => {
const globalStyles = useGlobalStyles();
return (
<View style={globalStyles.container}>
)
}
I have a connected component and have integrated MaterialUI in my component. For some reason, the tests keep on failing and I am not able to find some article online to resolve this.
Please advice.
Below is my code.
import React, {Component} from 'react';
import {connect} from 'react-redux';
import SourceCurrencyInput from './components/SourceCurrencyInput';
import TargetCurrencyInput from './components/TargetCurrencyInput';
import {fetchCurrencyConverterRates} from './actions/currencyConverterActions';
import CurrencyConverterValue from './components/CurrencyConverterValue';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import Typography from '#material-ui/core/Typography';
import Button from '#material-ui/core/Button';
import {withStyles} from '#material-ui/core/styles';
import './App.css';
import {
changeSourceCurrencyValue,
changeTargetCurrencyValue
} from './actions/actions';
const styles = {
root: {
flexGrow: 1,
},
grow: {
flexGrow: 1,
},
menuButton: {
marginLeft: -12,
marginRight: 20,
},
};
class App extends Component {
componentDidMount() {
this.props.fetchCurrencyConverterRates(
this.props.srcCurrencyType,
this.props.tgtCurrencyType
);
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (prevProps.srcCurrencyType !== this.props.srcCurrencyType
|| prevProps.tgtCurrencyType !== this.props.tgtCurrencyType) {
this.props.fetchCurrencyConverterRates(
this.props.srcCurrencyType,
this.props.tgtCurrencyType);
}
}
clearAll = () => {
this.props.sourceValue('');
this.props.targetValue('');
};
render() {
const {srcCurrencyType, tgtCurrencyType, srcCurrencyValue, tgtCurrencyValue, currencyConversionRate, classes} = this.props;
return (
<div className="App">
<AppBar position="static">
<Toolbar>
<Typography variant="h6" color="inherit" className={classes.grow}>
Currency Converter by Arun Manohar
</Typography>
</Toolbar>
</AppBar>
<header className="App-header">
<SourceCurrencyInput/>
<TargetCurrencyInput/>
<Button variant="contained" color="primary" className={classes.button}
onClick={() => this.clearAll()}>Clear</Button>
</header>
<CurrencyConverterValue srcCurrencyType={srcCurrencyType}
tgtCurrencyType={tgtCurrencyType}
srcCurrencyValue={srcCurrencyValue}
tgtCurrencyValue={tgtCurrencyValue}
currencyConversionRate={currencyConversionRate}
/>
<footer><a href={'https://currencyconverterapi.com'} target={'_blank'}>API from currencyconverterapi.com</a></footer>
</div>
);
}
}
const mapStateToProps = state => {
return {
srcCurrencyType: state.currencyConverterReducer.sourceCurrencyType,
tgtCurrencyType: state.currencyConverterReducer.targetCurrencyType,
srcCurrencyValue: state.currencyConverterReducer.sourceCurrencyValue,
tgtCurrencyValue: state.currencyConverterReducer.targetCurrencyValue,
currencyConversionRate: state.getConvertedRates.data[0]
};
};
const mapDispatchToProps = (dispatch) => ({
fetchCurrencyConverterRates: (src, tgt) => dispatch(fetchCurrencyConverterRates(src, tgt)),
sourceValue: (val) => dispatch(changeSourceCurrencyValue(val)),
targetValue: (val) => dispatch(changeTargetCurrencyValue(val)),
});
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(App));
Below is my test case.
import React from 'react';
import {Provider} from 'react-redux';
import configureStore from 'redux-mock-store';
import App from './App';
import {createMount} from '#material-ui/core/test-utils';
const mockStore = configureStore();
const initialState = {sourceCurrencyType: 'USD'};
const store = mockStore(initialState);
describe('<App />', () => {
let mount;
beforeEach(() => {
mount = createMount();
});
it('should work', () => {
const wrapper = mount(<Provider store={store}><App/></Provider>);
console.log(wrapper.debug());
});
});
This is the error I get.
TypeError: Cannot read property 'sourceCurrencyType' of undefined
I just need a way to access this component in my tests. Please help me out.
Your initial state must keep the same structure with the reducer object, such:
const initialState = {
currencyConverterReducer: {
sourceCurrencyType: 'USD',
// rest of attributes of currencyConverterReducer
}
};