I want to create only one socket in my React native app, I use useContext to create an global socket. But when I update socket in child component, socket Provider context updated, but child isn't.
SocketContext.js
import React from 'react';
const SocketContext = React.createContext();
export const SocketProvider = SocketContext.Provider;
export const SocketConsumer = SocketContext.Consumer;
export default SocketContext;
App.js
const setSocket = newSocket => {
socket = newSocket;
// console.log('Set new socket: ', socket);
};
return (
<>
{completed && (
<SocketProvider value={{socket, setSocket}}>
<MainContainer />
</SocketProvider>
)}
</>
);
In Child component
const {socket, setSocket} = useContext(SocketContext);
console.log('Socket: ', socket); //Here socket always null
const initSocket = () => {
setSocket(io(props.url, {timeout: 10000, reconnection: false}));
// Update socket provider in here but socket in child not update
};
useEffect(() => {
console.log(socket);
if (!socket) {
initSocket();
}
}, []);
And I want to update socket in child component and all child component updated.
Thank you.
if you want to use one socket instance through out the app you can use this
and import socket whenever you want to use. check in useEffect if socket is disconnected the use setSocketToken method to reconnect
import { io } from "socket.io-client";
export const socket = io('socket_url', {
path: '/socket.io',
transports: ['websocket'],
upgrade: false,
auth: {
token: `Bearer null`
}
});
export const setSocketToken = async (token) => {
socket.auth.token = `Bearer ${token}`;
socket.disconnect().connect() //reconnect
}
Related
I am new to react native and trying to create React Native context which will store array of objects. Context looks something like this:
import React, {useState, useCallback} from 'react';
export const NotificationContext = React.createContext({
notifications: [],
updateNotifications: () => {},
});
export default function NotificationContextProvider({children}) {
const [notifications, setNotifications] = useState([]);
const updateNotifications = n => {
notifications.push(n);
setNotifications(notifications);
};
const contextValue = {
notifications,
updateNotifications: useCallback(n => updateNotifications(n), []),
};
return (
<NotificationContext.Provider value={contextValue}>
{children}
</NotificationContext.Provider>
);
}
Now when I am trying to access the context, I am not getting the updated array value as desired.
var context = useContext(NotificationContext);
useEffect(() => {
(async () => {
console.log('Before', context);
console.log('Notification value', context.notifications);
context.updateNotifications([1]);
console.log('After', context);
})();
}, []);
I think you should pass dependencies variable context in useEffect
I am doing a component for a react-native application which must get the IP address of the device. The app returns three errors, but I don't find any fault in the code, I believe that they are due to a fail configuration:
The code is next:
import * as Network from 'expo-network';
import { useState, useEffect } from 'react';
function IpAddress ()
{
const [ip, setIp] = useState('');
useEffect(() => {
(async () => {
const { myIP } = await Network.getIpAddressAsync();
setIp(myIP);
})();
}, []);
return (
<>
{ip}
</>
)
}
export default IpAddress;
I am a bit lost. Where is the mistake?
Thanks
I am new and i want to using react native to create android application so after creating project i installed redux and redux thunk and do every config that redux wants to work .
I create a action file :
export const GETSURVEYOR = 'GETSURVEYOR';
const URL = "http://192.168.1.6:3000/";
export const fetchSurveyor = () => {
return async dispatch => {
const controller = new AbortController();
const timeout = setTimeout(
() => { controller.abort(); },
10000,
);
const response = await fetch(`${URL}GetSurveyorList`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({}),
signal: controller.signal
});
clearTimeout(timeout);
const resData = await response.json();
dispatch({
type: GETSURVEYOR,
surveyorList: resData.SurveyorList
});
}
}
after that i create reducer to handle this data :
import {GETSURVEYOR} from '../actions/surveyor'
const initialState = {
surveyorList: []
}
export default (state = initialState, action) => {
switch (action.type) {
case GETSURVEYOR:
return {
...state,
surveyorList: action.surveyorList
};
Now i am using by useSelector, useDispatch from 'react-redux .
import React, { useState, useEffect, useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import * as surveyorActions from '../store/actions/surveyor';
export default () => {
const [surveyorCount, setSurveyorCount] = useState(0);
const survayers = useSelector(state => state.surveyor.surveyorList);
const dispatch = useDispatch();
const loadSurvayer = useCallback(async () => {
await dispatch(surveyorActions.fetchSurveyor());
console.log('run use Callback');
console.log('returned :', survayers );
// setSurveyorCount(survayers.length);
}, [dispatch]);
useEffect(() => {
loadSurvayer();
}, [dispatch]);
return [loadSurvayer, surveyorCount];
}
When for first time this paged is rendered , of course that survayers is empty but after fetch data in action and set state to reducer , survayers nut to be an empty.
But i get empty still ? I am sure data is fetched from services but i got empty from survayers ?
LOG Running "RNAuditMngm" with {"rootTag":1}
LOG run use Callback
LOG returned : []
LOG run use Callback
LOG returned : []
if i change my useEffect code to this:
useEffect(() => {
loadSurvayer();
}, [dispatch,survayers]);
I fall to loop !!!! How could i change code without loop?
I think everything works fine, but you're not using the console.log in the right place. When you run the loadSurvayer the survayers is empty. It is empty even the second time because you are not passing it as a dependency in the useEffect hook. And like you said, if you pass it as a dependency, then it causes an infinite loop, and that's right because whenever the survayers change, that function will be called again and so on.
So, here's what you have to do:
Remove the dispatch dependency from your useEffect hook.
Change the console.log's outside of the loadSurvayer function.
Remove the await from the dispatch call because it is synchronous.
Here's how to modify your code to work the right way:
import React, { useState, useEffect, useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import * as surveyorActions from '../store/actions/surveyor';
export default () => {
const [surveyorCount, setSurveyorCount] = useState(0);
const survayers = useSelector(state => state.surveyor.surveyorList);
const dispatch = useDispatch();
const loadSurvayer = useCallback(async () => {
dispatch(surveyorActions.fetchSurveyor()); // Remove the `await`
console.log('run use Callback');
// setSurveyorCount(survayers.length);
}, [dispatch]);
useEffect(() => {
loadSurvayer();
}, []); // <-- remove the `dispatch` from here.
console.log('returned :', survayers ); // <-- Move the console log here
return [loadSurvayer, surveyorCount];
}
Improvement bonus and suggestion: remove the surveyorCount state variable because you don't actually need it as you can return the count directly.
import React, { useState, useEffect, useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import * as surveyorActions from '../store/actions/surveyor';
export default () => {
// Remove the `surveyorCount`
//const [surveyorCount, setSurveyorCount] = useState(0);
const survayers = useSelector(state => state.surveyor.surveyorList);
const dispatch = useDispatch();
const loadSurvayer = useCallback(async () => {
dispatch(surveyorActions.fetchSurveyor()); // Remove the `await`
console.log('run use Callback');
// setSurveyorCount(survayers.length);
}, [dispatch]);
useEffect(() => {
loadSurvayer();
}, []); // <-- remove the `dispatch` from here.
console.log('returned :', survayers ); // <-- Move the console log here
//return [loadSurvayer, surveyorCount];
return [loadSurvayer, survayers.length]; // <-- Use `survayers.length` instead of `surveyorCount`
}
In useSelector shouldn't you read surveyerList like this state.surveyorList ?. your state doesn't have any object named surveyor but you are currently reading like state.surveyor.surveyorList
Problem:
I have very simple todo app. There is one action - add todo. When I add a task, I simulate sending it to the server using a setTimeout.
When I receive a response from the server, I immediately check to see if there is an error to avoid further action. In stateful component, everything works, and in stateless component it doesn't.
See the code to better understand the problem.
Environment:
"react": "16.8.6",
"react-native": "0.60.5",
"react-redux": "^7.1.1",
"redux": "^4.0.4",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.3.0"
№ 1. Stateful component:
import React, {Component} from 'react';
import {View, Button, ActivityIndicator} from 'react-native';
import {connect} from 'react-redux';
import {addTodo as addTodoAction} from '../redux/reducer';
class MainScreen extends Component {
todoGenerator = () => ({
id: new Date().getTime(),
text: 'Pls help me ' + new Date().getTime(),
});
sendTodoToServer = async () => {
const todo = this.todoGenerator();
const {addTodo} = this.props;
await addTodo(todo);
// this
const {error} = this.props;
if (error) {
console.log('error', error);
}
};
render() {
const {isLoading} = this.props;
return (
<View>
<Button title="Generate todo" onPress={this.sendTodoToServer} />
{isLoading && <ActivityIndicator />}
</View>
);
}
}
export default connect(
state => ({
todos: state.todos,
error: state.error,
isLoading: state.isLoading,
}),
{
addTodo: addTodoAction,
},
)(MainScreen);
№ 1. Stateful component. Console:
As you can see,
const {error} = this.props;
if (error) {
console.log('error', error);
}
it's work. Okay, let's move on to functional components
№ 2. Stateless component with redux connect:
import React from 'react';
import {ActivityIndicator, Button, View} from 'react-native';
import {connect} from 'react-redux';
import {addTodo as addTodoAction} from '../redux/reducer';
const MainScreenFC = ({isLoading, addTodo, error}) => {
const todoGenerator = () => ({
id: new Date().getTime(),
text: 'Pls help me ' + new Date().getTime(),
});
const sendTodoToServer = async () => {
const todo = todoGenerator();
await addTodo(todo);
if (error) {
console.log('error', error);
}
};
return (
<View>
<Button title="Generate todo" onPress={sendTodoToServer} />
{isLoading && <ActivityIndicator />}
</View>
);
};
export default connect(
state => ({
todos: state.todos,
error: state.error,
isLoading: state.isLoading,
}),
{
addTodo: addTodoAction,
},
)(MainScreenFC);
№ 2. Stateless component with redux connect. Console:
The error did not display in the console, although it is in the reducer
№ 3. Stateless component with redux HOOKS:
import React from 'react';
import {ActivityIndicator, Button, View} from 'react-native';
import {connect, shallowEqual, useDispatch, useSelector} from 'react-redux';
import {addTodo as addTodoAction} from '../redux/reducer';
const MainScreenReduxHooks = () => {
const todos = useSelector((state: AppState) => state.todos, shallowEqual);
const error = useSelector((state: AppState) => state.error, shallowEqual);
const isLoading = useSelector(
(state: AppState) => state.isLoading,
shallowEqual,
);
const dispatch = useDispatch();
const todoGenerator = () => ({
id: new Date().getTime(),
text: 'Pls help me ' + new Date().getTime(),
});
const sendTodoToServer = async () => {
const todo = todoGenerator();
await dispatch(addTodoAction(todo));
if (error) {
console.log('error', error);
}
};
return (
<View>
<Button title="Generate todo" onPress={sendTodoToServer} />
{isLoading && <ActivityIndicator />}
</View>
);
};
export default connect(
state => ({
todos: state.todos,
error: state.error,
isLoading: state.isLoading,
}),
{
addTodo: addTodoAction,
},
)(MainScreenReduxHooks);
№ 3. Stateless component with redux HOOKS. Console:
It's the same here, as in the second example.
Questions:
Can redux be connected to a stateless component?
How do you make the second and third example work the same way as the first?
Other code:
App.js
import React from 'react';
import {Provider} from 'react-redux';
import {MainScreen, MainScreenFC, MainScreenReduxHooks} from './src/screens';
import store from './src/redux';
const App = () => {
return (
<Provider store={store}>
<MainScreenFC />
</Provider>
);
};
export default App;
store.js:
import {applyMiddleware, createStore} from 'redux';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import rootReducer from './reducer';
export default createStore(rootReducer, applyMiddleware(thunk, logger));
reducer.js:
const ADD_TODO_REQUEST = 'ADD_TODO_REQUEST';
const ADD_TODO_SUCCESS = 'ADD_TODO_SUCCESS';
const ADD_TODO_FAILURE = 'ADD_TODO_FAILURE';
const initialState = {
todos: [],
isLoading: false,
error: undefined,
};
export const addTodo = data => async dispatch => {
dispatch({
type: ADD_TODO_REQUEST,
payload: {
isLoading: true,
},
});
try {
const todo = await new Promise((resolve, reject) => {
setTimeout(() => {
reject('Ooops, error');
}, 3000);
});
dispatch({
type: ADD_TODO_SUCCESS,
payload: {
todo,
isLoading: false,
},
});
} catch (e) {
dispatch({
type: ADD_TODO_FAILURE,
payload: {
isLoading: false,
error: e,
},
});
}
};
export default function(state = initialState, {type, payload}) {
switch (type) {
case ADD_TODO_REQUEST: {
return {
...state,
isLoading: true,
};
}
case ADD_TODO_SUCCESS: {
return {
...state,
isLoading: false,
todos: [...state.todos, payload.todo],
};
}
case ADD_TODO_FAILURE: {
return {
...state,
isLoading: false,
error: payload,
};
}
default:
return state;
}
}
I think the problem with your code is that you try to wait for a response in the component, it's a bad idea for both stateful and stateless components. What would I recommend you to do, is to handle error somewhere in your middleware (redux-thunk, redux-saga, etc). In this case, your component should just represent data, and if you have to display an error, just take it from props, I believe it is stored somewhere in redux.
In any way, stateless components shouldn't be an async function, because the result of an async function is a promise but not a component. There are some libraries like async-reactor, but personally, I prefer to go with another approach.
If you tell me your use case, what would you like to do once you got an error, I'll give you a more useful answer.
Update:
export const addTodo = data => dispatch => {
// tell your application that request is sending,
// so you can handle it in UI (show a progress indicator)
dispatch({
type: ADD_TODO_REQUEST,
payload: data
});
try {
const response = await createTodo(data);
dispatch({
type: ADD_TODO_SUCCESS,
payload: response
});
// here you can dispatch navigation action as well
} catch (error) {
dispatch({
type: ADD_TODO_FAILURE,
error
});
// and here you can dispatch action with a toast
// to notify users that something went wrong
}
};
I am using redux-persist to store the data in my react-native app.
This is the code:
store.js
import { createStore, compose, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import {
persistStore,
persistCombineReducers,
} from 'redux-persist';
import { AsyncStorage } from 'react-native';
import { composeWithDevTools } from 'redux-devtools-extension';
import user from './reducers/user';
import auth from './reducers/auth';
const config = {
key: 'root',
storage: AsyncStorage,
};
const reducers = persistCombineReducers(config, {
user,
auth
});
export const configureStore = () => {
const store = createStore(
reducers,
compose(
applyMiddleware(thunk),
)
);
const persistor = persistStore(store);
return { persistor, store };
};
Then in the App.js I have this :
const { persistor, store } = configureStore();
const onBeforeLift = () => {
// take some action before the gate lifts
store.dispatch(startingApp());
}
return (
<Provider store={store}>
<PersistGate
loading={<HomeLoader />}
onBeforeLift={onBeforeLift}
persistor={persistor}>
<RootNav />
</PersistGate>
</Provider>
Everything works fine when I dispatch and action from the App.js componentDidMount.
The problem is that when I fire the action from component, for example, the state is not stored, so when I restart the app the state is gone.
What I do in is just calling the action and passing the data:
this.props.onSetAuthData(data.credentials);
The state is updated as I can see in the console, but if I restart the app, only the state created by the action in App.js is saved, not the one in
Maybe this has to do with the RootNav component ?
maybe I am exporting wrong the reducers?
I have
const user = (state = initialState, action = {}) => {}
export default user.
Same for the other reducer:
const auth = (state = initialState, action = {}) => {}
export default auth.
Then I export with
combineReducers({auth, user})
Is this wrong?
Use tool like Reacttotron to see if your store is persisted or not.
https://github.com/infinitered/reactotron
If it's already persisted your component should wait until the store rehydrated on app launch. Sometimes I can't use the redux persist using persistgate to wait for the persisted store to be rehydrated. So I set the store and persistor into state on async componentWillMount then in your render, check if the store is not empty (null) and already rehydrated then load your app.
constructor(){
super();
this.state = {store: null, persistor: null}
}
async componentWillMount () {
const store = configureStore();
this.setState({ store: store.store })
this.setState({ persistor: store.persistor })
}
render(){
return (
if (this.state.store === null) {
return (
<View>
<Text>Loading...</Text>
</View>
);
}
<Provider store={this.state.store} persistor={this.state.persistor}>
<RootNav />
</Provider>
Also try to change your storage from AsyncStorage to storage.
const config = {
key: 'root',
storage,
};
First import the storage import storage from 'redux-persist/es/storage';
sometimes it calls an error with the key in persistConfig. try key: 'primary'
const primary = {
key: 'root',
storage: AsyncStorage,
blacklist: [],
whitelist: ['user'],
};